1/*
2 * Copyright (C) 2008 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 static android.os.FileObserver.*;
20import static android.os.ParcelFileDescriptor.*;
21
22import android.app.IWallpaperManager;
23import android.app.IWallpaperManagerCallback;
24import android.app.PendingIntent;
25import android.app.WallpaperInfo;
26import android.app.backup.BackupManager;
27import android.content.ComponentName;
28import android.content.Context;
29import android.content.Intent;
30import android.content.ServiceConnection;
31import android.content.pm.PackageManager;
32import android.content.pm.ResolveInfo;
33import android.content.pm.ServiceInfo;
34import android.content.pm.PackageManager.NameNotFoundException;
35import android.content.res.Resources;
36import android.os.Binder;
37import android.os.Bundle;
38import android.os.FileUtils;
39import android.os.IBinder;
40import android.os.RemoteException;
41import android.os.FileObserver;
42import android.os.ParcelFileDescriptor;
43import android.os.RemoteCallbackList;
44import android.os.ServiceManager;
45import android.os.SystemClock;
46import android.service.wallpaper.IWallpaperConnection;
47import android.service.wallpaper.IWallpaperEngine;
48import android.service.wallpaper.IWallpaperService;
49import android.service.wallpaper.WallpaperService;
50import android.util.Slog;
51import android.util.Xml;
52import android.view.Display;
53import android.view.IWindowManager;
54import android.view.WindowManager;
55
56import java.io.FileDescriptor;
57import java.io.IOException;
58import java.io.InputStream;
59import java.io.File;
60import java.io.FileNotFoundException;
61import java.io.FileInputStream;
62import java.io.FileOutputStream;
63import java.io.PrintWriter;
64import java.util.List;
65
66import org.xmlpull.v1.XmlPullParser;
67import org.xmlpull.v1.XmlPullParserException;
68import org.xmlpull.v1.XmlSerializer;
69
70import com.android.internal.content.PackageMonitor;
71import com.android.internal.util.FastXmlSerializer;
72import com.android.internal.util.JournaledFile;
73
74class WallpaperManagerService extends IWallpaperManager.Stub {
75    static final String TAG = "WallpaperService";
76    static final boolean DEBUG = false;
77
78    final Object mLock = new Object[0];
79
80    /**
81     * Minimum time between crashes of a wallpaper service for us to consider
82     * restarting it vs. just reverting to the static wallpaper.
83     */
84    static final long MIN_WALLPAPER_CRASH_TIME = 10000;
85
86    static final File WALLPAPER_DIR = new File(
87            "/data/data/com.android.settings/files");
88    static final String WALLPAPER = "wallpaper";
89    static final File WALLPAPER_FILE = new File(WALLPAPER_DIR, WALLPAPER);
90
91    /**
92     * List of callbacks registered they should each be notified
93     * when the wallpaper is changed.
94     */
95    private final RemoteCallbackList<IWallpaperManagerCallback> mCallbacks
96            = new RemoteCallbackList<IWallpaperManagerCallback>();
97
98    /**
99     * Observes the wallpaper for changes and notifies all IWallpaperServiceCallbacks
100     * that the wallpaper has changed. The CREATE is triggered when there is no
101     * wallpaper set and is created for the first time. The CLOSE_WRITE is triggered
102     * everytime the wallpaper is changed.
103     */
104    private final FileObserver mWallpaperObserver = new FileObserver(
105            WALLPAPER_DIR.getAbsolutePath(), CLOSE_WRITE | DELETE | DELETE_SELF) {
106                @Override
107                public void onEvent(int event, String path) {
108                    if (path == null) {
109                        return;
110                    }
111                    synchronized (mLock) {
112                        // changing the wallpaper means we'll need to back up the new one
113                        long origId = Binder.clearCallingIdentity();
114                        BackupManager bm = new BackupManager(mContext);
115                        bm.dataChanged();
116                        Binder.restoreCallingIdentity(origId);
117
118                        File changedFile = new File(WALLPAPER_DIR, path);
119                        if (WALLPAPER_FILE.equals(changedFile)) {
120                            notifyCallbacksLocked();
121                            if (mWallpaperComponent == null || event != CLOSE_WRITE
122                                    || mImageWallpaperPending) {
123                                if (event == CLOSE_WRITE) {
124                                    mImageWallpaperPending = false;
125                                }
126                                bindWallpaperComponentLocked(mImageWallpaperComponent,
127                                        true, false);
128                                saveSettingsLocked();
129                            }
130                        }
131                    }
132                }
133            };
134
135    final Context mContext;
136    final IWindowManager mIWindowManager;
137    final MyPackageMonitor mMonitor;
138
139    int mWidth = -1;
140    int mHeight = -1;
141
142    /**
143     * Client is currently writing a new image wallpaper.
144     */
145    boolean mImageWallpaperPending;
146
147    /**
148     * Resource name if using a picture from the wallpaper gallery
149     */
150    String mName = "";
151
152    /**
153     * The component name of the currently set live wallpaper.
154     */
155    ComponentName mWallpaperComponent;
156
157    /**
158     * The component name of the wallpaper that should be set next.
159     */
160    ComponentName mNextWallpaperComponent;
161
162    /**
163     * Name of the component used to display bitmap wallpapers from either the gallery or
164     * built-in wallpapers.
165     */
166    ComponentName mImageWallpaperComponent = new ComponentName("com.android.systemui",
167            "com.android.systemui.ImageWallpaper");
168
169    WallpaperConnection mWallpaperConnection;
170    long mLastDiedTime;
171    boolean mWallpaperUpdating;
172
173    class WallpaperConnection extends IWallpaperConnection.Stub
174            implements ServiceConnection {
175        final WallpaperInfo mInfo;
176        final Binder mToken = new Binder();
177        IWallpaperService mService;
178        IWallpaperEngine mEngine;
179
180        public WallpaperConnection(WallpaperInfo info) {
181            mInfo = info;
182        }
183
184        public void onServiceConnected(ComponentName name, IBinder service) {
185            synchronized (mLock) {
186                if (mWallpaperConnection == this) {
187                    mLastDiedTime = SystemClock.uptimeMillis();
188                    mService = IWallpaperService.Stub.asInterface(service);
189                    attachServiceLocked(this);
190                    // XXX should probably do saveSettingsLocked() later
191                    // when we have an engine, but I'm not sure about
192                    // locking there and anyway we always need to be able to
193                    // recover if there is something wrong.
194                    saveSettingsLocked();
195                }
196            }
197        }
198
199        public void onServiceDisconnected(ComponentName name) {
200            synchronized (mLock) {
201                mService = null;
202                mEngine = null;
203                if (mWallpaperConnection == this) {
204                    Slog.w(TAG, "Wallpaper service gone: " + mWallpaperComponent);
205                    if (!mWallpaperUpdating && (mLastDiedTime+MIN_WALLPAPER_CRASH_TIME)
206                                > SystemClock.uptimeMillis()) {
207                        Slog.w(TAG, "Reverting to built-in wallpaper!");
208                        clearWallpaperLocked(true);
209                    }
210                }
211            }
212        }
213
214        public void attachEngine(IWallpaperEngine engine) {
215            mEngine = engine;
216        }
217
218        public ParcelFileDescriptor setWallpaper(String name) {
219            synchronized (mLock) {
220                if (mWallpaperConnection == this) {
221                    return updateWallpaperBitmapLocked(name);
222                }
223                return null;
224            }
225        }
226    }
227
228    class MyPackageMonitor extends PackageMonitor {
229        @Override
230        public void onPackageUpdateFinished(String packageName, int uid) {
231            synchronized (mLock) {
232                if (mWallpaperComponent != null &&
233                        mWallpaperComponent.getPackageName().equals(packageName)) {
234                    mWallpaperUpdating = false;
235                    ComponentName comp = mWallpaperComponent;
236                    clearWallpaperComponentLocked();
237                    if (!bindWallpaperComponentLocked(comp, false, false)) {
238                        Slog.w(TAG, "Wallpaper no longer available; reverting to default");
239                        clearWallpaperLocked(false);
240                    }
241                }
242            }
243        }
244
245        @Override
246        public void onPackageModified(String packageName) {
247            synchronized (mLock) {
248                if (mWallpaperComponent == null ||
249                        !mWallpaperComponent.getPackageName().equals(packageName)) {
250                    return;
251                }
252            }
253            doPackagesChanged(true);
254        }
255
256        @Override
257        public void onPackageUpdateStarted(String packageName, int uid) {
258            synchronized (mLock) {
259                if (mWallpaperComponent != null &&
260                        mWallpaperComponent.getPackageName().equals(packageName)) {
261                    mWallpaperUpdating = true;
262                }
263            }
264        }
265
266        @Override
267        public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
268            return doPackagesChanged(doit);
269        }
270
271        @Override
272        public void onSomePackagesChanged() {
273            doPackagesChanged(true);
274        }
275
276        boolean doPackagesChanged(boolean doit) {
277            boolean changed = false;
278            synchronized (mLock) {
279                if (mWallpaperComponent != null) {
280                    int change = isPackageDisappearing(mWallpaperComponent.getPackageName());
281                    if (change == PACKAGE_PERMANENT_CHANGE
282                            || change == PACKAGE_TEMPORARY_CHANGE) {
283                        changed = true;
284                        if (doit) {
285                            Slog.w(TAG, "Wallpaper uninstalled, removing: " + mWallpaperComponent);
286                            clearWallpaperLocked(false);
287                        }
288                    }
289                }
290                if (mNextWallpaperComponent != null) {
291                    int change = isPackageDisappearing(mNextWallpaperComponent.getPackageName());
292                    if (change == PACKAGE_PERMANENT_CHANGE
293                            || change == PACKAGE_TEMPORARY_CHANGE) {
294                        mNextWallpaperComponent = null;
295                    }
296                }
297                if (mWallpaperComponent != null
298                        && isPackageModified(mWallpaperComponent.getPackageName())) {
299                    try {
300                        mContext.getPackageManager().getServiceInfo(
301                                mWallpaperComponent, 0);
302                    } catch (NameNotFoundException e) {
303                        Slog.w(TAG, "Wallpaper component gone, removing: " + mWallpaperComponent);
304                        clearWallpaperLocked(false);
305                    }
306                }
307                if (mNextWallpaperComponent != null
308                        && isPackageModified(mNextWallpaperComponent.getPackageName())) {
309                    try {
310                        mContext.getPackageManager().getServiceInfo(
311                                mNextWallpaperComponent, 0);
312                    } catch (NameNotFoundException e) {
313                        mNextWallpaperComponent = null;
314                    }
315                }
316            }
317            return changed;
318        }
319    }
320
321    public WallpaperManagerService(Context context) {
322        if (DEBUG) Slog.v(TAG, "WallpaperService startup");
323        mContext = context;
324        mIWindowManager = IWindowManager.Stub.asInterface(
325                ServiceManager.getService(Context.WINDOW_SERVICE));
326        mMonitor = new MyPackageMonitor();
327        mMonitor.register(context, true);
328        WALLPAPER_DIR.mkdirs();
329        loadSettingsLocked();
330        mWallpaperObserver.startWatching();
331    }
332
333    @Override
334    protected void finalize() throws Throwable {
335        super.finalize();
336        mWallpaperObserver.stopWatching();
337    }
338
339    public void systemReady() {
340        if (DEBUG) Slog.v(TAG, "systemReady");
341        synchronized (mLock) {
342            RuntimeException e = null;
343            try {
344                if (bindWallpaperComponentLocked(mNextWallpaperComponent, false, false)) {
345                    return;
346                }
347            } catch (RuntimeException e1) {
348                e = e1;
349            }
350            Slog.w(TAG, "Failure starting previous wallpaper", e);
351            clearWallpaperLocked(false);
352        }
353    }
354
355    public void clearWallpaper() {
356        if (DEBUG) Slog.v(TAG, "clearWallpaper");
357        synchronized (mLock) {
358            clearWallpaperLocked(false);
359        }
360    }
361
362    public void clearWallpaperLocked(boolean defaultFailed) {
363        File f = WALLPAPER_FILE;
364        if (f.exists()) {
365            f.delete();
366        }
367        final long ident = Binder.clearCallingIdentity();
368        RuntimeException e = null;
369        try {
370            mImageWallpaperPending = false;
371            if (bindWallpaperComponentLocked(defaultFailed
372                    ? mImageWallpaperComponent : null, true, false)) {
373                return;
374            }
375        } catch (IllegalArgumentException e1) {
376            e = e1;
377        } finally {
378            Binder.restoreCallingIdentity(ident);
379        }
380
381        // This can happen if the default wallpaper component doesn't
382        // exist.  This should be a system configuration problem, but
383        // let's not let it crash the system and just live with no
384        // wallpaper.
385        Slog.e(TAG, "Default wallpaper component not found!", e);
386        clearWallpaperComponentLocked();
387    }
388
389    public void setDimensionHints(int width, int height) throws RemoteException {
390        checkPermission(android.Manifest.permission.SET_WALLPAPER_HINTS);
391
392        if (width <= 0 || height <= 0) {
393            throw new IllegalArgumentException("width and height must be > 0");
394        }
395
396        synchronized (mLock) {
397            if (width != mWidth || height != mHeight) {
398                mWidth = width;
399                mHeight = height;
400                saveSettingsLocked();
401                if (mWallpaperConnection != null) {
402                    if (mWallpaperConnection.mEngine != null) {
403                        try {
404                            mWallpaperConnection.mEngine.setDesiredSize(
405                                    width, height);
406                        } catch (RemoteException e) {
407                        }
408                        notifyCallbacksLocked();
409                    }
410                }
411            }
412        }
413    }
414
415    public int getWidthHint() throws RemoteException {
416        synchronized (mLock) {
417            return mWidth;
418        }
419    }
420
421    public int getHeightHint() throws RemoteException {
422        synchronized (mLock) {
423            return mHeight;
424        }
425    }
426
427    public ParcelFileDescriptor getWallpaper(IWallpaperManagerCallback cb,
428            Bundle outParams) {
429        synchronized (mLock) {
430            try {
431                if (outParams != null) {
432                    outParams.putInt("width", mWidth);
433                    outParams.putInt("height", mHeight);
434                }
435                mCallbacks.register(cb);
436                File f = WALLPAPER_FILE;
437                if (!f.exists()) {
438                    return null;
439                }
440                return ParcelFileDescriptor.open(f, MODE_READ_ONLY);
441            } catch (FileNotFoundException e) {
442                /* Shouldn't happen as we check to see if the file exists */
443                Slog.w(TAG, "Error getting wallpaper", e);
444            }
445            return null;
446        }
447    }
448
449    public WallpaperInfo getWallpaperInfo() {
450        synchronized (mLock) {
451            if (mWallpaperConnection != null) {
452                return mWallpaperConnection.mInfo;
453            }
454            return null;
455        }
456    }
457
458    public ParcelFileDescriptor setWallpaper(String name) {
459        if (DEBUG) Slog.v(TAG, "setWallpaper");
460
461        checkPermission(android.Manifest.permission.SET_WALLPAPER);
462        synchronized (mLock) {
463            final long ident = Binder.clearCallingIdentity();
464            try {
465                ParcelFileDescriptor pfd = updateWallpaperBitmapLocked(name);
466                if (pfd != null) {
467                    mImageWallpaperPending = true;
468                }
469                return pfd;
470            } finally {
471                Binder.restoreCallingIdentity(ident);
472            }
473        }
474    }
475
476    ParcelFileDescriptor updateWallpaperBitmapLocked(String name) {
477        if (name == null) name = "";
478        try {
479            if (!WALLPAPER_DIR.exists()) {
480                WALLPAPER_DIR.mkdir();
481                FileUtils.setPermissions(
482                        WALLPAPER_DIR.getPath(),
483                        FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
484                        -1, -1);
485            }
486            ParcelFileDescriptor fd = ParcelFileDescriptor.open(WALLPAPER_FILE,
487                    MODE_CREATE|MODE_READ_WRITE);
488            mName = name;
489            return fd;
490        } catch (FileNotFoundException e) {
491            Slog.w(TAG, "Error setting wallpaper", e);
492        }
493        return null;
494    }
495
496    public void setWallpaperComponent(ComponentName name) {
497        if (DEBUG) Slog.v(TAG, "setWallpaperComponent name=" + name);
498        checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT);
499        synchronized (mLock) {
500            final long ident = Binder.clearCallingIdentity();
501            try {
502                mImageWallpaperPending = false;
503                bindWallpaperComponentLocked(name, false, true);
504            } finally {
505                Binder.restoreCallingIdentity(ident);
506            }
507        }
508    }
509
510    boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force, boolean fromUser) {
511        if (DEBUG) Slog.v(TAG, "bindWallpaperComponentLocked: componentName=" + componentName);
512
513        // Has the component changed?
514        if (!force) {
515            if (mWallpaperConnection != null) {
516                if (mWallpaperComponent == null) {
517                    if (componentName == null) {
518                        if (DEBUG) Slog.v(TAG, "bindWallpaperComponentLocked: still using default");
519                        // Still using default wallpaper.
520                        return true;
521                    }
522                } else if (mWallpaperComponent.equals(componentName)) {
523                    // Changing to same wallpaper.
524                    if (DEBUG) Slog.v(TAG, "same wallpaper");
525                    return true;
526                }
527            }
528        }
529
530        try {
531            if (componentName == null) {
532                String defaultComponent =
533                    mContext.getString(com.android.internal.R.string.default_wallpaper_component);
534                if (defaultComponent != null) {
535                    // See if there is a default wallpaper component specified
536                    componentName = ComponentName.unflattenFromString(defaultComponent);
537                    if (DEBUG) Slog.v(TAG, "Use default component wallpaper:" + componentName);
538                }
539                if (componentName == null) {
540                    // Fall back to static image wallpaper
541                    componentName = mImageWallpaperComponent;
542                    //clearWallpaperComponentLocked();
543                    //return;
544                    if (DEBUG) Slog.v(TAG, "Using image wallpaper");
545                }
546            }
547            ServiceInfo si = mContext.getPackageManager().getServiceInfo(componentName,
548                    PackageManager.GET_META_DATA | PackageManager.GET_PERMISSIONS);
549            if (!android.Manifest.permission.BIND_WALLPAPER.equals(si.permission)) {
550                String msg = "Selected service does not require "
551                        + android.Manifest.permission.BIND_WALLPAPER
552                        + ": " + componentName;
553                if (fromUser) {
554                    throw new SecurityException(msg);
555                }
556                Slog.w(TAG, msg);
557                return false;
558            }
559
560            WallpaperInfo wi = null;
561
562            Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE);
563            if (componentName != null && !componentName.equals(mImageWallpaperComponent)) {
564                // Make sure the selected service is actually a wallpaper service.
565                List<ResolveInfo> ris = mContext.getPackageManager()
566                        .queryIntentServices(intent, PackageManager.GET_META_DATA);
567                for (int i=0; i<ris.size(); i++) {
568                    ServiceInfo rsi = ris.get(i).serviceInfo;
569                    if (rsi.name.equals(si.name) &&
570                            rsi.packageName.equals(si.packageName)) {
571                        try {
572                            wi = new WallpaperInfo(mContext, ris.get(i));
573                        } catch (XmlPullParserException e) {
574                            if (fromUser) {
575                                throw new IllegalArgumentException(e);
576                            }
577                            Slog.w(TAG, e);
578                            return false;
579                        } catch (IOException e) {
580                            if (fromUser) {
581                                throw new IllegalArgumentException(e);
582                            }
583                            Slog.w(TAG, e);
584                            return false;
585                        }
586                        break;
587                    }
588                }
589                if (wi == null) {
590                    String msg = "Selected service is not a wallpaper: "
591                            + componentName;
592                    if (fromUser) {
593                        throw new SecurityException(msg);
594                    }
595                    Slog.w(TAG, msg);
596                    return false;
597                }
598            }
599
600            // Bind the service!
601            if (DEBUG) Slog.v(TAG, "Binding to:" + componentName);
602            WallpaperConnection newConn = new WallpaperConnection(wi);
603            intent.setComponent(componentName);
604            intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
605                    com.android.internal.R.string.wallpaper_binding_label);
606            intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
607                    mContext, 0,
608                    Intent.createChooser(new Intent(Intent.ACTION_SET_WALLPAPER),
609                            mContext.getText(com.android.internal.R.string.chooser_wallpaper)),
610                            0));
611            if (!mContext.bindService(intent, newConn,
612                    Context.BIND_AUTO_CREATE)) {
613                String msg = "Unable to bind service: "
614                        + componentName;
615                if (fromUser) {
616                    throw new IllegalArgumentException(msg);
617                }
618                Slog.w(TAG, msg);
619                return false;
620            }
621
622            clearWallpaperComponentLocked();
623            mWallpaperComponent = componentName;
624            mWallpaperConnection = newConn;
625            mLastDiedTime = SystemClock.uptimeMillis();
626            try {
627                if (DEBUG) Slog.v(TAG, "Adding window token: " + newConn.mToken);
628                mIWindowManager.addWindowToken(newConn.mToken,
629                        WindowManager.LayoutParams.TYPE_WALLPAPER);
630            } catch (RemoteException e) {
631            }
632
633        } catch (PackageManager.NameNotFoundException e) {
634            String msg = "Unknown component " + componentName;
635            if (fromUser) {
636                throw new IllegalArgumentException(msg);
637            }
638            Slog.w(TAG, msg);
639            return false;
640        }
641        return true;
642    }
643
644    void clearWallpaperComponentLocked() {
645        mWallpaperComponent = null;
646        if (mWallpaperConnection != null) {
647            if (mWallpaperConnection.mEngine != null) {
648                try {
649                    mWallpaperConnection.mEngine.destroy();
650                } catch (RemoteException e) {
651                }
652            }
653            mContext.unbindService(mWallpaperConnection);
654            try {
655                if (DEBUG) Slog.v(TAG, "Removing window token: "
656                        + mWallpaperConnection.mToken);
657                mIWindowManager.removeWindowToken(mWallpaperConnection.mToken);
658            } catch (RemoteException e) {
659            }
660            mWallpaperConnection.mService = null;
661            mWallpaperConnection.mEngine = null;
662            mWallpaperConnection = null;
663        }
664    }
665
666    void attachServiceLocked(WallpaperConnection conn) {
667        try {
668            conn.mService.attach(conn, conn.mToken,
669                    WindowManager.LayoutParams.TYPE_WALLPAPER, false,
670                    mWidth, mHeight);
671        } catch (RemoteException e) {
672            Slog.w(TAG, "Failed attaching wallpaper; clearing", e);
673            if (!mWallpaperUpdating) {
674                bindWallpaperComponentLocked(null, false, false);
675            }
676        }
677    }
678
679    private void notifyCallbacksLocked() {
680        final int n = mCallbacks.beginBroadcast();
681        for (int i = 0; i < n; i++) {
682            try {
683                mCallbacks.getBroadcastItem(i).onWallpaperChanged();
684            } catch (RemoteException e) {
685
686                // The RemoteCallbackList will take care of removing
687                // the dead object for us.
688            }
689        }
690        mCallbacks.finishBroadcast();
691        final Intent intent = new Intent(Intent.ACTION_WALLPAPER_CHANGED);
692        mContext.sendBroadcast(intent);
693    }
694
695    private void checkPermission(String permission) {
696        if (PackageManager.PERMISSION_GRANTED!= mContext.checkCallingOrSelfPermission(permission)) {
697            throw new SecurityException("Access denied to process: " + Binder.getCallingPid()
698                    + ", must have permission " + permission);
699        }
700    }
701
702    private static JournaledFile makeJournaledFile() {
703        final String base = "/data/system/wallpaper_info.xml";
704        return new JournaledFile(new File(base), new File(base + ".tmp"));
705    }
706
707    private void saveSettingsLocked() {
708        JournaledFile journal = makeJournaledFile();
709        FileOutputStream stream = null;
710        try {
711            stream = new FileOutputStream(journal.chooseForWrite(), false);
712            XmlSerializer out = new FastXmlSerializer();
713            out.setOutput(stream, "utf-8");
714            out.startDocument(null, true);
715
716            out.startTag(null, "wp");
717            out.attribute(null, "width", Integer.toString(mWidth));
718            out.attribute(null, "height", Integer.toString(mHeight));
719            out.attribute(null, "name", mName);
720            if (mWallpaperComponent != null &&
721                    !mWallpaperComponent.equals(mImageWallpaperComponent)) {
722                out.attribute(null, "component",
723                        mWallpaperComponent.flattenToShortString());
724            }
725            out.endTag(null, "wp");
726
727            out.endDocument();
728            stream.close();
729            journal.commit();
730        } catch (IOException e) {
731            try {
732                if (stream != null) {
733                    stream.close();
734                }
735            } catch (IOException ex) {
736                // Ignore
737            }
738            journal.rollback();
739        }
740    }
741
742    private void loadSettingsLocked() {
743        if (DEBUG) Slog.v(TAG, "loadSettingsLocked");
744
745        JournaledFile journal = makeJournaledFile();
746        FileInputStream stream = null;
747        File file = journal.chooseForRead();
748        boolean success = false;
749        try {
750            stream = new FileInputStream(file);
751            XmlPullParser parser = Xml.newPullParser();
752            parser.setInput(stream, null);
753
754            int type;
755            do {
756                type = parser.next();
757                if (type == XmlPullParser.START_TAG) {
758                    String tag = parser.getName();
759                    if ("wp".equals(tag)) {
760                        mWidth = Integer.parseInt(parser.getAttributeValue(null, "width"));
761                        mHeight = Integer.parseInt(parser.getAttributeValue(null, "height"));
762                        mName = parser.getAttributeValue(null, "name");
763                        String comp = parser.getAttributeValue(null, "component");
764                        mNextWallpaperComponent = comp != null
765                                ? ComponentName.unflattenFromString(comp)
766                                : null;
767                        if (mNextWallpaperComponent == null ||
768                                "android".equals(mNextWallpaperComponent.getPackageName())) {
769                            mNextWallpaperComponent = mImageWallpaperComponent;
770                        }
771
772                        if (DEBUG) {
773                            Slog.v(TAG, "mWidth:" + mWidth);
774                            Slog.v(TAG, "mHeight:" + mHeight);
775                            Slog.v(TAG, "mName:" + mName);
776                            Slog.v(TAG, "mNextWallpaperComponent:" + mNextWallpaperComponent);
777                        }
778                    }
779                }
780            } while (type != XmlPullParser.END_DOCUMENT);
781            success = true;
782        } catch (NullPointerException e) {
783            Slog.w(TAG, "failed parsing " + file + " " + e);
784        } catch (NumberFormatException e) {
785            Slog.w(TAG, "failed parsing " + file + " " + e);
786        } catch (XmlPullParserException e) {
787            Slog.w(TAG, "failed parsing " + file + " " + e);
788        } catch (IOException e) {
789            Slog.w(TAG, "failed parsing " + file + " " + e);
790        } catch (IndexOutOfBoundsException e) {
791            Slog.w(TAG, "failed parsing " + file + " " + e);
792        }
793        try {
794            if (stream != null) {
795                stream.close();
796            }
797        } catch (IOException e) {
798            // Ignore
799        }
800
801        if (!success) {
802            mWidth = -1;
803            mHeight = -1;
804            mName = "";
805        }
806
807        // We always want to have some reasonable width hint.
808        WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
809        Display d = wm.getDefaultDisplay();
810        int baseSize = d.getMaximumSizeDimension();
811        if (mWidth < baseSize) {
812            mWidth = baseSize;
813        }
814        if (mHeight < baseSize) {
815            mHeight = baseSize;
816        }
817    }
818
819    // Called by SystemBackupAgent after files are restored to disk.
820    void settingsRestored() {
821        if (DEBUG) Slog.v(TAG, "settingsRestored");
822
823        boolean success = false;
824        synchronized (mLock) {
825            loadSettingsLocked();
826            if (mNextWallpaperComponent != null &&
827                    !mNextWallpaperComponent.equals(mImageWallpaperComponent)) {
828                if (!bindWallpaperComponentLocked(mNextWallpaperComponent, false, false)) {
829                    // No such live wallpaper or other failure; fall back to the default
830                    // live wallpaper (since the profile being restored indicated that the
831                    // user had selected a live rather than static one).
832                    bindWallpaperComponentLocked(null, false, false);
833                }
834                success = true;
835            } else {
836                // If there's a wallpaper name, we use that.  If that can't be loaded, then we
837                // use the default.
838                if ("".equals(mName)) {
839                    if (DEBUG) Slog.v(TAG, "settingsRestored: name is empty");
840                    success = true;
841                } else {
842                    if (DEBUG) Slog.v(TAG, "settingsRestored: attempting to restore named resource");
843                    success = restoreNamedResourceLocked();
844                }
845                if (DEBUG) Slog.v(TAG, "settingsRestored: success=" + success);
846                if (success) {
847                    bindWallpaperComponentLocked(mNextWallpaperComponent, false, false);
848                }
849            }
850        }
851
852        if (!success) {
853            Slog.e(TAG, "Failed to restore wallpaper: '" + mName + "'");
854            mName = "";
855            WALLPAPER_FILE.delete();
856        }
857
858        synchronized (mLock) {
859            saveSettingsLocked();
860        }
861    }
862
863    boolean restoreNamedResourceLocked() {
864        if (mName.length() > 4 && "res:".equals(mName.substring(0, 4))) {
865            String resName = mName.substring(4);
866
867            String pkg = null;
868            int colon = resName.indexOf(':');
869            if (colon > 0) {
870                pkg = resName.substring(0, colon);
871            }
872
873            String ident = null;
874            int slash = resName.lastIndexOf('/');
875            if (slash > 0) {
876                ident = resName.substring(slash+1);
877            }
878
879            String type = null;
880            if (colon > 0 && slash > 0 && (slash-colon) > 1) {
881                type = resName.substring(colon+1, slash);
882            }
883
884            if (pkg != null && ident != null && type != null) {
885                int resId = -1;
886                InputStream res = null;
887                FileOutputStream fos = null;
888                try {
889                    Context c = mContext.createPackageContext(pkg, Context.CONTEXT_RESTRICTED);
890                    Resources r = c.getResources();
891                    resId = r.getIdentifier(resName, null, null);
892                    if (resId == 0) {
893                        Slog.e(TAG, "couldn't resolve identifier pkg=" + pkg + " type=" + type
894                                + " ident=" + ident);
895                        return false;
896                    }
897
898                    res = r.openRawResource(resId);
899                    if (WALLPAPER_FILE.exists()) {
900                        WALLPAPER_FILE.delete();
901                    }
902                    fos = new FileOutputStream(WALLPAPER_FILE);
903
904                    byte[] buffer = new byte[32768];
905                    int amt;
906                    while ((amt=res.read(buffer)) > 0) {
907                        fos.write(buffer, 0, amt);
908                    }
909                    // mWallpaperObserver will notice the close and send the change broadcast
910
911                    Slog.v(TAG, "Restored wallpaper: " + resName);
912                    return true;
913                } catch (NameNotFoundException e) {
914                    Slog.e(TAG, "Package name " + pkg + " not found");
915                } catch (Resources.NotFoundException e) {
916                    Slog.e(TAG, "Resource not found: " + resId);
917                } catch (IOException e) {
918                    Slog.e(TAG, "IOException while restoring wallpaper ", e);
919                } finally {
920                    if (res != null) {
921                        try {
922                            res.close();
923                        } catch (IOException ex) {}
924                    }
925                    if (fos != null) {
926                        FileUtils.sync(fos);
927                        try {
928                            fos.close();
929                        } catch (IOException ex) {}
930                    }
931                }
932            }
933        }
934        return false;
935    }
936
937    @Override
938    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
939        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
940                != PackageManager.PERMISSION_GRANTED) {
941
942            pw.println("Permission Denial: can't dump wallpaper service from from pid="
943                    + Binder.getCallingPid()
944                    + ", uid=" + Binder.getCallingUid());
945            return;
946        }
947
948        synchronized (mLock) {
949            pw.println("Current Wallpaper Service state:");
950            pw.print("  mWidth="); pw.print(mWidth);
951                    pw.print(" mHeight="); pw.println(mHeight);
952            pw.print("  mName="); pw.println(mName);
953            pw.print("  mWallpaperComponent="); pw.println(mWallpaperComponent);
954            if (mWallpaperConnection != null) {
955                WallpaperConnection conn = mWallpaperConnection;
956                pw.print("  Wallpaper connection ");
957                        pw.print(conn); pw.println(":");
958                pw.print("    mInfo.component="); pw.println(conn.mInfo.getComponent());
959                pw.print("    mToken="); pw.println(conn.mToken);
960                pw.print("    mService="); pw.println(conn.mService);
961                pw.print("    mEngine="); pw.println(conn.mEngine);
962                pw.print("    mLastDiedTime=");
963                        pw.println(mLastDiedTime - SystemClock.uptimeMillis());
964            }
965        }
966    }
967}
968