/* * 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.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.PackageInfo; import android.content.pm.ResolveInfo; import android.content.pm.PackageItemInfo; 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.RemoteException; import android.os.SystemClock; import android.util.AttributeSet; import android.util.Log; import android.util.Xml; import android.widget.RemoteViews; import java.io.IOException; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.HashMap; import java.util.HashSet; import com.android.internal.appwidget.IAppWidgetService; import com.android.internal.appwidget.IAppWidgetHost; import com.android.internal.util.XmlUtils; import com.android.internal.util.FastXmlSerializer; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; class AppWidgetService extends IAppWidgetService.Stub { private static final String TAG = "AppWidgetService"; private static final String SETTINGS_FILENAME = "appwidgets.xml"; private static final String SETTINGS_TMP_FILENAME = SETTINGS_FILENAME + ".tmp"; /* * 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(); 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(); 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; } Context mContext; PackageManager mPackageManager; AlarmManager mAlarmManager; ArrayList<Provider> mInstalledProviders = new ArrayList(); int mNextAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID + 1; ArrayList<AppWidgetId> mAppWidgetIds = new ArrayList(); ArrayList<Host> mHosts = new ArrayList(); boolean mSafeMode; AppWidgetService(Context context) { mContext = context; mPackageManager = context.getPackageManager(); mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE); } public void systemReady(boolean safeMode) { mSafeMode = safeMode; loadAppWidgetList(); loadStateLocked(); // 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 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_REMOVED); filter.addDataScheme("package"); mContext.registerReceiver(mBroadcastReceiver, filter); } @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: (size=" + N + ")"); for (int i=0; i<N; i++) { Provider p = mInstalledProviders.get(i); AppWidgetProviderInfo info = p.info; pw.println(" [" + i + "] provder=" + info.provider + " min=(" + info.minWidth + "x" + info.minHeight + ")" + " updatePeriodMillis=" + info.updatePeriodMillis + " initialLayout=" + info.initialLayout + " zombie=" + p.zombie); } N = mAppWidgetIds.size(); pw.println("AppWidgetIds: (size=" + N + ")"); for (int i=0; i<N; i++) { AppWidgetId id = mAppWidgetIds.get(i); pw.println(" [" + i + "] appWidgetId=" + id.appWidgetId + " host=" + id.host.hostId + "/" + id.host.packageName + " provider=" + (id.provider == null ? "null" : id.provider.info.provider) + " host.callbacks=" + (id.host != null ? id.host.callbacks : "(no host)") + " views=" + id.views); } N = mHosts.size(); pw.println("Hosts: (size=" + N + ")"); for (int i=0; i<N; i++) { Host host = mHosts.get(i); pw.println(" [" + i + "] packageName=" + host.packageName + " uid=" + host.uid + " hostId=" + host.hostId + " callbacks=" + host.callbacks + " instances.size=" + host.instances.size() + " zombie=" + host.zombie); } } } public int allocateAppWidgetId(String packageName, int hostId) { int callingUid = enforceCallingUid(packageName); synchronized (mAppWidgetIds) { int appWidgetId = mNextAppWidgetId++; Host host = lookupOrAddHostLocked(callingUid, packageName, hostId); AppWidgetId id = new AppWidgetId(); id.appWidgetId = appWidgetId; id.host = host; host.instances.add(id); mAppWidgetIds.add(id); saveStateLocked(); return appWidgetId; } } public void deleteAppWidgetId(int appWidgetId) { synchronized (mAppWidgetIds) { AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); if (id != null) { deleteAppWidgetLocked(id); saveStateLocked(); } } } public void deleteHost(int hostId) { synchronized (mAppWidgetIds) { int callingUid = getCallingUid(); Host host = lookupHostLocked(callingUid, hostId); if (host != null) { deleteHostLocked(host); saveStateLocked(); } } } public void deleteAllHosts() { synchronized (mAppWidgetIds) { int callingUid = getCallingUid(); final int N = mHosts.size(); boolean changed = false; for (int i=N-1; i>=0; i--) { Host host = mHosts.get(i); if (host.uid == callingUid) { deleteHostLocked(host); changed = true; } } if (changed) { saveStateLocked(); } } } void deleteHostLocked(Host host) { final int N = host.instances.size(); for (int i=N-1; i>=0; i--) { AppWidgetId id = host.instances.get(i); deleteAppWidgetLocked(id); } host.instances.clear(); mHosts.remove(host); // it's gone or going away, abruptly drop the callback connection host.callbacks = null; } void deleteAppWidgetLocked(AppWidgetId 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); synchronized (mAppWidgetIds) { 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(); } } public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) { synchronized (mAppWidgetIds) { 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) { AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); if (id != null) { return id.views; } return null; } } public List<AppWidgetProviderInfo> getInstalledProviders() { synchronized (mAppWidgetIds) { final int N = mInstalledProviders.size(); ArrayList<AppWidgetProviderInfo> result = new ArrayList(N); for (int i=0; i<N; i++) { Provider p = mInstalledProviders.get(i); if (!p.zombie) { result.add(p.info); } } return result; } } public void updateAppWidgetIds(int[] appWidgetIds, RemoteViews views) { if (appWidgetIds == null) { return; } if (appWidgetIds.length == 0) { return; } final int N = appWidgetIds.length; synchronized (mAppWidgetIds) { for (int i=0; i<N; i++) { AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]); updateAppWidgetInstanceLocked(id, views); } } } public void updateAppWidgetProvider(ComponentName provider, RemoteViews views) { synchronized (mAppWidgetIds) { Provider p = lookupProviderLocked(provider); if (p == null) { Log.w(TAG, "updateAppWidgetProvider: provider doesn't exist: " + provider); return; } ArrayList<AppWidgetId> instances = p.instances; final int N = instances.size(); for (int i=0; i<N; i++) { AppWidgetId id = instances.get(i); updateAppWidgetInstanceLocked(id, views); } } } void updateAppWidgetInstanceLocked(AppWidgetId id, RemoteViews views) { // 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) { id.views = views; // is anyone listening? if (id.host.callbacks != null) { try { // the lock is held, but this is a oneway call id.host.callbacks.updateAppWidget(id.appWidgetId, views); } catch (RemoteException e) { // It failed; remove the callback. No need to prune because // we know that this host is still referenced by this instance. id.host.callbacks = null; } } } } public int[] startListening(IAppWidgetHost callbacks, String packageName, int hostId, List<RemoteViews> updatedViews) { int callingUid = enforceCallingUid(packageName); synchronized (mAppWidgetIds) { Host host = lookupOrAddHostLocked(callingUid, packageName, hostId); host.callbacks = callbacks; updatedViews.clear(); ArrayList<AppWidgetId> instances = host.instances; int N = instances.size(); int[] updatedIds = new int[N]; for (int i=0; i<N; i++) { AppWidgetId id = instances.get(i); updatedIds[i] = id.appWidgetId; updatedViews.add(id.views); } return updatedIds; } } public void stopListening(int hostId) { synchronized (mAppWidgetIds) { Host host = lookupHostLocked(getCallingUid(), hostId); host.callbacks = null; pruneHostLocked(host); } } boolean canAccessAppWidgetId(AppWidgetId id, int callingUid) { if (id.host.uid == callingUid) { // Apps hosting the AppWidget have access to it. return true; } if (id.provider != null && id.provider.uid == callingUid) { // Apps providing the AppWidget have access to it (if the appWidgetId has been bound) return true; } if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.BIND_APPWIDGET) == PackageManager.PERMISSION_GRANTED) { // Apps that can bind have access to all appWidgetIds. return true; } // Nobody else can access it. return false; } AppWidgetId lookupAppWidgetIdLocked(int appWidgetId) { int callingUid = getCallingUid(); final int N = mAppWidgetIds.size(); for (int i=0; i<N; i++) { AppWidgetId id = mAppWidgetIds.get(i); if (id.appWidgetId == appWidgetId && canAccessAppWidgetId(id, callingUid)) { return id; } } return null; } Provider lookupProviderLocked(ComponentName provider) { final int N = mInstalledProviders.size(); for (int i=0; i<N; i++) { Provider p = mInstalledProviders.get(i); if (p.info.provider.equals(provider)) { return p; } } return null; } Host lookupHostLocked(int uid, int hostId) { final int N = mHosts.size(); for (int i=0; i<N; i++) { Host h = mHosts.get(i); if (h.uid == uid && h.hostId == hostId) { return h; } } return null; } Host lookupOrAddHostLocked(int uid, String packageName, int hostId) { final int N = mHosts.size(); for (int i=0; i<N; i++) { Host h = mHosts.get(i); if (h.hostId == hostId && h.packageName.equals(packageName)) { return h; } } Host host = new Host(); host.packageName = packageName; host.uid = uid; host.hostId = hostId; mHosts.add(host); return host; } void pruneHostLocked(Host host) { if (host.instances.size() == 0 && host.callbacks == null) { mHosts.remove(host); } } void loadAppWidgetList() { PackageManager pm = mPackageManager; Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); List<ResolveInfo> broadcastReceivers = pm.queryBroadcastReceivers(intent, PackageManager.GET_META_DATA); final int N = broadcastReceivers.size(); for (int i=0; i<N; i++) { ResolveInfo ri = broadcastReceivers.get(i); addProviderLocked(ri); } } boolean addProviderLocked(ResolveInfo ri) { 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); // no need to send the DISABLE broadcast, since the receiver is gone anyway cancelBroadcasts(p); } void sendEnableIntentLocked(Provider p) { Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLED); intent.setComponent(p.info.provider); mContext.sendBroadcast(intent); } void sendUpdateIntentLocked(Provider p, int[] appWidgetIds) { if (appWidgetIds != null && appWidgetIds.length > 0) { Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); intent.setComponent(p.info.provider); mContext.sendBroadcast(intent); } } void registerForBroadcastsLocked(Provider p, int[] appWidgetIds) { if (p.info.updatePeriodMillis > 0) { // if this is the first instance, set the alarm. otherwise, // rely on the fact that we've already set it and that // PendingIntent.getBroadcast will update the extras. boolean alreadyRegistered = p.broadcast != null; int instancesSize = p.instances.size(); 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) { mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + p.info.updatePeriodMillis, p.info.updatePeriodMillis, 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) { Provider p = lookupProviderLocked(provider); if (p != null && getCallingUid() == p.uid) { return getAppWidgetIds(p); } else { return new int[0]; } } } private Provider parseProviderInfoXml(ComponentName component, ResolveInfo ri) { Provider p = null; ActivityInfo activityInfo = ri.activityInfo; XmlResourceParser parser = null; try { parser = activityInfo.loadXmlMetaData(mPackageManager, AppWidgetManager.META_DATA_APPWIDGET_PROVIDER); if (parser == null) { Log.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)) { Log.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; TypedArray sa = mContext.getResources().obtainAttributes(attrs, com.android.internal.R.styleable.AppWidgetProviderInfo); info.minWidth = sa.getDimensionPixelSize( com.android.internal.R.styleable.AppWidgetProviderInfo_minWidth, 0); info.minHeight = sa.getDimensionPixelSize( com.android.internal.R.styleable.AppWidgetProviderInfo_minHeight, 0); info.updatePeriodMillis = sa.getInt( com.android.internal.R.styleable.AppWidgetProviderInfo_updatePeriodMillis, 0); info.initialLayout = sa.getResourceId( com.android.internal.R.styleable.AppWidgetProviderInfo_initialLayout, 0); String className = sa.getString( com.android.internal.R.styleable.AppWidgetProviderInfo_configure); if (className != null) { info.configure = new ComponentName(component.getPackageName(), className); } info.label = activityInfo.loadLabel(mPackageManager).toString(); info.icon = ri.getIconResource(); 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. Log.w(TAG, "XML parsing failed for AppWidget provider '" + component + '\'', e); return null; } finally { if (parser != null) parser.close(); } return p; } int getUidForPackage(String packageName) throws PackageManager.NameNotFoundException { PackageInfo pkgInfo = mPackageManager.getPackageInfo(packageName, 0); if (pkgInfo == null || pkgInfo.applicationInfo == null) { throw new PackageManager.NameNotFoundException(); } return pkgInfo.applicationInfo.uid; } int enforceCallingUid(String packageName) throws IllegalArgumentException { int callingUid = getCallingUid(); int packageUid; try { packageUid = getUidForPackage(packageName); } catch (PackageManager.NameNotFoundException ex) { throw new IllegalArgumentException("packageName and uid don't match packageName=" + packageName); } if (callingUid != packageUid) { throw new IllegalArgumentException("packageName and uid don't match packageName=" + packageName); } return callingUid; } void sendInitialBroadcasts() { synchronized (mAppWidgetIds) { 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() { File temp = savedStateTempFile(); File real = savedStateRealFile(); // prefer the real file. If it doesn't exist, use the temp one, and then copy it to the // real one. if there is both a real file and a temp one, assume that the temp one isn't // fully written and delete it. if (real.exists()) { readStateFromFileLocked(real); if (temp.exists()) { temp.delete(); } } else if (temp.exists()) { readStateFromFileLocked(temp); temp.renameTo(real); } } void saveStateLocked() { File temp = savedStateTempFile(); File real = savedStateRealFile(); if (!real.exists()) { // If the real one doesn't exist, it's either because this is the first time // or because something went wrong while copying them. In this case, we can't // trust anything that's in temp. In order to have the loadState code not // use the temporary one until it's fully written, create an empty file // for real, which will we'll shortly delete. try { real.createNewFile(); } catch (IOException e) { } } if (temp.exists()) { temp.delete(); } writeStateToFileLocked(temp); real.delete(); temp.renameTo(real); } void writeStateToFileLocked(File file) { FileOutputStream stream = null; int N; try { stream = new FileOutputStream(file, false); XmlSerializer out = new FastXmlSerializer(); out.setOutput(stream, "utf-8"); out.startDocument(null, true); out.startTag(null, "gs"); int providerIndex = 0; N = mInstalledProviders.size(); for (int i=0; i<N; i++) { Provider p = mInstalledProviders.get(i); if (p.instances.size() > 0) { out.startTag(null, "p"); out.attribute(null, "pkg", p.info.provider.getPackageName()); out.attribute(null, "cl", p.info.provider.getClassName()); out.endTag(null, "h"); p.tag = providerIndex; providerIndex++; } } N = mHosts.size(); for (int i=0; i<N; i++) { Host host = mHosts.get(i); out.startTag(null, "h"); out.attribute(null, "pkg", host.packageName); out.attribute(null, "id", Integer.toHexString(host.hostId)); out.endTag(null, "h"); host.tag = i; } N = mAppWidgetIds.size(); for (int i=0; i<N; i++) { AppWidgetId id = mAppWidgetIds.get(i); out.startTag(null, "g"); out.attribute(null, "id", Integer.toHexString(id.appWidgetId)); out.attribute(null, "h", Integer.toHexString(id.host.tag)); if (id.provider != null) { out.attribute(null, "p", Integer.toHexString(id.provider.tag)); } out.endTag(null, "g"); } out.endTag(null, "gs"); out.endDocument(); stream.close(); } catch (IOException e) { try { if (stream != null) { stream.close(); } } catch (IOException ex) { } if (file.exists()) { file.delete(); } } } void readStateFromFileLocked(File file) { FileInputStream stream = null; boolean success = false; try { stream = new FileInputStream(file); XmlPullParser parser = Xml.newPullParser(); parser.setInput(stream, null); int type; int providerIndex = 0; HashMap<Integer,Provider> 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"); 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) { Log.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) { Log.w(TAG, "failed parsing " + file, e); } catch (NumberFormatException e) { Log.w(TAG, "failed parsing " + file, e); } catch (XmlPullParserException e) { Log.w(TAG, "failed parsing " + file, e); } catch (IOException e) { Log.w(TAG, "failed parsing " + file, e); } catch (IndexOutOfBoundsException e) { Log.w(TAG, "failed parsing " + file, e); } try { if (stream != null) { stream.close(); } } catch (IOException e) { } if (success) { // delete any hosts that didn't manage to get connected (should happen) // if it matters, they'll be reconnected. final int N = mHosts.size(); for (int i=0; i<N; i++) { pruneHostLocked(mHosts.get(i)); } } else { // failed reading, clean up mAppWidgetIds.clear(); mHosts.clear(); final int N = mInstalledProviders.size(); for (int i=0; i<N; i++) { mInstalledProviders.get(i).instances.clear(); } } } File savedStateTempFile() { return new File("/data/system/" + SETTINGS_TMP_FILENAME); //return new File(mContext.getFilesDir(), SETTINGS_FILENAME); } File savedStateRealFile() { return new File("/data/system/" + SETTINGS_FILENAME); //return new File(mContext.getFilesDir(), SETTINGS_TMP_FILENAME); } BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); //Log.d(TAG, "received " + action); if (Intent.ACTION_BOOT_COMPLETED.equals(action)) { sendInitialBroadcasts(); } else { Uri uri = intent.getData(); if (uri == null) { return; } String pkgName = uri.getSchemeSpecificPart(); if (pkgName == null) { return; } if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { synchronized (mAppWidgetIds) { Bundle extras = intent.getExtras(); if (extras != null && extras.getBoolean(Intent.EXTRA_REPLACING, false)) { // The package was just upgraded updateProvidersForPackageLocked(pkgName); } else { // The package was just added addProvidersForPackageLocked(pkgName); } saveStateLocked(); } } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { 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) { removeProvidersForPackageLocked(pkgName); saveStateLocked(); } } } } } }; // TODO: If there's a better way of matching an intent filter against the // packages for a given package, use that. void addProvidersForPackageLocked(String pkgName) { Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); List<ResolveInfo> broadcastReceivers = mPackageManager.queryBroadcastReceivers(intent, PackageManager.GET_META_DATA); final int N = broadcastReceivers.size(); for (int i=0; i<N; i++) { ResolveInfo ri = broadcastReceivers.get(i); ActivityInfo ai = ri.activityInfo; if (pkgName.equals(ai.packageName)) { addProviderLocked(ri); } } } // TODO: If there's a better way of matching an intent filter against the // packages for a given package, use that. void updateProvidersForPackageLocked(String pkgName) { HashSet<String> keep = new HashSet(); Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); List<ResolveInfo> broadcastReceivers = mPackageManager.queryBroadcastReceivers(intent, PackageManager.GET_META_DATA); // add the missing ones and collect which ones to keep int N = broadcastReceivers.size(); for (int i=0; i<N; i++) { ResolveInfo ri = broadcastReceivers.get(i); ActivityInfo ai = ri.activityInfo; if (pkgName.equals(ai.packageName)) { ComponentName component = new ComponentName(ai.packageName, ai.name); Provider p = lookupProviderLocked(component); if (p == null) { if (addProviderLocked(ri)) { keep.add(ai.name); } } else { Provider parsed = parseProviderInfoXml(component, ri); if (parsed != null) { keep.add(ai.name); // Use the new AppWidgetProviderInfo. AppWidgetProviderInfo oldInfo = p.info; 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); if (id.host != null && id.host.callbacks != null) { try { id.host.callbacks.providerChanged(id.appWidgetId, p.info); } catch (RemoteException ex) { // It failed; remove the callback. No need to prune because // we know that this host is still referenced by this // instance. id.host.callbacks = null; } } } // Now that we've told the host, push out an update. sendUpdateIntentLocked(p, appWidgetIds); } } } } } // prune the ones we don't want to keep N = mInstalledProviders.size(); for (int i=N-1; i>=0; i--) { Provider p = mInstalledProviders.get(i); if (pkgName.equals(p.info.provider.getPackageName()) && !keep.contains(p.info.provider.getClassName())) { removeProviderLocked(i, p); } } } void removeProvidersForPackageLocked(String pkgName) { int N = mInstalledProviders.size(); for (int i=N-1; i>=0; i--) { Provider p = mInstalledProviders.get(i); if (pkgName.equals(p.info.provider.getPackageName())) { removeProviderLocked(i, p); } } // Delete the hosts for this package too // // By now, we have removed any AppWidgets that were in any hosts here, // so we don't need to worry about sending DISABLE broadcasts to them. N = mHosts.size(); for (int i=N-1; i>=0; i--) { Host host = mHosts.get(i); if (pkgName.equals(host.packageName)) { deleteHostLocked(host); } } } }