diff options
Diffstat (limited to 'services/java')
44 files changed, 6857 insertions, 4745 deletions
diff --git a/services/java/com/android/server/AppWidgetService.java b/services/java/com/android/server/AppWidgetService.java index 4f81178..081f1f4 100644 --- a/services/java/com/android/server/AppWidgetService.java +++ b/services/java/com/android/server/AppWidgetService.java @@ -24,67 +24,35 @@ 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 +93,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 +121,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 +130,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. @@ -216,216 +172,24 @@ class AppWidgetService extends IAppWidgetService.Stub 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 int allocateAppWidgetId(String packageName, int hostId) throws RemoteException { + return getImplForUser().allocateAppWidgetId(packageName, hostId); } - - 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(); - } - } + + @Override + public void deleteAppWidgetId(int appWidgetId) throws RemoteException { + getImplForUser().deleteAppWidgetId(appWidgetId); } - 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; + @Override + public void deleteHost(int hostId) throws RemoteException { + getImplForUser().deleteHost(hostId); } - 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); - } - } - } + @Override + public void deleteAllHosts() throws RemoteException { + getImplForUser().deleteAllHosts(); } void cancelBroadcasts(Provider p) { @@ -441,617 +205,58 @@ class AppWidgetService extends IAppWidgetService.Stub } } - 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() { - @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 - } - }; - - // 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); - } - - // 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); - } - } - } - - 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 = 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 - } - }; - - // 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); - } - } - } - } - } - } - - 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(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 = 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; + @Override + public void bindAppWidgetId(int appWidgetId, ComponentName provider) throws RemoteException { + getImplForUser().bindAppWidgetId(appWidgetId, provider); } - 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; + @Override + public void bindRemoteViewsService(int appWidgetId, Intent intent, IBinder connection) + throws RemoteException { + getImplForUser().bindRemoteViewsService(appWidgetId, intent, connection); } - 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; + @Override + public int[] startListening(IAppWidgetHost host, String packageName, int hostId, + List<RemoteViews> updatedViews) throws RemoteException { + return getImplForUser().startListening(host, packageName, hostId, updatedViews); } - void pruneHostLocked(Host host) { - if (host.instances.size() == 0 && host.callbacks == null) { - mHosts.remove(host); - } + // TODO: Call this from PackageManagerService when a user is removed + public void removeUser(int userId) { } - 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); + 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); } - } - 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; - } + return service; } - 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 int[] getAppWidgetIds(ComponentName provider) throws RemoteException { + return getImplForUser().getAppWidgetIds(provider); } - void sendEnableIntentLocked(Provider p) { - Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLED); - intent.setComponent(p.info.provider); - mContext.sendBroadcast(intent); + @Override + public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) throws RemoteException { + return getImplForUser().getAppWidgetInfo(appWidgetId); } - 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 RemoteViews getAppWidgetViews(int appWidgetId) throws RemoteException { + return getImplForUser().getAppWidgetViews(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 +265,70 @@ 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(); - } - } - } - } + // TODO: Verify that this only needs to be delivered for the related user and not + // all the users + getImplForUser().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..250386f --- /dev/null +++ b/services/java/com/android/server/AppWidgetServiceImpl.java @@ -0,0 +1,1606 @@ +/* + * 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.PendingIntent; +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProviderInfo; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.Intent.FilterComparison; +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.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 com.android.server.am.ActivityManagerService; + +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; + 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; + PackageManager mPackageManager; + 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>(); + 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; + mPackageManager = context.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); + 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); + } + + Binder.restoreCallingIdentity(ident); + + 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); + } + } + + // 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() { + @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 + } + }; + + // 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); + } + + // 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); + } + } + } + + 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 + } + }; + + // 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); + } + } + } + } + } + } + + 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() { + 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); + } + } + + 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); + } + + 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); + } + } + + 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(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; + } + + 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; + } + + 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"); + } + + 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 ("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(); + } + } + } + + AtomicFile savedStateFile() { + int userId = Binder.getOrigCallingUser(); + File dir = new File("/data/system/users/" + userId); + File settingsFile = new File(dir, SETTINGS_FILENAME); + if (!dir.exists()) { + dir.mkdirs(); + if (userId == 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 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/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java index 4d5e0a6..a7b08f5 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; @@ -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) { @@ -1612,6 +1635,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 +1645,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 +1675,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 +1840,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 +1870,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 +1901,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 +1930,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 +1941,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 +1961,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 +1976,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 @@ -1941,6 +1992,7 @@ class BackupManagerService extends IBackupManager.Stub { 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 +2006,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 +2028,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 +2060,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 +2076,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 +2097,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 +2138,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 +2155,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 +2167,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 +2177,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 +2186,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 +2242,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 +2278,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 +2286,7 @@ class BackupManagerService extends IBackupManager.Stub { } void restartBackupAlarm() { + addBackupTrace("setting backup trigger"); synchronized (mQueueLock) { try { startBackupAlarmsLocked(mTransport.requestBackupTime()); @@ -2216,6 +2297,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 +2328,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 +2346,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 +2440,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 +2526,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,11 +2543,6 @@ class BackupManagerService extends IBackupManager.Stub { backupOnePackage(pkg, out); } - // Shared storage if requested - if (mIncludeShared) { - backupSharedStorage(); - } - // Done! finalizeBackup(out); } catch (RemoteException e) { @@ -2554,19 +2648,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 +2737,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 +2962,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 +3840,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, @@ -4715,6 +4784,8 @@ class BackupManagerService extends IBackupManager.Stub { // one already there, then overwrite it, but no harm done. BackupRequest req = new BackupRequest(packageName); if (mPendingBackups.put(app.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. @@ -5736,6 +5807,17 @@ 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++) { diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index 97fb0b0..a372fb8 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -992,11 +992,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()); + } } } @@ -1031,9 +1035,14 @@ private NetworkStateTracker makeWimaxStateTracker() { if ((ni.isConnectedOrConnecting() == true) && !network.isTeardownRequested()) { if (ni.isConnected() == true) { - // add the pid-specific dns - handleDnsConfigurationChange(usedNetworkType); - if (VDBG) log("special network already active"); + final long token = Binder.clearCallingIdentity(); + try { + // add the pid-specific dns + handleDnsConfigurationChange(usedNetworkType); + if (VDBG) log("special network already active"); + } finally { + Binder.restoreCallingIdentity(token); + } return Phone.APN_ALREADY_ACTIVE; } if (VDBG) log("special network already connecting"); @@ -1221,6 +1230,7 @@ private NetworkStateTracker makeWimaxStateTracker() { } if (!ConnectivityManager.isNetworkTypeValid(networkType)) { + if (DBG) log("requestRouteToHostAddress on invalid network: " + networkType); return false; } NetworkStateTracker tracker = mNetTrackers[networkType]; @@ -1233,11 +1243,16 @@ private NetworkStateTracker makeWimaxStateTracker() { } return false; } + final long token = Binder.clearCallingIdentity(); try { InetAddress addr = InetAddress.getByAddress(hostAddress); LinkProperties lp = tracker.getLinkProperties(); return addRouteToAddress(lp, addr); - } catch (UnknownHostException e) {} + } catch (UnknownHostException e) { + if (DBG) log("requestRouteToHostAddress got " + e.toString()); + } finally { + Binder.restoreCallingIdentity(token); + } return false; } @@ -1277,7 +1292,10 @@ private NetworkStateTracker makeWimaxStateTracker() { private boolean modifyRoute(String ifaceName, LinkProperties lp, RouteInfo r, int cycleCount, boolean doAdd, boolean toDefaultTable) { - if ((ifaceName == null) || (lp == null) || (r == null)) return false; + if ((ifaceName == null) || (lp == null) || (r == null)) { + if (DBG) log("modifyRoute got unexpected null: " + ifaceName + ", " + lp + ", " + r); + return false; + } if (cycleCount > MAX_HOSTROUTE_CYCLE_COUNT) { loge("Error modifying route - too much recursion"); @@ -1309,7 +1327,7 @@ private NetworkStateTracker makeWimaxStateTracker() { } } catch (Exception e) { // never crash - catch them all - if (VDBG) loge("Exception trying to add a route: " + e); + if (DBG) loge("Exception trying to add a route: " + e); return false; } } else { @@ -1323,7 +1341,7 @@ private NetworkStateTracker makeWimaxStateTracker() { mNetd.removeRoute(ifaceName, r); } catch (Exception e) { // never crash - catch them all - if (VDBG) loge("Exception trying to remove a route: " + e); + if (DBG) loge("Exception trying to remove a route: " + e); return false; } } else { @@ -1335,7 +1353,7 @@ private NetworkStateTracker makeWimaxStateTracker() { mNetd.removeSecondaryRoute(ifaceName, r); } catch (Exception e) { // never crash - catch them all - if (VDBG) loge("Exception trying to remove a route: " + e); + if (DBG) loge("Exception trying to remove a route: " + e); return false; } } @@ -2004,7 +2022,7 @@ private NetworkStateTracker makeWimaxStateTracker() { mNetd.removeRoute(ifaceName, r); } catch (Exception e) { // never crash - catch them all - if (VDBG) loge("Exception trying to remove a route: " + e); + if (DBG) loge("Exception trying to remove a route: " + e); } } } @@ -2218,7 +2236,7 @@ private NetworkStateTracker makeWimaxStateTracker() { mNetd.setDnsServersForInterface(iface, NetworkUtils.makeStrings(dnses)); mNetd.setDefaultInterfaceForDns(iface); } catch (Exception e) { - if (VDBG) loge("exception setting default dns interface: " + e); + if (DBG) loge("exception setting default dns interface: " + e); } } if (!domains.equals(SystemProperties.get("net.dns.search"))) { @@ -2248,7 +2266,7 @@ private NetworkStateTracker makeWimaxStateTracker() { mNetd.setDnsServersForInterface(p.getInterfaceName(), NetworkUtils.makeStrings(dnses)); } catch (Exception e) { - if (VDBG) loge("exception setting dns servers: " + e); + if (DBG) loge("exception setting dns servers: " + e); } // set per-pid dns for attached secondary nets List pids = mNetRequestersPids[netType]; 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..0bcec2e 100644 --- a/services/java/com/android/server/EventLogTags.logtags +++ b/services/java/com/android/server/EventLogTags.logtags @@ -142,5 +142,5 @@ option java_package com.android.server # --------------------------- # 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 f5c4ed4..aba1bc6 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -142,6 +142,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; @@ -2163,6 +2166,15 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + if (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID) { + final InputMethodSubtype currentSubtype = getCurrentInputMethodSubtype(); + if (currentSubtype != null) { + final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId); + lastInputMethodSubtypeId = + getSubtypeIdFromHashCode(currentImi, currentSubtype.hashCode()); + } + } + final int N = imList.size(); mIms = new InputMethodInfo[N]; mSubtypeIds = new int[N]; @@ -2472,7 +2484,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); @@ -2506,15 +2517,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) { diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java index 56afe7f..8cb9d99b 100644 --- a/services/java/com/android/server/LocationManagerService.java +++ b/services/java/com/android/server/LocationManagerService.java @@ -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..366160b 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; @@ -564,8 +565,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 +633,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> @@ -839,7 +840,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 +910,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 +938,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 +953,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 +1169,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 +1393,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,7 +1427,8 @@ 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]; } @@ -1456,9 +1441,8 @@ class MountService extends IMountService.Stub 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); } catch (NativeDaemonConnectorException e) { rc = StorageResultCode.OperationFailedInternalError; } @@ -1477,7 +1461,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 @@ -1503,7 +1487,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 +1524,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 +1561,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 +1610,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 +1624,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 +1645,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) { @@ -1709,14 +1692,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 +1758,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 +1788,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 +1829,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 +1852,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 +1885,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 +2248,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 +2331,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 +2429,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..308661f 100644 --- a/services/java/com/android/server/NativeDaemonConnector.java +++ b/services/java/com/android/server/NativeDaemonConnector.java @@ -22,59 +22,51 @@ 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; /** - * 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 BlockingQueue<NativeDaemonEvent> mResponseQueue; - class ResponseCode { - public static final int ActionInitiated = 100; - - public static final int CommandOkay = 200; - - // 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 INativeDaemonConnectorCallbacks mCallbacks; + private Handler mCallbackHandler; - 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 LinkedBlockingQueue<NativeDaemonEvent>(responseQueueSize); + TAG = logTag != null ? logTag : "NativeDaemonConnector"; + mLocalLog = new LocalLog(maxLogSize); } @Override @@ -136,29 +128,35 @@ 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); + Slog.e(TAG, "Failed to put response onto queue: " + ex); } } - } catch (NumberFormatException nfe) { - Slog.w(TAG, String.format("Bad msg (%s)", event)); + } catch (IllegalArgumentException e) { + Slog.w(TAG, "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. @@ -195,137 +193,265 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo } } - private void sendCommandLocked(String command) throws NativeDaemonConnectorException { - sendCommandLocked(command, null); - } - /** - * Sends a command to the daemon with a single argument + * Send command to daemon, escaping arguments as needed. * - * @param command The command to send to the daemon - * @param argument The argument to send with the command (or null) + * @return the final command issued. */ - private void sendCommandLocked(String command, String argument) + private String sendCommandLocked(String cmd, Object... args) throws NativeDaemonConnectorException { - if (command != null && command.indexOf('\0') >= 0) { - throw new IllegalArgumentException("unexpected command: " + command); + // TODO: eventually enforce that cmd doesn't contain arguments + if (cmd.indexOf('\0') >= 0) { + throw new IllegalArgumentException("unexpected command: " + cmd); } - if (argument != null && argument.indexOf('\0') >= 0) { - throw new IllegalArgumentException("unexpected argument: " + argument); + + final StringBuilder builder = new StringBuilder(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); } - if (LOCAL_LOGD) Slog.d(TAG, String.format("SND -> {%s} {%s}", command, argument)); + final String unterminated = builder.toString(); + log("SND -> {" + unterminated + "}"); + + builder.append('\0'); + if (mOutputStream == null) { - Slog.e(TAG, "No connection to daemon", new IllegalStateException()); - throw new NativeDaemonConnectorException("No output stream!"); + throw new NativeDaemonConnectorException("missing output stream"); } else { - StringBuilder builder = new StringBuilder(command); - if (argument != null) { - builder.append(argument); - } - builder.append('\0'); - try { - mOutputStream.write(builder.toString().getBytes()); - } catch (IOException ex) { - Slog.e(TAG, "IOException in sendCommand", ex); + mOutputStream.write(builder.toString().getBytes(Charsets.UTF_8)); + } catch (IOException e) { + throw new NativeDaemonConnectorException("problem sending command", e); } } + + return unterminated; + } + + /** + * 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()); + } + + /** + * 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(String cmd, Object... args) + throws NativeDaemonConnectorException { + final NativeDaemonEvent[] events = executeForList(cmd, args); + if (events.length != 1) { + throw new NativeDaemonConnectorException( + "Expected exactly one response, but received " + events.length); + } + return events[0]; } /** - * Issue a command to the native daemon and return the responses + * 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 ArrayList<String> doCommand(String cmd) throws NativeDaemonConnectorException { + 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 { synchronized (mDaemonLock) { - return doCommandLocked(cmd); + return executeLocked(cmd, args); } } - private ArrayList<String> doCommandLocked(String cmd) throws NativeDaemonConnectorException { - mResponseQueue.clear(); - sendCommandLocked(cmd); - - ArrayList<String> response = new ArrayList<String>(); - boolean complete = false; - int code = -1; + private NativeDaemonEvent[] executeLocked(String cmd, Object... args) + throws NativeDaemonConnectorException { + final ArrayList<NativeDaemonEvent> events = Lists.newArrayList(); - while (!complete) { + while (mResponseQueue.size() > 0) { 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)); - } + log("ignoring {" + mResponseQueue.take() + "}"); + } catch (Exception e) {} + } - if ((code >= 200) && (code < 600)) { - complete = true; - } - response.add(line); - } catch (InterruptedException ex) { - Slog.e(TAG, "Failed to process response", ex); + final String sentCommand = sendCommandLocked(cmd, args); + + NativeDaemonEvent event = null; + do { + try { + event = mResponseQueue.take(); + } catch (InterruptedException e) { + Slog.w(TAG, "interrupted waiting for event line"); + continue; } + events.add(event); + } while (event.isClassContinue()); + + if (event.isClassClientError()) { + throw new NativeDaemonArgumentException(sentCommand, event); + } + if (event.isClassServerError()) { + throw new NativeDaemonFailureException(sentCommand, event); } - 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)); + return events.toArray(new NativeDaemonEvent[events.size()]); + } + + /** + * 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 { + final ArrayList<String> rawEvents = Lists.newArrayList(); + final NativeDaemonEvent[] events = executeForList(cmd); + for (NativeDaemonEvent event : events) { + rawEvents.add(event.getRawEvent()); } - return response; + return rawEvents; } /** - * Issues a list command and returns the cooked list + * Issues a list command and returns the cooked list of all + * {@link NativeDaemonEvent#getMessage()} which match requested code. */ - public String[] doListCommand(String cmd, int expectedResponseCode) + @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); + } + } - ArrayList<String> rsp = doCommand(cmd); - String[] rdata = new String[rsp.size()-1]; - int idx = 0; + final NativeDaemonEvent finalEvent = events[events.length - 1]; + if (!finalEvent.isClassOk()) { + throw new NativeDaemonConnectorException("unexpected final event: " + finalEvent); + } - 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))); - } - } - return rdata; - } else { - throw new NativeDaemonConnectorException( - String.format("Expected list response %d, but got %d", - expectedResponseCode, code)); - } - } catch (NumberFormatException nfe) { - throw new NativeDaemonConnectorException( - String.format("Error reading code '%s'", line)); + return list.toArray(new String[list.size()]); + } + + /** + * 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('"'); + } + + 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); } } - throw new NativeDaemonConnectorException("Got an empty response"); + + 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); + } + } + + /** + * Command builder that handles argument list building. + */ + 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); + } + } + + public Command appendArg(Object arg) { + mArguments.add(arg); + return this; + } } /** {@inheritDoc} */ public void monitor() { synchronized (mDaemonLock) { } } + + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + mLocalLog.dump(fd, pw, args); + } + + private void log(String logstring) { + if (LOGD) Slog.d(TAG, logstring); + mLocalLog.log(logstring); + } } 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..62084c0 --- /dev/null +++ b/services/java/com/android/server/NativeDaemonEvent.java @@ -0,0 +1,142 @@ +/* + * 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 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 mCode; + private final String mMessage; + private final String mRawEvent; + + private NativeDaemonEvent(int code, String message, String rawEvent) { + mCode = code; + mMessage = message; + mRawEvent = rawEvent; + } + + 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 mCode >= 600 && mCode < 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 int splitIndex = rawEvent.indexOf(' '); + if (splitIndex == -1) { + throw new IllegalArgumentException("unable to find ' ' separator"); + } + + final int code; + try { + code = Integer.parseInt(rawEvent.substring(0, splitIndex)); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("problem parsing code", e); + } + + final String message = rawEvent.substring(splitIndex + 1); + return new NativeDaemonEvent(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()]); + } +} diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java index 75e5366..7bb7938 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"; @@ -125,8 +135,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 +157,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 +190,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 +202,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 +233,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 +266,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 +366,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 +579,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 +651,248 @@ 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)); + mConnector.execute("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..a7d1992 100644 --- a/services/java/com/android/server/NetworkTimeUpdateService.java +++ b/services/java/com/android/server/NetworkTimeUpdateService.java @@ -234,8 +234,9 @@ 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 && diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java index 5039294..34a8a02 100755 --- a/services/java/com/android/server/NotificationManagerService.java +++ b/services/java/com/android/server/NotificationManagerService.java @@ -45,6 +45,7 @@ import android.os.IBinder; import android.os.Message; import android.os.Process; import android.os.RemoteException; +import android.os.UserId; import android.os.Vibrator; import android.provider.Settings; import android.telephony.TelephonyManager; @@ -1034,7 +1035,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); } diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java index 2a0d2a0..bb0ac3e 100644 --- a/services/java/com/android/server/PowerManagerService.java +++ b/services/java/com/android/server/PowerManagerService.java @@ -51,7 +51,6 @@ import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; import android.os.WorkSource; -import android.provider.Settings.SettingNotFoundException; import android.provider.Settings; import android.util.EventLog; import android.util.Log; @@ -60,6 +59,7 @@ import android.view.WindowManagerPolicy; 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; @@ -106,6 +106,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,6 +126,9 @@ 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 SCREEN_ON_BIT = 0x00000001; private static final int SCREEN_BRIGHT_BIT = 0x00000002; @@ -150,6 +161,8 @@ public class PowerManagerService extends IPowerManager.Stub static final int ANIM_STEPS = 60/4; // Slower animation for autobrightness changes static final int AUTOBRIGHTNESS_ANIM_STEPS = 60; + // 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 @@ -227,6 +240,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 +254,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; @@ -460,6 +475,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)); @@ -624,9 +642,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(); @@ -1163,7 +1184,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 @@ -1706,11 +1728,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; } } } @@ -2235,20 +2252,15 @@ public class PowerManagerService extends IPowerManager.Stub } 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) { @@ -2444,7 +2456,34 @@ 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); @@ -2473,7 +2512,7 @@ public class PowerManagerService extends IPowerManager.Stub int value = (int)mLightSensorPendingValue; mLightSensorPendingDecrease = false; mLightSensorPendingIncrease = false; - lightSensorChangedLocked(value); + lightSensorChangedLocked(value, false); } } } @@ -2483,18 +2522,19 @@ public class PowerManagerService extends IPowerManager.Stub 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); } @@ -2540,7 +2580,8 @@ public class PowerManagerService extends IPowerManager.Stub if (mAutoBrightessEnabled && mScreenBrightnessOverride < 0) { if (!mSkippedScreenOn) { - mScreenBrightness.setTargetLocked(lcdValue, AUTOBRIGHTNESS_ANIM_STEPS, + mScreenBrightness.setTargetLocked(lcdValue, + immediate ? IMMEDIATE_ANIM_STEPS : AUTOBRIGHTNESS_ANIM_STEPS, INITIAL_SCREEN_BRIGHTNESS, (int)mScreenBrightness.curValue); } } @@ -2688,7 +2729,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); @@ -2948,10 +2989,28 @@ public class PowerManagerService extends IPowerManager.Stub Binder.restoreCallingIdentity(identity); } - // update our animation state - synchronized (mLocks) { - mScreenBrightness.targetValue = brightness; - mScreenBrightness.jumpToTargetLocked(); + 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 +3115,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 +3185,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/SystemServer.java b/services/java/com/android/server/SystemServer.java index 3ae62ad..762acbb 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -133,8 +133,8 @@ class ServerThread extends Thread { // 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(); diff --git a/services/java/com/android/server/UiModeManagerService.java b/services/java/com/android/server/UiModeManagerService.java index c7fbc00..c5c2901 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; @@ -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/WifiService.java b/services/java/com/android/server/WifiService.java index aef3426..5208785 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; @@ -112,6 +113,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; @@ -527,6 +532,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 +690,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; + } } /** @@ -985,6 +1004,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,6 +1201,13 @@ 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); diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java index b70ed96..8bda755 100644 --- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -46,7 +46,6 @@ import android.text.TextUtils.SimpleStringSplitter; import android.util.Slog; import android.util.SparseArray; import android.view.IWindow; -import android.view.View; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; @@ -96,6 +95,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private static final int DO_SET_SERVICE_INFO = 10; + public static final int ACTIVE_WINDOW_ID = -1; + + public static final long ROOT_NODE_ID = -1; + private static int sNextWindowId; final HandlerCaller mCaller; @@ -467,7 +470,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - public void registerEventListener(IEventListener listener) { + public void registerUiTestAutomationService(IEventListener listener, + AccessibilityServiceInfo accessibilityServiceInfo) { mSecurityPolicy.enforceCallingPermission(Manifest.permission.RETRIEVE_WINDOW_CONTENT, FUNCTION_REGISTER_EVENT_LISTENER); ComponentName componentName = new ComponentName("foo.bar", @@ -490,13 +494,23 @@ 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()); } + public void unregisterUiTestAutomationService(IEventListener listener) { + synchronized (mLock) { + final int serviceCount = mServices.size(); + for (int i = 0; i < serviceCount; i++) { + Service service = mServices.get(i); + if (service.mServiceInterface == listener && service.mIsAutomation) { + // Automation service is not bound, so pretend it died to perform clean up. + service.binderDied(); + } + } + } + } + /** * Removes an AccessibilityInteractionConnection. * @@ -1070,10 +1084,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - public float findAccessibilityNodeInfoByViewIdInActiveWindow(int viewId, - int interactionId, IAccessibilityInteractionConnectionCallback callback, - long interrogatingTid) + 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,12 +1096,8 @@ 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; } } @@ -1094,44 +1105,33 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub final int interrogatingPid = Binder.getCallingPid(); final long identityToken = Binder.clearCallingIdentity(); try { - connection.findAccessibilityNodeInfoByViewId(viewId, interactionId, callback, - interrogatingPid, interrogatingTid); + connection.findAccessibilityNodeInfoByViewId(accessibilityNodeId, viewId, + interactionId, callback, 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()); - } - - public float findAccessibilityNodeInfosByViewTextInActiveWindow( - String text, int interactionId, - IAccessibilityInteractionConnectionCallback callback, long threadId) - throws RemoteException { - return findAccessibilityNodeInfosByViewText(text, - mSecurityPolicy.mRetrievalAlowingWindowId, View.NO_ID, interactionId, callback, - threadId); + return getCompatibilityScale(resolvedWindowId); } - public float findAccessibilityNodeInfosByViewText(String text, - int accessibilityWindowId, int accessibilityViewId, int interactionId, + public float findAccessibilityNodeInfosByText(int accessibilityWindowId, + long accessibilityNodeId, String text, 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; } } @@ -1139,89 +1139,77 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub final int interrogatingPid = Binder.getCallingPid(); final long identityToken = Binder.clearCallingIdentity(); try { - connection.findAccessibilityNodeInfosByViewText(text, accessibilityViewId, + connection.findAccessibilityNodeInfosByText(accessibilityNodeId, text, interactionId, callback, interrogatingPid, interrogatingTid); } catch (RemoteException re) { if (DEBUG) { - Slog.e(LOG_TAG, "Error finding node."); + Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfosByText()"); } } finally { Binder.restoreCallingIdentity(identityToken); } - return getCompatibilityScale(accessibilityWindowId); + return getCompatibilityScale(resolvedWindowId); } public float findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId, - int accessibilityViewId, int interactionId, + long accessibilityNodeId, 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 interrogatingPid = Binder.getCallingPid(); final long identityToken = Binder.clearCallingIdentity(); try { - connection.findAccessibilityNodeInfoByAccessibilityId(accessibilityViewId, + connection.findAccessibilityNodeInfoByAccessibilityId(accessibilityNodeId, interactionId, callback, interrogatingPid, interrogatingTid); } catch (RemoteException re) { if (DEBUG) { - Slog.e(LOG_TAG, "Error requesting node with accessibilityViewId: " - + accessibilityViewId); + Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfoByAccessibilityId()"); } } finally { Binder.restoreCallingIdentity(identityToken); } - return getCompatibilityScale(accessibilityWindowId); + return getCompatibilityScale(resolvedWindowId); } public boolean performAccessibilityAction(int accessibilityWindowId, - int accessibilityViewId, int action, int interactionId, + long accessibilityNodeId, int action, 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); 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, + connection.performAccessibilityAction(accessibilityNodeId, action, interactionId, callback, 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); @@ -1265,14 +1253,26 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - private IAccessibilityInteractionConnection getConnectionToRetrievalAllowingWindowLocked() { - final int windowId = mSecurityPolicy.getRetrievalAllowingWindowLocked(); + 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 == ACTIVE_WINDOW_ID) { + return mSecurityPolicy.mRetrievalAlowingWindowId; + } + return accessibilityWindowId; } private float getCompatibilityScale(int windowId) { diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index df58e83..6fd5c07 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -75,6 +75,7 @@ import android.content.pm.PathPermission; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; +import android.content.pm.UserInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; @@ -104,6 +105,7 @@ 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.util.EventLog; import android.util.Pair; @@ -111,6 +113,7 @@ import android.util.Slog; import android.util.Log; import android.util.PrintWriterPrinter; import android.util.SparseArray; +import android.util.SparseIntArray; import android.util.TimeUtils; import android.view.Gravity; import android.view.LayoutInflater; @@ -135,6 +138,7 @@ 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; @@ -149,7 +153,9 @@ 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; @@ -158,6 +164,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; @@ -171,6 +178,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; @@ -221,7 +229,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; @@ -276,33 +285,832 @@ public final class ActivityManagerService extends ActivityManagerNative = 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. + * BROADCASTS + * + * We keep two broadcast queues and associated bookkeeping, one for those at + * foreground priority, and one for normal (background-priority) broadcasts. */ - final ArrayList<BroadcastRecord> mParallelBroadcasts - = new ArrayList<BroadcastRecord>(); + public class BroadcastQueue { + static final String TAG = "BroadcastQueue"; - /** - * 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>(); + static final int MAX_BROADCAST_HISTORY = 25; - /** - * Historical data of past broadcasts, for debugging. - */ - static final int MAX_BROADCAST_HISTORY = 25; - final BroadcastRecord[] mBroadcastHistory - = new BroadcastRecord[MAX_BROADCAST_HISTORY]; + /** + * Recognizable moniker for this queue + */ + String mQueueName; - /** - * Set when we current have a BROADCAST_INTENT_MSG in flight. - */ - boolean mBroadcastsScheduled = false; + /** + * Timeout period for this queue's broadcasts + */ + 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; + + BroadcastQueue(String name, long timeoutPeriod) { + 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; + } + + 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 final void processNextBroadcast(boolean fromMsg) { + synchronized(ActivityManagerService.this) { + BroadcastRecord r; + + if (DEBUG_BROADCAST) Slog.v(TAG, "processNextBroadcast [" + + mQueueName + "]: " + + 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 [" + + 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 (mPidsSelfLocked) { + isDead = (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! + 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*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 = 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) { + info.activityInfo = getActivityInfoForUser(info.activityInfo, UserId + .getUserId(r.callingUid)); + } + 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); + } 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 = 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=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, 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 (mDidDexOpt) { + // Delay timeouts until dexopt finishes. + mDidDexOpt = false; + long timeoutTime = SystemClock.uptimeMillis() + mTimeoutPeriod; + 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 + 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 != MY_PID) { + synchronized (ActivityManagerService.this.mPidsSelfLocked) { + app = ActivityManagerService.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, 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; + } + } + + final BroadcastQueue mFgBroadcastQueue = new BroadcastQueue("foreground", BROADCAST_FG_TIMEOUT); + final BroadcastQueue mBgBroadcastQueue = new BroadcastQueue("background", BROADCAST_BG_TIMEOUT); + // Convenient for easy iteration over the queues. Foreground is first + // so that dispatch of foreground broadcasts gets precedence. + final BroadcastQueue[] mBroadcastQueues = { mFgBroadcastQueue, mBgBroadcastQueue }; + + 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; + } + + 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. @@ -456,25 +1264,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. @@ -513,17 +1302,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 @@ -589,6 +1368,8 @@ public final class ActivityManagerService extends ActivityManagerNative 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 * application is currently being launched and the provider will be @@ -619,6 +1400,7 @@ public final class ActivityManagerService extends ActivityManagerNative uid = _uid; } } + private static ThreadLocal<Identity> sCallerIdentity = new ThreadLocal<Identity>(); /** @@ -910,11 +1692,12 @@ 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")); @@ -987,11 +1770,13 @@ public final class ActivityManagerService extends ActivityManagerNative case BROADCAST_INTENT_MSG: { if (DEBUG_BROADCAST) Slog.v( TAG, "Received BROADCAST_INTENT_MSG"); - processNextBroadcast(true); + BroadcastQueue queue = (BroadcastQueue) msg.obj; + queue.processNextBroadcast(true); } break; case BROADCAST_TIMEOUT_MSG: { + final BroadcastQueue queue = (BroadcastQueue) msg.obj; synchronized (ActivityManagerService.this) { - broadcastTimeoutLocked(true); + queue.broadcastTimeoutLocked(true); } } break; case SERVICE_TIMEOUT_MSG: { @@ -1299,6 +2084,7 @@ public final class ActivityManagerService extends ActivityManagerNative ServiceManager.addService("activity", m); 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)); } @@ -1453,6 +2239,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) { @@ -1790,6 +2596,7 @@ public final class ActivityManagerService extends ActivityManagerNative processName); return procs != null ? procs.valueAt(0) : null; } + // uid = applyUserId(uid); ProcessRecord proc = mProcessNames.get(processName, uid); return proc; } @@ -1919,6 +2726,7 @@ public final class ActivityManagerService extends ActivityManagerNative try { int uid = app.info.uid; + int[] gids = null; try { gids = mContext.getPackageManager().getPackageGids( @@ -2030,7 +2838,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } - boolean startHomeActivityLocked() { + boolean startHomeActivityLocked(int userId) { if (mFactoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL && mTopAction == null) { // We are running in factory test mode, but unable to find @@ -2053,6 +2861,8 @@ 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) { @@ -2061,11 +2871,10 @@ public final class ActivityManagerService extends ActivityManagerNative null, null, 0, 0, 0, false, false, null); } } - - + return true; } - + /** * Starts the "new version setup screen" if appropriate. */ @@ -2229,10 +3038,23 @@ public final class ActivityManagerService extends ActivityManagerNative int grantedMode, IBinder resultTo, String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug, String profileFile, ParcelFileDescriptor profileFd, boolean autoStopProfiler) { + 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); + grantedUriPermissions, grantedMode, resultTo, resultWho, requestCode, onlyIfNeeded, + debug, profileFile, profileFd, autoStopProfiler, null, null, userId); } public final WaitResult startActivityAndWait(IApplicationThread caller, @@ -2241,10 +3063,11 @@ public final class ActivityManagerService extends ActivityManagerNative String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug, String profileFile, ParcelFileDescriptor profileFd, boolean autoStopProfiler) { 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); + res, null, userId); return res; } @@ -2253,9 +3076,11 @@ public final class ActivityManagerService extends ActivityManagerNative int grantedMode, IBinder resultTo, String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug, Configuration config) { - return mMainStack.startActivityMayWait(caller, -1, intent, resolvedType, + int ret = mMainStack.startActivityMayWait(caller, -1, intent, resolvedType, grantedUriPermissions, grantedMode, resultTo, resultWho, - requestCode, onlyIfNeeded, debug, null, null, false, null, config); + requestCode, onlyIfNeeded, + debug, null, null, false, null, config, Binder.getOrigCallingUser()); + return ret; } public int startActivityIntentSender(IApplicationThread caller, @@ -2283,9 +3108,9 @@ public final class ActivityManagerService extends ActivityManagerNative mAppSwitchesAllowedTime = 0; } } - - return pir.sendInner(0, fillInIntent, resolvedType, null, + int ret = pir.sendInner(0, fillInIntent, resolvedType, null, null, resultTo, resultWho, requestCode, flagsMask, flagsValues); + return ret; } public boolean startNextMatchingActivity(IBinder callingActivity, @@ -2388,20 +3213,24 @@ public final class ActivityManagerService extends ActivityManagerNative // 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, + int ret = mMainStack.startActivityMayWait(null, uid, intent, resolvedType, null, 0, resultTo, resultWho, requestCode, onlyIfNeeded, false, - null, null, false, null, null); + null, null, false, null, null, userId); + return ret; } public final int startActivities(IApplicationThread caller, Intent[] intents, String[] resolvedTypes, IBinder resultTo) { - return mMainStack.startActivities(caller, -1, intents, resolvedTypes, resultTo); + int ret = mMainStack.startActivities(caller, -1, intents, resolvedTypes, resultTo, + Binder.getOrigCallingUser()); + return ret; } public final int startActivitiesInPackage(int uid, @@ -2414,8 +3243,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, + UserId.getUserId(uid)); + return ret; } final void addRecentTaskLocked(TaskRecord task) { @@ -2427,8 +3257,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--; @@ -2787,7 +3618,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); @@ -3164,7 +3994,7 @@ public final class ActivityManagerService extends ActivityManagerNative } public boolean clearApplicationUserData(final String packageName, - final IPackageDataObserver observer) { + final IPackageDataObserver observer, final int userId) { int uid = Binder.getCallingUid(); int pid = Binder.getCallingPid(); long callingId = Binder.clearCallingIdentity(); @@ -3199,7 +4029,7 @@ public final class ActivityManagerService extends ActivityManagerNative 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 { @@ -3294,7 +4124,7 @@ public final class ActivityManagerService extends ActivityManagerNative Slog.w(TAG, msg); throw new SecurityException(msg); } - + final int userId = Binder.getOrigCallingUser(); long callingId = Binder.clearCallingIdentity(); try { IPackageManager pm = AppGlobals.getPackageManager(); @@ -3302,6 +4132,8 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized(this) { try { pkgUid = pm.getPackageUid(packageName); + // Convert the uid to the one for the calling user + pkgUid = UserId.getUid(userId, pkgUid); } catch (RemoteException e) { } if (pkgUid == -1) { @@ -3384,7 +4216,7 @@ 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); } @@ -3444,7 +4276,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, @@ -3548,7 +4381,8 @@ public final class ActivityManagerService extends ActivityManagerNative } ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>(); - for (ServiceRecord service : mServices.values()) { + int userId = UserId.getUserId(uid); + for (ServiceRecord service : mServiceMap.getAllServices(userId)) { if (service.packageName.equals(name) && (service.app == null || evenPersistent || !service.app.persistent)) { if (!doit) { @@ -3687,12 +4521,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); @@ -3891,23 +4722,13 @@ 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; } } @@ -4024,11 +4845,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()); } } } @@ -4169,7 +4992,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (callingUid != 0 && callingUid != Process.SYSTEM_UID) { int uid = AppGlobals.getPackageManager() .getPackageUid(packageName); - if (uid != Binder.getCallingUid()) { + if (UserId.getAppId(callingUid) != uid) { String msg = "Permission Denial: getIntentSender() from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() @@ -4180,7 +5003,10 @@ public final class ActivityManagerService extends ActivityManagerNative } } - return getIntentSenderLocked(type, packageName, callingUid, + 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); } catch (RemoteException e) { @@ -4192,6 +5018,8 @@ 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) { + if (DEBUG_MU) + Slog.v(TAG_MU, "getIntentSenderLocked(): uid=" + callingUid); ActivityRecord activity = null; if (type == INTENT_SENDER_ACTIVITY_RESULT) { activity = mMainStack.isInStackLocked(token); @@ -4435,7 +5263,7 @@ public final class ActivityManagerService extends ActivityManagerNative } // 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. @@ -4469,7 +5297,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); } /** @@ -4479,7 +5307,7 @@ public final class ActivityManagerService extends ActivityManagerNative int checkCallingPermission(String permission) { return checkPermission(permission, Binder.getCallingPid(), - Binder.getCallingUid()); + UserId.getAppId(Binder.getCallingUid())); } /** @@ -4593,6 +5421,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; @@ -4635,7 +5464,8 @@ 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 { @@ -4891,7 +5721,8 @@ public final class ActivityManagerService extends ActivityManagerNative final String authority = uri.getAuthority(); ProviderInfo pi = null; - ContentProviderRecord cpr = mProvidersByName.get(authority); + ContentProviderRecord cpr = mProviderMap.getProviderByName(authority, + UserId.getUserId(callingUid)); if (cpr != null) { pi = cpr.info; } else { @@ -4985,7 +5816,8 @@ public final class ActivityManagerService extends ActivityManagerNative final String authority = uri.getAuthority(); ProviderInfo pi = null; - ContentProviderRecord cpr = mProvidersByName.get(authority); + ContentProviderRecord cpr = mProviderMap.getProviderByName(authority, + UserId.getUserId(r.info.uid)); if (cpr != null) { pi = cpr.info; } else { @@ -5224,6 +6056,12 @@ 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()"); @@ -5236,11 +6074,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) @@ -5329,7 +6170,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(root.userId)) { if (sr.packageName.equals(component.getPackageName())) { services.add(sr); } @@ -5700,17 +6541,23 @@ public final class ActivityManagerService extends ActivityManagerNative STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS); } catch (RemoteException ex) { } + if (DEBUG_MU) + Slog.v(TAG_MU, "generateApplicationProvidersLocked, app.info.uid = " + app.info.uid); + int userId = UserId.getUserId(app.info.uid); 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); + 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); @@ -5820,8 +6667,8 @@ public final class ActivityManagerService extends ActivityManagerNative return false; } - private final ContentProviderHolder getContentProviderImpl( - IApplicationThread caller, String name) { + private final ContentProviderHolder getContentProviderImpl(IApplicationThread caller, + String name) { ContentProviderRecord cpr; ProviderInfo cpi = null; @@ -5838,7 +6685,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.info.uid : Binder.getCallingUid()); + cpr = mProviderMap.getProviderByName(name, userId); boolean providerRunning = cpr != null; if (providerRunning) { cpi = cpr.info; @@ -5923,6 +6771,9 @@ public final class ActivityManagerService extends ActivityManagerNative return null; } + cpi.applicationInfo = getAppInfoForUser(cpi.applicationInfo, + Binder.getOrigCallingUser()); + String msg; if ((msg=checkContentProviderPermissionLocked(cpi, r)) != null) { throw new SecurityException(msg); @@ -5938,7 +6789,7 @@ public final class ActivityManagerService extends ActivityManagerNative } ComponentName comp = new ComponentName(cpi.packageName, cpi.name); - cpr = mProvidersByClass.get(comp); + cpr = mProviderMap.getProviderByClass(comp, Binder.getOrigCallingUser()); final boolean firstClass = cpr == null; if (firstClass) { try { @@ -5952,6 +6803,7 @@ public final class ActivityManagerService extends ActivityManagerNative + cpi.name); return null; } + ai = getAppInfoForUser(ai, Binder.getOrigCallingUser()); cpr = new ContentProviderRecord(cpi, ai, comp); } catch (RemoteException ex) { // pm is in same process, this will never happen. @@ -6020,9 +6872,9 @@ 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); + mProviderMap.putProviderByName(name, cpr); incProviderCount(r, cpr); } } @@ -6041,6 +6893,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) { } @@ -6058,7 +6914,8 @@ public final class ActivityManagerService extends ActivityManagerNative throw new SecurityException(msg); } - return getContentProviderImpl(caller, name); + ContentProviderHolder contentProvider = getContentProviderImpl(caller, name); + return contentProvider; } private ContentProviderHolder getContentProviderExternal(String name) { @@ -6071,7 +6928,8 @@ public final class ActivityManagerService extends ActivityManagerNative */ public void removeContentProvider(IApplicationThread caller, String name) { 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 + @@ -6086,8 +6944,11 @@ 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); @@ -6102,7 +6963,8 @@ public final class ActivityManagerService extends ActivityManagerNative private void removeContentProviderExternal(String name) { 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"); @@ -6111,7 +6973,8 @@ 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); + ContentProviderRecord localCpr = mProviderMap.getProviderByClass(comp, + Binder.getOrigCallingUser()); localCpr.externals--; if (localCpr.externals < 0) { Slog.e(TAG, "Externals < 0 for content provider " + localCpr); @@ -6128,6 +6991,8 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized(this) { final ProcessRecord r = getRecordForAppLocked(caller); + if (DEBUG_MU) + Slog.v(TAG_MU, "ProcessRecord uid = " + r.info.uid); if (r == null) { throw new SecurityException( "Unable to find app for caller " + caller @@ -6144,12 +7009,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(); @@ -6894,8 +7761,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, + Process.SYSTEM_UID); if (finisher != null) { mWaitingUpdate = true; } @@ -7205,28 +8074,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); } } @@ -7945,6 +8794,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"); @@ -8027,6 +8877,23 @@ public final class ActivityManagerService extends ActivityManagerNative 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."); + } + return; } else if ("providers".equals(cmd) || "prov".equals(cmd)) { synchronized (this) { dumpProvidersLocked(fd, pw, args, opti, true, null); @@ -8523,8 +9390,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 { @@ -8542,18 +9415,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) { } } } @@ -8611,6 +9490,110 @@ 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) { + ArrayList<ContentProviderRecord> providers = new ArrayList<ContentProviderRecord>(); + + if ("all".equals(name)) { + synchronized (this) { + for (ContentProviderRecord r1 : mProvidersByClass.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 : mProvidersByClass.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"); + } + } + } + static class ItemMatcher { ArrayList<ComponentName> components; ArrayList<String> strings; @@ -8827,84 +9810,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) { @@ -8941,7 +9851,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; @@ -8950,6 +9863,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; @@ -8958,75 +9874,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)); + 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) { @@ -9136,76 +10064,8 @@ public final class ActivityManagerService extends ActivityManagerNative 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; @@ -9616,6 +10476,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; @@ -10059,10 +10951,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(); @@ -10338,8 +11230,10 @@ public final class ActivityManagerService extends ActivityManagerNative 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())); } @@ -10359,7 +11253,8 @@ public final class ActivityManagerService extends ActivityManagerNative public PendingIntent getRunningServiceControlPanel(ComponentName name) { 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++) { @@ -10375,7 +11270,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; } @@ -10393,11 +11288,11 @@ public final class ActivityManagerService extends ActivityManagerNative String resolvedType) { ServiceRecord r = null; if (service.getComponent() != null) { - r = mServices.get(service.getComponent()); + r = mServiceMap.getServiceByName(service.getComponent(), Binder.getOrigCallingUser()); } if (r == null) { Intent.FilterComparison filter = new Intent.FilterComparison(service); - r = mServicesByIntent.get(filter); + r = mServiceMap.getServiceByIntent(filter, Binder.getOrigCallingUser()); } if (r == null) { @@ -10413,7 +11308,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. } @@ -10460,11 +11355,17 @@ public final class ActivityManagerService extends ActivityManagerNative private ServiceLookupResult retrieveServiceLocked(Intent service, String resolvedType, int callingPid, int callingUid) { ServiceRecord r = null; + if (DEBUG_SERVICE) + Slog.v(TAG, "retrieveServiceLocked: " + service + " type=" + resolvedType + + " origCallingUid=" + callingUid); + if (service.getComponent() != null) { - r = mServices.get(service.getComponent()); + r = mServiceMap.getServiceByName(service.getComponent(), Binder.getOrigCallingUser()); + } + if (r == null) { + Intent.FilterComparison filter = new Intent.FilterComparison(service); + r = mServiceMap.getServiceByIntent(filter, Binder.getOrigCallingUser()); } - Intent.FilterComparison filter = new Intent.FilterComparison(service); - r = mServicesByIntent.get(filter); if (r == null) { try { ResolveInfo rInfo = @@ -10477,12 +11378,16 @@ public final class ActivityManagerService extends ActivityManagerNative ": not found"); return null; } - + if (Binder.getOrigCallingUser() > 0) { + sInfo.applicationInfo = getAppInfoForUser(sInfo.applicationInfo, + Binder.getOrigCallingUser()); + } ComponentName name = new ComponentName( sInfo.applicationInfo.packageName, sInfo.name); - r = mServices.get(name); + r = mServiceMap.getServiceByName(name, Binder.getOrigCallingUser()); 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(); @@ -10493,8 +11398,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(); @@ -10641,7 +11546,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.info.uid); r.app = app; r.restartTime = r.lastActivity = SystemClock.uptimeMillis(); @@ -10838,6 +11745,8 @@ public final class ActivityManagerService extends ActivityManagerNative final String appName = r.processName; ProcessRecord 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); @@ -10942,8 +11851,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); @@ -11057,6 +11966,8 @@ public final class ActivityManagerService extends ActivityManagerNative 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(); @@ -11071,6 +11982,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); @@ -11274,6 +12187,9 @@ public final class ActivityManagerService extends ActivityManagerNative 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( @@ -11316,7 +12232,7 @@ public final class ActivityManagerService extends ActivityManagerNative ServiceLookupResult res = retrieveServiceLocked(service, resolvedType, - Binder.getCallingPid(), Binder.getCallingUid()); + Binder.getCallingPid(), Binder.getOrigCallingUid()); if (res == null) { return 0; } @@ -11662,7 +12578,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); @@ -11893,15 +12811,25 @@ 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); + } - if (mBroadcastsScheduled) { - return; + void skipPendingBroadcastLocked(int pid) { + Slog.w(TAG, "Unattached app died before broadcast acknowledged, skipping"); + for (BroadcastQueue queue : mBroadcastQueues) { + queue.skipPendingBroadcastLocked(pid); + } + } + + // 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, @@ -11983,13 +12911,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(); } } @@ -12000,38 +12927,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) { @@ -12058,7 +12993,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. @@ -12233,10 +13169,11 @@ public final class ActivityManagerService extends ActivityManagerNative if (ai != null) { receivers = new ArrayList(); ResolveInfo ri = new ResolveInfo(); - ri.activityInfo = ai; + ri.activityInfo = getActivityInfoForUser(ai, userId); receivers.add(ri); } } else { + // TODO: Apply userId // Need to resolve the intent to interested receivers... if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) { @@ -12261,28 +13198,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; @@ -12362,32 +13288,22 @@ 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(); } } @@ -12426,7 +13342,7 @@ 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) { synchronized(this) { intent = verifyBroadcastLocked(intent); @@ -12437,8 +13353,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; } @@ -12447,21 +13363,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"); @@ -12494,54 +13410,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, @@ -12553,153 +13429,25 @@ public final class ActivityManagerService extends ActivityManagerNative throw new IllegalArgumentException("File descriptors passed in Bundle"); } - boolean doNext; - final long origId = Binder.clearCallingIdentity(); + try { + boolean doNext = false; + BroadcastRecord r = null; - 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); + synchronized(this) { + r = broadcastRecordForReceiverLocked(who); + if (r != null) { + doNext = r.queue.finishReceiverLocked(r, resultCode, + resultData, resultExtras, resultAbort, true); } } - } 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)); + if (doNext) { + r.queue.processNextBroadcast(false); + } + trimApplications(); + } finally { + Binder.restoreCallingIdentity(origId); } } @@ -12834,334 +13582,6 @@ public final class ActivityManagerService extends ActivityManagerNative } } - 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); - } - - // 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); - } - - // 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; - } - - mPendingBroadcast = r; - mPendingBroadcastRecvIndex = recIdx; - } - } - // ========================================================= // INSTRUMENTATION // ========================================================= @@ -13433,12 +13853,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 */); } } } @@ -13484,6 +13904,28 @@ public final class ActivityManagerService extends ActivityManagerNative // 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) { @@ -13549,6 +13991,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; @@ -13560,12 +14003,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 @@ -14006,8 +14451,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)); } @@ -14751,8 +15201,157 @@ 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); + } + } return true; } + + private boolean userExists(int userId) { + try { + List<UserInfo> users = AppGlobals.getPackageManager().getUsers(); + for (UserInfo user : users) { + if (user.id == userId) { + return true; + } + } + } catch (RemoteException re) { + // Won't happen, in same process + } + + return false; + } + + + private int applyUserId(int uid, int userId) { + return UserId.getUid(userId, uid); + } + + private ApplicationInfo getAppInfoForUser(ApplicationInfo info, int userId) { + ApplicationInfo newInfo = new ApplicationInfo(info); + newInfo.uid = applyUserId(info.uid, userId); + if (newInfo.uid >= Process.FIRST_APPLICATION_UID) { + newInfo.dataDir = USER_DATA_DIR + userId + "/" + + info.packageName; + } + return newInfo; + } + + ActivityInfo getActivityInfoForUser(ActivityInfo aInfo, int userId) { + if (aInfo.applicationInfo.uid < Process.FIRST_APPLICATION_UID + || userId < 1) { + 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 c819114..cdab6c6 100644 --- a/services/java/com/android/server/am/ActivityRecord.java +++ b/services/java/com/android/server/am/ActivityRecord.java @@ -25,6 +25,7 @@ import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.res.CompatibilityInfo; +import android.content.pm.PackageManager; import android.content.res.Configuration; import android.graphics.Bitmap; import android.os.Build; @@ -34,6 +35,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 +57,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 @@ -124,6 +127,7 @@ 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(prefix); pw.print("userId="); pw.print(userId); pw.print(" app="); pw.println(app); pw.print(prefix); pw.println(intent.toInsecureString()); pw.print(prefix); pw.print("frontOfTask="); pw.print(frontOfTask); @@ -281,6 +285,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; diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java index 6c11953..c3ae6a1 100644 --- a/services/java/com/android/server/am/ActivityStack.java +++ b/services/java/com/android/server/am/ActivityStack.java @@ -60,6 +60,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; @@ -274,6 +275,8 @@ final class ActivityStack { int mThumbnailWidth = -1; int mThumbnailHeight = -1; + private int mCurrentUser; + static final int SLEEP_TIMEOUT_MSG = 8; static final int PAUSE_TIMEOUT_MSG = 9; static final int IDLE_TIMEOUT_MSG = 10; @@ -365,6 +368,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); @@ -377,6 +381,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); @@ -398,6 +403,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); @@ -444,10 +450,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() @@ -487,12 +494,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); @@ -511,6 +519,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 { @@ -1272,7 +1317,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); } } @@ -1384,6 +1429,7 @@ final class ActivityStack { // Launching this app's activity, make sure the app is no longer // considered stopped. try { + // TODO: Apply to the correct userId AppGlobals.getPackageManager().setPackageStoppedState( next.packageName, false); } catch (RemoteException e1) { @@ -2354,7 +2400,7 @@ final class ActivityStack { } } } - + ActivityRecord r = new ActivityRecord(mService, this, callerApp, callingUid, intent, resolvedType, aInfo, mService.mConfiguration, resultRecord, resultWho, requestCode, componentSpecified); @@ -2420,7 +2466,8 @@ final class ActivityStack { int grantedMode, boolean onlyIfNeeded, boolean doResume) { 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 @@ -2648,7 +2695,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 @@ -2821,12 +2868,12 @@ final class ActivityStack { int grantedMode, IBinder resultTo, String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug, String profileFile, ParcelFileDescriptor profileFd, - boolean autoStopProfiler, WaitResult outResult, Configuration config) { + boolean autoStopProfiler, + WaitResult outResult, Configuration config, 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! @@ -2835,6 +2882,7 @@ final class ActivityStack { // Collect information about the target of the Intent. ActivityInfo aInfo = resolveActivity(intent, resolvedType, debug, profileFile, profileFd, autoStopProfiler); + aInfo = mService.getActivityInfoForUser(aInfo, userId); synchronized (mService) { int callingPid; @@ -2915,6 +2963,7 @@ final class ActivityStack { PackageManager.MATCH_DEFAULT_ONLY | ActivityManagerService.STOCK_PM_FLAGS); aInfo = rInfo != null ? rInfo.activityInfo : null; + aInfo = mService.getActivityInfoForUser(aInfo, userId); } catch (RemoteException e) { aInfo = null; } @@ -2977,7 +3026,8 @@ final class ActivityStack { } final int startActivities(IApplicationThread caller, int callingUid, - Intent[] intents, String[] resolvedTypes, IBinder resultTo) { + Intent[] intents, + String[] resolvedTypes, IBinder resultTo, int userId) { if (intents == null) { throw new NullPointerException("intents is null"); } @@ -3022,6 +3072,8 @@ final class ActivityStack { // Collect information about the target of the Intent. ActivityInfo aInfo = resolveActivity(intent, resolvedTypes[i], false, null, null, false); + // 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) { diff --git a/services/java/com/android/server/am/BroadcastRecord.java b/services/java/com/android/server/am/BroadcastRecord.java index bcb0134..6738e4f 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? + ActivityManagerService.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(ActivityManagerService.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/PendingIntentRecord.java b/services/java/com/android/server/am/PendingIntentRecord.java index abd2a1f..3b6a97c 100644 --- a/services/java/com/android/server/am/PendingIntentRecord.java +++ b/services/java/com/android/server/am/PendingIntentRecord.java @@ -24,6 +24,7 @@ import android.content.Intent; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; +import android.os.UserId; import android.util.Slog; import java.io.PrintWriter; @@ -248,7 +249,8 @@ class PendingIntentRecord extends IIntentSender.Stub { 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, 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..44e7ecc --- /dev/null +++ b/services/java/com/android/server/am/ProviderMap.java @@ -0,0 +1,244 @@ +/* + * 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.content.pm.PackageManager; +import android.os.Binder; +import android.os.Process; +import android.os.UserId; +import android.util.Slog; +import android.util.SparseArray; + +import java.io.PrintWriter; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +/** + * 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; + } + } + + private 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.print(r.name.toShortString()); + /* + if (r.app != null) { + pw.println(":"); + pw.print(" "); + pw.println(r.app); + } else { + pw.println(); + } + */ + } + } + } + + 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)); + } + } + } +} diff --git a/services/java/com/android/server/am/ServiceRecord.java b/services/java/com/android/server/am/ServiceRecord.java index 257113b..75ba947 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 @@ -289,6 +292,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..47ec218 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) { @@ -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/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java index cc1df4f..9573fda 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) { 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/location/GpsLocationProvider.java b/services/java/com/android/server/location/GpsLocationProvider.java index 00788ba..0ce5499 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/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java index 8c0f1e0..9772d6a 100644 --- a/services/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java @@ -48,6 +48,7 @@ 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.text.format.DateUtils.DAY_IN_MILLIS; import static com.android.internal.util.Preconditions.checkNotNull; import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT; @@ -153,10 +154,8 @@ 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; // @VisibleForTesting public static final int TYPE_WARNING = 0x1; @@ -175,6 +174,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { 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_UID = "uid"; private static final String ATTR_POLICY = "policy"; @@ -182,7 +184,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { // @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; @@ -331,6 +335,11 @@ 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); + } private IProcessObserver mProcessObserver = new IProcessObserver.Stub() { @@ -416,6 +425,21 @@ 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); + } + }; + + /** * Observer that watches for {@link INetworkManagementService} alerts. */ private INetworkManagementEventObserver mAlertObserver = new NetworkAlertObserver() { @@ -456,7 +480,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { 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 +490,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); } } @@ -532,7 +556,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 +569,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 +601,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 +636,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { break; } + builder.setOngoing(true); builder.setSmallIcon(R.drawable.stat_notify_error); builder.setTicker(title); builder.setContentTitle(title); @@ -718,10 +749,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); } } @@ -819,9 +851,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; @@ -890,8 +926,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final int cycleDay = time.monthDay; final NetworkTemplate template = buildTemplateMobileAll(subscriberId); - mNetworkPolicy.put(template, new NetworkPolicy( - template, cycleDay, warningBytes, LIMIT_DISABLED, SNOOZE_NEVER)); + mNetworkPolicy.put(template, new NetworkPolicy(template, cycleDay, warningBytes, + LIMIT_DISABLED, SNOOZE_NEVER, SNOOZE_NEVER, true)); writePolicyLocked(); } } @@ -929,17 +965,40 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final int cycleDay = readIntAttribute(in, ATTR_CYCLE_DAY); 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 { - lastSnooze = SNOOZE_NEVER; + 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 NetworkTemplate template = new NetworkTemplate( networkTemplate, subscriberId); - mNetworkPolicy.put(template, new NetworkPolicy( - template, cycleDay, warningBytes, limitBytes, lastSnooze)); + mNetworkPolicy.put(template, new NetworkPolicy(template, cycleDay, + warningBytes, limitBytes, lastWarningSnooze, lastLimitSnooze, + metered)); } else if (TAG_UID_POLICY.equals(tag)) { final int uid = readIntAttribute(in, ATTR_UID); @@ -994,7 +1053,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_SPLIT_SNOOZE); writeBooleanAttribute(out, ATTR_RESTRICT_BACKGROUND, mRestrictBackground); // write all known network policies @@ -1010,7 +1069,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { writeIntAttribute(out, ATTR_CYCLE_DAY, policy.cycleDay); 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); out.endTag(null, TAG_NETWORK_POLICY); } @@ -1120,9 +1181,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 +1196,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(); @@ -1225,12 +1298,17 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } 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; } @@ -1552,6 +1630,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private long getTotalBytes(NetworkTemplate template, long start, long end) { try { return mNetworkStats.getSummaryForNetwork(template, start, end).getTotalBytes(); + } 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 +1656,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..70038d9 --- /dev/null +++ b/services/java/com/android/server/net/NetworkStatsCollection.java @@ -0,0 +1,510 @@ +/* + * 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 { + private static final String TAG = "NetworkStatsCollection"; + + /** 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 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; + } + } + + /** {@inheritDoc} */ + 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; + } + + /** {@inheritDoc} */ + 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..e7ba358 --- /dev/null +++ b/services/java/com/android/server/net/NetworkStatsRecorder.java @@ -0,0 +1,341 @@ +/* + * 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 = true; + + 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 (LOGD && 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); + } + } + + /** + * 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"); + } + + /** {@inheritDoc} */ + public void reset() { + // ignored + } + + /** {@inheritDoc} */ + public void read(InputStream in) throws IOException { + mCollection.read(in); + } + + /** {@inheritDoc} */ + public boolean shouldWrite() { + return true; + } + + /** {@inheritDoc} */ + 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; + } + + /** {@inheritDoc} */ + public void reset() { + mTemp.reset(); + } + + /** {@inheritDoc} */ + public void read(InputStream in) throws IOException { + mTemp.read(in); + mTemp.clearDirty(); + mTemp.removeUid(mUid); + } + + /** {@inheritDoc} */ + public boolean shouldWrite() { + return mTemp.isDirty(); + } + + /** {@inheritDoc} */ + 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..c9b67fc 100644 --- a/services/java/com/android/server/net/NetworkStatsService.java +++ b/services/java/com/android/server/net/NetworkStatsService.java @@ -34,14 +34,19 @@ 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.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; @@ -61,19 +66,18 @@ 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.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 +98,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 +117,12 @@ 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 boolean LOGD = true; + private static final boolean LOGV = true; 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 +130,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 +139,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 +154,72 @@ 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; + + 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; - 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 +241,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 +251,27 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } public void systemReady() { + 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); @@ -312,8 +305,14 @@ public class NetworkStatsService extends INetworkStatsService.Stub { 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() { @@ -325,18 +324,44 @@ public class NetworkStatsService extends INetworkStatsService.Stub { mTeleManager.listen(mPhoneListener, LISTEN_NONE); - if (mNetworkStatsLoaded) { - writeNetworkDevStatsLocked(); - writeNetworkXtStatsLocked(); - } - 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; + } + + 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 +392,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); @@ -378,161 +403,39 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @Override public NetworkStatsHistory getHistoryForNetwork(NetworkTemplate template, int fields) { - mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG); - return getHistoryForNetworkDev(template, fields); + return mDevStatsCached.getHistory(template, UID_ALL, SET_ALL, TAG_NONE, fields); } - private NetworkStatsHistory getHistoryForNetworkDev(NetworkTemplate template, int fields) { - return getHistoryForNetwork(template, fields, mNetworkDevStats); - } - - private NetworkStatsHistory getHistoryForNetworkXt(NetworkTemplate template, int fields) { - return getHistoryForNetwork(template, fields, mNetworkXtStats); - } - - 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); - } - } - } - return combined; - } + @Override + public NetworkStats getSummaryForNetwork(NetworkTemplate template, long start, long end) { + return mDevStatsCached.getSummary(template, start, end); } @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); - } - } - - 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); - } - - private NetworkStats getSummaryForNetworkXt(NetworkTemplate template, long start, long end) { - return getSummaryForNetwork(template, start, end, mNetworkXtStats); - } - - 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); - } - } - - return stats; - } - } - - 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()); - } + // TODO: transition to stats sessions to avoid WeakReferences + if (tag == TAG_NONE) { + return mUidRecorder.getOrLoadCompleteLocked().getHistory( + template, uid, set, tag, fields); + } else { + return mUidTagRecorder.getOrLoadCompleteLocked().getHistory( + template, uid, set, tag, fields); } - return start; } @Override public NetworkStats getSummaryForAllUid( NetworkTemplate template, long start, long end, boolean includeTags) { - 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; - } + // TODO: transition to stats sessions to avoid WeakReferences + final NetworkStats stats = mUidRecorder.getOrLoadCompleteLocked().getSummary( + template, start, end); + if (includeTags) { + final NetworkStats tagStats = mUidTagRecorder.getOrLoadCompleteLocked().getSummary( + template, start, end); + stats.combineAllValues(tagStats); + } + return stats; } @Override @@ -543,7 +446,14 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // 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); + } + final NetworkStats dataLayer = new NetworkStats( networkLayer.getElapsedRealtime(), networkLayer.size()); @@ -555,7 +465,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } // splice in operation counts - dataLayer.spliceOperationsFrom(mOperations); + dataLayer.spliceOperationsFrom(mUidOperations); return dataLayer; } @@ -574,8 +484,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 +508,13 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @Override public void forceUpdate() { mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG); - performPoll(FLAG_PERSIST_ALL); + + final long token = Binder.clearCallingIdentity(); + try { + performPoll(FLAG_PERSIST_ALL); + } finally { + Binder.restoreCallingIdentity(token); + } } /** @@ -680,7 +598,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(); } } }; @@ -743,13 +661,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(); @@ -773,12 +695,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) { @@ -818,27 +748,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 +767,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 +787,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 +799,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); + 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); + 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 +858,68 @@ public class NetworkStatsService extends INetworkStatsService.Stub { argSet.add(arg); } - final boolean fullHistory = argSet.contains("full"); + // usage: dumpsys netstats --full --uid --tag + final boolean poll = argSet.contains("--poll") || argSet.contains("poll"); + 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"); - synchronized (mStatsLock) { - // TODO: remove this testing code, since it corrupts stats - if (argSet.contains("generate")) { - generateRandomLocked(args); - pw.println("Generated stub stats"); - return; - } + final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); - if (argSet.contains("poll")) { + synchronized (mStatsLock) { + if (poll) { performPollLocked(FLAG_PERSIST_ALL | FLAG_PERSIST_FORCE); pw.println("Forced poll"); 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,35 +936,6 @@ 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} */ public boolean handleMessage(Message msg) { @@ -1633,6 +949,10 @@ public class NetworkStatsService extends INetworkStatsService.Stub { updateIfaces(); return true; } + case MSG_REGISTER_GLOBAL_ALERT: { + registerGlobalAlert(); + return true; + } default: { return false; } @@ -1646,40 +966,31 @@ public class NetworkStatsService extends INetworkStatsService.Stub { return telephony.getSubscriberId(); } - /** - * 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; - } - - @Override - public int hashCode() { - return Objects.hashCode(ident, uid, set, tag); - } - - @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; - } + private boolean isBandwidthControlEnabled() { + try { + return mNetworkManager.isBandwidthControlEnabled(); + } catch (RemoteException e) { + // ignored; service lives in system_server return false; } + } + private class DropBoxNonMonotonicObserver implements NonMonotonicObserver<String> { /** {@inheritDoc} */ - public int compareTo(UidStatsKey another) { - return Integer.compare(uid, another.uid); + public void foundNonMonotonic(NetworkStats left, int leftIndex, NetworkStats right, + int rightIndex, String cookie) { + Log.w(TAG, "found non-monotonic values; saving to dropbox"); + + // 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'); + + final DropBoxManager dropBox = (DropBoxManager) mContext.getSystemService( + Context.DROPBOX_SERVICE); + dropBox.addText(TAG_NETSTATS_ERROR, builder.toString()); } } @@ -1705,26 +1016,35 @@ public class NetworkStatsService extends INetworkStatsService.Stub { 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); + 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); + 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); + public boolean getSampleEnabled() { + return getSecureBoolean(NETSTATS_SAMPLE_ENABLED, true); } - public long getUidMaxHistory() { - return getSecureLong(NETSTATS_UID_MAX_HISTORY, 90 * DAY_IN_MILLIS); + + 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); + + 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; + + 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 6b61c47..38c128c 100644 --- a/services/java/com/android/server/pm/PackageManagerService.java +++ b/services/java/com/android/server/pm/PackageManagerService.java @@ -92,6 +92,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; @@ -620,11 +621,11 @@ 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()); @@ -1743,12 +1744,16 @@ public class PackageManagerService extends IPackageManager.Stub { } public ActivityInfo getActivityInfo(ComponentName component, int flags) { + return getActivityInfo(component, flags, Binder.getOrigCallingUser()); + } + + ActivityInfo getActivityInfo(ComponentName component, int flags, int userId) { 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); + return PackageParser.generateActivityInfo(a, flags, userId); } if (mResolveComponentName.equals(component)) { return mResolveActivity; @@ -1758,36 +1763,48 @@ public class PackageManagerService extends IPackageManager.Stub { } public ActivityInfo getReceiverInfo(ComponentName component, int flags) { + return getReceiverInfo(component, flags, Binder.getOrigCallingUser()); + } + + ActivityInfo getReceiverInfo(ComponentName component, int flags, int userId) { 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); + return PackageParser.generateActivityInfo(a, flags, userId); } } return null; } public ServiceInfo getServiceInfo(ComponentName component, int flags) { + return getServiceInfo(component, flags, Binder.getOrigCallingUser()); + } + + ServiceInfo getServiceInfo(ComponentName component, int flags, int userId) { 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); + return PackageParser.generateServiceInfo(s, flags, userId); } } return null; } public ProviderInfo getProviderInfo(ComponentName component, int flags) { + return getProviderInfo(component, flags, UserId.getUserId(Binder.getCallingUid())); + } + + ProviderInfo getProviderInfo(ComponentName component, int flags, int userId) { 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); + return PackageParser.generateProviderInfo(p, flags, userId); } } return null; @@ -1850,7 +1867,7 @@ public class PackageManagerService extends IPackageManager.Stub { 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)) { @@ -1881,7 +1898,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 " @@ -2010,6 +2027,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 +2087,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 +2112,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,7 +2131,7 @@ 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; @@ -2252,6 +2273,9 @@ public class PackageManagerService extends IPackageManager.Stub { comp = intent.getComponent(); } } + + final int userId = UserId.getUserId(Binder.getCallingUid()); + if (comp != null) { final List<ResolveInfo> list = new ArrayList<ResolveInfo>(1); final ActivityInfo ai = getActivityInfo(comp, flags); @@ -2603,6 +2627,7 @@ public class PackageManagerService extends IPackageManager.Stub { Arrays.sort(keys); int i = getContinuationPoint(keys, lastRead); final int N = keys.length; + final int userId = UserId.getUserId(Binder.getCallingUid()); while (i < N) { final String packageName = keys[i++]; @@ -2616,7 +2641,7 @@ public class PackageManagerService extends IPackageManager.Stub { } else { final PackageParser.Package p = mPackages.get(packageName); if (p != null) { - ai = PackageParser.generateApplicationInfo(p, flags); + ai = PackageParser.generateApplicationInfo(p, flags, userId); } } @@ -2639,12 +2664,13 @@ public class PackageManagerService extends IPackageManager.Stub { // reader synchronized (mPackages) { final Iterator<PackageParser.Package> i = mPackages.values().iterator(); + final int userId = UserId.getUserId(Binder.getCallingUid()); 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)); + finalList.add(PackageParser.generateApplicationInfo(p, flags, userId)); } } } @@ -2660,7 +2686,8 @@ public class PackageManagerService extends IPackageManager.Stub { && mSettings.isEnabledLPr(provider.info, flags) && (!mSafeMode || (provider.info.applicationInfo.flags &ApplicationInfo.FLAG_SYSTEM) != 0) - ? PackageParser.generateProviderInfo(provider, flags) + ? PackageParser.generateProviderInfo(provider, flags, + UserId.getUserId(Binder.getCallingUid())) : null; } } @@ -2674,7 +2701,7 @@ public class PackageManagerService extends IPackageManager.Stub { synchronized (mPackages) { final Iterator<Map.Entry<String, PackageParser.Provider>> i = mProviders.entrySet() .iterator(); - + final int userId = UserId.getUserId(Binder.getCallingUid()); while (i.hasNext()) { Map.Entry<String, PackageParser.Provider> entry = i.next(); PackageParser.Provider p = entry.getValue(); @@ -2683,7 +2710,7 @@ public class PackageManagerService extends IPackageManager.Stub { && (!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, userId)); } } } @@ -2696,19 +2723,21 @@ public class PackageManagerService extends IPackageManager.Stub { // reader synchronized (mPackages) { final Iterator<PackageParser.Provider> i = mProvidersByComponent.values().iterator(); + final int userId = UserId.getUserId(Binder.getCallingUid()); while (i.hasNext()) { final PackageParser.Provider p = i.next(); if (p.info.authority != null && (processName == null || (p.info.processName.equals(processName) - && p.info.applicationInfo.uid == uid)) + && UserId.getAppId(p.info.applicationInfo.uid) + == UserId.getAppId(uid))) && mSettings.isEnabledLPr(p.info, flags) && (!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, userId)); } } } @@ -4461,8 +4490,8 @@ public class PackageManagerService extends IPackageManager.Stub { return null; } final ResolveInfo res = new ResolveInfo(); - res.activityInfo = PackageParser.generateActivityInfo(activity, - mFlags); + res.activityInfo = PackageParser.generateActivityInfo(activity, mFlags, + Binder.getOrigCallingUser()); if ((mFlags&PackageManager.GET_RESOLVED_FILTER) != 0) { res.filter = info; } @@ -4637,8 +4666,8 @@ public class PackageManagerService extends IPackageManager.Stub { return null; } final ResolveInfo res = new ResolveInfo(); - res.serviceInfo = PackageParser.generateServiceInfo(service, - mFlags); + res.serviceInfo = PackageParser.generateServiceInfo(service, mFlags, + Binder.getOrigCallingUser()); if ((mFlags&PackageManager.GET_RESOLVED_FILTER) != 0) { res.filter = filter; } @@ -4742,8 +4771,10 @@ public class PackageManagerService extends IPackageManager.Stub { intent.setPackage(targetPkg); } intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + // TODO: Fix the userId argument am.broadcastIntent(null, intent, null, finishedReceiver, - 0, null, null, null, finishedReceiver != null, false); + 0, null, null, null, finishedReceiver != null, false, + Binder.getOrigCallingUser()); } catch (RemoteException ex) { } } @@ -7584,7 +7615,7 @@ public class PackageManagerService extends IPackageManager.Stub { "Unknown component: " + packageName + "/" + className); } - if (!allowedByPermission && (uid != pkgSetting.userId)) { + if (!allowedByPermission && (!UserId.isSameApp(uid, pkgSetting.userId))) { throw new SecurityException( "Permission Denial: attempt to change component state from pid=" + Binder.getCallingPid() @@ -8673,4 +8704,8 @@ public class PackageManagerService extends IPackageManager.Stub { return mSettings.getVerifierDeviceIdentityLPw(); } } + + public List<UserInfo> getUsers() { + return mUserManager.getUsers(); + } } diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java index 36442a0..3616aa2 100644 --- a/services/java/com/android/server/pm/Settings.java +++ b/services/java/com/android/server/pm/Settings.java @@ -62,6 +62,7 @@ import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.Map; import libcore.io.IoUtils; @@ -713,8 +714,7 @@ final class Settings { mBackupStoppedPackagesFilename.delete(); FileUtils.setPermissions(mStoppedPackagesFilename.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! @@ -930,7 +930,7 @@ final class Settings { } 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 +951,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,8 +1006,7 @@ 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(); @@ -2147,7 +2145,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; @@ -2261,4 +2259,4 @@ final class Settings { pw.println("Settings parse messages:"); pw.print(mReadMessages.toString()); } -}
\ No newline at end of file +} diff --git a/services/java/com/android/server/pm/UserManager.java b/services/java/com/android/server/pm/UserManager.java index 76fa5ab..5eacf4a 100644 --- a/services/java/com/android/server/pm/UserManager.java +++ b/services/java/com/android/server/pm/UserManager.java @@ -24,6 +24,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; @@ -140,6 +141,13 @@ public class UserManager { fallbackToSingleUser(); } catch (XmlPullParserException pe) { fallbackToSingleUser(); + } finally { + if (fis != null) { + try { + fis.close(); + } catch (IOException e) { + } + } } } @@ -162,9 +170,10 @@ public class UserManager { * </user> */ private void writeUser(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 +195,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) { + } + } } } @@ -198,8 +214,9 @@ public class UserManager { * </users> */ private void writeUserList() { + 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 +239,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,13 +289,19 @@ 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; } @@ -317,7 +347,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); } } @@ -354,6 +384,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() { @@ -377,14 +409,8 @@ public class UserManager { 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); - } - } + mInstaller.cloneUserData(0, id, false); + final long stopTime = SystemClock.elapsedRealtime(); Log.i(LOG_TAG, "Time to create " + apps.size() + " packages = " + (stopTime - startTime) + "ms"); diff --git a/services/java/com/android/server/wm/DragState.java b/services/java/com/android/server/wm/DragState.java index 73cd64e..a19035a 100644 --- a/services/java/com/android/server/wm/DragState.java +++ b/services/java/com/android/server/wm/DragState.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import com.android.server.wm.WindowManagerService.DragInputEventReceiver; import com.android.server.wm.WindowManagerService.H; import android.content.ClipData; @@ -28,7 +29,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 +50,7 @@ class DragState { float mCurrentX, mCurrentY; float mThumbOffsetX, mThumbOffsetY; InputChannel mServerChannel, mClientChannel; + DragInputEventReceiver mInputEventReceiver; InputApplicationHandle mDragApplicationHandle; InputWindowHandle mDragWindowHandle; WindowState mTargetWindow; @@ -90,8 +91,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 +140,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..121ce18 100644 --- a/services/java/com/android/server/wm/FakeWindowImpl.java +++ b/services/java/com/android/server/wm/FakeWindowImpl.java @@ -20,7 +20,7 @@ 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 +29,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 +44,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 +91,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/ScreenRotationAnimation.java b/services/java/com/android/server/wm/ScreenRotationAnimation.java index 8fc9a70..55fb038 100644 --- a/services/java/com/android/server/wm/ScreenRotationAnimation.java +++ b/services/java/com/android/server/wm/ScreenRotationAnimation.java @@ -17,13 +17,8 @@ package com.android.server.wm; 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,7 +29,8 @@ 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 int FREEZE_LAYER = WindowManagerService.TYPE_LAYER_MULTIPLIER * 200; @@ -49,11 +45,51 @@ class ScreenRotationAnimation { 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(); + + // 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(); + + // 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(); + + // 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(); + + // Complete transformations being applied. final Transformation mExitTransformation = new Transformation(); - Animation mEnterAnimation; final Transformation mEnterTransformation = new Transformation(); + boolean mStarted; + boolean mAnimRunning; + boolean mFinishAnimReady; + long mFinishAnimStartTime; final Matrix mSnapshotInitialMatrix = new Matrix(); final Matrix mSnapshotFinalMatrix = new Matrix(); @@ -133,7 +169,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,7 +203,7 @@ 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 @@ -176,46 +212,78 @@ class ScreenRotationAnimation { int delta = deltaRotation(rotation, mSnapshotRotation); 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); + return startAnimation(session, maxAnimationDuration, animationScale, + finalWidth, finalHeight, 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 (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); + 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 (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); 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); 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); 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); break; } @@ -224,35 +292,85 @@ class ScreenRotationAnimation { // 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 (firstStart) { + if (DEBUG_STATE) Slog.v(TAG, "Initializing start and finish animations"); + mStartEnterAnimation.initialize(finalWidth, finalHeight, + mOriginalWidth, mOriginalHeight); + mStartExitAnimation.initialize(finalWidth, finalHeight, + mOriginalWidth, mOriginalHeight); + mFinishEnterAnimation.initialize(finalWidth, finalHeight, + mOriginalWidth, mOriginalHeight); + mFinishExitAnimation.initialize(finalWidth, finalHeight, + mOriginalWidth, mOriginalHeight); + } + mRotateEnterAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight); + mRotateExitAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight); + mAnimRunning = false; + mFinishAnimReady = false; + mFinishAnimStartTime = -1; + + if (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); + } + mRotateExitAnimation.restrictDuration(maxAnimationDuration); + mRotateExitAnimation.scaleCurrentDuration(animationScale); + mRotateEnterAnimation.restrictDuration(maxAnimationDuration); + mRotateEnterAnimation.scaleCurrentDuration(animationScale); + + if (mBlackFrame == null) { + if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i( + WindowManagerService.TAG, + ">>> OPEN TRANSACTION ScreenRotationAnimation.startAnimation"); + Surface.openTransaction(); - mExitAnimation.restrictDuration(maxAnimationDuration); - mExitAnimation.scaleCurrentDuration(animationScale); - mEnterAnimation.restrictDuration(maxAnimationDuration); - mEnterAnimation.scaleCurrentDuration(animationScale); + try { + Rect outer = new Rect(-finalWidth*1, -finalHeight*1, 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 || DEBUG_STATE) Slog.i( + WindowManagerService.TAG, + "<<< CLOSE TRANSACTION ScreenRotationAnimation.startAnimation"); + } + } - if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS) Slog.i(WindowManagerService.TAG, - ">>> OPEN TRANSACTION ScreenRotationAnimation.dismiss"); - Surface.openTransaction(); + return true; + } - 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"); + /** + * 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, @@ -262,74 +380,198 @@ class ScreenRotationAnimation { } if (mBlackFrame != null) { mBlackFrame.kill(); + mBlackFrame = null; + } + if (mStartExitAnimation != null) { + mStartExitAnimation.cancel(); + mStartExitAnimation = null; + } + if (mStartEnterAnimation != null) { + mStartEnterAnimation.cancel(); + mStartEnterAnimation = null; } - if (mExitAnimation != null) { - mExitAnimation.cancel(); - mExitAnimation = null; + if (mFinishExitAnimation != null) { + mFinishExitAnimation.cancel(); + mFinishExitAnimation = null; } - if (mEnterAnimation != null) { - mEnterAnimation.cancel(); - mEnterAnimation = null; + if (mStartEnterAnimation != null) { + mStartEnterAnimation.cancel(); + mStartEnterAnimation = null; + } + if (mRotateExitAnimation != null) { + mRotateExitAnimation.cancel(); + mRotateExitAnimation = null; + } + if (mRotateEnterAnimation != null) { + mRotateEnterAnimation.cancel(); + mRotateEnterAnimation = null; } } public boolean isAnimating() { - return mEnterAnimation != null || mExitAnimation != null; + return mStartEnterAnimation != null || mStartExitAnimation != null + && mFinishEnterAnimation != null || mFinishExitAnimation != null + && mRotateEnterAnimation != null || mRotateExitAnimation != null; } public boolean stepAnimation(long now) { - if (mEnterAnimation == null && mExitAnimation == null) { + if (!isAnimating()) { + if (DEBUG_STATE) Slog.v(TAG, "Step: no animations running"); return false; } - if (!mStarted) { - if (mEnterAnimation != null) { - mEnterAnimation.setStartTime(now); + if (!mAnimRunning) { + if (DEBUG_STATE) Slog.v(TAG, "Step: starting start, finish, rotate"); + if (mStartEnterAnimation != null) { + mStartEnterAnimation.setStartTime(now); } - if (mExitAnimation != null) { - mExitAnimation.setStartTime(now); + if (mStartExitAnimation != null) { + mStartExitAnimation.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 (mFinishEnterAnimation != null) { + mFinishEnterAnimation.setStartTime(0); + } + if (mFinishExitAnimation != null) { + mFinishExitAnimation.setStartTime(0); + } + if (mRotateEnterAnimation != null) { + mRotateEnterAnimation.setStartTime(now); } + if (mRotateExitAnimation != null) { + mRotateExitAnimation.setStartTime(now); + } + mAnimRunning = true; } - 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(); - } - } else { - if (mBlackFrame != null) { - mBlackFrame.setMatrix(mEnterTransformation.getMatrix()); - } + if (mFinishAnimReady && mFinishAnimStartTime < 0) { + if (DEBUG_STATE) Slog.v(TAG, "Step: finish anim now ready"); + mFinishAnimStartTime = now; + } + + // If the start animation is no longer running, we want to keep its + // transformation intact until the finish animation also completes. + + boolean moreStartExit = false; + if (mStartExitAnimation != null) { + mStartExitTransformation.clear(); + moreStartExit = mStartExitAnimation.getTransformation(now, mStartExitTransformation); + if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped start exit: " + mStartExitTransformation); + if (!moreStartExit) { + if (DEBUG_STATE) Slog.v(TAG, "Start exit animation done!"); + mStartExitAnimation.cancel(); + mStartExitAnimation = null; + } + } + + boolean moreStartEnter = false; + if (mStartEnterAnimation != null) { + mStartEnterTransformation.clear(); + moreStartEnter = mStartEnterAnimation.getTransformation(now, mStartEnterTransformation); + if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped start enter: " + mStartEnterTransformation); + if (!moreStartEnter) { + if (DEBUG_STATE) Slog.v(TAG, "Start enter animation done!"); + mStartEnterAnimation.cancel(); + mStartEnterAnimation = null; + } + } + + long finishNow = mFinishAnimReady ? (now - mFinishAnimStartTime) : 0; + if (DEBUG_STATE) Slog.v(TAG, "Step: finishNow=" + finishNow); + + mFinishExitTransformation.clear(); + boolean moreFinishExit = false; + if (mFinishExitAnimation != null) { + moreFinishExit = mFinishExitAnimation.getTransformation(finishNow, mFinishExitTransformation); + if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped finish exit: " + mFinishExitTransformation); + if (!moreStartExit && !moreFinishExit) { + if (DEBUG_STATE) Slog.v(TAG, "Finish exit animation done, clearing start/finish anims!"); + mStartExitTransformation.clear(); + mFinishExitAnimation.cancel(); + mFinishExitAnimation = null; + mFinishExitTransformation.clear(); + } + } + + mFinishEnterTransformation.clear(); + boolean moreFinishEnter = false; + if (mFinishEnterAnimation != null) { + moreFinishEnter = mFinishEnterAnimation.getTransformation(finishNow, mFinishEnterTransformation); + if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped finish enter: " + mFinishEnterTransformation); + if (!moreStartEnter && !moreFinishEnter) { + if (DEBUG_STATE) Slog.v(TAG, "Finish enter animation done, clearing start/finish anims!"); + mStartEnterTransformation.clear(); + mFinishEnterAnimation.cancel(); + mFinishEnterAnimation = null; + mFinishEnterTransformation.clear(); + } + } + + mRotateExitTransformation.clear(); + boolean moreRotateExit = false; + if (mRotateExitAnimation != null) { + moreRotateExit = mRotateExitAnimation.getTransformation(now, mRotateExitTransformation); + if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped rotate exit: " + mRotateExitTransformation); + } + + if (!moreFinishExit && !moreRotateExit) { + if (DEBUG_STATE) Slog.v(TAG, "Rotate exit animation done!"); + mRotateExitAnimation.cancel(); + mRotateExitAnimation = null; + mRotateExitTransformation.clear(); + } + + mRotateEnterTransformation.clear(); + boolean moreRotateEnter = false; + if (mRotateEnterAnimation != null) { + moreRotateEnter = mRotateEnterAnimation.getTransformation(now, mRotateEnterTransformation); + if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped rotate enter: " + mRotateEnterTransformation); + } + + if (!moreFinishEnter && !moreRotateEnter) { + if (DEBUG_STATE) Slog.v(TAG, "Rotate enter animation done!"); + mRotateEnterAnimation.cancel(); + mRotateEnterAnimation = null; + mRotateEnterTransformation.clear(); + } + + mExitTransformation.set(mRotateExitTransformation); + mExitTransformation.compose(mStartExitTransformation); + mExitTransformation.compose(mFinishExitTransformation); + + mEnterTransformation.set(mRotateEnterTransformation); + 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 (!moreStartExit && !moreFinishExit && !moreRotateExit) { + if (mSurface != null) { + if (DEBUG_STATE) Slog.v(TAG, "Exit animations done, hiding screenshot surface"); + mSurface.hide(); + } + } + + if (!moreStartEnter && !moreFinishEnter && !moreRotateEnter) { + if (mBlackFrame != null) { + if (DEBUG_STATE) Slog.v(TAG, "Enter animations done, hiding black frame"); + mBlackFrame.hide(); + } + } else { + if (mBlackFrame != null) { + mBlackFrame.setMatrix(mEnterTransformation.getMatrix()); } } mSnapshotFinalMatrix.setConcat(mExitTransformation.getMatrix(), mSnapshotInitialMatrix); setSnapshotTransform(mSnapshotFinalMatrix, mExitTransformation.getAlpha()); - return moreEnter || moreExit; + final boolean more = moreStartEnter || moreStartExit || moreFinishEnter || moreFinishExit + || moreRotateEnter || moreRotateExit || !mFinishAnimReady; + + if (DEBUG_STATE) Slog.v(TAG, "Step: more=" + more); + + return more; } public Transformation getEnterTransformation() { diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index bd33e0c..620d74c 100644 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -36,7 +36,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import com.android.internal.app.IBatteryStats; 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; @@ -96,6 +95,7 @@ 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 +107,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; @@ -143,7 +142,8 @@ import java.util.List; /** {@hide} */ public class WindowManagerService extends IWindowManager.Stub - implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs { + implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs, + Choreographer.OnAnimateListener { static final String TAG = "WindowManager"; static final boolean DEBUG = false; static final boolean DEBUG_ADD_REMOVE = false; @@ -458,7 +458,7 @@ public class WindowManagerService extends IWindowManager.Stub int mDeferredRotationPauseCount; boolean mLayoutNeeded = true; - boolean mAnimationPending = false; + boolean mTraversalScheduled = false; boolean mDisplayFrozen = false; boolean mWaitingForConfig = false; boolean mWindowsFreezingScreen = false; @@ -505,7 +505,9 @@ 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; @@ -561,6 +563,7 @@ public class WindowManagerService extends IWindowManager.Stub float mWindowAnimationScale = 1.0f; float mTransitionAnimationScale = 1.0f; + float mAnimatorDurationScale = 1.0f; final InputManager mInputManager; @@ -571,18 +574,25 @@ public class WindowManagerService extends IWindowManager.Stub boolean mTurnOnScreen; DragState mDragState = null; - final InputHandler mDragInputHandler = new BaseInputHandler() { + + final class DragInputEventReceiver extends InputEventReceiver { + public DragInputEventReceiver(InputChannel inputChannel, Looper looper) { + super(inputChannel, looper); + } + @Override - public void handleMotion(MotionEvent event, InputQueue.FinishedCallback finishedCallback) { + 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 +633,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 @@ -686,6 +696,7 @@ public class WindowManagerService extends IWindowManager.Stub Looper.prepare(); WindowManagerService s = new WindowManagerService(mContext, mPM, mHaveInputMethods, mAllowBootMessages); + s.mChoreographer.addOnAnimateListener(s); android.os.Process.setThreadPriority( android.os.Process.THREAD_PRIORITY_DISPLAY); android.os.Process.setCanSelfBackground(false); @@ -769,6 +780,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(); @@ -3448,7 +3461,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; @@ -4652,6 +4665,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 @@ -4671,6 +4685,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 @@ -4681,12 +4698,14 @@ 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 }; + return new float[] { mWindowAnimationScale, mTransitionAnimationScale, + mAnimatorDurationScale }; } public int getSwitchState(int sw) { @@ -5364,6 +5383,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"); @@ -5374,7 +5401,11 @@ public class WindowManagerService extends IWindowManager.Stub // it doesn't support hardware OpenGL emulation yet. if (CUSTOM_SCREEN_ROTATION && mScreenRotationAnimation != null && mScreenRotationAnimation.hasScreenshot()) { - mScreenRotationAnimation.setRotation(rotation); + if (mScreenRotationAnimation.setRotation(rotation, mFxSession, + MAX_ANIMATION_DURATION, mTransitionAnimationScale, + mCurDisplayWidth, mCurDisplayHeight)) { + mChoreographer.scheduleAnimation(); + } } Surface.setOrientation(0, rotation); } finally { @@ -5862,7 +5893,7 @@ 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; @@ -6013,12 +6044,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 @@ -6052,13 +6081,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); @@ -6080,36 +6113,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); + computeSmallestWidthAndScreenLayout(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; } @@ -6491,7 +6527,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; @@ -6585,9 +6621,9 @@ public class WindowManagerService extends IWindowManager.Stub } } break; - case ANIMATE: { + case DO_TRAVERSAL: { synchronized(mWindowMap) { - mAnimationPending = false; + mTraversalScheduled = false; performLayoutAndPlaceSurfacesLocked(); } } break; @@ -6803,12 +6839,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 (mChoreographer.isAnimationScheduled()) { // 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), @@ -7116,7 +7154,7 @@ public class WindowManagerService extends IWindowManager.Stub boolean configChanged = updateOrientationFromAppTokensLocked(false); mTempConfiguration.setToDefaults(); mTempConfiguration.fontScale = mCurConfiguration.fontScale; - if (computeNewConfigurationLocked(mTempConfiguration)) { + if (computeScreenConfigurationLocked(mTempConfiguration)) { if (mCurConfiguration.diff(mTempConfiguration) != 0) { configChanged = true; } @@ -7367,7 +7405,7 @@ public class WindowManagerService extends IWindowManager.Stub } else { mInLayout = false; if (mLayoutNeeded) { - requestAnimationLocked(0); + requestTraversalLocked(); } } if (mWindowsChanged && !mWindowChangeListeners.isEmpty()) { @@ -8800,10 +8838,9 @@ public class WindowManagerService extends IWindowManager.Stub needRelayout = adjustWallpaperWindowsLocked() != 0; } if (needRelayout) { - requestAnimationLocked(0); + requestTraversalLocked(); } else if (animating) { - final int refreshTimeUs = (int)(1000 / mDisplay.getRefreshRate()); - requestAnimationLocked(currentTime + refreshTimeUs - SystemClock.uptimeMillis()); + mChoreographer.scheduleAnimation(); } // Finally update all input windows now that the window changes have stabilized. @@ -8922,10 +8959,17 @@ 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); + } + } + + @Override + public void onAnimate() { + synchronized(mWindowMap) { + performLayoutAndPlaceSurfacesLocked(); } } @@ -9245,7 +9289,7 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_ORIENTATION) Slog.i(TAG, "**** Dismissing screen rotation animation"); if (mScreenRotationAnimation.dismiss(mFxSession, MAX_ANIMATION_DURATION, mTransitionAnimationScale, mCurDisplayWidth, mCurDisplayHeight)) { - requestAnimationLocked(0); + mChoreographer.scheduleAnimation(); } else { mScreenRotationAnimation = null; updateRotation = true; @@ -9391,11 +9435,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()) { @@ -9735,9 +9781,10 @@ public class WindowManagerService extends IWindowManager.Stub 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(" mTraversalScheduled="); pw.print(mTraversalScheduled); pw.print(" mWindowAnimationScale="); pw.print(mWindowAnimationScale); pw.print(" mTransitionWindowAnimationScale="); pw.println(mTransitionAnimationScale); + pw.print(" mAnimatorDurationScale="); pw.println(mAnimatorDurationScale); pw.print(" mNextAppTransition=0x"); pw.print(Integer.toHexString(mNextAppTransition)); pw.print(" mAppTransitionReady="); pw.println(mAppTransitionReady); diff --git a/services/java/com/android/server/wm/WindowState.java b/services/java/com/android/server/wm/WindowState.java index 1067cad..6868cf6 100644 --- a/services/java/com/android/server/wm/WindowState.java +++ b/services/java/com/android/server/wm/WindowState.java @@ -1593,7 +1593,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { mService.applyAnimationLocked(this, WindowManagerPolicy.TRANSIT_ENTER, true); } if (requestAnim) { - mService.requestAnimationLocked(0); + mService.mChoreographer.scheduleAnimation(); } return true; } @@ -1634,7 +1634,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { } } if (requestAnim) { - mService.requestAnimationLocked(0); + mService.mChoreographer.scheduleAnimation(); } return true; } |