summaryrefslogtreecommitdiffstats
path: root/services/appwidget
diff options
context:
space:
mode:
authorAmith Yamasani <yamasani@google.com>2013-11-22 08:25:26 -0800
committerAmith Yamasani <yamasani@google.com>2013-12-19 15:25:37 -0800
commit9158825f9c41869689d6b1786d7c7aa8bdd524ce (patch)
treef41944461539f0c70030668b4558296469c307d3 /services/appwidget
parent30d032928a294fbb6f385e9d0367a75b7bf2649b (diff)
downloadframeworks_base-9158825f9c41869689d6b1786d7c7aa8bdd524ce.zip
frameworks_base-9158825f9c41869689d6b1786d7c7aa8bdd524ce.tar.gz
frameworks_base-9158825f9c41869689d6b1786d7c7aa8bdd524ce.tar.bz2
Move some system services to separate directories
Refactored the directory structure so that services can be optionally excluded. This is step 1. Will be followed by another change that makes it possible to remove services from the build. Change-Id: Ideacedfd34b5e213217ad3ff4ebb21c4a8e73f85
Diffstat (limited to 'services/appwidget')
-rw-r--r--services/appwidget/java/com/android/server/appwidget/AppWidgetService.java363
-rw-r--r--services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java2126
-rw-r--r--services/appwidget/java/service.mk11
3 files changed, 2500 insertions, 0 deletions
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetService.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetService.java
new file mode 100644
index 0000000..6fd8871
--- /dev/null
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetService.java
@@ -0,0 +1,363 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appwidget;
+
+import android.app.ActivityManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Slog;
+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.BackgroundThread;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.Locale;
+
+
+/**
+ * Redirects calls to this service to the instance of the service for the appropriate user.
+ */
+public class AppWidgetService extends IAppWidgetService.Stub
+{
+ private static final String TAG = "AppWidgetService";
+
+ Context mContext;
+ Locale mLocale;
+ PackageManager mPackageManager;
+ boolean mSafeMode;
+ private final Handler mSaveStateHandler;
+
+ private final SparseArray<AppWidgetServiceImpl> mAppWidgetServices;
+
+ public AppWidgetService(Context context) {
+ mContext = context;
+
+ mSaveStateHandler = BackgroundThread.getHandler();
+
+ mAppWidgetServices = new SparseArray<AppWidgetServiceImpl>(5);
+ AppWidgetServiceImpl primary = new AppWidgetServiceImpl(context, 0, mSaveStateHandler);
+ mAppWidgetServices.append(0, primary);
+ }
+
+ public void systemRunning(boolean safeMode) {
+ mSafeMode = safeMode;
+
+ 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,
+ // because the system isn't ready to handle them yet.
+ mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
+ new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null);
+
+ // Register for configuration changes so we can update the names
+ // of the widgets when the locale changes.
+ mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
+ new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED), null, null);
+
+ // Register for broadcasts about package install, etc., so we can
+ // update the provider list.
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addDataScheme("package");
+ mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
+ filter, null, null);
+ // Register for events related to sdcard installation.
+ IntentFilter sdFilter = new IntentFilter();
+ sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+ sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
+ mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
+ sdFilter, null, null);
+
+ IntentFilter userFilter = new IntentFilter();
+ userFilter.addAction(Intent.ACTION_USER_REMOVED);
+ userFilter.addAction(Intent.ACTION_USER_STOPPING);
+ mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
+ onUserRemoved(intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
+ UserHandle.USER_NULL));
+ } else if (Intent.ACTION_USER_STOPPING.equals(intent.getAction())) {
+ onUserStopping(intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
+ UserHandle.USER_NULL));
+ }
+ }
+ }, userFilter);
+ }
+
+ @Override
+ public int allocateAppWidgetId(String packageName, int hostId, int userId)
+ throws RemoteException {
+ return getImplForUser(userId).allocateAppWidgetId(packageName, hostId);
+ }
+
+ @Override
+ public int[] getAppWidgetIdsForHost(int hostId, int userId) throws RemoteException {
+ return getImplForUser(userId).getAppWidgetIdsForHost(hostId);
+ }
+
+ @Override
+ public void deleteAppWidgetId(int appWidgetId, int userId) throws RemoteException {
+ getImplForUser(userId).deleteAppWidgetId(appWidgetId);
+ }
+
+ @Override
+ public void deleteHost(int hostId, int userId) throws RemoteException {
+ getImplForUser(userId).deleteHost(hostId);
+ }
+
+ @Override
+ public void deleteAllHosts(int userId) throws RemoteException {
+ getImplForUser(userId).deleteAllHosts();
+ }
+
+ @Override
+ public void bindAppWidgetId(int appWidgetId, ComponentName provider, Bundle options, int userId)
+ throws RemoteException {
+ getImplForUser(userId).bindAppWidgetId(appWidgetId, provider, options);
+ }
+
+ @Override
+ public boolean bindAppWidgetIdIfAllowed(
+ String packageName, int appWidgetId, ComponentName provider, Bundle options, int userId)
+ throws RemoteException {
+ return getImplForUser(userId).bindAppWidgetIdIfAllowed(
+ packageName, appWidgetId, provider, options);
+ }
+
+ @Override
+ public boolean hasBindAppWidgetPermission(String packageName, int userId)
+ throws RemoteException {
+ return getImplForUser(userId).hasBindAppWidgetPermission(packageName);
+ }
+
+ @Override
+ public void setBindAppWidgetPermission(String packageName, boolean permission, int userId)
+ throws RemoteException {
+ getImplForUser(userId).setBindAppWidgetPermission(packageName, permission);
+ }
+
+ @Override
+ public void bindRemoteViewsService(int appWidgetId, Intent intent, IBinder connection,
+ int userId) throws RemoteException {
+ getImplForUser(userId).bindRemoteViewsService(appWidgetId, intent, connection);
+ }
+
+ @Override
+ public int[] startListening(IAppWidgetHost host, String packageName, int hostId,
+ List<RemoteViews> updatedViews, int userId) throws RemoteException {
+ return getImplForUser(userId).startListening(host, packageName, hostId, updatedViews);
+ }
+
+ public void onUserRemoved(int userId) {
+ if (userId < 1) return;
+ synchronized (mAppWidgetServices) {
+ AppWidgetServiceImpl impl = mAppWidgetServices.get(userId);
+ mAppWidgetServices.remove(userId);
+
+ if (impl == null) {
+ AppWidgetServiceImpl.getSettingsFile(userId).delete();
+ } else {
+ impl.onUserRemoved();
+ }
+ }
+ }
+
+ public void onUserStopping(int userId) {
+ if (userId < 1) return;
+ synchronized (mAppWidgetServices) {
+ AppWidgetServiceImpl impl = mAppWidgetServices.get(userId);
+ if (impl != null) {
+ mAppWidgetServices.remove(userId);
+ impl.onUserStopping();
+ }
+ }
+ }
+
+ private void checkPermission(int userId) {
+ int realUserId = ActivityManager.handleIncomingUser(
+ Binder.getCallingPid(),
+ Binder.getCallingUid(),
+ userId,
+ false, /* allowAll */
+ true, /* requireFull */
+ this.getClass().getSimpleName(),
+ this.getClass().getPackage().getName());
+ }
+
+ private AppWidgetServiceImpl getImplForUser(int userId) {
+ checkPermission(userId);
+ boolean sendInitial = false;
+ AppWidgetServiceImpl service;
+ synchronized (mAppWidgetServices) {
+ service = mAppWidgetServices.get(userId);
+ if (service == null) {
+ Slog.i(TAG, "Unable to find AppWidgetServiceImpl for user " + userId + ", adding");
+ // TODO: Verify that it's a valid user
+ service = new AppWidgetServiceImpl(mContext, userId, mSaveStateHandler);
+ service.systemReady(mSafeMode);
+ // Assume that BOOT_COMPLETED was received, as this is a non-primary user.
+ mAppWidgetServices.append(userId, service);
+ sendInitial = true;
+ }
+ }
+ if (sendInitial) {
+ service.sendInitialBroadcasts();
+ }
+ return service;
+ }
+
+ @Override
+ public int[] getAppWidgetIds(ComponentName provider, int userId) throws RemoteException {
+ return getImplForUser(userId).getAppWidgetIds(provider);
+ }
+
+ @Override
+ public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId, int userId)
+ throws RemoteException {
+ return getImplForUser(userId).getAppWidgetInfo(appWidgetId);
+ }
+
+ @Override
+ public RemoteViews getAppWidgetViews(int appWidgetId, int userId) throws RemoteException {
+ return getImplForUser(userId).getAppWidgetViews(appWidgetId);
+ }
+
+ @Override
+ public void updateAppWidgetOptions(int appWidgetId, Bundle options, int userId) {
+ getImplForUser(userId).updateAppWidgetOptions(appWidgetId, options);
+ }
+
+ @Override
+ public Bundle getAppWidgetOptions(int appWidgetId, int userId) {
+ return getImplForUser(userId).getAppWidgetOptions(appWidgetId);
+ }
+
+ @Override
+ public List<AppWidgetProviderInfo> getInstalledProviders(int categoryFilter, int userId)
+ throws RemoteException {
+ return getImplForUser(userId).getInstalledProviders(categoryFilter);
+ }
+
+ @Override
+ public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId, int userId)
+ throws RemoteException {
+ getImplForUser(userId).notifyAppWidgetViewDataChanged(
+ appWidgetIds, viewId);
+ }
+
+ @Override
+ public void partiallyUpdateAppWidgetIds(int[] appWidgetIds, RemoteViews views, int userId)
+ throws RemoteException {
+ getImplForUser(userId).partiallyUpdateAppWidgetIds(
+ appWidgetIds, views);
+ }
+
+ @Override
+ public void stopListening(int hostId, int userId) throws RemoteException {
+ getImplForUser(userId).stopListening(hostId);
+ }
+
+ @Override
+ public void unbindRemoteViewsService(int appWidgetId, Intent intent, int userId)
+ throws RemoteException {
+ getImplForUser(userId).unbindRemoteViewsService(
+ appWidgetId, intent);
+ }
+
+ @Override
+ public void updateAppWidgetIds(int[] appWidgetIds, RemoteViews views, int userId)
+ throws RemoteException {
+ getImplForUser(userId).updateAppWidgetIds(appWidgetIds, views);
+ }
+
+ @Override
+ public void updateAppWidgetProvider(ComponentName provider, RemoteViews views, int userId)
+ throws RemoteException {
+ getImplForUser(userId).updateAppWidgetProvider(provider, views);
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
+
+ // Dump the state of all the app widget providers
+ synchronized (mAppWidgetServices) {
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+ for (int i = 0; i < mAppWidgetServices.size(); i++) {
+ pw.println("User: " + mAppWidgetServices.keyAt(i));
+ ipw.increaseIndent();
+ AppWidgetServiceImpl service = mAppWidgetServices.valueAt(i);
+ service.dump(fd, ipw, args);
+ ipw.decreaseIndent();
+ }
+ }
+ }
+
+ BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ // Slog.d(TAG, "received " + action);
+ if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
+ int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+ if (userId >= 0) {
+ getImplForUser(userId).sendInitialBroadcasts();
+ } else {
+ Slog.w(TAG, "Incorrect user handle supplied in " + intent);
+ }
+ } else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) {
+ for (int i = 0; i < mAppWidgetServices.size(); i++) {
+ AppWidgetServiceImpl service = mAppWidgetServices.valueAt(i);
+ service.onConfigurationChanged();
+ }
+ } else {
+ int sendingUser = getSendingUserId();
+ if (sendingUser == UserHandle.USER_ALL) {
+ for (int i = 0; i < mAppWidgetServices.size(); i++) {
+ AppWidgetServiceImpl service = mAppWidgetServices.valueAt(i);
+ service.onBroadcastReceived(intent);
+ }
+ } else {
+ AppWidgetServiceImpl service = mAppWidgetServices.get(sendingUser);
+ if (service != null) {
+ service.onBroadcastReceived(intent);
+ }
+ }
+ }
+ }
+ };
+}
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
new file mode 100644
index 0000000..98dead3
--- /dev/null
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -0,0 +1,2126 @@
+/*
+ * 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.appwidget;
+
+import android.app.AlarmManager;
+import android.app.AppGlobals;
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.Intent.FilterComparison;
+import android.content.ServiceConnection;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.Point;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.AtomicFile;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Slog;
+import android.util.TypedValue;
+import android.util.Xml;
+import android.view.Display;
+import android.view.WindowManager;
+import android.widget.RemoteViews;
+
+import com.android.internal.appwidget.IAppWidgetHost;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.widget.IRemoteViewsAdapterConnection;
+import com.android.internal.widget.IRemoteViewsFactory;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+
+class AppWidgetServiceImpl {
+
+ private static final String KEYGUARD_HOST_PACKAGE = "com.android.keyguard";
+ private static final int KEYGUARD_HOST_ID = 0x4b455947;
+ 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
+ private static final int CURRENT_VERSION = 1; // Bump if the stored widgets need to be upgraded.
+
+ private static boolean DBG = false;
+
+ /*
+ * 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)
+
+ boolean uidMatches(int callingUid) {
+ if (UserHandle.getAppId(callingUid) == Process.myUid()) {
+ // For a host that's in the system process, ignore the user id
+ return UserHandle.isSameApp(this.uid, callingUid);
+ } else {
+ return this.uid == callingUid;
+ }
+ }
+ }
+
+ static class AppWidgetId {
+ int appWidgetId;
+ Provider provider;
+ RemoteViews views;
+ Bundle options;
+ 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>>();
+
+ final Context mContext;
+ final IPackageManager mPm;
+ final AlarmManager mAlarmManager;
+ final ArrayList<Provider> mInstalledProviders = new ArrayList<Provider>();
+ final int mUserId;
+ final boolean mHasFeature;
+
+ Locale mLocale;
+ int mNextAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID + 1;
+ final ArrayList<AppWidgetId> mAppWidgetIds = new ArrayList<AppWidgetId>();
+ final ArrayList<Host> mHosts = new ArrayList<Host>();
+ // set of package names
+ final HashSet<String> mPackagesWithBindWidgetPermission = new HashSet<String>();
+ boolean mSafeMode;
+ boolean mStateLoaded;
+ int mMaxWidgetBitmapMemory;
+
+ private final Handler mSaveStateHandler;
+
+ // 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, Handler saveStateHandler) {
+ mContext = context;
+ mPm = AppGlobals.getPackageManager();
+ mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+ mUserId = userId;
+ mSaveStateHandler = saveStateHandler;
+ mHasFeature = context.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_APP_WIDGETS);
+ computeMaximumWidgetBitmapMemory();
+ }
+
+ void computeMaximumWidgetBitmapMemory() {
+ WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+ Display display = wm.getDefaultDisplay();
+ Point size = new Point();
+ display.getRealSize(size);
+ // Cap memory usage at 1.5 times the size of the display
+ // 1.5 * 4 bytes/pixel * w * h ==> 6 * w * h
+ mMaxWidgetBitmapMemory = 6 * size.x * size.y;
+ }
+
+ public void systemReady(boolean safeMode) {
+ mSafeMode = safeMode;
+
+ synchronized (mAppWidgetIds) {
+ ensureStateLoadedLocked();
+ }
+ }
+
+ private void log(String msg) {
+ Slog.i(TAG, "u=" + mUserId + ": " + msg);
+ }
+
+ void onConfigurationChanged() {
+ if (DBG) log("Got onConfigurationChanged()");
+ Locale revised = Locale.getDefault();
+ if (revised == null || mLocale == null || !(revised.equals(mLocale))) {
+ mLocale = revised;
+
+ synchronized (mAppWidgetIds) {
+ ensureStateLoadedLocked();
+ // Note: updateProvidersForPackageLocked() may remove providers, so we must copy the
+ // list of installed providers and skip providers that we don't need to update.
+ // Also note that remove the provider does not clear the Provider component data.
+ ArrayList<Provider> installedProviders =
+ new ArrayList<Provider>(mInstalledProviders);
+ HashSet<ComponentName> removedProviders = new HashSet<ComponentName>();
+ int N = installedProviders.size();
+ for (int i = N - 1; i >= 0; i--) {
+ Provider p = installedProviders.get(i);
+ ComponentName cn = p.info.provider;
+ if (!removedProviders.contains(cn)) {
+ updateProvidersForPackageLocked(cn.getPackageName(), removedProviders);
+ }
+ }
+ saveStateAsync();
+ }
+ }
+ }
+
+ void onBroadcastReceived(Intent intent) {
+ if (DBG) log("onBroadcast " + intent);
+ final String action = intent.getAction();
+ boolean added = false;
+ boolean changed = false;
+ boolean providersModified = false;
+ String pkgList[] = null;
+ if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
+ pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ 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
+ providersModified |= updateProvidersForPackageLocked(pkgName, null);
+ }
+ } else {
+ // The package was just added
+ for (String pkgName : pkgList) {
+ providersModified |= addProvidersForPackageLocked(pkgName);
+ }
+ }
+ saveStateAsync();
+ }
+ } 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) {
+ providersModified |= removeProvidersForPackageLocked(pkgName);
+ saveStateAsync();
+ }
+ }
+ }
+ }
+
+ if (providersModified) {
+ // If the set of providers has been modified, notify each active AppWidgetHost
+ synchronized (mAppWidgetIds) {
+ ensureStateLoadedLocked();
+ notifyHostsForProvidersChangedLocked();
+ }
+ }
+ }
+
+ private void dumpProvider(Provider p, int index, PrintWriter pw) {
+ 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(info.widgetCategory);
+ pw.print(" autoAdvanceViewId=");
+ pw.print(info.autoAdvanceViewId);
+ pw.print(" initialLayout=#");
+ pw.print(Integer.toHexString(info.initialLayout));
+ pw.print(" uid="); pw.print(p.uid);
+ 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) {
+ if (!mHasFeature) {
+ return;
+ }
+ loadAppWidgetListLocked();
+ loadStateLocked();
+ mStateLoaded = true;
+ }
+ }
+
+ public int allocateAppWidgetId(String packageName, int hostId) {
+ int callingUid = enforceSystemOrCallingUid(packageName);
+ synchronized (mAppWidgetIds) {
+ if (!mHasFeature) {
+ return -1;
+ }
+ 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);
+
+ saveStateAsync();
+ if (DBG) log("Allocating AppWidgetId for " + packageName + " host=" + hostId
+ + " id=" + appWidgetId);
+ return appWidgetId;
+ }
+ }
+
+ public void deleteAppWidgetId(int appWidgetId) {
+ synchronized (mAppWidgetIds) {
+ if (!mHasFeature) {
+ return;
+ }
+ ensureStateLoadedLocked();
+ AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
+ if (id != null) {
+ deleteAppWidgetLocked(id);
+ saveStateAsync();
+ }
+ }
+ }
+
+ public void deleteHost(int hostId) {
+ synchronized (mAppWidgetIds) {
+ if (!mHasFeature) {
+ return;
+ }
+ ensureStateLoadedLocked();
+ int callingUid = Binder.getCallingUid();
+ Host host = lookupHostLocked(callingUid, hostId);
+ if (host != null) {
+ deleteHostLocked(host);
+ saveStateAsync();
+ }
+ }
+ }
+
+ public void deleteAllHosts() {
+ synchronized (mAppWidgetIds) {
+ if (!mHasFeature) {
+ return;
+ }
+ 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.uidMatches(callingUid)) {
+ deleteHostLocked(host);
+ changed = true;
+ }
+ }
+ if (changed) {
+ saveStateAsync();
+ }
+ }
+ }
+
+ 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.sendBroadcastAsUser(intent, new UserHandle(mUserId));
+ if (p.instances.size() == 0) {
+ // cancel the future updates
+ cancelBroadcasts(p);
+
+ // send the broacast saying that the provider is not in use any more
+ intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DISABLED);
+ intent.setComponent(p.info.provider);
+ mContext.sendBroadcastAsUser(intent, new UserHandle(mUserId));
+ }
+ }
+ }
+ }
+
+ void cancelBroadcasts(Provider p) {
+ if (DBG) log("cancelBroadcasts for " + p);
+ if (p.broadcast != null) {
+ mAlarmManager.cancel(p.broadcast);
+ long token = Binder.clearCallingIdentity();
+ try {
+ p.broadcast.cancel();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ p.broadcast = null;
+ }
+ }
+
+ private void bindAppWidgetIdImpl(int appWidgetId, ComponentName provider, Bundle options) {
+ if (DBG) log("bindAppWidgetIdImpl appwid=" + appWidgetId
+ + " provider=" + provider);
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mAppWidgetIds) {
+ if (!mHasFeature) {
+ return;
+ }
+ options = cloneIfLocalBinder(options);
+ 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;
+ if (options == null) {
+ options = new Bundle();
+ }
+ id.options = options;
+
+ // We need to provide a default value for the widget category if it is not specified
+ if (!options.containsKey(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY)) {
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
+ AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN);
+ }
+
+ p.instances.add(id);
+ int instancesSize = p.instances.size();
+ if (instancesSize == 1) {
+ // 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));
+ saveStateAsync();
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ public void bindAppWidgetId(int appWidgetId, ComponentName provider, Bundle options) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BIND_APPWIDGET,
+ "bindAppWidgetId appWidgetId=" + appWidgetId + " provider=" + provider);
+ bindAppWidgetIdImpl(appWidgetId, provider, options);
+ }
+
+ public boolean bindAppWidgetIdIfAllowed(
+ String packageName, int appWidgetId, ComponentName provider, Bundle options) {
+ if (!mHasFeature) {
+ return false;
+ }
+ try {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BIND_APPWIDGET, null);
+ } catch (SecurityException se) {
+ if (!callerHasBindAppWidgetPermission(packageName)) {
+ return false;
+ }
+ }
+ bindAppWidgetIdImpl(appWidgetId, provider, options);
+ return true;
+ }
+
+ private boolean callerHasBindAppWidgetPermission(String packageName) {
+ int callingUid = Binder.getCallingUid();
+ try {
+ if (!UserHandle.isSameApp(callingUid, getUidForPackage(packageName))) {
+ return false;
+ }
+ } catch (Exception e) {
+ return false;
+ }
+ synchronized (mAppWidgetIds) {
+ ensureStateLoadedLocked();
+ return mPackagesWithBindWidgetPermission.contains(packageName);
+ }
+ }
+
+ public boolean hasBindAppWidgetPermission(String packageName) {
+ if (!mHasFeature) {
+ return false;
+ }
+ mContext.enforceCallingPermission(
+ android.Manifest.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS,
+ "hasBindAppWidgetPermission packageName=" + packageName);
+
+ synchronized (mAppWidgetIds) {
+ ensureStateLoadedLocked();
+ return mPackagesWithBindWidgetPermission.contains(packageName);
+ }
+ }
+
+ public void setBindAppWidgetPermission(String packageName, boolean permission) {
+ if (!mHasFeature) {
+ return;
+ }
+ mContext.enforceCallingPermission(
+ android.Manifest.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS,
+ "setBindAppWidgetPermission packageName=" + packageName);
+
+ synchronized (mAppWidgetIds) {
+ ensureStateLoadedLocked();
+ if (permission) {
+ mPackagesWithBindWidgetPermission.add(packageName);
+ } else {
+ mPackagesWithBindWidgetPermission.remove(packageName);
+ }
+ saveStateAsync();
+ }
+ }
+
+ // Binds to a specific RemoteViewsService
+ public void bindRemoteViewsService(int appWidgetId, Intent intent, IBinder connection) {
+ synchronized (mAppWidgetIds) {
+ if (!mHasFeature) {
+ return;
+ }
+ ensureStateLoadedLocked();
+ AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
+ if (id == null) {
+ throw new IllegalArgumentException("bad appWidgetId");
+ }
+ final ComponentName componentName = intent.getComponent();
+ try {
+ final ServiceInfo si = AppGlobals.getPackageManager().getServiceInfo(componentName,
+ PackageManager.GET_PERMISSIONS, mUserId);
+ if (!android.Manifest.permission.BIND_REMOTEVIEWS.equals(si.permission)) {
+ throw new SecurityException("Selected service does not require "
+ + android.Manifest.permission.BIND_REMOTEVIEWS + ": " + componentName);
+ }
+ } catch (RemoteException e) {
+ throw new IllegalArgumentException("Unknown component " + componentName);
+ }
+
+ // If there is already a connection made for this service intent, then disconnect from
+ // that first. (This does not allow multiple connections to the same service under
+ // the same key)
+ ServiceConnectionProxy conn = null;
+ FilterComparison fc = new FilterComparison(intent);
+ Pair<Integer, FilterComparison> key = Pair.create(appWidgetId, fc);
+ if (mBoundRemoteViewsServices.containsKey(key)) {
+ conn = (ServiceConnectionProxy) mBoundRemoteViewsServices.get(key);
+ conn.disconnect();
+ mContext.unbindService(conn);
+ mBoundRemoteViewsServices.remove(key);
+ }
+
+ int userId = UserHandle.getUserId(id.provider.uid);
+ if (userId != mUserId) {
+ Slog.w(TAG, "AppWidgetServiceImpl of user " + mUserId
+ + " binding to provider on user " + userId);
+ }
+ // 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.bindServiceAsUser(intent, conn, Context.BIND_AUTO_CREATE,
+ new UserHandle(userId));
+ mBoundRemoteViewsServices.put(key, conn);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+
+ // Add it to the mapping of RemoteViewsService to appWidgetIds so that we can determine
+ // when we can call back to the RemoteViewsService later to destroy associated
+ // factories.
+ incrementAppWidgetServiceRefCount(appWidgetId, fc);
+ }
+ }
+
+ // Unbinds from a specific RemoteViewsService
+ public void unbindRemoteViewsService(int appWidgetId, Intent intent) {
+ synchronized (mAppWidgetIds) {
+ if (!mHasFeature) {
+ return;
+ }
+ 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);
+ }
+ }
+ }
+
+ // Unbinds from a RemoteViewsService when we delete an app widget
+ private void unbindAppWidgetRemoteViewsServicesLocked(AppWidgetId id) {
+ int appWidgetId = id.appWidgetId;
+ // Unbind all connections to Services bound to this AppWidgetId
+ Iterator<Pair<Integer, Intent.FilterComparison>> it = mBoundRemoteViewsServices.keySet()
+ .iterator();
+ while (it.hasNext()) {
+ final Pair<Integer, Intent.FilterComparison> key = it.next();
+ if (key.first.intValue() == appWidgetId) {
+ final ServiceConnectionProxy conn = (ServiceConnectionProxy) mBoundRemoteViewsServices
+ .get(key);
+ conn.disconnect();
+ mContext.unbindService(conn);
+ it.remove();
+ }
+ }
+
+ // Check if we need to destroy any services (if no other app widgets are
+ // referencing the same service)
+ decrementAppWidgetServiceRefCount(id);
+ }
+
+ // Destroys the cached factory on the RemoteViewsService's side related to the specified intent
+ private void destroyRemoteViewsService(final Intent intent, AppWidgetId id) {
+ final ServiceConnection conn = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ final IRemoteViewsFactory cb = IRemoteViewsFactory.Stub.asInterface(service);
+ try {
+ cb.onDestroy(intent);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ } catch (RuntimeException e) {
+ e.printStackTrace();
+ }
+ mContext.unbindService(this);
+ }
+
+ @Override
+ public void onServiceDisconnected(android.content.ComponentName name) {
+ // Do nothing
+ }
+ };
+
+ int userId = UserHandle.getUserId(id.provider.uid);
+ // Bind to the service and remove the static intent->factory mapping in the
+ // RemoteViewsService.
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mContext.bindServiceAsUser(intent, conn, Context.BIND_AUTO_CREATE,
+ new UserHandle(userId));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ // Adds to the ref-count for a given RemoteViewsService intent
+ private void incrementAppWidgetServiceRefCount(int appWidgetId, FilterComparison fc) {
+ HashSet<Integer> appWidgetIds = null;
+ if (mRemoteViewsServicesAppWidgets.containsKey(fc)) {
+ appWidgetIds = mRemoteViewsServicesAppWidgets.get(fc);
+ } else {
+ appWidgetIds = new HashSet<Integer>();
+ mRemoteViewsServicesAppWidgets.put(fc, appWidgetIds);
+ }
+ appWidgetIds.add(appWidgetId);
+ }
+
+ // Subtracts from the ref-count for a given RemoteViewsService intent, prompting a delete if
+ // the ref-count reaches zero.
+ private void decrementAppWidgetServiceRefCount(AppWidgetId id) {
+ Iterator<FilterComparison> it = mRemoteViewsServicesAppWidgets.keySet().iterator();
+ while (it.hasNext()) {
+ final FilterComparison key = it.next();
+ final HashSet<Integer> ids = mRemoteViewsServicesAppWidgets.get(key);
+ if (ids.remove(id.appWidgetId)) {
+ // If we have removed the last app widget referencing this service, then we
+ // should destroy it and remove it from this set
+ if (ids.isEmpty()) {
+ destroyRemoteViewsService(key.getIntent(), id);
+ it.remove();
+ }
+ }
+ }
+ }
+
+ public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) {
+ synchronized (mAppWidgetIds) {
+ if (!mHasFeature) {
+ return null;
+ }
+ ensureStateLoadedLocked();
+ AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
+ if (id != null && id.provider != null && !id.provider.zombie) {
+ return cloneIfLocalBinder(id.provider.info);
+ }
+ return null;
+ }
+ }
+
+ public RemoteViews getAppWidgetViews(int appWidgetId) {
+ if (DBG) log("getAppWidgetViews id=" + appWidgetId);
+ synchronized (mAppWidgetIds) {
+ if (!mHasFeature) {
+ return null;
+ }
+ ensureStateLoadedLocked();
+ AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
+ if (id != null) {
+ return cloneIfLocalBinder(id.views);
+ }
+ if (DBG) log(" couldn't find appwidgetid");
+ return null;
+ }
+ }
+
+ public List<AppWidgetProviderInfo> getInstalledProviders(int categoryFilter) {
+ synchronized (mAppWidgetIds) {
+ if (!mHasFeature) {
+ return new ArrayList<AppWidgetProviderInfo>(0);
+ }
+ 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 && (p.info.widgetCategory & categoryFilter) != 0) {
+ result.add(cloneIfLocalBinder(p.info));
+ }
+ }
+ return result;
+ }
+ }
+
+ public void updateAppWidgetIds(int[] appWidgetIds, RemoteViews views) {
+ if (!mHasFeature) {
+ return;
+ }
+ if (appWidgetIds == null) {
+ return;
+ }
+ if (DBG) log("updateAppWidgetIds views: " + views);
+ int bitmapMemoryUsage = 0;
+ if (views != null) {
+ bitmapMemoryUsage = views.estimateMemoryUsage();
+ }
+ if (bitmapMemoryUsage > mMaxWidgetBitmapMemory) {
+ throw new IllegalArgumentException("RemoteViews for widget update exceeds maximum" +
+ " bitmap memory usage (used: " + bitmapMemoryUsage + ", max: " +
+ mMaxWidgetBitmapMemory + ") The total memory cannot exceed that required to" +
+ " fill the device's screen once.");
+ }
+
+ 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);
+ }
+ }
+ }
+
+ private void saveStateAsync() {
+ mSaveStateHandler.post(mSaveStateRunnable);
+ }
+
+ private final Runnable mSaveStateRunnable = new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mAppWidgetIds) {
+ ensureStateLoadedLocked();
+ saveStateLocked();
+ }
+ }
+ };
+
+ public void updateAppWidgetOptions(int appWidgetId, Bundle options) {
+ synchronized (mAppWidgetIds) {
+ if (!mHasFeature) {
+ return;
+ }
+ options = cloneIfLocalBinder(options);
+ ensureStateLoadedLocked();
+ AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
+
+ if (id == null) {
+ return;
+ }
+
+ Provider p = id.provider;
+ // Merge the options
+ id.options.putAll(options);
+
+ // send the broacast saying that this appWidgetId has been deleted
+ Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED);
+ intent.setComponent(p.info.provider);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id.appWidgetId);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, id.options);
+ mContext.sendBroadcastAsUser(intent, new UserHandle(mUserId));
+ saveStateAsync();
+ }
+ }
+
+ public Bundle getAppWidgetOptions(int appWidgetId) {
+ synchronized (mAppWidgetIds) {
+ if (!mHasFeature) {
+ return Bundle.EMPTY;
+ }
+ ensureStateLoadedLocked();
+ AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
+ if (id != null && id.options != null) {
+ return cloneIfLocalBinder(id.options);
+ } else {
+ return Bundle.EMPTY;
+ }
+ }
+ }
+
+ public void partiallyUpdateAppWidgetIds(int[] appWidgetIds, RemoteViews views) {
+ if (!mHasFeature) {
+ return;
+ }
+ 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]);
+ if (id == null) {
+ Slog.w(TAG, "widget id " + appWidgetIds[i] + " not found!");
+ } else if (id.views != null) {
+ // Only trigger a partial update for a widget if it has received a full update
+ updateAppWidgetInstanceLocked(id, views, true);
+ }
+ }
+ }
+ }
+
+ public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) {
+ if (!mHasFeature) {
+ return;
+ }
+ 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) {
+ if (!mHasFeature) {
+ return;
+ }
+ 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) {
+
+ if (!isPartialUpdate) {
+ // For a full update we replace the RemoteViews completely.
+ id.views = views;
+ } else {
+ // For a partial update, we merge the new RemoteViews with the old.
+ id.views.mergeRemoteViews(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, mUserId);
+ } 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, mUserId);
+ } catch (RemoteException e) {
+ // It failed; remove the callback. No need to prune because
+ // we know that this host is still referenced by this instance.
+ id.host.callbacks = null;
+ }
+ }
+
+ // If the host is unavailable, then we call the associated
+ // RemoteViewsFactory.onDataSetChanged() directly
+ if (id.host.callbacks == null) {
+ Set<FilterComparison> keys = mRemoteViewsServicesAppWidgets.keySet();
+ for (FilterComparison key : keys) {
+ if (mRemoteViewsServicesAppWidgets.get(key).contains(id.appWidgetId)) {
+ Intent intent = key.getIntent();
+
+ final ServiceConnection conn = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ IRemoteViewsFactory cb = IRemoteViewsFactory.Stub
+ .asInterface(service);
+ try {
+ cb.onDataSetChangedAsync();
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ } catch (RuntimeException e) {
+ e.printStackTrace();
+ }
+ mContext.unbindService(this);
+ }
+
+ @Override
+ public void onServiceDisconnected(android.content.ComponentName name) {
+ // Do nothing
+ }
+ };
+
+ int userId = UserHandle.getUserId(id.provider.uid);
+ // Bind to the service and call onDataSetChanged()
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mContext.bindServiceAsUser(intent, conn, Context.BIND_AUTO_CREATE,
+ new UserHandle(userId));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private boolean isLocalBinder() {
+ return Process.myPid() == Binder.getCallingPid();
+ }
+
+ private RemoteViews cloneIfLocalBinder(RemoteViews rv) {
+ if (isLocalBinder() && rv != null) {
+ return rv.clone();
+ }
+ return rv;
+ }
+
+ private AppWidgetProviderInfo cloneIfLocalBinder(AppWidgetProviderInfo info) {
+ if (isLocalBinder() && info != null) {
+ return info.clone();
+ }
+ return info;
+ }
+
+ private Bundle cloneIfLocalBinder(Bundle bundle) {
+ // Note: this is only a shallow copy. For now this will be fine, but it could be problematic
+ // if we start adding objects to the options. Further, it would only be an issue if keyguard
+ // used such options.
+ if (isLocalBinder() && bundle != null) {
+ return (Bundle) bundle.clone();
+ }
+ return bundle;
+ }
+
+ public int[] startListening(IAppWidgetHost callbacks, String packageName, int hostId,
+ List<RemoteViews> updatedViews) {
+ if (!mHasFeature) {
+ return new int[0];
+ }
+ 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(cloneIfLocalBinder(id.views));
+ }
+ return updatedIds;
+ }
+ }
+
+ public void stopListening(int hostId) {
+ synchronized (mAppWidgetIds) {
+ if (!mHasFeature) {
+ return;
+ }
+ ensureStateLoadedLocked();
+ Host host = lookupHostLocked(Binder.getCallingUid(), hostId);
+ if (host != null) {
+ host.callbacks = null;
+ pruneHostLocked(host);
+ }
+ }
+ }
+
+ boolean canAccessAppWidgetId(AppWidgetId id, int callingUid) {
+ if (id.host.uidMatches(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.uidMatches(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 loadAppWidgetListLocked() {
+ Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
+ try {
+ List<ResolveInfo> broadcastReceivers = mPm.queryIntentReceivers(intent,
+ intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+ PackageManager.GET_META_DATA, mUserId);
+
+ final int N = broadcastReceivers == null ? 0 : broadcastReceivers.size();
+ for (int i = 0; i < N; i++) {
+ ResolveInfo ri = broadcastReceivers.get(i);
+ addProviderLocked(ri);
+ }
+ } catch (RemoteException re) {
+ // Shouldn't happen, local call
+ }
+ }
+
+ boolean addProviderLocked(ResolveInfo ri) {
+ if ((ri.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) {
+ return false;
+ }
+ if (!ri.activityInfo.isEnabled()) {
+ return false;
+ }
+ Provider p = parseProviderInfoXml(new ComponentName(ri.activityInfo.packageName,
+ ri.activityInfo.name), ri);
+ if (p != null) {
+ mInstalledProviders.add(p);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ void removeProviderLocked(int index, Provider p) {
+ int N = p.instances.size();
+ for (int i = 0; i < N; i++) {
+ AppWidgetId id = p.instances.get(i);
+ // Call back with empty RemoteViews
+ updateAppWidgetInstanceLocked(id, null);
+ // Stop telling the host about updates for this from now on
+ cancelBroadcasts(p);
+ // clear out references to this appWidgetId
+ id.host.instances.remove(id);
+ mAppWidgetIds.remove(id);
+ id.provider = null;
+ pruneHostLocked(id.host);
+ id.host = null;
+ }
+ p.instances.clear();
+ mInstalledProviders.remove(index);
+ mDeletedProviders.add(p);
+ // no need to send the DISABLE broadcast, since the receiver is gone anyway
+ cancelBroadcasts(p);
+ }
+
+ void sendEnableIntentLocked(Provider p) {
+ Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLED);
+ intent.setComponent(p.info.provider);
+ mContext.sendBroadcastAsUser(intent, new UserHandle(mUserId));
+ }
+
+ void sendUpdateIntentLocked(Provider p, int[] appWidgetIds) {
+ if (appWidgetIds != null && appWidgetIds.length > 0) {
+ Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
+ intent.setComponent(p.info.provider);
+ mContext.sendBroadcastAsUser(intent, new UserHandle(mUserId));
+ }
+ }
+
+ void registerForBroadcastsLocked(Provider p, int[] appWidgetIds) {
+ if (p.info.updatePeriodMillis > 0) {
+ // if this is the first instance, set the alarm. otherwise,
+ // rely on the fact that we've already set it and that
+ // PendingIntent.getBroadcast will update the extras.
+ boolean alreadyRegistered = p.broadcast != null;
+ Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
+ intent.setComponent(p.info.provider);
+ long token = Binder.clearCallingIdentity();
+ try {
+ p.broadcast = PendingIntent.getBroadcastAsUser(mContext, 1, intent,
+ PendingIntent.FLAG_UPDATE_CURRENT, new UserHandle(mUserId));
+ } 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];
+ }
+ }
+ }
+
+ static int[] getAppWidgetIds(Host h) {
+ int instancesSize = h.instances.size();
+ int appWidgetIds[] = new int[instancesSize];
+ for (int i = 0; i < instancesSize; i++) {
+ appWidgetIds[i] = h.instances.get(i).appWidgetId;
+ }
+ return appWidgetIds;
+ }
+
+ public int[] getAppWidgetIdsForHost(int hostId) {
+ synchronized (mAppWidgetIds) {
+ ensureStateLoadedLocked();
+ int callingUid = Binder.getCallingUid();
+ Host host = lookupHostLocked(callingUid, hostId);
+ if (host != null) {
+ return getAppWidgetIds(host);
+ } else {
+ return new int[0];
+ }
+ }
+ }
+
+ private Provider parseProviderInfoXml(ComponentName component, ResolveInfo ri) {
+ Provider p = null;
+
+ ActivityInfo activityInfo = ri.activityInfo;
+ XmlResourceParser parser = null;
+ try {
+ parser = activityInfo.loadXmlMetaData(mContext.getPackageManager(),
+ AppWidgetManager.META_DATA_APPWIDGET_PROVIDER);
+ if (parser == null) {
+ Slog.w(TAG, "No " + AppWidgetManager.META_DATA_APPWIDGET_PROVIDER
+ + " meta-data for " + "AppWidget provider '" + component + '\'');
+ return null;
+ }
+
+ AttributeSet attrs = Xml.asAttributeSet(parser);
+
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && type != XmlPullParser.START_TAG) {
+ // drain whitespace, comments, etc.
+ }
+
+ String nodeName = parser.getName();
+ if (!"appwidget-provider".equals(nodeName)) {
+ Slog.w(TAG, "Meta-data does not start with appwidget-provider tag for"
+ + " AppWidget provider '" + component + '\'');
+ return null;
+ }
+
+ p = new Provider();
+ AppWidgetProviderInfo info = p.info = new AppWidgetProviderInfo();
+ info.provider = component;
+ p.uid = activityInfo.applicationInfo.uid;
+
+ Resources res = mContext.getPackageManager()
+ .getResourcesForApplicationAsUser(activityInfo.packageName, mUserId);
+
+ 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);
+ info.initialKeyguardLayout = sa.getResourceId(com.android.internal.R.styleable.
+ AppWidgetProviderInfo_initialKeyguardLayout, 0);
+ String className = sa
+ .getString(com.android.internal.R.styleable.AppWidgetProviderInfo_configure);
+ if (className != null) {
+ info.configure = new ComponentName(component.getPackageName(), className);
+ }
+ info.label = activityInfo.loadLabel(mContext.getPackageManager()).toString();
+ info.icon = ri.getIconResource();
+ info.previewImage = sa.getResourceId(
+ com.android.internal.R.styleable.AppWidgetProviderInfo_previewImage, 0);
+ info.autoAdvanceViewId = sa.getResourceId(
+ com.android.internal.R.styleable.AppWidgetProviderInfo_autoAdvanceViewId, -1);
+ info.resizeMode = sa.getInt(
+ com.android.internal.R.styleable.AppWidgetProviderInfo_resizeMode,
+ AppWidgetProviderInfo.RESIZE_NONE);
+ info.widgetCategory = sa.getInt(
+ com.android.internal.R.styleable.AppWidgetProviderInfo_widgetCategory,
+ AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN);
+
+ sa.recycle();
+ } catch (Exception e) {
+ // Ok to catch Exception here, because anything going wrong because
+ // of what a client process passes to us should not be fatal for the
+ // system process.
+ Slog.w(TAG, "XML parsing failed for AppWidget provider '" + component + '\'', e);
+ return null;
+ } finally {
+ if (parser != null)
+ parser.close();
+ }
+ return p;
+ }
+
+ int getUidForPackage(String packageName) throws PackageManager.NameNotFoundException {
+ PackageInfo pkgInfo = null;
+ try {
+ pkgInfo = mPm.getPackageInfo(packageName, 0, mUserId);
+ } catch (RemoteException re) {
+ // Shouldn't happen, local call
+ }
+ if (pkgInfo == null || pkgInfo.applicationInfo == null) {
+ throw new PackageManager.NameNotFoundException();
+ }
+ return pkgInfo.applicationInfo.uid;
+ }
+
+ int enforceSystemOrCallingUid(String packageName) throws IllegalArgumentException {
+ int callingUid = Binder.getCallingUid();
+ if (UserHandle.getAppId(callingUid) == Process.SYSTEM_UID || callingUid == 0) {
+ return callingUid;
+ }
+ return enforceCallingUid(packageName);
+ }
+
+ 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 (!UserHandle.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() {
+ if (!mHasFeature) {
+ return;
+ }
+ 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");
+ out.attribute(null, "version", String.valueOf(CURRENT_VERSION));
+ 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));
+ }
+ if (id.options != null) {
+ out.attribute(null, "min_width", Integer.toHexString(id.options.getInt(
+ AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH)));
+ out.attribute(null, "min_height", Integer.toHexString(id.options.getInt(
+ AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT)));
+ out.attribute(null, "max_width", Integer.toHexString(id.options.getInt(
+ AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH)));
+ out.attribute(null, "max_height", Integer.toHexString(id.options.getInt(
+ AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT)));
+ out.attribute(null, "host_category", Integer.toHexString(id.options.getInt(
+ AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY)));
+ }
+ out.endTag(null, "g");
+ }
+
+ Iterator<String> it = mPackagesWithBindWidgetPermission.iterator();
+ while (it.hasNext()) {
+ out.startTag(null, "b");
+ out.attribute(null, "packageName", it.next());
+ out.endTag(null, "b");
+ }
+
+ out.endTag(null, "gs");
+
+ out.endDocument();
+ return true;
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to write state: " + e);
+ return false;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ void readStateFromFileLocked(FileInputStream stream) {
+ boolean success = false;
+ int version = 0;
+ 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 ("gs".equals(tag)) {
+ String attributeValue = parser.getAttributeValue(null, "version");
+ try {
+ version = Integer.parseInt(attributeValue);
+ } catch (NumberFormatException e) {
+ version = 0;
+ }
+ } else 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 IPackageManager packageManager = AppGlobals.getPackageManager();
+ try {
+ packageManager.getReceiverInfo(new ComponentName(pkg, cl), 0, mUserId);
+ } catch (RemoteException e) {
+ String[] pkgs = mContext.getPackageManager()
+ .currentToCanonicalPackageNames(new String[] { pkg });
+ pkg = pkgs[0];
+ }
+
+ Provider p = lookupProviderLocked(new ComponentName(pkg, cl));
+ if (p == null && mSafeMode) {
+ // if we're in safe mode, make a temporary one
+ p = new Provider();
+ p.info = new AppWidgetProviderInfo();
+ p.info.provider = new ComponentName(pkg, cl);
+ p.zombie = true;
+ mInstalledProviders.add(p);
+ }
+ if (p != null) {
+ // if it wasn't uninstalled or something
+ loadedProviders.put(providerIndex, p);
+ }
+ providerIndex++;
+ } else if ("h".equals(tag)) {
+ Host host = new Host();
+
+ // TODO: do we need to check that this package has the same signature
+ // as before?
+ host.packageName = parser.getAttributeValue(null, "pkg");
+ try {
+ host.uid = getUidForPackage(host.packageName);
+ } catch (PackageManager.NameNotFoundException ex) {
+ host.zombie = true;
+ }
+ if (!host.zombie || mSafeMode) {
+ // In safe mode, we don't discard the hosts we don't recognize
+ // so that they're not pruned from our list. Otherwise, we do.
+ host.hostId = Integer
+ .parseInt(parser.getAttributeValue(null, "id"), 16);
+ mHosts.add(host);
+ }
+ } else if ("b".equals(tag)) {
+ String packageName = parser.getAttributeValue(null, "packageName");
+ if (packageName != null) {
+ mPackagesWithBindWidgetPermission.add(packageName);
+ }
+ } else if ("g".equals(tag)) {
+ AppWidgetId id = new AppWidgetId();
+ id.appWidgetId = Integer.parseInt(parser.getAttributeValue(null, "id"), 16);
+ if (id.appWidgetId >= mNextAppWidgetId) {
+ mNextAppWidgetId = id.appWidgetId + 1;
+ }
+
+ Bundle options = new Bundle();
+ String minWidthString = parser.getAttributeValue(null, "min_width");
+ if (minWidthString != null) {
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH,
+ Integer.parseInt(minWidthString, 16));
+ }
+ String minHeightString = parser.getAttributeValue(null, "min_height");
+ if (minHeightString != null) {
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT,
+ Integer.parseInt(minHeightString, 16));
+ }
+ String maxWidthString = parser.getAttributeValue(null, "max_width");
+ if (maxWidthString != null) {
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH,
+ Integer.parseInt(maxWidthString, 16));
+ }
+ String maxHeightString = parser.getAttributeValue(null, "max_height");
+ if (maxHeightString != null) {
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT,
+ Integer.parseInt(maxHeightString, 16));
+ }
+ String categoryString = parser.getAttributeValue(null, "host_category");
+ if (categoryString != null) {
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
+ Integer.parseInt(categoryString, 16));
+ }
+ id.options = options;
+
+ String providerString = parser.getAttributeValue(null, "p");
+ if (providerString != null) {
+ // there's no provider if it hasn't been bound yet.
+ // 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));
+ }
+ // upgrade the database if needed
+ performUpgrade(version);
+ } 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();
+ }
+ }
+ }
+
+ private void performUpgrade(int fromVersion) {
+ if (fromVersion < CURRENT_VERSION) {
+ Slog.v(TAG, "Upgrading widget database from " + fromVersion + " to " + CURRENT_VERSION
+ + " for user " + mUserId);
+ }
+
+ int version = fromVersion;
+
+ // Update 1: keyguard moved from package "android" to "com.android.keyguard"
+ if (version == 0) {
+ for (int i = 0; i < mHosts.size(); i++) {
+ Host host = mHosts.get(i);
+ if (host != null && "android".equals(host.packageName)
+ && host.hostId == KEYGUARD_HOST_ID) {
+ host.packageName = KEYGUARD_HOST_PACKAGE;
+ }
+ }
+ version = 1;
+ }
+
+ if (version != CURRENT_VERSION) {
+ throw new IllegalStateException("Failed to upgrade widget database");
+ }
+ }
+
+ static File getSettingsFile(int userId) {
+ return new File(Environment.getUserSystemDirectory(userId), SETTINGS_FILENAME);
+ }
+
+ AtomicFile savedStateFile() {
+ File dir = Environment.getUserSystemDirectory(mUserId);
+ File settingsFile = getSettingsFile(mUserId);
+ if (!settingsFile.exists() && mUserId == 0) {
+ if (!dir.exists()) {
+ dir.mkdirs();
+ }
+ // 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 onUserStopping() {
+ // prune the ones we don't want to keep
+ int N = mInstalledProviders.size();
+ for (int i = N - 1; i >= 0; i--) {
+ Provider p = mInstalledProviders.get(i);
+ cancelBroadcasts(p);
+ }
+ }
+
+ void onUserRemoved() {
+ getSettingsFile(mUserId).delete();
+ }
+
+ boolean addProvidersForPackageLocked(String pkgName) {
+ boolean providersAdded = false;
+ Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
+ intent.setPackage(pkgName);
+ List<ResolveInfo> broadcastReceivers;
+ try {
+ broadcastReceivers = mPm.queryIntentReceivers(intent,
+ intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+ PackageManager.GET_META_DATA, mUserId);
+ } catch (RemoteException re) {
+ // Shouldn't happen, local call
+ return false;
+ }
+ 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);
+ providersAdded = true;
+ }
+ }
+
+ return providersAdded;
+ }
+
+ /**
+ * Updates all providers with the specified package names, and records any providers that were
+ * pruned.
+ *
+ * @return whether any providers were updated
+ */
+ boolean updateProvidersForPackageLocked(String pkgName, Set<ComponentName> removedProviders) {
+ boolean providersUpdated = false;
+ HashSet<String> keep = new HashSet<String>();
+ Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
+ intent.setPackage(pkgName);
+ List<ResolveInfo> broadcastReceivers;
+ try {
+ broadcastReceivers = mPm.queryIntentReceivers(intent,
+ intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+ PackageManager.GET_META_DATA, mUserId);
+ } catch (RemoteException re) {
+ // Shouldn't happen, local call
+ return false;
+ }
+
+ // 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);
+ providersUpdated = true;
+ }
+ } 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,
+ mUserId);
+ } 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);
+ providersUpdated = true;
+ }
+ }
+ }
+ }
+ }
+
+ // 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())) {
+ if (removedProviders != null) {
+ removedProviders.add(p.info.provider);
+ }
+ removeProviderLocked(i, p);
+ providersUpdated = true;
+ }
+ }
+
+ return providersUpdated;
+ }
+
+ boolean removeProvidersForPackageLocked(String pkgName) {
+ boolean providersRemoved = false;
+ int N = mInstalledProviders.size();
+ for (int i = N - 1; i >= 0; i--) {
+ Provider p = mInstalledProviders.get(i);
+ if (pkgName.equals(p.info.provider.getPackageName())) {
+ removeProviderLocked(i, p);
+ providersRemoved = true;
+ }
+ }
+
+ // 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);
+ }
+ }
+
+ return providersRemoved;
+ }
+
+ void notifyHostsForProvidersChangedLocked() {
+ final int N = mHosts.size();
+ for (int i = N - 1; i >= 0; i--) {
+ Host host = mHosts.get(i);
+ try {
+ if (host.callbacks != null) {
+ host.callbacks.providersChanged(mUserId);
+ }
+ } catch (RemoteException ex) {
+ // It failed; remove the callback. No need to prune because
+ // we know that this host is still referenced by this
+ // instance.
+ host.callbacks = null;
+ }
+ }
+ }
+}
diff --git a/services/appwidget/java/service.mk b/services/appwidget/java/service.mk
new file mode 100644
index 0000000..0e33446
--- /dev/null
+++ b/services/appwidget/java/service.mk
@@ -0,0 +1,11 @@
+# Include only if the service is required
+ifneq ($(findstring appwidget,$(REQUIRED_SERVICES)),)
+
+SUB_DIR := appwidget/java
+
+LOCAL_SRC_FILES += \
+ $(call all-java-files-under,$(SUB_DIR))
+
+#DEFINED_SERVICES += com.android.server.appwidget.AppWidgetService
+
+endif