/* * 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.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageInfo; import android.content.pm.ResolveInfo; 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.Process; import android.os.RemoteException; import android.os.SystemClock; import android.util.AttributeSet; import android.util.Slog; import android.util.TypedValue; 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.Locale; 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.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"; 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; } 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; 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 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_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); } @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 getInstalledProviders() { synchronized (mAppWidgetIds) { 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 updatedViews) { int callingUid = enforceCallingUid(packageName); synchronized (mAppWidgetIds) { 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 " + file, e); } catch (NumberFormatException e) { Slog.w(TAG, "failed parsing " + file, e); } catch (XmlPullParserException e) { Slog.w(TAG, "failed parsing " + file, e); } catch (IOException e) { Slog.w(TAG, "failed parsing " + file, e); } catch (IndexOutOfBoundsException e) { Slog.w(TAG, "failed parsing " + file, e); } try { if (stream != null) { stream.close(); } } catch (IOException e) { // Ignore } 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 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; 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); } if (pkgList == null || pkgList.length == 0) { return; } if (added) { synchronized (mAppWidgetIds) { Bundle extras = intent.getExtras(); if (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) { 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); } } } }