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