AppWidgetService.java revision 63c2d9ed9cb2174137ee43acb6ee2a86ff4451ea
1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server;
18
19import android.app.AlarmManager;
20import android.app.PendingIntent;
21import android.appwidget.AppWidgetManager;
22import android.appwidget.AppWidgetProviderInfo;
23import android.content.BroadcastReceiver;
24import android.content.ComponentName;
25import android.content.Context;
26import android.content.Intent;
27import android.content.IntentFilter;
28import android.content.pm.ActivityInfo;
29import android.content.pm.PackageManager;
30import android.content.pm.PackageInfo;
31import android.content.pm.ResolveInfo;
32import android.content.res.TypedArray;
33import android.content.res.XmlResourceParser;
34import android.net.Uri;
35import android.os.Binder;
36import android.os.Bundle;
37import android.os.Process;
38import android.os.RemoteException;
39import android.os.SystemClock;
40import android.util.AttributeSet;
41import android.util.Log;
42import android.util.TypedValue;
43import android.util.Xml;
44import android.widget.RemoteViews;
45
46import java.io.IOException;
47import java.io.File;
48import java.io.FileDescriptor;
49import java.io.FileInputStream;
50import java.io.FileOutputStream;
51import java.io.PrintWriter;
52import java.util.ArrayList;
53import java.util.List;
54import java.util.Locale;
55import java.util.HashMap;
56import java.util.HashSet;
57
58import com.android.internal.appwidget.IAppWidgetService;
59import com.android.internal.appwidget.IAppWidgetHost;
60import com.android.internal.util.FastXmlSerializer;
61
62import org.xmlpull.v1.XmlPullParser;
63import org.xmlpull.v1.XmlPullParserException;
64import org.xmlpull.v1.XmlSerializer;
65
66class AppWidgetService extends IAppWidgetService.Stub
67{
68    private static final String TAG = "AppWidgetService";
69
70    private static final String SETTINGS_FILENAME = "appwidgets.xml";
71    private static final String SETTINGS_TMP_FILENAME = SETTINGS_FILENAME + ".tmp";
72    private static final int MIN_UPDATE_PERIOD = 30 * 60 * 1000; // 30 minutes
73
74    /*
75     * When identifying a Host or Provider based on the calling process, use the uid field.
76     * When identifying a Host or Provider based on a package manager broadcast, use the
77     * package given.
78     */
79
80    static class Provider {
81        int uid;
82        AppWidgetProviderInfo info;
83        ArrayList<AppWidgetId> instances = new ArrayList<AppWidgetId>();
84        PendingIntent broadcast;
85        boolean zombie; // if we're in safe mode, don't prune this just because nobody references it
86
87        int tag;    // for use while saving state (the index)
88    }
89
90    static class Host {
91        int uid;
92        int hostId;
93        String packageName;
94        ArrayList<AppWidgetId> instances = new ArrayList<AppWidgetId>();
95        IAppWidgetHost callbacks;
96        boolean zombie; // if we're in safe mode, don't prune this just because nobody references it
97
98        int tag;    // for use while saving state (the index)
99    }
100
101    static class AppWidgetId {
102        int appWidgetId;
103        Provider provider;
104        RemoteViews views;
105        Host host;
106    }
107
108    Context mContext;
109    Locale mLocale;
110    PackageManager mPackageManager;
111    AlarmManager mAlarmManager;
112    ArrayList<Provider> mInstalledProviders = new ArrayList<Provider>();
113    int mNextAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID + 1;
114    final ArrayList<AppWidgetId> mAppWidgetIds = new ArrayList<AppWidgetId>();
115    ArrayList<Host> mHosts = new ArrayList<Host>();
116    boolean mSafeMode;
117
118    AppWidgetService(Context context) {
119        mContext = context;
120        mPackageManager = context.getPackageManager();
121        mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
122    }
123
124    public void systemReady(boolean safeMode) {
125        mSafeMode = safeMode;
126
127        loadAppWidgetList();
128        loadStateLocked();
129
130        // Register for the boot completed broadcast, so we can send the
131        // ENABLE broacasts.  If we try to send them now, they time out,
132        // because the system isn't ready to handle them yet.
133        mContext.registerReceiver(mBroadcastReceiver,
134                new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null);
135
136        // Register for configuration changes so we can update the names
137        // of the widgets when the locale changes.
138        mContext.registerReceiver(mBroadcastReceiver,
139                new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED), null, null);
140
141        // Register for broadcasts about package install, etc., so we can
142        // update the provider list.
143        IntentFilter filter = new IntentFilter();
144        filter.addAction(Intent.ACTION_PACKAGE_ADDED);
145        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
146        filter.addDataScheme("package");
147        mContext.registerReceiver(mBroadcastReceiver, filter);
148    }
149
150    @Override
151    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
152        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
153                != PackageManager.PERMISSION_GRANTED) {
154            pw.println("Permission Denial: can't dump from from pid="
155                    + Binder.getCallingPid()
156                    + ", uid=" + Binder.getCallingUid());
157            return;
158        }
159
160        synchronized (mAppWidgetIds) {
161            int N = mInstalledProviders.size();
162            pw.println("Providers:");
163            for (int i=0; i<N; i++) {
164                Provider p = mInstalledProviders.get(i);
165                AppWidgetProviderInfo info = p.info;
166                pw.print("  ["); pw.print(i); pw.print("] provider ");
167                        pw.print(info.provider.flattenToShortString());
168                        pw.println(':');
169                pw.print("    min=("); pw.print(info.minWidth);
170                        pw.print("x"); pw.print(info.minHeight);
171                        pw.print(") updatePeriodMillis=");
172                        pw.print(info.updatePeriodMillis);
173                        pw.print(" initialLayout=#");
174                        pw.print(Integer.toHexString(info.initialLayout));
175                        pw.print(" zombie="); pw.println(p.zombie);
176            }
177
178            N = mAppWidgetIds.size();
179            pw.println(" ");
180            pw.println("AppWidgetIds:");
181            for (int i=0; i<N; i++) {
182                AppWidgetId id = mAppWidgetIds.get(i);
183                pw.print("  ["); pw.print(i); pw.print("] id=");
184                        pw.println(id.appWidgetId);
185                pw.print("    hostId=");
186                        pw.print(id.host.hostId); pw.print(' ');
187                        pw.print(id.host.packageName); pw.print('/');
188                        pw.println(id.host.uid);
189                if (id.provider != null) {
190                    pw.print("    provider=");
191                            pw.println(id.provider.info.provider.flattenToShortString());
192                }
193                if (id.host != null) {
194                    pw.print("    host.callbacks="); pw.println(id.host.callbacks);
195                }
196                if (id.views != null) {
197                    pw.print("    views="); pw.println(id.views);
198                }
199            }
200
201            N = mHosts.size();
202            pw.println(" ");
203            pw.println("Hosts:");
204            for (int i=0; i<N; i++) {
205                Host host = mHosts.get(i);
206                pw.print("  ["); pw.print(i); pw.print("] hostId=");
207                        pw.print(host.hostId); pw.print(' ');
208                        pw.print(host.packageName); pw.print('/');
209                        pw.print(host.uid); pw.println(':');
210                pw.print("    callbacks="); pw.println(host.callbacks);
211                pw.print("    instances.size="); pw.print(host.instances.size());
212                        pw.print(" zombie="); pw.println(host.zombie);
213            }
214        }
215    }
216
217    public int allocateAppWidgetId(String packageName, int hostId) {
218        int callingUid = enforceCallingUid(packageName);
219        synchronized (mAppWidgetIds) {
220            int appWidgetId = mNextAppWidgetId++;
221
222            Host host = lookupOrAddHostLocked(callingUid, packageName, hostId);
223
224            AppWidgetId id = new AppWidgetId();
225            id.appWidgetId = appWidgetId;
226            id.host = host;
227
228            host.instances.add(id);
229            mAppWidgetIds.add(id);
230
231            saveStateLocked();
232
233            return appWidgetId;
234        }
235    }
236
237    public void deleteAppWidgetId(int appWidgetId) {
238        synchronized (mAppWidgetIds) {
239            AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
240            if (id != null) {
241                deleteAppWidgetLocked(id);
242                saveStateLocked();
243            }
244        }
245    }
246
247    public void deleteHost(int hostId) {
248        synchronized (mAppWidgetIds) {
249            int callingUid = getCallingUid();
250            Host host = lookupHostLocked(callingUid, hostId);
251            if (host != null) {
252                deleteHostLocked(host);
253                saveStateLocked();
254            }
255        }
256    }
257
258    public void deleteAllHosts() {
259        synchronized (mAppWidgetIds) {
260            int callingUid = getCallingUid();
261            final int N = mHosts.size();
262            boolean changed = false;
263            for (int i=N-1; i>=0; i--) {
264                Host host = mHosts.get(i);
265                if (host.uid == callingUid) {
266                    deleteHostLocked(host);
267                    changed = true;
268                }
269            }
270            if (changed) {
271                saveStateLocked();
272            }
273        }
274    }
275
276    void deleteHostLocked(Host host) {
277        final int N = host.instances.size();
278        for (int i=N-1; i>=0; i--) {
279            AppWidgetId id = host.instances.get(i);
280            deleteAppWidgetLocked(id);
281        }
282        host.instances.clear();
283        mHosts.remove(host);
284        // it's gone or going away, abruptly drop the callback connection
285        host.callbacks = null;
286    }
287
288    void deleteAppWidgetLocked(AppWidgetId id) {
289        Host host = id.host;
290        host.instances.remove(id);
291        pruneHostLocked(host);
292
293        mAppWidgetIds.remove(id);
294
295        Provider p = id.provider;
296        if (p != null) {
297            p.instances.remove(id);
298            if (!p.zombie) {
299                // send the broacast saying that this appWidgetId has been deleted
300                Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DELETED);
301                intent.setComponent(p.info.provider);
302                intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id.appWidgetId);
303                mContext.sendBroadcast(intent);
304                if (p.instances.size() == 0) {
305                    // cancel the future updates
306                    cancelBroadcasts(p);
307
308                    // send the broacast saying that the provider is not in use any more
309                    intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DISABLED);
310                    intent.setComponent(p.info.provider);
311                    mContext.sendBroadcast(intent);
312                }
313            }
314        }
315    }
316
317    void cancelBroadcasts(Provider p) {
318        if (p.broadcast != null) {
319            mAlarmManager.cancel(p.broadcast);
320            long token = Binder.clearCallingIdentity();
321            try {
322                p.broadcast.cancel();
323            } finally {
324                Binder.restoreCallingIdentity(token);
325            }
326            p.broadcast = null;
327        }
328    }
329
330    public void bindAppWidgetId(int appWidgetId, ComponentName provider) {
331        mContext.enforceCallingPermission(android.Manifest.permission.BIND_APPWIDGET,
332                "bindGagetId appWidgetId=" + appWidgetId + " provider=" + provider);
333        synchronized (mAppWidgetIds) {
334            AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
335            if (id == null) {
336                throw new IllegalArgumentException("bad appWidgetId");
337            }
338            if (id.provider != null) {
339                throw new IllegalArgumentException("appWidgetId " + appWidgetId + " already bound to "
340                        + id.provider.info.provider);
341            }
342            Provider p = lookupProviderLocked(provider);
343            if (p == null) {
344                throw new IllegalArgumentException("not a appwidget provider: " + provider);
345            }
346            if (p.zombie) {
347                throw new IllegalArgumentException("can't bind to a 3rd party provider in"
348                        + " safe mode: " + provider);
349            }
350
351            id.provider = p;
352            p.instances.add(id);
353            int instancesSize = p.instances.size();
354            if (instancesSize == 1) {
355                // tell the provider that it's ready
356                sendEnableIntentLocked(p);
357            }
358
359            // send an update now -- We need this update now, and just for this appWidgetId.
360            // It's less critical when the next one happens, so when we schdule the next one,
361            // we add updatePeriodMillis to its start time.  That time will have some slop,
362            // but that's okay.
363            sendUpdateIntentLocked(p, new int[] { appWidgetId });
364
365            // schedule the future updates
366            registerForBroadcastsLocked(p, getAppWidgetIds(p));
367            saveStateLocked();
368        }
369    }
370
371    public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) {
372        synchronized (mAppWidgetIds) {
373            AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
374            if (id != null && id.provider != null && !id.provider.zombie) {
375                return id.provider.info;
376            }
377            return null;
378        }
379    }
380
381    public RemoteViews getAppWidgetViews(int appWidgetId) {
382        synchronized (mAppWidgetIds) {
383            AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
384            if (id != null) {
385                return id.views;
386            }
387            return null;
388        }
389    }
390
391    public List<AppWidgetProviderInfo> getInstalledProviders() {
392        synchronized (mAppWidgetIds) {
393            final int N = mInstalledProviders.size();
394            ArrayList<AppWidgetProviderInfo> result = new ArrayList<AppWidgetProviderInfo>(N);
395            for (int i=0; i<N; i++) {
396                Provider p = mInstalledProviders.get(i);
397                if (!p.zombie) {
398                    result.add(p.info);
399                }
400            }
401            return result;
402        }
403    }
404
405    public void updateAppWidgetIds(int[] appWidgetIds, RemoteViews views) {
406        if (appWidgetIds == null) {
407            return;
408        }
409        if (appWidgetIds.length == 0) {
410            return;
411        }
412        final int N = appWidgetIds.length;
413
414        synchronized (mAppWidgetIds) {
415            for (int i=0; i<N; i++) {
416                AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]);
417                updateAppWidgetInstanceLocked(id, views);
418            }
419        }
420    }
421
422    public void updateAppWidgetProvider(ComponentName provider, RemoteViews views) {
423        synchronized (mAppWidgetIds) {
424            Provider p = lookupProviderLocked(provider);
425            if (p == null) {
426                Log.w(TAG, "updateAppWidgetProvider: provider doesn't exist: " + provider);
427                return;
428            }
429            ArrayList<AppWidgetId> instances = p.instances;
430            final int N = instances.size();
431            for (int i=0; i<N; i++) {
432                AppWidgetId id = instances.get(i);
433                updateAppWidgetInstanceLocked(id, views);
434            }
435        }
436    }
437
438    void updateAppWidgetInstanceLocked(AppWidgetId id, RemoteViews views) {
439        // allow for stale appWidgetIds and other badness
440        // lookup also checks that the calling process can access the appWidgetId
441        // drop unbound appWidgetIds (shouldn't be possible under normal circumstances)
442        if (id != null && id.provider != null && !id.provider.zombie && !id.host.zombie) {
443            id.views = views;
444
445            // is anyone listening?
446            if (id.host.callbacks != null) {
447                try {
448                    // the lock is held, but this is a oneway call
449                    id.host.callbacks.updateAppWidget(id.appWidgetId, views);
450                } catch (RemoteException e) {
451                    // It failed; remove the callback. No need to prune because
452                    // we know that this host is still referenced by this instance.
453                    id.host.callbacks = null;
454                }
455            }
456        }
457    }
458
459    public int[] startListening(IAppWidgetHost callbacks, String packageName, int hostId,
460            List<RemoteViews> updatedViews) {
461        int callingUid = enforceCallingUid(packageName);
462        synchronized (mAppWidgetIds) {
463            Host host = lookupOrAddHostLocked(callingUid, packageName, hostId);
464            host.callbacks = callbacks;
465
466            updatedViews.clear();
467
468            ArrayList<AppWidgetId> instances = host.instances;
469            int N = instances.size();
470            int[] updatedIds = new int[N];
471            for (int i=0; i<N; i++) {
472                AppWidgetId id = instances.get(i);
473                updatedIds[i] = id.appWidgetId;
474                updatedViews.add(id.views);
475            }
476            return updatedIds;
477        }
478    }
479
480    public void stopListening(int hostId) {
481        synchronized (mAppWidgetIds) {
482            Host host = lookupHostLocked(getCallingUid(), hostId);
483            if (host != null) {
484                host.callbacks = null;
485                pruneHostLocked(host);
486            }
487        }
488    }
489
490    boolean canAccessAppWidgetId(AppWidgetId id, int callingUid) {
491        if (id.host.uid == callingUid) {
492            // Apps hosting the AppWidget have access to it.
493            return true;
494        }
495        if (id.provider != null && id.provider.uid == callingUid) {
496            // Apps providing the AppWidget have access to it (if the appWidgetId has been bound)
497            return true;
498        }
499        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.BIND_APPWIDGET)
500                == PackageManager.PERMISSION_GRANTED) {
501            // Apps that can bind have access to all appWidgetIds.
502            return true;
503        }
504        // Nobody else can access it.
505        return false;
506    }
507
508   AppWidgetId lookupAppWidgetIdLocked(int appWidgetId) {
509        int callingUid = getCallingUid();
510        final int N = mAppWidgetIds.size();
511        for (int i=0; i<N; i++) {
512            AppWidgetId id = mAppWidgetIds.get(i);
513            if (id.appWidgetId == appWidgetId && canAccessAppWidgetId(id, callingUid)) {
514                return id;
515            }
516        }
517        return null;
518    }
519
520    Provider lookupProviderLocked(ComponentName provider) {
521        final int N = mInstalledProviders.size();
522        for (int i=0; i<N; i++) {
523            Provider p = mInstalledProviders.get(i);
524            if (p.info.provider.equals(provider)) {
525                return p;
526            }
527        }
528        return null;
529    }
530
531    Host lookupHostLocked(int uid, int hostId) {
532        final int N = mHosts.size();
533        for (int i=0; i<N; i++) {
534            Host h = mHosts.get(i);
535            if (h.uid == uid && h.hostId == hostId) {
536                return h;
537            }
538        }
539        return null;
540    }
541
542    Host lookupOrAddHostLocked(int uid, String packageName, int hostId) {
543        final int N = mHosts.size();
544        for (int i=0; i<N; i++) {
545            Host h = mHosts.get(i);
546            if (h.hostId == hostId && h.packageName.equals(packageName)) {
547                return h;
548            }
549        }
550        Host host = new Host();
551        host.packageName = packageName;
552        host.uid = uid;
553        host.hostId = hostId;
554        mHosts.add(host);
555        return host;
556    }
557
558    void pruneHostLocked(Host host) {
559        if (host.instances.size() == 0 && host.callbacks == null) {
560            mHosts.remove(host);
561        }
562    }
563
564    void loadAppWidgetList() {
565        PackageManager pm = mPackageManager;
566
567        Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
568        List<ResolveInfo> broadcastReceivers = pm.queryBroadcastReceivers(intent,
569                PackageManager.GET_META_DATA);
570
571        final int N = broadcastReceivers.size();
572        for (int i=0; i<N; i++) {
573            ResolveInfo ri = broadcastReceivers.get(i);
574            addProviderLocked(ri);
575        }
576    }
577
578    boolean addProviderLocked(ResolveInfo ri) {
579        Provider p = parseProviderInfoXml(new ComponentName(ri.activityInfo.packageName,
580                    ri.activityInfo.name), ri);
581        if (p != null) {
582            mInstalledProviders.add(p);
583            return true;
584        } else {
585            return false;
586        }
587    }
588
589    void removeProviderLocked(int index, Provider p) {
590        int N = p.instances.size();
591        for (int i=0; i<N; i++) {
592            AppWidgetId id = p.instances.get(i);
593            // Call back with empty RemoteViews
594            updateAppWidgetInstanceLocked(id, null);
595            // Stop telling the host about updates for this from now on
596            cancelBroadcasts(p);
597            // clear out references to this appWidgetId
598            id.host.instances.remove(id);
599            mAppWidgetIds.remove(id);
600            id.provider = null;
601            pruneHostLocked(id.host);
602            id.host = null;
603        }
604        p.instances.clear();
605        mInstalledProviders.remove(index);
606        // no need to send the DISABLE broadcast, since the receiver is gone anyway
607        cancelBroadcasts(p);
608    }
609
610    void sendEnableIntentLocked(Provider p) {
611        Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLED);
612        intent.setComponent(p.info.provider);
613        mContext.sendBroadcast(intent);
614    }
615
616    void sendUpdateIntentLocked(Provider p, int[] appWidgetIds) {
617        if (appWidgetIds != null && appWidgetIds.length > 0) {
618            Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
619            intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
620            intent.setComponent(p.info.provider);
621            mContext.sendBroadcast(intent);
622        }
623    }
624
625    void registerForBroadcastsLocked(Provider p, int[] appWidgetIds) {
626        if (p.info.updatePeriodMillis > 0) {
627            // if this is the first instance, set the alarm.  otherwise,
628            // rely on the fact that we've already set it and that
629            // PendingIntent.getBroadcast will update the extras.
630            boolean alreadyRegistered = p.broadcast != null;
631            Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
632            intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
633            intent.setComponent(p.info.provider);
634            long token = Binder.clearCallingIdentity();
635            try {
636                p.broadcast = PendingIntent.getBroadcast(mContext, 1, intent,
637                        PendingIntent.FLAG_UPDATE_CURRENT);
638            } finally {
639                Binder.restoreCallingIdentity(token);
640            }
641            if (!alreadyRegistered) {
642                long period = p.info.updatePeriodMillis;
643                if (period < MIN_UPDATE_PERIOD) {
644                    period = MIN_UPDATE_PERIOD;
645                }
646                mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
647                        SystemClock.elapsedRealtime() + period, period, p.broadcast);
648            }
649        }
650    }
651
652    static int[] getAppWidgetIds(Provider p) {
653        int instancesSize = p.instances.size();
654        int appWidgetIds[] = new int[instancesSize];
655        for (int i=0; i<instancesSize; i++) {
656            appWidgetIds[i] = p.instances.get(i).appWidgetId;
657        }
658        return appWidgetIds;
659    }
660
661    public int[] getAppWidgetIds(ComponentName provider) {
662        synchronized (mAppWidgetIds) {
663            Provider p = lookupProviderLocked(provider);
664            if (p != null && getCallingUid() == p.uid) {
665                return getAppWidgetIds(p);
666            } else {
667                return new int[0];
668            }
669        }
670    }
671
672    private Provider parseProviderInfoXml(ComponentName component, ResolveInfo ri) {
673        Provider p = null;
674
675        ActivityInfo activityInfo = ri.activityInfo;
676        XmlResourceParser parser = null;
677        try {
678            parser = activityInfo.loadXmlMetaData(mPackageManager,
679                    AppWidgetManager.META_DATA_APPWIDGET_PROVIDER);
680            if (parser == null) {
681                Log.w(TAG, "No " + AppWidgetManager.META_DATA_APPWIDGET_PROVIDER + " meta-data for "
682                        + "AppWidget provider '" + component + '\'');
683                return null;
684            }
685
686            AttributeSet attrs = Xml.asAttributeSet(parser);
687
688            int type;
689            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
690                    && type != XmlPullParser.START_TAG) {
691                // drain whitespace, comments, etc.
692            }
693
694            String nodeName = parser.getName();
695            if (!"appwidget-provider".equals(nodeName)) {
696                Log.w(TAG, "Meta-data does not start with appwidget-provider tag for"
697                        + " AppWidget provider '" + component + '\'');
698                return null;
699            }
700
701            p = new Provider();
702            AppWidgetProviderInfo info = p.info = new AppWidgetProviderInfo();
703
704            info.provider = component;
705            p.uid = activityInfo.applicationInfo.uid;
706
707            TypedArray sa = mContext.getResources().obtainAttributes(attrs,
708                    com.android.internal.R.styleable.AppWidgetProviderInfo);
709
710            // These dimensions has to be resolved in the application's context.
711            // We simply send back the raw complex data, which will be
712            // converted to dp in {@link AppWidgetManager#getAppWidgetInfo}.
713            TypedValue value = sa.peekValue(
714                    com.android.internal.R.styleable.AppWidgetProviderInfo_minWidth);
715            info.minWidth = value != null ? value.data : 0;
716            value = sa.peekValue(com.android.internal.R.styleable.AppWidgetProviderInfo_minHeight);
717            info.minHeight = value != null ? value.data : 0;
718
719            info.updatePeriodMillis = sa.getInt(
720                    com.android.internal.R.styleable.AppWidgetProviderInfo_updatePeriodMillis, 0);
721            info.initialLayout = sa.getResourceId(
722                    com.android.internal.R.styleable.AppWidgetProviderInfo_initialLayout, 0);
723            String className = sa.getString(
724                    com.android.internal.R.styleable.AppWidgetProviderInfo_configure);
725            if (className != null) {
726                info.configure = new ComponentName(component.getPackageName(), className);
727            }
728            info.label = activityInfo.loadLabel(mPackageManager).toString();
729            info.icon = ri.getIconResource();
730            sa.recycle();
731        } catch (Exception e) {
732            // Ok to catch Exception here, because anything going wrong because
733            // of what a client process passes to us should not be fatal for the
734            // system process.
735            Log.w(TAG, "XML parsing failed for AppWidget provider '" + component + '\'', e);
736            return null;
737        } finally {
738            if (parser != null) parser.close();
739        }
740        return p;
741    }
742
743    int getUidForPackage(String packageName) throws PackageManager.NameNotFoundException {
744        PackageInfo pkgInfo = mPackageManager.getPackageInfo(packageName, 0);
745        if (pkgInfo == null || pkgInfo.applicationInfo == null) {
746            throw new PackageManager.NameNotFoundException();
747        }
748        return pkgInfo.applicationInfo.uid;
749    }
750
751    int enforceCallingUid(String packageName) throws IllegalArgumentException {
752        int callingUid = getCallingUid();
753        int packageUid;
754        try {
755            packageUid = getUidForPackage(packageName);
756        } catch (PackageManager.NameNotFoundException ex) {
757            throw new IllegalArgumentException("packageName and uid don't match packageName="
758                    + packageName);
759        }
760        if (callingUid != packageUid && Process.supportsProcesses()) {
761            throw new IllegalArgumentException("packageName and uid don't match packageName="
762                    + packageName);
763        }
764        return callingUid;
765    }
766
767    void sendInitialBroadcasts() {
768        synchronized (mAppWidgetIds) {
769            final int N = mInstalledProviders.size();
770            for (int i=0; i<N; i++) {
771                Provider p = mInstalledProviders.get(i);
772                if (p.instances.size() > 0) {
773                    sendEnableIntentLocked(p);
774                    int[] appWidgetIds = getAppWidgetIds(p);
775                    sendUpdateIntentLocked(p, appWidgetIds);
776                    registerForBroadcastsLocked(p, appWidgetIds);
777                }
778            }
779        }
780    }
781
782    // only call from initialization -- it assumes that the data structures are all empty
783    void loadStateLocked() {
784        File temp = savedStateTempFile();
785        File real = savedStateRealFile();
786
787        // prefer the real file.  If it doesn't exist, use the temp one, and then copy it to the
788        // real one.  if there is both a real file and a temp one, assume that the temp one isn't
789        // fully written and delete it.
790        if (real.exists()) {
791            readStateFromFileLocked(real);
792            if (temp.exists()) {
793                //noinspection ResultOfMethodCallIgnored
794                temp.delete();
795            }
796        } else if (temp.exists()) {
797            readStateFromFileLocked(temp);
798            //noinspection ResultOfMethodCallIgnored
799            temp.renameTo(real);
800        }
801    }
802
803    void saveStateLocked() {
804        File temp = savedStateTempFile();
805        File real = savedStateRealFile();
806
807        if (!real.exists()) {
808            // If the real one doesn't exist, it's either because this is the first time
809            // or because something went wrong while copying them.  In this case, we can't
810            // trust anything that's in temp.  In order to have the loadState code not
811            // use the temporary one until it's fully written, create an empty file
812            // for real, which will we'll shortly delete.
813            try {
814                //noinspection ResultOfMethodCallIgnored
815                real.createNewFile();
816            } catch (IOException e) {
817                // Ignore
818            }
819        }
820
821        if (temp.exists()) {
822            //noinspection ResultOfMethodCallIgnored
823            temp.delete();
824        }
825
826        if (!writeStateToFileLocked(temp)) {
827            Log.w(TAG, "Failed to persist new settings");
828            return;
829        }
830
831        //noinspection ResultOfMethodCallIgnored
832        real.delete();
833        //noinspection ResultOfMethodCallIgnored
834        temp.renameTo(real);
835    }
836
837    boolean writeStateToFileLocked(File file) {
838        FileOutputStream stream = null;
839        int N;
840
841        try {
842            stream = new FileOutputStream(file, false);
843            XmlSerializer out = new FastXmlSerializer();
844            out.setOutput(stream, "utf-8");
845            out.startDocument(null, true);
846
847
848            out.startTag(null, "gs");
849
850            int providerIndex = 0;
851            N = mInstalledProviders.size();
852            for (int i=0; i<N; i++) {
853                Provider p = mInstalledProviders.get(i);
854                if (p.instances.size() > 0) {
855                    out.startTag(null, "p");
856                    out.attribute(null, "pkg", p.info.provider.getPackageName());
857                    out.attribute(null, "cl", p.info.provider.getClassName());
858                    out.endTag(null, "h");
859                    p.tag = providerIndex;
860                    providerIndex++;
861                }
862            }
863
864            N = mHosts.size();
865            for (int i=0; i<N; i++) {
866                Host host = mHosts.get(i);
867                out.startTag(null, "h");
868                out.attribute(null, "pkg", host.packageName);
869                out.attribute(null, "id", Integer.toHexString(host.hostId));
870                out.endTag(null, "h");
871                host.tag = i;
872            }
873
874            N = mAppWidgetIds.size();
875            for (int i=0; i<N; i++) {
876                AppWidgetId id = mAppWidgetIds.get(i);
877                out.startTag(null, "g");
878                out.attribute(null, "id", Integer.toHexString(id.appWidgetId));
879                out.attribute(null, "h", Integer.toHexString(id.host.tag));
880                if (id.provider != null) {
881                    out.attribute(null, "p", Integer.toHexString(id.provider.tag));
882                }
883                out.endTag(null, "g");
884            }
885
886            out.endTag(null, "gs");
887
888            out.endDocument();
889            stream.close();
890            return true;
891        } catch (IOException e) {
892            try {
893                if (stream != null) {
894                    stream.close();
895                }
896            } catch (IOException ex) {
897                // Ignore
898            }
899            if (file.exists()) {
900                //noinspection ResultOfMethodCallIgnored
901                file.delete();
902            }
903            return false;
904        }
905    }
906
907    void readStateFromFileLocked(File file) {
908        FileInputStream stream = null;
909
910        boolean success = false;
911
912        try {
913            stream = new FileInputStream(file);
914            XmlPullParser parser = Xml.newPullParser();
915            parser.setInput(stream, null);
916
917            int type;
918            int providerIndex = 0;
919            HashMap<Integer,Provider> loadedProviders = new HashMap<Integer, Provider>();
920            do {
921                type = parser.next();
922                if (type == XmlPullParser.START_TAG) {
923                    String tag = parser.getName();
924                    if ("p".equals(tag)) {
925                        // TODO: do we need to check that this package has the same signature
926                        // as before?
927                        String pkg = parser.getAttributeValue(null, "pkg");
928                        String cl = parser.getAttributeValue(null, "cl");
929                        Provider p = lookupProviderLocked(new ComponentName(pkg, cl));
930                        if (p == null && mSafeMode) {
931                            // if we're in safe mode, make a temporary one
932                            p = new Provider();
933                            p.info = new AppWidgetProviderInfo();
934                            p.info.provider = new ComponentName(pkg, cl);
935                            p.zombie = true;
936                            mInstalledProviders.add(p);
937                        }
938                        if (p != null) {
939                            // if it wasn't uninstalled or something
940                            loadedProviders.put(providerIndex, p);
941                        }
942                        providerIndex++;
943                    }
944                    else if ("h".equals(tag)) {
945                        Host host = new Host();
946
947                        // TODO: do we need to check that this package has the same signature
948                        // as before?
949                        host.packageName = parser.getAttributeValue(null, "pkg");
950                        try {
951                            host.uid = getUidForPackage(host.packageName);
952                        } catch (PackageManager.NameNotFoundException ex) {
953                            host.zombie = true;
954                        }
955                        if (!host.zombie || mSafeMode) {
956                            // In safe mode, we don't discard the hosts we don't recognize
957                            // so that they're not pruned from our list.  Otherwise, we do.
958                            host.hostId = Integer.parseInt(
959                                    parser.getAttributeValue(null, "id"), 16);
960                            mHosts.add(host);
961                        }
962                    }
963                    else if ("g".equals(tag)) {
964                        AppWidgetId id = new AppWidgetId();
965                        id.appWidgetId = Integer.parseInt(parser.getAttributeValue(null, "id"), 16);
966                        if (id.appWidgetId >= mNextAppWidgetId) {
967                            mNextAppWidgetId = id.appWidgetId + 1;
968                        }
969
970                        String providerString = parser.getAttributeValue(null, "p");
971                        if (providerString != null) {
972                            // there's no provider if it hasn't been bound yet.
973                            // maybe we don't have to save this, but it brings the system
974                            // to the state it was in.
975                            int pIndex = Integer.parseInt(providerString, 16);
976                            id.provider = loadedProviders.get(pIndex);
977                            if (false) {
978                                Log.d(TAG, "bound appWidgetId=" + id.appWidgetId + " to provider "
979                                        + pIndex + " which is " + id.provider);
980                            }
981                            if (id.provider == null) {
982                                // This provider is gone.  We just let the host figure out
983                                // that this happened when it fails to load it.
984                                continue;
985                            }
986                        }
987
988                        int hIndex = Integer.parseInt(parser.getAttributeValue(null, "h"), 16);
989                        id.host = mHosts.get(hIndex);
990                        if (id.host == null) {
991                            // This host is gone.
992                            continue;
993                        }
994
995                        if (id.provider != null) {
996                            id.provider.instances.add(id);
997                        }
998                        id.host.instances.add(id);
999                        mAppWidgetIds.add(id);
1000                    }
1001                }
1002            } while (type != XmlPullParser.END_DOCUMENT);
1003            success = true;
1004        } catch (NullPointerException e) {
1005            Log.w(TAG, "failed parsing " + file, e);
1006        } catch (NumberFormatException e) {
1007            Log.w(TAG, "failed parsing " + file, e);
1008        } catch (XmlPullParserException e) {
1009            Log.w(TAG, "failed parsing " + file, e);
1010        } catch (IOException e) {
1011            Log.w(TAG, "failed parsing " + file, e);
1012        } catch (IndexOutOfBoundsException e) {
1013            Log.w(TAG, "failed parsing " + file, e);
1014        }
1015        try {
1016            if (stream != null) {
1017                stream.close();
1018            }
1019        } catch (IOException e) {
1020            // Ignore
1021        }
1022
1023        if (success) {
1024            // delete any hosts that didn't manage to get connected (should happen)
1025            // if it matters, they'll be reconnected.
1026            for (int i=mHosts.size()-1; i>=0; i--) {
1027                pruneHostLocked(mHosts.get(i));
1028            }
1029        } else {
1030            // failed reading, clean up
1031            mAppWidgetIds.clear();
1032            mHosts.clear();
1033            final int N = mInstalledProviders.size();
1034            for (int i=0; i<N; i++) {
1035                mInstalledProviders.get(i).instances.clear();
1036            }
1037        }
1038    }
1039
1040    File savedStateTempFile() {
1041        return new File("/data/system/" + SETTINGS_TMP_FILENAME);
1042        //return new File(mContext.getFilesDir(), SETTINGS_FILENAME);
1043    }
1044
1045    File savedStateRealFile() {
1046        return new File("/data/system/" + SETTINGS_FILENAME);
1047        //return new File(mContext.getFilesDir(), SETTINGS_TMP_FILENAME);
1048    }
1049
1050    BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
1051        public void onReceive(Context context, Intent intent) {
1052            String action = intent.getAction();
1053            //Log.d(TAG, "received " + action);
1054            if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
1055                sendInitialBroadcasts();
1056            } else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) {
1057                Locale revised = Locale.getDefault();
1058                if (revised == null || mLocale == null ||
1059                    !(revised.equals(mLocale))) {
1060                    mLocale = revised;
1061
1062                    synchronized (mAppWidgetIds) {
1063                        int N = mInstalledProviders.size();
1064                        for (int i=N-1; i>=0; i--) {
1065                            Provider p = mInstalledProviders.get(i);
1066                            String pkgName = p.info.provider.getPackageName();
1067                            updateProvidersForPackageLocked(pkgName);
1068                        }
1069                        saveStateLocked();
1070                    }
1071                }
1072            } else {
1073                Uri uri = intent.getData();
1074                if (uri == null) {
1075                    return;
1076                }
1077                String pkgName = uri.getSchemeSpecificPart();
1078                if (pkgName == null) {
1079                    return;
1080                }
1081
1082                if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
1083                    synchronized (mAppWidgetIds) {
1084                        Bundle extras = intent.getExtras();
1085                        if (extras != null && extras.getBoolean(Intent.EXTRA_REPLACING, false)) {
1086                            // The package was just upgraded
1087                            updateProvidersForPackageLocked(pkgName);
1088                        } else {
1089                            // The package was just added
1090                            addProvidersForPackageLocked(pkgName);
1091                        }
1092                        saveStateLocked();
1093                    }
1094                }
1095                else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
1096                    Bundle extras = intent.getExtras();
1097                    if (extras != null && extras.getBoolean(Intent.EXTRA_REPLACING, false)) {
1098                        // The package is being updated.  We'll receive a PACKAGE_ADDED shortly.
1099                    } else {
1100                        synchronized (mAppWidgetIds) {
1101                            removeProvidersForPackageLocked(pkgName);
1102                            saveStateLocked();
1103                        }
1104                    }
1105                }
1106            }
1107        }
1108    };
1109
1110    // TODO: If there's a better way of matching an intent filter against the
1111    // packages for a given package, use that.
1112    void addProvidersForPackageLocked(String pkgName) {
1113        Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
1114        List<ResolveInfo> broadcastReceivers = mPackageManager.queryBroadcastReceivers(intent,
1115                PackageManager.GET_META_DATA);
1116
1117        final int N = broadcastReceivers.size();
1118        for (int i=0; i<N; i++) {
1119            ResolveInfo ri = broadcastReceivers.get(i);
1120            ActivityInfo ai = ri.activityInfo;
1121
1122            if (pkgName.equals(ai.packageName)) {
1123                addProviderLocked(ri);
1124            }
1125        }
1126    }
1127
1128    // TODO: If there's a better way of matching an intent filter against the
1129    // packages for a given package, use that.
1130    void updateProvidersForPackageLocked(String pkgName) {
1131        HashSet<String> keep = new HashSet<String>();
1132        Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
1133        List<ResolveInfo> broadcastReceivers = mPackageManager.queryBroadcastReceivers(intent,
1134                PackageManager.GET_META_DATA);
1135
1136        // add the missing ones and collect which ones to keep
1137        int N = broadcastReceivers.size();
1138        for (int i=0; i<N; i++) {
1139            ResolveInfo ri = broadcastReceivers.get(i);
1140            ActivityInfo ai = ri.activityInfo;
1141            if (pkgName.equals(ai.packageName)) {
1142                ComponentName component = new ComponentName(ai.packageName, ai.name);
1143                Provider p = lookupProviderLocked(component);
1144                if (p == null) {
1145                    if (addProviderLocked(ri)) {
1146                        keep.add(ai.name);
1147                    }
1148                } else {
1149                    Provider parsed = parseProviderInfoXml(component, ri);
1150                    if (parsed != null) {
1151                        keep.add(ai.name);
1152                        // Use the new AppWidgetProviderInfo.
1153                        p.info = parsed.info;
1154                        // If it's enabled
1155                        final int M = p.instances.size();
1156                        if (M > 0) {
1157                            int[] appWidgetIds = getAppWidgetIds(p);
1158                            // Reschedule for the new updatePeriodMillis (don't worry about handling
1159                            // it specially if updatePeriodMillis didn't change because we just sent
1160                            // an update, and the next one will be updatePeriodMillis from now).
1161                            cancelBroadcasts(p);
1162                            registerForBroadcastsLocked(p, appWidgetIds);
1163                            // If it's currently showing, call back with the new AppWidgetProviderInfo.
1164                            for (int j=0; j<M; j++) {
1165                                AppWidgetId id = p.instances.get(j);
1166                                if (id.host != null && id.host.callbacks != null) {
1167                                    try {
1168                                        id.host.callbacks.providerChanged(id.appWidgetId, p.info);
1169                                    } catch (RemoteException ex) {
1170                                        // It failed; remove the callback. No need to prune because
1171                                        // we know that this host is still referenced by this
1172                                        // instance.
1173                                        id.host.callbacks = null;
1174                                    }
1175                                }
1176                            }
1177                            // Now that we've told the host, push out an update.
1178                            sendUpdateIntentLocked(p, appWidgetIds);
1179                        }
1180                    }
1181                }
1182            }
1183        }
1184
1185        // prune the ones we don't want to keep
1186        N = mInstalledProviders.size();
1187        for (int i=N-1; i>=0; i--) {
1188            Provider p = mInstalledProviders.get(i);
1189            if (pkgName.equals(p.info.provider.getPackageName())
1190                    && !keep.contains(p.info.provider.getClassName())) {
1191                removeProviderLocked(i, p);
1192            }
1193        }
1194    }
1195
1196    void removeProvidersForPackageLocked(String pkgName) {
1197        int N = mInstalledProviders.size();
1198        for (int i=N-1; i>=0; i--) {
1199            Provider p = mInstalledProviders.get(i);
1200            if (pkgName.equals(p.info.provider.getPackageName())) {
1201                removeProviderLocked(i, p);
1202            }
1203        }
1204
1205        // Delete the hosts for this package too
1206        //
1207        // By now, we have removed any AppWidgets that were in any hosts here,
1208        // so we don't need to worry about sending DISABLE broadcasts to them.
1209        N = mHosts.size();
1210        for (int i=N-1; i>=0; i--) {
1211            Host host = mHosts.get(i);
1212            if (pkgName.equals(host.packageName)) {
1213                deleteHostLocked(host);
1214            }
1215        }
1216    }
1217}
1218
1219