summaryrefslogtreecommitdiffstats
path: root/services/appwidget
diff options
context:
space:
mode:
authorAmith Yamasani <yamasani@google.com>2013-12-19 23:30:35 +0000
committerAndroid Git Automerger <android-git-automerger@android.com>2013-12-19 23:30:35 +0000
commit49782e46c0eb85a25ae2abcf80880c48dbab5aea (patch)
tree9fab2a40c41004d78b7001dc766d85f61d24f582 /services/appwidget
parent4dace6f66d498c2d119adf265776aa83b28452af (diff)
parent9158825f9c41869689d6b1786d7c7aa8bdd524ce (diff)
downloadframeworks_base-49782e46c0eb85a25ae2abcf80880c48dbab5aea.zip
frameworks_base-49782e46c0eb85a25ae2abcf80880c48dbab5aea.tar.gz
frameworks_base-49782e46c0eb85a25ae2abcf80880c48dbab5aea.tar.bz2
am 9158825f: Move some system services to separate directories
* commit '9158825f9c41869689d6b1786d7c7aa8bdd524ce': Move some system services to separate directories
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.java2123
-rw-r--r--services/appwidget/java/service.mk11
3 files changed, 2497 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..b6391b6
--- /dev/null
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -0,0 +1,2123 @@
+/*
+ * 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.IBinder;
+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.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