/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server; import android.app.AlarmManager; import android.app.PendingIntent; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProviderInfo; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.Intent.FilterComparison; import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.RemoteException; import android.os.SystemClock; import android.util.AttributeSet; import android.util.Log; import android.util.Pair; import android.util.Slog; import android.util.TypedValue; import android.util.Xml; import android.widget.RemoteViews; import com.android.internal.appwidget.IAppWidgetHost; import com.android.internal.appwidget.IAppWidgetService; import com.android.internal.os.AtomicFile; import com.android.internal.util.FastXmlSerializer; import com.android.internal.widget.IRemoteViewsAdapterConnection; import com.android.internal.widget.IRemoteViewsFactory; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Set; class AppWidgetService extends IAppWidgetService.Stub { private static final String TAG = "AppWidgetService"; private static final String SETTINGS_FILENAME = "appwidgets.xml"; private static final int MIN_UPDATE_PERIOD = 30 * 60 * 1000; // 30 minutes /* * When identifying a Host or Provider based on the calling process, use the uid field. * When identifying a Host or Provider based on a package manager broadcast, use the * package given. */ static class Provider { int uid; AppWidgetProviderInfo info; ArrayList instances = new ArrayList(); 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 instances = new ArrayList(); IAppWidgetHost callbacks; boolean zombie; // if we're in safe mode, don't prune this just because nobody references it int tag; // for use while saving state (the index) } static class AppWidgetId { int appWidgetId; Provider provider; RemoteViews views; Host host; } /** * Acts as a proxy between the ServiceConnection and the RemoteViewsAdapterConnection. * This needs to be a static inner class since a reference to the ServiceConnection is held * globally and may lead us to leak AppWidgetService instances (if there were more than one). */ static class ServiceConnectionProxy implements ServiceConnection { private final Pair mKey; private final IBinder mConnectionCb; ServiceConnectionProxy(Pair key, IBinder connectionCb) { mKey = key; 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, ServiceConnection> mBoundRemoteViewsServices = new HashMap,ServiceConnection>(); // Manages persistent references to RemoteViewsServices from different App Widgets private final HashMap> mRemoteViewsServicesAppWidgets = new HashMap>(); Context mContext; Locale mLocale; PackageManager mPackageManager; AlarmManager mAlarmManager; ArrayList mInstalledProviders = new ArrayList(); int mNextAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID + 1; final ArrayList mAppWidgetIds = new ArrayList(); ArrayList mHosts = new ArrayList(); boolean mSafeMode; boolean mStateLoaded; // These are for debugging only -- widgets are going missing in some rare instances ArrayList mDeletedProviders = new ArrayList(); ArrayList mDeletedHosts = new ArrayList(); AppWidgetService(Context context) { mContext = context; mPackageManager = context.getPackageManager(); mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE); } public void systemReady(boolean safeMode) { mSafeMode = safeMode; synchronized (mAppWidgetIds) { ensureStateLoadedLocked(); } // Register for the boot completed broadcast, so we can send the // ENABLE broacasts. If we try to send them now, they time out, // because the system isn't ready to handle them yet. mContext.registerReceiver(mBroadcastReceiver, new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null); // Register for configuration changes so we can update the names // of the widgets when the locale changes. mContext.registerReceiver(mBroadcastReceiver, new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED), null, null); // 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.registerReceiver(mBroadcastReceiver, filter); // Register for events related to sdcard installation. IntentFilter sdFilter = new IntentFilter(); sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE); sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); mContext.registerReceiver(mBroadcastReceiver, sdFilter); } private void ensureStateLoadedLocked() { if (!mStateLoaded) { loadAppWidgetList(); loadStateLocked(); mStateLoaded = true; } } private void dumpProvider(Provider p, int index, PrintWriter pw) { AppWidgetProviderInfo info = p.info; pw.print(" ["); pw.print(index); pw.print("] provider "); pw.print(info.provider.flattenToShortString()); pw.println(':'); pw.print(" min=("); pw.print(info.minWidth); pw.print("x"); pw.print(info.minHeight); pw.print(") minResize=("); pw.print(info.minResizeWidth); pw.print("x"); pw.print(info.minResizeHeight); pw.print(") updatePeriodMillis="); pw.print(info.updatePeriodMillis); pw.print(" resizeMode="); pw.print(info.resizeMode); pw.print(" autoAdvanceViewId="); pw.print(info.autoAdvanceViewId); pw.print(" initialLayout=#"); pw.print(Integer.toHexString(info.initialLayout)); pw.print(" zombie="); pw.println(p.zombie); } private void dumpHost(Host host, int index, PrintWriter pw) { pw.print(" ["); pw.print(index); pw.print("] hostId="); pw.print(host.hostId); pw.print(' '); pw.print(host.packageName); pw.print('/'); pw.print(host.uid); pw.println(':'); pw.print(" callbacks="); pw.println(host.callbacks); pw.print(" instances.size="); pw.print(host.instances.size()); pw.print(" zombie="); pw.println(host.zombie); } private void dumpAppWidgetId(AppWidgetId id, int index, PrintWriter pw) { pw.print(" ["); pw.print(index); pw.print("] id="); pw.println(id.appWidgetId); pw.print(" hostId="); pw.print(id.host.hostId); pw.print(' '); pw.print(id.host.packageName); pw.print('/'); pw.println(id.host.uid); if (id.provider != null) { pw.print(" provider="); pw.println(id.provider.info.provider.flattenToShortString()); } if (id.host != null) { pw.print(" host.callbacks="); pw.println(id.host.callbacks); } if (id.views != null) { pw.print(" views="); pw.println(id.views); } } @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) { pw.println("Permission Denial: can't dump from from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()); return; } synchronized (mAppWidgetIds) { int N = mInstalledProviders.size(); pw.println("Providers:"); for (int i=0; i=0; i--) { Host host = mHosts.get(i); if (host.uid == callingUid) { deleteHostLocked(host); changed = true; } } if (changed) { saveStateLocked(); } } } void deleteHostLocked(Host host) { final int N = host.instances.size(); for (int i=N-1; i>=0; i--) { AppWidgetId id = host.instances.get(i); deleteAppWidgetLocked(id); } host.instances.clear(); mHosts.remove(host); mDeletedHosts.add(host); // it's gone or going away, abruptly drop the callback connection host.callbacks = null; } void deleteAppWidgetLocked(AppWidgetId id) { // We first unbind all services that are bound to this id unbindAppWidgetRemoteViewsServicesLocked(id); Host host = id.host; host.instances.remove(id); pruneHostLocked(host); mAppWidgetIds.remove(id); Provider p = id.provider; if (p != null) { p.instances.remove(id); if (!p.zombie) { // send the broacast saying that this appWidgetId has been deleted Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DELETED); intent.setComponent(p.info.provider); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id.appWidgetId); mContext.sendBroadcast(intent); if (p.instances.size() == 0) { // cancel the future updates cancelBroadcasts(p); // send the broacast saying that the provider is not in use any more intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DISABLED); intent.setComponent(p.info.provider); mContext.sendBroadcast(intent); } } } } void cancelBroadcasts(Provider p) { if (p.broadcast != null) { mAlarmManager.cancel(p.broadcast); long token = Binder.clearCallingIdentity(); try { p.broadcast.cancel(); } finally { Binder.restoreCallingIdentity(token); } p.broadcast = null; } } public void bindAppWidgetId(int appWidgetId, ComponentName provider) { mContext.enforceCallingPermission(android.Manifest.permission.BIND_APPWIDGET, "bindGagetId appWidgetId=" + appWidgetId + " provider=" + provider); final long ident = Binder.clearCallingIdentity(); try { synchronized (mAppWidgetIds) { ensureStateLoadedLocked(); AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); if (id == null) { throw new IllegalArgumentException("bad appWidgetId"); } if (id.provider != null) { throw new IllegalArgumentException("appWidgetId " + appWidgetId + " already bound to " + id.provider.info.provider); } Provider p = lookupProviderLocked(provider); if (p == null) { throw new IllegalArgumentException("not a appwidget provider: " + provider); } if (p.zombie) { throw new IllegalArgumentException("can't bind to a 3rd party provider in" + " safe mode: " + provider); } id.provider = p; p.instances.add(id); int instancesSize = p.instances.size(); if (instancesSize == 1) { // tell the provider that it's ready sendEnableIntentLocked(p); } // send an update now -- We need this update now, and just for this appWidgetId. // It's less critical when the next one happens, so when we schdule the next one, // we add updatePeriodMillis to its start time. That time will have some slop, // but that's okay. sendUpdateIntentLocked(p, new int[] { appWidgetId }); // schedule the future updates registerForBroadcastsLocked(p, getAppWidgetIds(p)); saveStateLocked(); } } finally { Binder.restoreCallingIdentity(ident); } } // Binds to a specific RemoteViewsService public void bindRemoteViewsService(int appWidgetId, Intent intent, IBinder connection) { synchronized (mAppWidgetIds) { ensureStateLoadedLocked(); AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); if (id == null) { throw new IllegalArgumentException("bad appWidgetId"); } final ComponentName componentName = intent.getComponent(); try { final ServiceInfo si = mContext.getPackageManager().getServiceInfo(componentName, PackageManager.GET_PERMISSIONS); if (!android.Manifest.permission.BIND_REMOTEVIEWS.equals(si.permission)) { throw new SecurityException("Selected service does not require " + android.Manifest.permission.BIND_REMOTEVIEWS + ": " + componentName); } } catch (PackageManager.NameNotFoundException e) { throw new IllegalArgumentException("Unknown component " + componentName); } // If there is already a connection made for this service intent, then disconnect from // that first. (This does not allow multiple connections to the same service under // the same key) ServiceConnectionProxy conn = null; FilterComparison fc = new FilterComparison(intent); Pair key = Pair.create(appWidgetId, fc); if (mBoundRemoteViewsServices.containsKey(key)) { conn = (ServiceConnectionProxy) mBoundRemoteViewsServices.get(key); conn.disconnect(); mContext.unbindService(conn); mBoundRemoteViewsServices.remove(key); } // Bind to the RemoteViewsService (which will trigger a callback to the // RemoteViewsAdapter.onServiceConnected()) final long token = Binder.clearCallingIdentity(); try { conn = new ServiceConnectionProxy(key, connection); mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE); mBoundRemoteViewsServices.put(key, conn); } finally { Binder.restoreCallingIdentity(token); } // Add it to the mapping of RemoteViewsService to appWidgetIds so that we can determine // when we can call back to the RemoteViewsService later to destroy associated // factories. incrementAppWidgetServiceRefCount(appWidgetId, fc); } } // Unbinds from a specific RemoteViewsService public void unbindRemoteViewsService(int appWidgetId, Intent intent) { synchronized (mAppWidgetIds) { ensureStateLoadedLocked(); // Unbind from the RemoteViewsService (which will trigger a callback to the bound // RemoteViewsAdapter) Pair key = Pair.create(appWidgetId, new FilterComparison(intent)); if (mBoundRemoteViewsServices.containsKey(key)) { // We don't need to use the appWidgetId until after we are sure there is something // to unbind. Note that this may mask certain issues with apps calling unbind() // more than necessary. AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); if (id == null) { throw new IllegalArgumentException("bad appWidgetId"); } ServiceConnectionProxy conn = (ServiceConnectionProxy) mBoundRemoteViewsServices.get(key); conn.disconnect(); mContext.unbindService(conn); mBoundRemoteViewsServices.remove(key); } else { Log.e("AppWidgetService", "Error (unbindRemoteViewsService): Connection not bound"); } } } // Unbinds from a RemoteViewsService when we delete an app widget private void unbindAppWidgetRemoteViewsServicesLocked(AppWidgetId id) { int appWidgetId = id.appWidgetId; // Unbind all connections to Services bound to this AppWidgetId Iterator> it = mBoundRemoteViewsServices.keySet().iterator(); while (it.hasNext()) { final Pair key = it.next(); if (key.first.intValue() == appWidgetId) { final ServiceConnectionProxy conn = (ServiceConnectionProxy) mBoundRemoteViewsServices.get(key); conn.disconnect(); mContext.unbindService(conn); it.remove(); } } // Check if we need to destroy any services (if no other app widgets are // referencing the same service) decrementAppWidgetServiceRefCount(appWidgetId); } // Destroys the cached factory on the RemoteViewsService's side related to the specified intent private void destroyRemoteViewsService(final Intent intent) { final ServiceConnection conn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { final IRemoteViewsFactory cb = IRemoteViewsFactory.Stub.asInterface(service); try { cb.onDestroy(intent); } catch (RemoteException e) { e.printStackTrace(); } catch (RuntimeException e) { e.printStackTrace(); } mContext.unbindService(this); } @Override public void onServiceDisconnected(android.content.ComponentName name) { // Do nothing } }; // Bind to the service and remove the static intent->factory mapping in the // RemoteViewsService. final long token = Binder.clearCallingIdentity(); try { mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE); } finally { Binder.restoreCallingIdentity(token); } } // Adds to the ref-count for a given RemoteViewsService intent private void incrementAppWidgetServiceRefCount(int appWidgetId, FilterComparison fc) { HashSet appWidgetIds = null; if (mRemoteViewsServicesAppWidgets.containsKey(fc)) { appWidgetIds = mRemoteViewsServicesAppWidgets.get(fc); } else { appWidgetIds = new HashSet(); mRemoteViewsServicesAppWidgets.put(fc, appWidgetIds); } appWidgetIds.add(appWidgetId); } // Subtracts from the ref-count for a given RemoteViewsService intent, prompting a delete if // the ref-count reaches zero. private void decrementAppWidgetServiceRefCount(int appWidgetId) { Iterator it = mRemoteViewsServicesAppWidgets.keySet().iterator(); while (it.hasNext()) { final FilterComparison key = it.next(); final HashSet ids = mRemoteViewsServicesAppWidgets.get(key); if (ids.remove(appWidgetId)) { // If we have removed the last app widget referencing this service, then we // should destroy it and remove it from this set if (ids.isEmpty()) { destroyRemoteViewsService(key.getIntent()); it.remove(); } } } } public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) { synchronized (mAppWidgetIds) { ensureStateLoadedLocked(); AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); if (id != null && id.provider != null && !id.provider.zombie) { return id.provider.info; } return null; } } public RemoteViews getAppWidgetViews(int appWidgetId) { synchronized (mAppWidgetIds) { ensureStateLoadedLocked(); AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); if (id != null) { return id.views; } return null; } } public List getInstalledProviders() { synchronized (mAppWidgetIds) { ensureStateLoadedLocked(); final int N = mInstalledProviders.size(); ArrayList result = new ArrayList(N); for (int i=0; i instances = p.instances; final int callingUid = getCallingUid(); final int N = instances.size(); for (int i=0; i keys = mRemoteViewsServicesAppWidgets.keySet(); for (FilterComparison key : keys) { if (mRemoteViewsServicesAppWidgets.get(key).contains(id.appWidgetId)) { Intent intent = key.getIntent(); final ServiceConnection conn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { IRemoteViewsFactory cb = IRemoteViewsFactory.Stub.asInterface(service); try { cb.onDataSetChangedAsync(); } catch (RemoteException e) { e.printStackTrace(); } catch (RuntimeException e) { e.printStackTrace(); } mContext.unbindService(this); } @Override public void onServiceDisconnected(android.content.ComponentName name) { // Do nothing } }; // Bind to the service and call onDataSetChanged() final long token = Binder.clearCallingIdentity(); try { mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE); } finally { Binder.restoreCallingIdentity(token); } } } } } } public int[] startListening(IAppWidgetHost callbacks, String packageName, int hostId, List updatedViews) { int callingUid = enforceCallingUid(packageName); synchronized (mAppWidgetIds) { ensureStateLoadedLocked(); Host host = lookupOrAddHostLocked(callingUid, packageName, hostId); host.callbacks = callbacks; updatedViews.clear(); ArrayList instances = host.instances; int N = instances.size(); int[] updatedIds = new int[N]; for (int i=0; i broadcastReceivers = pm.queryBroadcastReceivers(intent, PackageManager.GET_META_DATA); final int N = broadcastReceivers == null ? 0 : broadcastReceivers.size(); for (int i=0; i 0) { Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); intent.setComponent(p.info.provider); mContext.sendBroadcast(intent); } } void registerForBroadcastsLocked(Provider p, int[] appWidgetIds) { if (p.info.updatePeriodMillis > 0) { // if this is the first instance, set the alarm. otherwise, // rely on the fact that we've already set it and that // PendingIntent.getBroadcast will update the extras. boolean alreadyRegistered = p.broadcast != null; Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); intent.setComponent(p.info.provider); long token = Binder.clearCallingIdentity(); try { p.broadcast = PendingIntent.getBroadcast(mContext, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT); } finally { Binder.restoreCallingIdentity(token); } if (!alreadyRegistered) { long period = p.info.updatePeriodMillis; if (period < MIN_UPDATE_PERIOD) { period = MIN_UPDATE_PERIOD; } mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + period, period, p.broadcast); } } } static int[] getAppWidgetIds(Provider p) { int instancesSize = p.instances.size(); int appWidgetIds[] = new int[instancesSize]; for (int i=0; i 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 loadedProviders = new HashMap(); do { type = parser.next(); if (type == XmlPullParser.START_TAG) { String tag = parser.getName(); if ("p".equals(tag)) { // TODO: do we need to check that this package has the same signature // as before? String pkg = parser.getAttributeValue(null, "pkg"); String cl = parser.getAttributeValue(null, "cl"); final PackageManager packageManager = mContext.getPackageManager(); try { packageManager.getReceiverInfo(new ComponentName(pkg, cl), 0); } catch (PackageManager.NameNotFoundException e) { String[] pkgs = packageManager.currentToCanonicalPackageNames( new String[] { pkg }); pkg = pkgs[0]; } Provider p = lookupProviderLocked(new ComponentName(pkg, cl)); if (p == null && mSafeMode) { // if we're in safe mode, make a temporary one p = new Provider(); p.info = new AppWidgetProviderInfo(); p.info.provider = new ComponentName(pkg, cl); p.zombie = true; mInstalledProviders.add(p); } if (p != null) { // if it wasn't uninstalled or something loadedProviders.put(providerIndex, p); } providerIndex++; } else if ("h".equals(tag)) { Host host = new Host(); // TODO: do we need to check that this package has the same signature // as before? host.packageName = parser.getAttributeValue(null, "pkg"); try { host.uid = getUidForPackage(host.packageName); } catch (PackageManager.NameNotFoundException ex) { host.zombie = true; } if (!host.zombie || mSafeMode) { // In safe mode, we don't discard the hosts we don't recognize // so that they're not pruned from our list. Otherwise, we do. host.hostId = Integer.parseInt( parser.getAttributeValue(null, "id"), 16); mHosts.add(host); } } else if ("g".equals(tag)) { AppWidgetId id = new AppWidgetId(); id.appWidgetId = Integer.parseInt(parser.getAttributeValue(null, "id"), 16); if (id.appWidgetId >= mNextAppWidgetId) { mNextAppWidgetId = id.appWidgetId + 1; } String providerString = parser.getAttributeValue(null, "p"); if (providerString != null) { // there's no provider if it hasn't been bound yet. // maybe we don't have to save this, but it brings the system // to the state it was in. int pIndex = Integer.parseInt(providerString, 16); id.provider = loadedProviders.get(pIndex); if (false) { Slog.d(TAG, "bound appWidgetId=" + id.appWidgetId + " to provider " + pIndex + " which is " + id.provider); } if (id.provider == null) { // This provider is gone. We just let the host figure out // that this happened when it fails to load it. continue; } } int hIndex = Integer.parseInt(parser.getAttributeValue(null, "h"), 16); id.host = mHosts.get(hIndex); if (id.host == null) { // This host is gone. continue; } if (id.provider != null) { id.provider.instances.add(id); } id.host.instances.add(id); mAppWidgetIds.add(id); } } } while (type != XmlPullParser.END_DOCUMENT); success = true; } catch (NullPointerException e) { Slog.w(TAG, "failed parsing " + e); } catch (NumberFormatException e) { Slog.w(TAG, "failed parsing " + e); } catch (XmlPullParserException e) { Slog.w(TAG, "failed parsing " + e); } catch (IOException e) { Slog.w(TAG, "failed parsing " + e); } catch (IndexOutOfBoundsException e) { Slog.w(TAG, "failed parsing " + e); } if (success) { // delete any hosts that didn't manage to get connected (should happen) // if it matters, they'll be reconnected. for (int i=mHosts.size()-1; i>=0; i--) { pruneHostLocked(mHosts.get(i)); } } else { // failed reading, clean up Slog.w(TAG, "Failed to read state, clearing widgets and hosts."); mAppWidgetIds.clear(); mHosts.clear(); final int N = mInstalledProviders.size(); for (int i=0; i=0; i--) { Provider p = mInstalledProviders.get(i); String pkgName = p.info.provider.getPackageName(); updateProvidersForPackageLocked(pkgName); } saveStateLocked(); } } } else { boolean added = false; boolean changed = false; String pkgList[] = null; if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) { pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); added = true; } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) { pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); added = false; } else { Uri uri = intent.getData(); if (uri == null) { return; } String pkgName = uri.getSchemeSpecificPart(); if (pkgName == null) { return; } pkgList = new String[] { pkgName }; added = Intent.ACTION_PACKAGE_ADDED.equals(action); changed = Intent.ACTION_PACKAGE_CHANGED.equals(action); } if (pkgList == null || pkgList.length == 0) { return; } if (added || changed) { synchronized (mAppWidgetIds) { ensureStateLoadedLocked(); Bundle extras = intent.getExtras(); if (changed || (extras != null && extras.getBoolean(Intent.EXTRA_REPLACING, false))) { for (String pkgName : pkgList) { // The package was just upgraded updateProvidersForPackageLocked(pkgName); } } else { // The package was just added for (String pkgName : pkgList) { addProvidersForPackageLocked(pkgName); } } saveStateLocked(); } } else { Bundle extras = intent.getExtras(); if (extras != null && extras.getBoolean(Intent.EXTRA_REPLACING, false)) { // The package is being updated. We'll receive a PACKAGE_ADDED shortly. } else { synchronized (mAppWidgetIds) { ensureStateLoadedLocked(); for (String pkgName : pkgList) { removeProvidersForPackageLocked(pkgName); saveStateLocked(); } } } } } } }; void addProvidersForPackageLocked(String pkgName) { Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); intent.setPackage(pkgName); List broadcastReceivers = mPackageManager.queryBroadcastReceivers(intent, PackageManager.GET_META_DATA); final int N = broadcastReceivers == null ? 0 : broadcastReceivers.size(); for (int i=0; i keep = new HashSet(); Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); intent.setPackage(pkgName); List broadcastReceivers = mPackageManager.queryBroadcastReceivers(intent, PackageManager.GET_META_DATA); // add the missing ones and collect which ones to keep int N = broadcastReceivers == null ? 0 : broadcastReceivers.size(); for (int i=0; i 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=0; i--) { Provider p = mInstalledProviders.get(i); if (pkgName.equals(p.info.provider.getPackageName()) && !keep.contains(p.info.provider.getClassName())) { removeProviderLocked(i, p); } } } void removeProvidersForPackageLocked(String pkgName) { int N = mInstalledProviders.size(); for (int i=N-1; i>=0; i--) { Provider p = mInstalledProviders.get(i); if (pkgName.equals(p.info.provider.getPackageName())) { removeProviderLocked(i, p); } } // Delete the hosts for this package too // // By now, we have removed any AppWidgets that were in any hosts here, // so we don't need to worry about sending DISABLE broadcasts to them. N = mHosts.size(); for (int i=N-1; i>=0; i--) { Host host = mHosts.get(i); if (pkgName.equals(host.packageName)) { deleteHostLocked(host); } } } }