WallpaperManagerService.java revision 1e1e2e013fbd2b77ecf3bb87f58ba9d4849d172a
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.wallpaper;
18
19import static android.os.ParcelFileDescriptor.*;
20
21import android.app.ActivityManagerNative;
22import android.app.AppGlobals;
23import android.app.AppOpsManager;
24import android.app.IUserSwitchObserver;
25import android.app.IWallpaperManager;
26import android.app.IWallpaperManagerCallback;
27import android.app.PendingIntent;
28import android.app.WallpaperInfo;
29import android.app.WallpaperManager;
30import android.app.backup.BackupManager;
31import android.app.backup.WallpaperBackupHelper;
32import android.content.BroadcastReceiver;
33import android.content.ComponentName;
34import android.content.Context;
35import android.content.Intent;
36import android.content.IntentFilter;
37import android.content.ServiceConnection;
38import android.content.pm.IPackageManager;
39import android.content.pm.PackageManager;
40import android.content.pm.ResolveInfo;
41import android.content.pm.ServiceInfo;
42import android.content.pm.PackageManager.NameNotFoundException;
43import android.content.pm.UserInfo;
44import android.content.res.Resources;
45import android.graphics.Bitmap;
46import android.graphics.BitmapFactory;
47import android.graphics.BitmapRegionDecoder;
48import android.graphics.Point;
49import android.graphics.Rect;
50import android.os.Binder;
51import android.os.Bundle;
52import android.os.Environment;
53import android.os.FileUtils;
54import android.os.IBinder;
55import android.os.IRemoteCallback;
56import android.os.RemoteException;
57import android.os.FileObserver;
58import android.os.ParcelFileDescriptor;
59import android.os.RemoteCallbackList;
60import android.os.SELinux;
61import android.os.ServiceManager;
62import android.os.SystemClock;
63import android.os.UserHandle;
64import android.os.UserManager;
65import android.service.wallpaper.IWallpaperConnection;
66import android.service.wallpaper.IWallpaperEngine;
67import android.service.wallpaper.IWallpaperService;
68import android.service.wallpaper.WallpaperService;
69import android.util.EventLog;
70import android.util.Slog;
71import android.util.SparseArray;
72import android.util.Xml;
73import android.view.Display;
74import android.view.IWindowManager;
75import android.view.WindowManager;
76
77import java.io.BufferedOutputStream;
78import java.io.FileDescriptor;
79import java.io.IOException;
80import java.io.InputStream;
81import java.io.File;
82import java.io.FileNotFoundException;
83import java.io.FileInputStream;
84import java.io.FileOutputStream;
85import java.io.PrintWriter;
86import java.nio.charset.StandardCharsets;
87import java.util.List;
88
89import org.xmlpull.v1.XmlPullParser;
90import org.xmlpull.v1.XmlPullParserException;
91import org.xmlpull.v1.XmlSerializer;
92
93import com.android.internal.content.PackageMonitor;
94import com.android.internal.util.FastXmlSerializer;
95import com.android.internal.util.JournaledFile;
96import com.android.internal.R;
97import com.android.server.EventLogTags;
98
99import libcore.io.IoUtils;
100
101public class WallpaperManagerService extends IWallpaperManager.Stub {
102    static final String TAG = "WallpaperManagerService";
103    static final boolean DEBUG = true;
104
105    final Object mLock = new Object[0];
106
107    /**
108     * Minimum time between crashes of a wallpaper service for us to consider
109     * restarting it vs. just reverting to the static wallpaper.
110     */
111    static final long MIN_WALLPAPER_CRASH_TIME = 10000;
112    static final int MAX_WALLPAPER_COMPONENT_LOG_LENGTH = 128;
113    static final String WALLPAPER = "wallpaper_orig";
114    static final String WALLPAPER_CROP = "wallpaper";
115    static final String WALLPAPER_INFO = "wallpaper_info.xml";
116
117    /**
118     * Observes the wallpaper for changes and notifies all IWallpaperServiceCallbacks
119     * that the wallpaper has changed. The CREATE is triggered when there is no
120     * wallpaper set and is created for the first time. The CLOSE_WRITE is triggered
121     * everytime the wallpaper is changed.
122     */
123    private class WallpaperObserver extends FileObserver {
124
125        final WallpaperData mWallpaper;
126        final File mWallpaperDir;
127        final File mWallpaperFile;
128        final File mWallpaperCropFile;
129        final File mWallpaperInfoFile;
130
131        public WallpaperObserver(WallpaperData wallpaper) {
132            super(getWallpaperDir(wallpaper.userId).getAbsolutePath(),
133                    CLOSE_WRITE | MOVED_TO | DELETE | DELETE_SELF);
134            mWallpaperDir = getWallpaperDir(wallpaper.userId);
135            mWallpaper = wallpaper;
136            mWallpaperFile = new File(mWallpaperDir, WALLPAPER);
137            mWallpaperCropFile = new File(mWallpaperDir, WALLPAPER_CROP);
138            mWallpaperInfoFile = new File(mWallpaperDir, WALLPAPER_INFO);
139        }
140
141        @Override
142        public void onEvent(int event, String path) {
143            if (path == null) {
144                return;
145            }
146            final boolean written = (event == CLOSE_WRITE || event == MOVED_TO);
147            final File changedFile = new File(mWallpaperDir, path);
148
149            synchronized (mLock) {
150                if (mWallpaperFile.equals(changedFile)
151                        || mWallpaperInfoFile.equals(changedFile)) {
152                    // changing the wallpaper means we'll need to back up the new one
153                    long origId = Binder.clearCallingIdentity();
154                    BackupManager bm = new BackupManager(mContext);
155                    bm.dataChanged();
156                    Binder.restoreCallingIdentity(origId);
157                }
158                if (mWallpaperFile.equals(changedFile)) {
159                    notifyCallbacksLocked(mWallpaper);
160                    if (mWallpaper.wallpaperComponent == null
161                            || event != CLOSE_WRITE // includes the MOVED_TO case
162                            || mWallpaper.imageWallpaperPending) {
163                        if (written) {
164                            // The image source has finished writing the source image,
165                            // so we now produce the crop rect (in the background), and
166                            // only publish the new displayable (sub)image as a result
167                            // of that work.
168                            generateCrop(mWallpaper);
169                            mWallpaper.imageWallpaperPending = false;
170                            if (mWallpaper.setComplete != null) {
171                                try {
172                                    mWallpaper.setComplete.onWallpaperChanged();
173                                } catch (RemoteException e) {
174                                    // if this fails we don't really care; the setting app may just
175                                    // have crashed and that sort of thing is a fact of life.
176                                }
177                            }
178                            bindWallpaperComponentLocked(mImageWallpaper, true,
179                                    false, mWallpaper, null);
180                            saveSettingsLocked(mWallpaper);
181                        }
182                    }
183                }
184            }
185        }
186    }
187
188    /**
189     * Once a new wallpaper has been written via setWallpaper(...), it needs to be cropped
190     * for display.
191     */
192    private void generateCrop(WallpaperData wallpaper) {
193        boolean success = false;
194        boolean needCrop = false;
195
196        // Analyse the source; needed in multiple cases
197        BitmapFactory.Options options = new BitmapFactory.Options();
198        options.inJustDecodeBounds = true;
199        BitmapFactory.decodeFile(wallpaper.wallpaperFile.getAbsolutePath(), options);
200
201        // Legacy case uses an empty crop rect here, so we just preserve the
202        // source image verbatim
203        if (!wallpaper.cropHint.isEmpty()) {
204            // ...clamp the crop rect to the measured bounds...
205            wallpaper.cropHint.right = Math.min(wallpaper.cropHint.right, options.outWidth);
206            wallpaper.cropHint.bottom = Math.min(wallpaper.cropHint.bottom, options.outHeight);
207            // ...and don't bother cropping if what we're left with is identity
208            needCrop = (options.outHeight >= wallpaper.cropHint.height()
209                    && options.outWidth >= wallpaper.cropHint.width());
210        }
211
212        if (!needCrop) {
213            // Simple case:  the nominal crop is at least as big as the source image,
214            // so we take the whole thing and just copy the image file directly.
215            if (DEBUG) {
216                Slog.v(TAG, "Null crop of new wallpaper; copying");
217            }
218            success = FileUtils.copyFile(wallpaper.wallpaperFile, wallpaper.cropFile);
219            if (!success) {
220                wallpaper.cropFile.delete();
221                // TODO: fall back to default wallpaper in this case
222            }
223        } else {
224            // Fancy case: the crop is a subrect of the source
225            FileOutputStream f = null;
226            BufferedOutputStream bos = null;
227            try {
228                BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(
229                        wallpaper.wallpaperFile.getAbsolutePath(), false);
230                Bitmap cropped = decoder.decodeRegion(wallpaper.cropHint, null);
231                decoder.recycle();
232
233                if (cropped == null) {
234                    Slog.e(TAG, "Could not decode new wallpaper");
235                } else {
236                    f = new FileOutputStream(wallpaper.cropFile);
237                    bos = new BufferedOutputStream(f, 32*1024);
238                    cropped.compress(Bitmap.CompressFormat.PNG, 90, bos);
239                    bos.flush();  // don't rely on the implicit flush-at-close when noting success
240                    success = true;
241                }
242            } catch (IOException e) {
243                if (DEBUG) {
244                    Slog.e(TAG, "I/O error decoding crop: " + e.getMessage());
245                }
246            } finally {
247                IoUtils.closeQuietly(bos);
248                IoUtils.closeQuietly(f);
249            }
250        }
251
252        if (!success) {
253            Slog.e(TAG, "Unable to apply new wallpaper");
254            wallpaper.cropFile.delete();
255        }
256
257        if (wallpaper.cropFile.exists()) {
258            boolean didRestorecon = SELinux.restorecon(wallpaper.cropFile.getAbsoluteFile());
259            if (DEBUG) {
260                Slog.v(TAG, "restorecon() of crop file returned " + didRestorecon);
261            }
262        }
263    }
264
265    final Context mContext;
266    final IWindowManager mIWindowManager;
267    final IPackageManager mIPackageManager;
268    final MyPackageMonitor mMonitor;
269    final AppOpsManager mAppOpsManager;
270    WallpaperData mLastWallpaper;
271
272    /**
273     * ID of the current wallpaper, changed every time anything sets a wallpaper.
274     * This is used for external detection of wallpaper update activity.
275     */
276    int mWallpaperId;
277
278    /**
279     * Name of the component used to display bitmap wallpapers from either the gallery or
280     * built-in wallpapers.
281     */
282    final ComponentName mImageWallpaper;
283
284    SparseArray<WallpaperData> mWallpaperMap = new SparseArray<WallpaperData>();
285
286    int mCurrentUserId;
287
288    static class WallpaperData {
289
290        int userId;
291
292        final File wallpaperFile;
293        final File cropFile;
294
295        /**
296         * Client is currently writing a new image wallpaper.
297         */
298        boolean imageWallpaperPending;
299
300        /**
301         * Callback once the set + crop is finished
302         */
303        IWallpaperManagerCallback setComplete;
304
305        /**
306         * Resource name if using a picture from the wallpaper gallery
307         */
308        String name = "";
309
310        /**
311         * The component name of the currently set live wallpaper.
312         */
313        ComponentName wallpaperComponent;
314
315        /**
316         * The component name of the wallpaper that should be set next.
317         */
318        ComponentName nextWallpaperComponent;
319
320        /**
321         * The ID of this wallpaper
322         */
323        int wallpaperId;
324
325        WallpaperConnection connection;
326        long lastDiedTime;
327        boolean wallpaperUpdating;
328        WallpaperObserver wallpaperObserver;
329
330        /**
331         * List of callbacks registered they should each be notified when the wallpaper is changed.
332         */
333        private RemoteCallbackList<IWallpaperManagerCallback> callbacks
334                = new RemoteCallbackList<IWallpaperManagerCallback>();
335
336        int width = -1;
337        int height = -1;
338
339        /**
340         * The crop hint supplied for displaying a subset of the source image
341         */
342        final Rect cropHint = new Rect(0, 0, 0, 0);
343
344        final Rect padding = new Rect(0, 0, 0, 0);
345
346        WallpaperData(int userId) {
347            this.userId = userId;
348            wallpaperFile = new File(getWallpaperDir(userId), WALLPAPER);
349            cropFile = new File(getWallpaperDir(userId), WALLPAPER_CROP);
350        }
351
352        // Only called in single-threaded boot sequence mode
353        boolean ensureCropExists() {
354            // if the crop file is not present, copy over the source image to use verbatim
355            if (!cropFile.exists()) {
356                return FileUtils.copyFile(wallpaperFile, cropFile);
357            }
358            return true;
359        }
360    }
361
362    int makeWallpaperIdLocked() {
363        do {
364            ++mWallpaperId;
365        } while (mWallpaperId == 0);
366        return mWallpaperId;
367    }
368
369    class WallpaperConnection extends IWallpaperConnection.Stub
370            implements ServiceConnection {
371        final WallpaperInfo mInfo;
372        final Binder mToken = new Binder();
373        IWallpaperService mService;
374        IWallpaperEngine mEngine;
375        WallpaperData mWallpaper;
376        IRemoteCallback mReply;
377
378        boolean mDimensionsChanged = false;
379        boolean mPaddingChanged = false;
380
381        public WallpaperConnection(WallpaperInfo info, WallpaperData wallpaper) {
382            mInfo = info;
383            mWallpaper = wallpaper;
384        }
385
386        @Override
387        public void onServiceConnected(ComponentName name, IBinder service) {
388            synchronized (mLock) {
389                if (mWallpaper.connection == this) {
390                    mService = IWallpaperService.Stub.asInterface(service);
391                    attachServiceLocked(this, mWallpaper);
392                    // XXX should probably do saveSettingsLocked() later
393                    // when we have an engine, but I'm not sure about
394                    // locking there and anyway we always need to be able to
395                    // recover if there is something wrong.
396                    saveSettingsLocked(mWallpaper);
397                }
398            }
399        }
400
401        @Override
402        public void onServiceDisconnected(ComponentName name) {
403            synchronized (mLock) {
404                mService = null;
405                mEngine = null;
406                if (mWallpaper.connection == this) {
407                    Slog.w(TAG, "Wallpaper service gone: " + mWallpaper.wallpaperComponent);
408                    if (!mWallpaper.wallpaperUpdating
409                            && mWallpaper.userId == mCurrentUserId) {
410                        // There is a race condition which causes
411                        // {@link #mWallpaper.wallpaperUpdating} to be false even if it is
412                        // currently updating since the broadcast notifying us is async.
413                        // This race is overcome by the general rule that we only reset the
414                        // wallpaper if its service was shut down twice
415                        // during {@link #MIN_WALLPAPER_CRASH_TIME} millis.
416                        if (mWallpaper.lastDiedTime != 0
417                                && mWallpaper.lastDiedTime + MIN_WALLPAPER_CRASH_TIME
418                                    > SystemClock.uptimeMillis()) {
419                            Slog.w(TAG, "Reverting to built-in wallpaper!");
420                            clearWallpaperLocked(true, mWallpaper.userId, null);
421                        } else {
422                            mWallpaper.lastDiedTime = SystemClock.uptimeMillis();
423                        }
424                        final String flattened = name.flattenToString();
425                        EventLog.writeEvent(EventLogTags.WP_WALLPAPER_CRASHED,
426                                flattened.substring(0, Math.min(flattened.length(),
427                                        MAX_WALLPAPER_COMPONENT_LOG_LENGTH)));
428                    }
429                }
430            }
431        }
432
433        @Override
434        public void attachEngine(IWallpaperEngine engine) {
435            synchronized (mLock) {
436                mEngine = engine;
437                if (mDimensionsChanged) {
438                    try {
439                        mEngine.setDesiredSize(mWallpaper.width, mWallpaper.height);
440                    } catch (RemoteException e) {
441                        Slog.w(TAG, "Failed to set wallpaper dimensions", e);
442                    }
443                    mDimensionsChanged = false;
444                }
445                if (mPaddingChanged) {
446                    try {
447                        mEngine.setDisplayPadding(mWallpaper.padding);
448                    } catch (RemoteException e) {
449                        Slog.w(TAG, "Failed to set wallpaper padding", e);
450                    }
451                    mPaddingChanged = false;
452                }
453            }
454        }
455
456        @Override
457        public void engineShown(IWallpaperEngine engine) {
458            synchronized (mLock) {
459                if (mReply != null) {
460                    long ident = Binder.clearCallingIdentity();
461                    try {
462                        mReply.sendResult(null);
463                    } catch (RemoteException e) {
464                        Binder.restoreCallingIdentity(ident);
465                    }
466                    mReply = null;
467                }
468            }
469        }
470
471        @Override
472        public ParcelFileDescriptor setWallpaper(String name) {
473            synchronized (mLock) {
474                if (mWallpaper.connection == this) {
475                    return updateWallpaperBitmapLocked(name, mWallpaper, null);
476                }
477                return null;
478            }
479        }
480    }
481
482    class MyPackageMonitor extends PackageMonitor {
483        @Override
484        public void onPackageUpdateFinished(String packageName, int uid) {
485            synchronized (mLock) {
486                if (mCurrentUserId != getChangingUserId()) {
487                    return;
488                }
489                WallpaperData wallpaper = mWallpaperMap.get(mCurrentUserId);
490                if (wallpaper != null) {
491                    if (wallpaper.wallpaperComponent != null
492                            && wallpaper.wallpaperComponent.getPackageName().equals(packageName)) {
493                        wallpaper.wallpaperUpdating = false;
494                        ComponentName comp = wallpaper.wallpaperComponent;
495                        clearWallpaperComponentLocked(wallpaper);
496                        if (!bindWallpaperComponentLocked(comp, false, false,
497                                wallpaper, null)) {
498                            Slog.w(TAG, "Wallpaper no longer available; reverting to default");
499                            clearWallpaperLocked(false, wallpaper.userId, null);
500                        }
501                    }
502                }
503            }
504        }
505
506        @Override
507        public void onPackageModified(String packageName) {
508            synchronized (mLock) {
509                if (mCurrentUserId != getChangingUserId()) {
510                    return;
511                }
512                WallpaperData wallpaper = mWallpaperMap.get(mCurrentUserId);
513                if (wallpaper != null) {
514                    if (wallpaper.wallpaperComponent == null
515                            || !wallpaper.wallpaperComponent.getPackageName().equals(packageName)) {
516                        return;
517                    }
518                    doPackagesChangedLocked(true, wallpaper);
519                }
520            }
521        }
522
523        @Override
524        public void onPackageUpdateStarted(String packageName, int uid) {
525            synchronized (mLock) {
526                if (mCurrentUserId != getChangingUserId()) {
527                    return;
528                }
529                WallpaperData wallpaper = mWallpaperMap.get(mCurrentUserId);
530                if (wallpaper != null) {
531                    if (wallpaper.wallpaperComponent != null
532                            && wallpaper.wallpaperComponent.getPackageName().equals(packageName)) {
533                        wallpaper.wallpaperUpdating = true;
534                    }
535                }
536            }
537        }
538
539        @Override
540        public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
541            synchronized (mLock) {
542                boolean changed = false;
543                if (mCurrentUserId != getChangingUserId()) {
544                    return false;
545                }
546                WallpaperData wallpaper = mWallpaperMap.get(mCurrentUserId);
547                if (wallpaper != null) {
548                    boolean res = doPackagesChangedLocked(doit, wallpaper);
549                    changed |= res;
550                }
551                return changed;
552            }
553        }
554
555        @Override
556        public void onSomePackagesChanged() {
557            synchronized (mLock) {
558                if (mCurrentUserId != getChangingUserId()) {
559                    return;
560                }
561                WallpaperData wallpaper = mWallpaperMap.get(mCurrentUserId);
562                if (wallpaper != null) {
563                    doPackagesChangedLocked(true, wallpaper);
564                }
565            }
566        }
567
568        boolean doPackagesChangedLocked(boolean doit, WallpaperData wallpaper) {
569            boolean changed = false;
570            if (wallpaper.wallpaperComponent != null) {
571                int change = isPackageDisappearing(wallpaper.wallpaperComponent
572                        .getPackageName());
573                if (change == PACKAGE_PERMANENT_CHANGE
574                        || change == PACKAGE_TEMPORARY_CHANGE) {
575                    changed = true;
576                    if (doit) {
577                        Slog.w(TAG, "Wallpaper uninstalled, removing: "
578                                + wallpaper.wallpaperComponent);
579                        clearWallpaperLocked(false, wallpaper.userId, null);
580                    }
581                }
582            }
583            if (wallpaper.nextWallpaperComponent != null) {
584                int change = isPackageDisappearing(wallpaper.nextWallpaperComponent
585                        .getPackageName());
586                if (change == PACKAGE_PERMANENT_CHANGE
587                        || change == PACKAGE_TEMPORARY_CHANGE) {
588                    wallpaper.nextWallpaperComponent = null;
589                }
590            }
591            if (wallpaper.wallpaperComponent != null
592                    && isPackageModified(wallpaper.wallpaperComponent.getPackageName())) {
593                try {
594                    mContext.getPackageManager().getServiceInfo(
595                            wallpaper.wallpaperComponent, 0);
596                } catch (NameNotFoundException e) {
597                    Slog.w(TAG, "Wallpaper component gone, removing: "
598                            + wallpaper.wallpaperComponent);
599                    clearWallpaperLocked(false, wallpaper.userId, null);
600                }
601            }
602            if (wallpaper.nextWallpaperComponent != null
603                    && isPackageModified(wallpaper.nextWallpaperComponent.getPackageName())) {
604                try {
605                    mContext.getPackageManager().getServiceInfo(
606                            wallpaper.nextWallpaperComponent, 0);
607                } catch (NameNotFoundException e) {
608                    wallpaper.nextWallpaperComponent = null;
609                }
610            }
611            return changed;
612        }
613    }
614
615    public WallpaperManagerService(Context context) {
616        if (DEBUG) Slog.v(TAG, "WallpaperService startup");
617        mContext = context;
618        mImageWallpaper = ComponentName.unflattenFromString(
619                context.getResources().getString(R.string.image_wallpaper_component));
620        mIWindowManager = IWindowManager.Stub.asInterface(
621                ServiceManager.getService(Context.WINDOW_SERVICE));
622        mIPackageManager = AppGlobals.getPackageManager();
623        mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
624        mMonitor = new MyPackageMonitor();
625        mMonitor.register(context, null, UserHandle.ALL, true);
626        getWallpaperDir(UserHandle.USER_SYSTEM).mkdirs();
627        loadSettingsLocked(UserHandle.USER_SYSTEM);
628    }
629
630    private static File getWallpaperDir(int userId) {
631        return Environment.getUserSystemDirectory(userId);
632    }
633
634    @Override
635    protected void finalize() throws Throwable {
636        super.finalize();
637        for (int i = 0; i < mWallpaperMap.size(); i++) {
638            WallpaperData wallpaper = mWallpaperMap.valueAt(i);
639            wallpaper.wallpaperObserver.stopWatching();
640        }
641    }
642
643    public void systemRunning() {
644        if (DEBUG) Slog.v(TAG, "systemReady");
645        WallpaperData wallpaper = mWallpaperMap.get(UserHandle.USER_SYSTEM);
646        if (!wallpaper.ensureCropExists()) {
647            clearWallpaperLocked(false, UserHandle.USER_SYSTEM, null);
648        }
649        switchWallpaper(wallpaper, null);
650        wallpaper.wallpaperObserver = new WallpaperObserver(wallpaper);
651        wallpaper.wallpaperObserver.startWatching();
652
653        IntentFilter userFilter = new IntentFilter();
654        userFilter.addAction(Intent.ACTION_USER_REMOVED);
655        userFilter.addAction(Intent.ACTION_USER_STOPPING);
656        mContext.registerReceiver(new BroadcastReceiver() {
657            @Override
658            public void onReceive(Context context, Intent intent) {
659                String action = intent.getAction();
660                if (Intent.ACTION_USER_REMOVED.equals(action)) {
661                    onRemoveUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
662                            UserHandle.USER_NULL));
663                }
664                // TODO: Race condition causing problems when cleaning up on stopping a user.
665                // Comment this out for now.
666                // else if (Intent.ACTION_USER_STOPPING.equals(action)) {
667                //     onStoppingUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
668                //             UserHandle.USER_NULL));
669                // }
670            }
671        }, userFilter);
672
673        try {
674            ActivityManagerNative.getDefault().registerUserSwitchObserver(
675                    new IUserSwitchObserver.Stub() {
676                        @Override
677                        public void onUserSwitching(int newUserId, IRemoteCallback reply) {
678                            switchUser(newUserId, reply);
679                        }
680
681                        @Override
682                        public void onUserSwitchComplete(int newUserId) throws RemoteException {
683                        }
684
685                        @Override
686                        public void onForegroundProfileSwitch(int newProfileId) {
687                            // Ignore.
688                        }
689                    });
690        } catch (RemoteException e) {
691            // TODO Auto-generated catch block
692            e.printStackTrace();
693        }
694    }
695
696    /** Called by SystemBackupAgent */
697    public String getName() {
698        // Verify caller is the system
699        if (Binder.getCallingUid() != android.os.Process.SYSTEM_UID) {
700            throw new RuntimeException("getName() can only be called from the system process");
701        }
702        synchronized (mLock) {
703            return mWallpaperMap.get(0).name;
704        }
705    }
706
707    void onStoppingUser(int userId) {
708        if (userId < 1) return;
709        synchronized (mLock) {
710            WallpaperData wallpaper = mWallpaperMap.get(userId);
711            if (wallpaper != null) {
712                if (wallpaper.wallpaperObserver != null) {
713                    wallpaper.wallpaperObserver.stopWatching();
714                    wallpaper.wallpaperObserver = null;
715                }
716                mWallpaperMap.remove(userId);
717            }
718        }
719    }
720
721    void onRemoveUser(int userId) {
722        if (userId < 1) return;
723        synchronized (mLock) {
724            onStoppingUser(userId);
725            File wallpaperFile = new File(getWallpaperDir(userId), WALLPAPER);
726            wallpaperFile.delete();
727            File cropFile = new File(getWallpaperDir(userId), WALLPAPER_CROP);
728            cropFile.delete();
729            File wallpaperInfoFile = new File(getWallpaperDir(userId), WALLPAPER_INFO);
730            wallpaperInfoFile.delete();
731        }
732    }
733
734    void switchUser(int userId, IRemoteCallback reply) {
735        synchronized (mLock) {
736            mCurrentUserId = userId;
737            WallpaperData wallpaper = getWallpaperSafeLocked(userId);
738            // Not started watching yet, in case wallpaper data was loaded for other reasons.
739            if (wallpaper.wallpaperObserver == null) {
740                wallpaper.wallpaperObserver = new WallpaperObserver(wallpaper);
741                wallpaper.wallpaperObserver.startWatching();
742            }
743            switchWallpaper(wallpaper, reply);
744        }
745    }
746
747    void switchWallpaper(WallpaperData wallpaper, IRemoteCallback reply) {
748        synchronized (mLock) {
749            RuntimeException e = null;
750            try {
751                ComponentName cname = wallpaper.wallpaperComponent != null ?
752                        wallpaper.wallpaperComponent : wallpaper.nextWallpaperComponent;
753                if (bindWallpaperComponentLocked(cname, true, false, wallpaper, reply)) {
754                    return;
755                }
756            } catch (RuntimeException e1) {
757                e = e1;
758            }
759            Slog.w(TAG, "Failure starting previous wallpaper", e);
760            clearWallpaperLocked(false, wallpaper.userId, reply);
761        }
762    }
763
764    public void clearWallpaper(String callingPackage) {
765        if (DEBUG) Slog.v(TAG, "clearWallpaper");
766        checkPermission(android.Manifest.permission.SET_WALLPAPER);
767        if (!isWallpaperSupported(callingPackage)) {
768            return;
769        }
770        synchronized (mLock) {
771            clearWallpaperLocked(false, UserHandle.getCallingUserId(), null);
772        }
773    }
774
775    void clearWallpaperLocked(boolean defaultFailed, int userId, IRemoteCallback reply) {
776        WallpaperData wallpaper = mWallpaperMap.get(userId);
777        if (wallpaper == null) {
778            return;
779        }
780        if (wallpaper.wallpaperFile.exists()) {
781            wallpaper.wallpaperFile.delete();
782            wallpaper.cropFile.delete();
783        }
784        final long ident = Binder.clearCallingIdentity();
785        try {
786            RuntimeException e = null;
787            try {
788                wallpaper.imageWallpaperPending = false;
789                if (userId != mCurrentUserId) return;
790                if (bindWallpaperComponentLocked(defaultFailed
791                        ? mImageWallpaper
792                                : null, true, false, wallpaper, reply)) {
793                    return;
794                }
795            } catch (IllegalArgumentException e1) {
796                e = e1;
797            }
798
799            // This can happen if the default wallpaper component doesn't
800            // exist.  This should be a system configuration problem, but
801            // let's not let it crash the system and just live with no
802            // wallpaper.
803            Slog.e(TAG, "Default wallpaper component not found!", e);
804            clearWallpaperComponentLocked(wallpaper);
805            if (reply != null) {
806                try {
807                    reply.sendResult(null);
808                } catch (RemoteException e1) {
809                }
810            }
811        } finally {
812            Binder.restoreCallingIdentity(ident);
813        }
814    }
815
816    public boolean hasNamedWallpaper(String name) {
817        synchronized (mLock) {
818            List<UserInfo> users;
819            long ident = Binder.clearCallingIdentity();
820            try {
821                users = ((UserManager) mContext.getSystemService(Context.USER_SERVICE)).getUsers();
822            } finally {
823                Binder.restoreCallingIdentity(ident);
824            }
825            for (UserInfo user: users) {
826                // ignore managed profiles
827                if (user.isManagedProfile()) {
828                    continue;
829                }
830                WallpaperData wd = mWallpaperMap.get(user.id);
831                if (wd == null) {
832                    // User hasn't started yet, so load her settings to peek at the wallpaper
833                    loadSettingsLocked(user.id);
834                    wd = mWallpaperMap.get(user.id);
835                }
836                if (wd != null && name.equals(wd.name)) {
837                    return true;
838                }
839            }
840        }
841        return false;
842    }
843
844    private Point getDefaultDisplaySize() {
845        Point p = new Point();
846        WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
847        Display d = wm.getDefaultDisplay();
848        d.getRealSize(p);
849        return p;
850    }
851
852    public void setDimensionHints(int width, int height, String callingPackage)
853            throws RemoteException {
854        checkPermission(android.Manifest.permission.SET_WALLPAPER_HINTS);
855        if (!isWallpaperSupported(callingPackage)) {
856            return;
857        }
858        synchronized (mLock) {
859            int userId = UserHandle.getCallingUserId();
860            WallpaperData wallpaper = getWallpaperSafeLocked(userId);
861            if (width <= 0 || height <= 0) {
862                throw new IllegalArgumentException("width and height must be > 0");
863            }
864            // Make sure it is at least as large as the display.
865            Point displaySize = getDefaultDisplaySize();
866            width = Math.max(width, displaySize.x);
867            height = Math.max(height, displaySize.y);
868
869            if (width != wallpaper.width || height != wallpaper.height) {
870                wallpaper.width = width;
871                wallpaper.height = height;
872                saveSettingsLocked(wallpaper);
873                if (mCurrentUserId != userId) return; // Don't change the properties now
874                if (wallpaper.connection != null) {
875                    if (wallpaper.connection.mEngine != null) {
876                        try {
877                            wallpaper.connection.mEngine.setDesiredSize(
878                                    width, height);
879                        } catch (RemoteException e) {
880                        }
881                        notifyCallbacksLocked(wallpaper);
882                    } else if (wallpaper.connection.mService != null) {
883                        // We've attached to the service but the engine hasn't attached back to us
884                        // yet. This means it will be created with the previous dimensions, so we
885                        // need to update it to the new dimensions once it attaches.
886                        wallpaper.connection.mDimensionsChanged = true;
887                    }
888                }
889            }
890        }
891    }
892
893    public int getWidthHint() throws RemoteException {
894        synchronized (mLock) {
895            WallpaperData wallpaper = mWallpaperMap.get(UserHandle.getCallingUserId());
896            if (wallpaper != null) {
897                return wallpaper.width;
898            } else {
899                return 0;
900            }
901        }
902    }
903
904    public int getHeightHint() throws RemoteException {
905        synchronized (mLock) {
906            WallpaperData wallpaper = mWallpaperMap.get(UserHandle.getCallingUserId());
907            if (wallpaper != null) {
908                return wallpaper.height;
909            } else {
910                return 0;
911            }
912        }
913    }
914
915    public void setDisplayPadding(Rect padding, String callingPackage) {
916        checkPermission(android.Manifest.permission.SET_WALLPAPER_HINTS);
917        if (!isWallpaperSupported(callingPackage)) {
918            return;
919        }
920        synchronized (mLock) {
921            int userId = UserHandle.getCallingUserId();
922            WallpaperData wallpaper = getWallpaperSafeLocked(userId);
923            if (padding.left < 0 || padding.top < 0 || padding.right < 0 || padding.bottom < 0) {
924                throw new IllegalArgumentException("padding must be positive: " + padding);
925            }
926
927            if (!padding.equals(wallpaper.padding)) {
928                wallpaper.padding.set(padding);
929                saveSettingsLocked(wallpaper);
930                if (mCurrentUserId != userId) return; // Don't change the properties now
931                if (wallpaper.connection != null) {
932                    if (wallpaper.connection.mEngine != null) {
933                        try {
934                            wallpaper.connection.mEngine.setDisplayPadding(padding);
935                        } catch (RemoteException e) {
936                        }
937                        notifyCallbacksLocked(wallpaper);
938                    } else if (wallpaper.connection.mService != null) {
939                        // We've attached to the service but the engine hasn't attached back to us
940                        // yet. This means it will be created with the previous dimensions, so we
941                        // need to update it to the new dimensions once it attaches.
942                        wallpaper.connection.mPaddingChanged = true;
943                    }
944                }
945            }
946        }
947    }
948
949    public ParcelFileDescriptor getWallpaper(IWallpaperManagerCallback cb,
950            Bundle outParams) {
951        synchronized (mLock) {
952            // This returns the current user's wallpaper, if called by a system service. Else it
953            // returns the wallpaper for the calling user.
954            int callingUid = Binder.getCallingUid();
955            int wallpaperUserId = 0;
956            if (callingUid == android.os.Process.SYSTEM_UID) {
957                wallpaperUserId = mCurrentUserId;
958            } else {
959                wallpaperUserId = UserHandle.getUserId(callingUid);
960            }
961            WallpaperData wallpaper = mWallpaperMap.get(wallpaperUserId);
962            if (wallpaper == null) {
963                return null;
964            }
965            try {
966                if (outParams != null) {
967                    outParams.putInt("width", wallpaper.width);
968                    outParams.putInt("height", wallpaper.height);
969                }
970                wallpaper.callbacks.register(cb);
971                if (!wallpaper.cropFile.exists()) {
972                    return null;
973                }
974                return ParcelFileDescriptor.open(wallpaper.cropFile, MODE_READ_ONLY);
975            } catch (FileNotFoundException e) {
976                /* Shouldn't happen as we check to see if the file exists */
977                Slog.w(TAG, "Error getting wallpaper", e);
978            }
979            return null;
980        }
981    }
982
983    public WallpaperInfo getWallpaperInfo() {
984        int userId = UserHandle.getCallingUserId();
985        synchronized (mLock) {
986            WallpaperData wallpaper = mWallpaperMap.get(userId);
987            if (wallpaper != null && wallpaper.connection != null) {
988                return wallpaper.connection.mInfo;
989            }
990            return null;
991        }
992    }
993
994    @Override
995    public ParcelFileDescriptor setWallpaper(String name, String callingPackage,
996            Rect cropHint, Bundle extras, int which, IWallpaperManagerCallback completion) {
997        checkPermission(android.Manifest.permission.SET_WALLPAPER);
998
999        if (which == 0) {
1000            return null;
1001        }
1002
1003        if (!isWallpaperSupported(callingPackage)) {
1004            return null;
1005        }
1006
1007        // "null" means the no-op crop, preserving the full input image
1008        if (cropHint == null) {
1009            cropHint = new Rect(0, 0, 0, 0);
1010        } else {
1011            if (cropHint.isEmpty()
1012                    || cropHint.left < 0
1013                    || cropHint.top < 0) {
1014                return null;
1015            }
1016        }
1017
1018        synchronized (mLock) {
1019            if (DEBUG) Slog.v(TAG, "setWallpaper");
1020            int userId = UserHandle.getCallingUserId();
1021            WallpaperData wallpaper = getWallpaperSafeLocked(userId);
1022            final long ident = Binder.clearCallingIdentity();
1023            try {
1024                ParcelFileDescriptor pfd = updateWallpaperBitmapLocked(name, wallpaper, extras);
1025                if (pfd != null) {
1026                    wallpaper.imageWallpaperPending = true;
1027                    wallpaper.setComplete = completion;
1028                    wallpaper.cropHint.set(cropHint);
1029                }
1030                return pfd;
1031            } finally {
1032                Binder.restoreCallingIdentity(ident);
1033            }
1034        }
1035    }
1036
1037    ParcelFileDescriptor updateWallpaperBitmapLocked(String name, WallpaperData wallpaper,
1038            Bundle extras) {
1039        if (name == null) name = "";
1040        try {
1041            File dir = getWallpaperDir(wallpaper.userId);
1042            if (!dir.exists()) {
1043                dir.mkdir();
1044                FileUtils.setPermissions(
1045                        dir.getPath(),
1046                        FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
1047                        -1, -1);
1048            }
1049            File file = new File(dir, WALLPAPER);
1050            ParcelFileDescriptor fd = ParcelFileDescriptor.open(file,
1051                    MODE_CREATE|MODE_READ_WRITE|MODE_TRUNCATE);
1052            if (!SELinux.restorecon(file)) {
1053                return null;
1054            }
1055            wallpaper.name = name;
1056            wallpaper.wallpaperId = makeWallpaperIdLocked();
1057            if (extras != null) {
1058                extras.putInt(WallpaperManager.EXTRA_NEW_WALLPAPER_ID, wallpaper.wallpaperId);
1059            }
1060            if (DEBUG) {
1061                Slog.v(TAG, "updateWallpaperBitmapLocked() : id=" + wallpaper.wallpaperId
1062                        + " name=" + name);
1063            }
1064            return fd;
1065        } catch (FileNotFoundException e) {
1066            Slog.w(TAG, "Error setting wallpaper", e);
1067        }
1068        return null;
1069    }
1070
1071    public void setWallpaperComponentChecked(ComponentName name, String callingPackage) {
1072        if (isWallpaperSupported(callingPackage)) {
1073            setWallpaperComponent(name);
1074        }
1075    }
1076
1077    // ToDo: Remove this version of the function
1078    public void setWallpaperComponent(ComponentName name) {
1079        checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT);
1080        synchronized (mLock) {
1081            if (DEBUG) Slog.v(TAG, "setWallpaperComponent name=" + name);
1082            int userId = UserHandle.getCallingUserId();
1083            WallpaperData wallpaper = mWallpaperMap.get(userId);
1084            if (wallpaper == null) {
1085                throw new IllegalStateException("Wallpaper not yet initialized for user " + userId);
1086            }
1087            final long ident = Binder.clearCallingIdentity();
1088            try {
1089                wallpaper.imageWallpaperPending = false;
1090                bindWallpaperComponentLocked(name, false, true, wallpaper, null);
1091            } finally {
1092                Binder.restoreCallingIdentity(ident);
1093            }
1094        }
1095    }
1096
1097    boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force,
1098            boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) {
1099        if (DEBUG) Slog.v(TAG, "bindWallpaperComponentLocked: componentName=" + componentName);
1100        // Has the component changed?
1101        if (!force) {
1102            if (wallpaper.connection != null) {
1103                if (wallpaper.wallpaperComponent == null) {
1104                    if (componentName == null) {
1105                        if (DEBUG) Slog.v(TAG, "bindWallpaperComponentLocked: still using default");
1106                        // Still using default wallpaper.
1107                        return true;
1108                    }
1109                } else if (wallpaper.wallpaperComponent.equals(componentName)) {
1110                    // Changing to same wallpaper.
1111                    if (DEBUG) Slog.v(TAG, "same wallpaper");
1112                    return true;
1113                }
1114            }
1115        }
1116
1117        try {
1118            if (componentName == null) {
1119                componentName = WallpaperManager.getDefaultWallpaperComponent(mContext);
1120                if (componentName == null) {
1121                    // Fall back to static image wallpaper
1122                    componentName = mImageWallpaper;
1123                    //clearWallpaperComponentLocked();
1124                    //return;
1125                    if (DEBUG) Slog.v(TAG, "Using image wallpaper");
1126                }
1127            }
1128            int serviceUserId = wallpaper.userId;
1129            ServiceInfo si = mIPackageManager.getServiceInfo(componentName,
1130                    PackageManager.GET_META_DATA | PackageManager.GET_PERMISSIONS, serviceUserId);
1131            if (si == null) {
1132                // The wallpaper component we're trying to use doesn't exist
1133                Slog.w(TAG, "Attempted wallpaper " + componentName + " is unavailable");
1134                return false;
1135            }
1136            if (!android.Manifest.permission.BIND_WALLPAPER.equals(si.permission)) {
1137                String msg = "Selected service does not require "
1138                        + android.Manifest.permission.BIND_WALLPAPER
1139                        + ": " + componentName;
1140                if (fromUser) {
1141                    throw new SecurityException(msg);
1142                }
1143                Slog.w(TAG, msg);
1144                return false;
1145            }
1146
1147            WallpaperInfo wi = null;
1148
1149            Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE);
1150            if (componentName != null && !componentName.equals(mImageWallpaper)) {
1151                // Make sure the selected service is actually a wallpaper service.
1152                List<ResolveInfo> ris =
1153                        mIPackageManager.queryIntentServices(intent,
1154                                intent.resolveTypeIfNeeded(mContext.getContentResolver()),
1155                                PackageManager.GET_META_DATA, serviceUserId);
1156                for (int i=0; i<ris.size(); i++) {
1157                    ServiceInfo rsi = ris.get(i).serviceInfo;
1158                    if (rsi.name.equals(si.name) &&
1159                            rsi.packageName.equals(si.packageName)) {
1160                        try {
1161                            wi = new WallpaperInfo(mContext, ris.get(i));
1162                        } catch (XmlPullParserException e) {
1163                            if (fromUser) {
1164                                throw new IllegalArgumentException(e);
1165                            }
1166                            Slog.w(TAG, e);
1167                            return false;
1168                        } catch (IOException e) {
1169                            if (fromUser) {
1170                                throw new IllegalArgumentException(e);
1171                            }
1172                            Slog.w(TAG, e);
1173                            return false;
1174                        }
1175                        break;
1176                    }
1177                }
1178                if (wi == null) {
1179                    String msg = "Selected service is not a wallpaper: "
1180                            + componentName;
1181                    if (fromUser) {
1182                        throw new SecurityException(msg);
1183                    }
1184                    Slog.w(TAG, msg);
1185                    return false;
1186                }
1187            }
1188
1189            // Bind the service!
1190            if (DEBUG) Slog.v(TAG, "Binding to:" + componentName);
1191            WallpaperConnection newConn = new WallpaperConnection(wi, wallpaper);
1192            intent.setComponent(componentName);
1193            intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
1194                    com.android.internal.R.string.wallpaper_binding_label);
1195            intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivityAsUser(
1196                    mContext, 0,
1197                    Intent.createChooser(new Intent(Intent.ACTION_SET_WALLPAPER),
1198                            mContext.getText(com.android.internal.R.string.chooser_wallpaper)),
1199                    0, null, new UserHandle(serviceUserId)));
1200            if (!mContext.bindServiceAsUser(intent, newConn,
1201                    Context.BIND_AUTO_CREATE | Context.BIND_SHOWING_UI
1202                            | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
1203                    new UserHandle(serviceUserId))) {
1204                String msg = "Unable to bind service: "
1205                        + componentName;
1206                if (fromUser) {
1207                    throw new IllegalArgumentException(msg);
1208                }
1209                Slog.w(TAG, msg);
1210                return false;
1211            }
1212            if (wallpaper.userId == mCurrentUserId && mLastWallpaper != null) {
1213                detachWallpaperLocked(mLastWallpaper);
1214            }
1215            wallpaper.wallpaperComponent = componentName;
1216            wallpaper.connection = newConn;
1217            newConn.mReply = reply;
1218            try {
1219                if (wallpaper.userId == mCurrentUserId) {
1220                    if (DEBUG)
1221                        Slog.v(TAG, "Adding window token: " + newConn.mToken);
1222                    mIWindowManager.addWindowToken(newConn.mToken,
1223                            WindowManager.LayoutParams.TYPE_WALLPAPER);
1224                    mLastWallpaper = wallpaper;
1225                }
1226            } catch (RemoteException e) {
1227            }
1228        } catch (RemoteException e) {
1229            String msg = "Remote exception for " + componentName + "\n" + e;
1230            if (fromUser) {
1231                throw new IllegalArgumentException(msg);
1232            }
1233            Slog.w(TAG, msg);
1234            return false;
1235        }
1236        return true;
1237    }
1238
1239    void detachWallpaperLocked(WallpaperData wallpaper) {
1240        if (wallpaper.connection != null) {
1241            if (wallpaper.connection.mReply != null) {
1242                try {
1243                    wallpaper.connection.mReply.sendResult(null);
1244                } catch (RemoteException e) {
1245                }
1246                wallpaper.connection.mReply = null;
1247            }
1248            if (wallpaper.connection.mEngine != null) {
1249                try {
1250                    wallpaper.connection.mEngine.destroy();
1251                } catch (RemoteException e) {
1252                }
1253            }
1254            mContext.unbindService(wallpaper.connection);
1255            try {
1256                if (DEBUG)
1257                    Slog.v(TAG, "Removing window token: " + wallpaper.connection.mToken);
1258                mIWindowManager.removeWindowToken(wallpaper.connection.mToken);
1259            } catch (RemoteException e) {
1260            }
1261            wallpaper.connection.mService = null;
1262            wallpaper.connection.mEngine = null;
1263            wallpaper.connection = null;
1264        }
1265    }
1266
1267    void clearWallpaperComponentLocked(WallpaperData wallpaper) {
1268        wallpaper.wallpaperComponent = null;
1269        detachWallpaperLocked(wallpaper);
1270    }
1271
1272    void attachServiceLocked(WallpaperConnection conn, WallpaperData wallpaper) {
1273        try {
1274            conn.mService.attach(conn, conn.mToken,
1275                    WindowManager.LayoutParams.TYPE_WALLPAPER, false,
1276                    wallpaper.width, wallpaper.height, wallpaper.padding);
1277        } catch (RemoteException e) {
1278            Slog.w(TAG, "Failed attaching wallpaper; clearing", e);
1279            if (!wallpaper.wallpaperUpdating) {
1280                bindWallpaperComponentLocked(null, false, false, wallpaper, null);
1281            }
1282        }
1283    }
1284
1285    private void notifyCallbacksLocked(WallpaperData wallpaper) {
1286        final int n = wallpaper.callbacks.beginBroadcast();
1287        for (int i = 0; i < n; i++) {
1288            try {
1289                wallpaper.callbacks.getBroadcastItem(i).onWallpaperChanged();
1290            } catch (RemoteException e) {
1291
1292                // The RemoteCallbackList will take care of removing
1293                // the dead object for us.
1294            }
1295        }
1296        wallpaper.callbacks.finishBroadcast();
1297        final Intent intent = new Intent(Intent.ACTION_WALLPAPER_CHANGED);
1298        mContext.sendBroadcastAsUser(intent, new UserHandle(mCurrentUserId));
1299    }
1300
1301    private void checkPermission(String permission) {
1302        if (PackageManager.PERMISSION_GRANTED!= mContext.checkCallingOrSelfPermission(permission)) {
1303            throw new SecurityException("Access denied to process: " + Binder.getCallingPid()
1304                    + ", must have permission " + permission);
1305        }
1306    }
1307
1308    /**
1309     * Certain user types do not support wallpapers (e.g. managed profiles). The check is
1310     * implemented through through the OP_WRITE_WALLPAPER AppOp.
1311     */
1312    public boolean isWallpaperSupported(String callingPackage) {
1313        return mAppOpsManager.checkOpNoThrow(AppOpsManager.OP_WRITE_WALLPAPER, Binder.getCallingUid(),
1314                callingPackage) == AppOpsManager.MODE_ALLOWED;
1315    }
1316
1317    private static JournaledFile makeJournaledFile(int userId) {
1318        final String base = new File(getWallpaperDir(userId), WALLPAPER_INFO).getAbsolutePath();
1319        return new JournaledFile(new File(base), new File(base + ".tmp"));
1320    }
1321
1322    private void saveSettingsLocked(WallpaperData wallpaper) {
1323        JournaledFile journal = makeJournaledFile(wallpaper.userId);
1324        FileOutputStream stream = null;
1325        try {
1326            stream = new FileOutputStream(journal.chooseForWrite(), false);
1327            XmlSerializer out = new FastXmlSerializer();
1328            out.setOutput(stream, StandardCharsets.UTF_8.name());
1329            out.startDocument(null, true);
1330
1331            out.startTag(null, "wp");
1332            out.attribute(null, "id", Integer.toString(wallpaper.wallpaperId));
1333            out.attribute(null, "width", Integer.toString(wallpaper.width));
1334            out.attribute(null, "height", Integer.toString(wallpaper.height));
1335
1336            out.attribute(null, "cropLeft", Integer.toString(wallpaper.cropHint.left));
1337            out.attribute(null, "cropTop", Integer.toString(wallpaper.cropHint.top));
1338            out.attribute(null, "cropRight", Integer.toString(wallpaper.cropHint.right));
1339            out.attribute(null, "cropBottom", Integer.toString(wallpaper.cropHint.bottom));
1340
1341            if (wallpaper.padding.left != 0) {
1342                out.attribute(null, "paddingLeft", Integer.toString(wallpaper.padding.left));
1343            }
1344            if (wallpaper.padding.top != 0) {
1345                out.attribute(null, "paddingTop", Integer.toString(wallpaper.padding.top));
1346            }
1347            if (wallpaper.padding.right != 0) {
1348                out.attribute(null, "paddingRight", Integer.toString(wallpaper.padding.right));
1349            }
1350            if (wallpaper.padding.bottom != 0) {
1351                out.attribute(null, "paddingBottom", Integer.toString(wallpaper.padding.bottom));
1352            }
1353
1354            out.attribute(null, "name", wallpaper.name);
1355            if (wallpaper.wallpaperComponent != null
1356                    && !wallpaper.wallpaperComponent.equals(mImageWallpaper)) {
1357                out.attribute(null, "component",
1358                        wallpaper.wallpaperComponent.flattenToShortString());
1359            }
1360            out.endTag(null, "wp");
1361
1362            out.endDocument();
1363            stream.flush();
1364            FileUtils.sync(stream);
1365            stream.close();
1366            journal.commit();
1367        } catch (IOException e) {
1368            IoUtils.closeQuietly(stream);
1369            journal.rollback();
1370        }
1371    }
1372
1373    private void migrateFromOld() {
1374        File oldWallpaper = new File(WallpaperBackupHelper.WALLPAPER_IMAGE_KEY);
1375        File oldInfo = new File(WallpaperBackupHelper.WALLPAPER_INFO_KEY);
1376        if (oldWallpaper.exists()) {
1377            File newWallpaper = new File(getWallpaperDir(0), WALLPAPER);
1378            oldWallpaper.renameTo(newWallpaper);
1379        }
1380        if (oldInfo.exists()) {
1381            File newInfo = new File(getWallpaperDir(0), WALLPAPER_INFO);
1382            oldInfo.renameTo(newInfo);
1383        }
1384    }
1385
1386    private int getAttributeInt(XmlPullParser parser, String name, int defValue) {
1387        String value = parser.getAttributeValue(null, name);
1388        if (value == null) {
1389            return defValue;
1390        }
1391        return Integer.parseInt(value);
1392    }
1393
1394    /**
1395     * Sometimes it is expected the wallpaper map may not have a user's data.  E.g. This could
1396     * happen during user switch.  The async user switch observer may not have received
1397     * the event yet.  We use this safe method when we don't care about this ordering and just
1398     * want to update the data.  The data is going to be applied when the user switch observer
1399     * is eventually executed.
1400     */
1401    private WallpaperData getWallpaperSafeLocked(int userId) {
1402        WallpaperData wallpaper = mWallpaperMap.get(userId);
1403        if (wallpaper == null) {
1404            loadSettingsLocked(userId);
1405            wallpaper = mWallpaperMap.get(userId);
1406        }
1407        return wallpaper;
1408    }
1409
1410    private void loadSettingsLocked(int userId) {
1411        if (DEBUG) Slog.v(TAG, "loadSettingsLocked");
1412
1413        JournaledFile journal = makeJournaledFile(userId);
1414        FileInputStream stream = null;
1415        File file = journal.chooseForRead();
1416        if (!file.exists()) {
1417            // This should only happen one time, when upgrading from a legacy system
1418            migrateFromOld();
1419        }
1420        WallpaperData wallpaper = mWallpaperMap.get(userId);
1421        if (wallpaper == null) {
1422            wallpaper = new WallpaperData(userId);
1423            mWallpaperMap.put(userId, wallpaper);
1424        }
1425        boolean success = false;
1426        try {
1427            stream = new FileInputStream(file);
1428            XmlPullParser parser = Xml.newPullParser();
1429            parser.setInput(stream, StandardCharsets.UTF_8.name());
1430
1431            int type;
1432            do {
1433                type = parser.next();
1434                if (type == XmlPullParser.START_TAG) {
1435                    String tag = parser.getName();
1436                    if ("wp".equals(tag)) {
1437                        final String idString = parser.getAttributeValue(null, "id");
1438                        if (idString != null) {
1439                            final int id = wallpaper.wallpaperId = Integer.parseInt(idString);
1440                            if (id > mWallpaperId) {
1441                                mWallpaperId = id;
1442                            }
1443                        } else {
1444                            wallpaper.wallpaperId = makeWallpaperIdLocked();
1445                        }
1446
1447                        wallpaper.width = Integer.parseInt(parser.getAttributeValue(null, "width"));
1448                        wallpaper.height = Integer.parseInt(parser
1449                                .getAttributeValue(null, "height"));
1450                        wallpaper.cropHint.left = getAttributeInt(parser, "cropLeft", 0);
1451                        wallpaper.cropHint.top = getAttributeInt(parser, "cropTop", 0);
1452                        wallpaper.cropHint.right = getAttributeInt(parser, "cropRight", 0);
1453                        wallpaper.cropHint.bottom = getAttributeInt(parser, "cropBottom", 0);
1454                        wallpaper.padding.left = getAttributeInt(parser, "paddingLeft", 0);
1455                        wallpaper.padding.top = getAttributeInt(parser, "paddingTop", 0);
1456                        wallpaper.padding.right = getAttributeInt(parser, "paddingRight", 0);
1457                        wallpaper.padding.bottom = getAttributeInt(parser, "paddingBottom", 0);
1458                        wallpaper.name = parser.getAttributeValue(null, "name");
1459                        String comp = parser.getAttributeValue(null, "component");
1460                        wallpaper.nextWallpaperComponent = comp != null
1461                                ? ComponentName.unflattenFromString(comp)
1462                                : null;
1463                        if (wallpaper.nextWallpaperComponent == null
1464                                || "android".equals(wallpaper.nextWallpaperComponent
1465                                        .getPackageName())) {
1466                            wallpaper.nextWallpaperComponent = mImageWallpaper;
1467                        }
1468
1469                        if (DEBUG) {
1470                            Slog.v(TAG, "mWidth:" + wallpaper.width);
1471                            Slog.v(TAG, "mHeight:" + wallpaper.height);
1472                            Slog.v(TAG, "cropRect:" + wallpaper.cropHint);
1473                            Slog.v(TAG, "mName:" + wallpaper.name);
1474                            Slog.v(TAG, "mNextWallpaperComponent:"
1475                                    + wallpaper.nextWallpaperComponent);
1476                        }
1477                    }
1478                }
1479            } while (type != XmlPullParser.END_DOCUMENT);
1480            success = true;
1481        } catch (FileNotFoundException e) {
1482            Slog.w(TAG, "no current wallpaper -- first boot?");
1483        } catch (NullPointerException e) {
1484            Slog.w(TAG, "failed parsing " + file + " " + e);
1485        } catch (NumberFormatException e) {
1486            Slog.w(TAG, "failed parsing " + file + " " + e);
1487        } catch (XmlPullParserException e) {
1488            Slog.w(TAG, "failed parsing " + file + " " + e);
1489        } catch (IOException e) {
1490            Slog.w(TAG, "failed parsing " + file + " " + e);
1491        } catch (IndexOutOfBoundsException e) {
1492            Slog.w(TAG, "failed parsing " + file + " " + e);
1493        }
1494        IoUtils.closeQuietly(stream);
1495
1496        if (!success) {
1497            wallpaper.width = -1;
1498            wallpaper.height = -1;
1499            wallpaper.cropHint.set(0, 0, 0, 0);
1500            wallpaper.padding.set(0, 0, 0, 0);
1501            wallpaper.name = "";
1502        } else {
1503            if (wallpaper.wallpaperId <= 0) {
1504                wallpaper.wallpaperId = makeWallpaperIdLocked();
1505                if (DEBUG) {
1506                    Slog.w(TAG, "Didn't set wallpaper id in loadSettingsLocked(" + userId
1507                            + "); now " + wallpaper.wallpaperId);
1508                }
1509            }
1510        }
1511
1512        // We always want to have some reasonable width hint.
1513        int baseSize = getMaximumSizeDimension();
1514        if (wallpaper.width < baseSize) {
1515            wallpaper.width = baseSize;
1516        }
1517        if (wallpaper.height < baseSize) {
1518            wallpaper.height = baseSize;
1519        }
1520        // and crop, if not previously specified
1521        if (wallpaper.cropHint.width() <= 0
1522                || wallpaper.cropHint.height() <= 0) {
1523            wallpaper.cropHint.set(0, 0, wallpaper.width, wallpaper.height);
1524        }
1525    }
1526
1527    private int getMaximumSizeDimension() {
1528        WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
1529        Display d = wm.getDefaultDisplay();
1530        return d.getMaximumSizeDimension();
1531    }
1532
1533    // Called by SystemBackupAgent after files are restored to disk.
1534    public void settingsRestored() {
1535        // Verify caller is the system
1536        if (Binder.getCallingUid() != android.os.Process.SYSTEM_UID) {
1537            throw new RuntimeException("settingsRestored() can only be called from the system process");
1538        }
1539        // TODO: If necessary, make it work for secondary users as well. This currently assumes
1540        // restores only to the primary user
1541        if (DEBUG) Slog.v(TAG, "settingsRestored");
1542        WallpaperData wallpaper = null;
1543        boolean success = false;
1544        synchronized (mLock) {
1545            loadSettingsLocked(0);
1546            wallpaper = mWallpaperMap.get(0);
1547            wallpaper.wallpaperId = makeWallpaperIdLocked();    // always bump id at restore
1548            if (wallpaper.nextWallpaperComponent != null
1549                    && !wallpaper.nextWallpaperComponent.equals(mImageWallpaper)) {
1550                if (!bindWallpaperComponentLocked(wallpaper.nextWallpaperComponent, false, false,
1551                        wallpaper, null)) {
1552                    // No such live wallpaper or other failure; fall back to the default
1553                    // live wallpaper (since the profile being restored indicated that the
1554                    // user had selected a live rather than static one).
1555                    bindWallpaperComponentLocked(null, false, false, wallpaper, null);
1556                }
1557                success = true;
1558            } else {
1559                // If there's a wallpaper name, we use that.  If that can't be loaded, then we
1560                // use the default.
1561                if ("".equals(wallpaper.name)) {
1562                    if (DEBUG) Slog.v(TAG, "settingsRestored: name is empty");
1563                    success = true;
1564                } else {
1565                    if (DEBUG) Slog.v(TAG, "settingsRestored: attempting to restore named resource");
1566                    success = restoreNamedResourceLocked(wallpaper);
1567                }
1568                if (DEBUG) Slog.v(TAG, "settingsRestored: success=" + success
1569                        + " id=" + wallpaper.wallpaperId);
1570                if (success) {
1571                    bindWallpaperComponentLocked(wallpaper.nextWallpaperComponent, false, false,
1572                            wallpaper, null);
1573                }
1574            }
1575        }
1576
1577        if (!success) {
1578            Slog.e(TAG, "Failed to restore wallpaper: '" + wallpaper.name + "'");
1579            wallpaper.name = "";
1580            getWallpaperDir(0).delete();
1581        }
1582
1583        synchronized (mLock) {
1584            saveSettingsLocked(wallpaper);
1585        }
1586    }
1587
1588    // Restore the named resource bitmap to both source + crop files
1589    boolean restoreNamedResourceLocked(WallpaperData wallpaper) {
1590        if (wallpaper.name.length() > 4 && "res:".equals(wallpaper.name.substring(0, 4))) {
1591            String resName = wallpaper.name.substring(4);
1592
1593            String pkg = null;
1594            int colon = resName.indexOf(':');
1595            if (colon > 0) {
1596                pkg = resName.substring(0, colon);
1597            }
1598
1599            String ident = null;
1600            int slash = resName.lastIndexOf('/');
1601            if (slash > 0) {
1602                ident = resName.substring(slash+1);
1603            }
1604
1605            String type = null;
1606            if (colon > 0 && slash > 0 && (slash-colon) > 1) {
1607                type = resName.substring(colon+1, slash);
1608            }
1609
1610            if (pkg != null && ident != null && type != null) {
1611                int resId = -1;
1612                InputStream res = null;
1613                FileOutputStream fos = null;
1614                FileOutputStream cos = null;
1615                try {
1616                    Context c = mContext.createPackageContext(pkg, Context.CONTEXT_RESTRICTED);
1617                    Resources r = c.getResources();
1618                    resId = r.getIdentifier(resName, null, null);
1619                    if (resId == 0) {
1620                        Slog.e(TAG, "couldn't resolve identifier pkg=" + pkg + " type=" + type
1621                                + " ident=" + ident);
1622                        return false;
1623                    }
1624
1625                    res = r.openRawResource(resId);
1626                    if (wallpaper.wallpaperFile.exists()) {
1627                        wallpaper.wallpaperFile.delete();
1628                        wallpaper.cropFile.delete();
1629                    }
1630                    fos = new FileOutputStream(wallpaper.wallpaperFile);
1631                    cos = new FileOutputStream(wallpaper.cropFile);
1632
1633                    byte[] buffer = new byte[32768];
1634                    int amt;
1635                    while ((amt=res.read(buffer)) > 0) {
1636                        fos.write(buffer, 0, amt);
1637                        cos.write(buffer, 0, amt);
1638                    }
1639                    // mWallpaperObserver will notice the close and send the change broadcast
1640
1641                    Slog.v(TAG, "Restored wallpaper: " + resName);
1642                    return true;
1643                } catch (NameNotFoundException e) {
1644                    Slog.e(TAG, "Package name " + pkg + " not found");
1645                } catch (Resources.NotFoundException e) {
1646                    Slog.e(TAG, "Resource not found: " + resId);
1647                } catch (IOException e) {
1648                    Slog.e(TAG, "IOException while restoring wallpaper ", e);
1649                } finally {
1650                    IoUtils.closeQuietly(res);
1651                    if (fos != null) {
1652                        FileUtils.sync(fos);
1653                    }
1654                    if (cos != null) {
1655                        FileUtils.sync(cos);
1656                    }
1657                    IoUtils.closeQuietly(fos);
1658                    IoUtils.closeQuietly(cos);
1659                }
1660            }
1661        }
1662        return false;
1663    }
1664
1665    @Override
1666    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1667        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
1668                != PackageManager.PERMISSION_GRANTED) {
1669
1670            pw.println("Permission Denial: can't dump wallpaper service from from pid="
1671                    + Binder.getCallingPid()
1672                    + ", uid=" + Binder.getCallingUid());
1673            return;
1674        }
1675
1676        synchronized (mLock) {
1677            pw.println("Current Wallpaper Service state:");
1678            for (int i = 0; i < mWallpaperMap.size(); i++) {
1679                WallpaperData wallpaper = mWallpaperMap.valueAt(i);
1680                pw.print(" User "); pw.print(wallpaper.userId);
1681                pw.print(": id="); pw.println(wallpaper.wallpaperId);
1682                pw.print("  mWidth=");
1683                    pw.print(wallpaper.width);
1684                    pw.print(" mHeight=");
1685                    pw.println(wallpaper.height);
1686                pw.print("  mCropHint="); pw.println(wallpaper.cropHint);
1687                pw.print("  mPadding="); pw.println(wallpaper.padding);
1688                pw.print("  mName=");  pw.println(wallpaper.name);
1689                pw.print("  mWallpaperComponent="); pw.println(wallpaper.wallpaperComponent);
1690                if (wallpaper.connection != null) {
1691                    WallpaperConnection conn = wallpaper.connection;
1692                    pw.print("  Wallpaper connection ");
1693                    pw.print(conn);
1694                    pw.println(":");
1695                    if (conn.mInfo != null) {
1696                        pw.print("    mInfo.component=");
1697                        pw.println(conn.mInfo.getComponent());
1698                    }
1699                    pw.print("    mToken=");
1700                    pw.println(conn.mToken);
1701                    pw.print("    mService=");
1702                    pw.println(conn.mService);
1703                    pw.print("    mEngine=");
1704                    pw.println(conn.mEngine);
1705                    pw.print("    mLastDiedTime=");
1706                    pw.println(wallpaper.lastDiedTime - SystemClock.uptimeMillis());
1707                }
1708            }
1709        }
1710    }
1711}
1712