1/*
2 * Copyright (C) 2016 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.systemui.statusbar.phone;
18
19import android.annotation.Nullable;
20import android.app.ActivityManager;
21import android.app.IWallpaperManager;
22import android.app.IWallpaperManagerCallback;
23import android.app.WallpaperManager;
24import android.content.Context;
25import android.content.res.Resources;
26import android.graphics.Bitmap;
27import android.graphics.BitmapFactory;
28import android.graphics.Rect;
29import android.graphics.Xfermode;
30import android.graphics.drawable.BitmapDrawable;
31import android.graphics.drawable.Drawable;
32import android.graphics.drawable.DrawableWrapper;
33import android.os.AsyncTask;
34import android.os.Handler;
35import android.os.ParcelFileDescriptor;
36import android.os.RemoteException;
37import android.os.ServiceManager;
38import android.os.UserHandle;
39import android.util.Log;
40
41import com.android.keyguard.KeyguardUpdateMonitor;
42
43import libcore.io.IoUtils;
44
45import java.util.Objects;
46
47/**
48 * Manages the lockscreen wallpaper.
49 */
50public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implements Runnable {
51
52    private static final String TAG = "LockscreenWallpaper";
53
54    private final StatusBar mBar;
55    private final WallpaperManager mWallpaperManager;
56    private final Handler mH;
57    private final KeyguardUpdateMonitor mUpdateMonitor;
58
59    private boolean mCached;
60    private Bitmap mCache;
61    private int mCurrentUserId;
62    // The user selected in the UI, or null if no user is selected or UI doesn't support selecting
63    // users.
64    private UserHandle mSelectedUser;
65    private AsyncTask<Void, Void, LoaderResult> mLoader;
66
67    public LockscreenWallpaper(Context ctx, StatusBar bar, Handler h) {
68        mBar = bar;
69        mH = h;
70        mWallpaperManager = (WallpaperManager) ctx.getSystemService(Context.WALLPAPER_SERVICE);
71        mCurrentUserId = ActivityManager.getCurrentUser();
72        mUpdateMonitor = KeyguardUpdateMonitor.getInstance(ctx);
73
74        IWallpaperManager service = IWallpaperManager.Stub.asInterface(
75                ServiceManager.getService(Context.WALLPAPER_SERVICE));
76        try {
77            service.setLockWallpaperCallback(this);
78        } catch (RemoteException e) {
79            Log.e(TAG, "System dead?" + e);
80        }
81    }
82
83    public Bitmap getBitmap() {
84        if (mCached) {
85            return mCache;
86        }
87        if (!mWallpaperManager.isWallpaperSupported()) {
88            mCached = true;
89            mCache = null;
90            return null;
91        }
92
93        LoaderResult result = loadBitmap(mCurrentUserId, mSelectedUser);
94        if (result.success) {
95            mCached = true;
96            mUpdateMonitor.setHasLockscreenWallpaper(result.bitmap != null);
97            mCache = result.bitmap;
98        }
99        return mCache;
100    }
101
102    public LoaderResult loadBitmap(int currentUserId, UserHandle selectedUser) {
103        // May be called on any thread - only use thread safe operations.
104
105        // Prefer the selected user (when specified) over the current user for the FLAG_SET_LOCK
106        // wallpaper.
107        final int lockWallpaperUserId =
108                selectedUser != null ? selectedUser.getIdentifier() : currentUserId;
109        ParcelFileDescriptor fd = mWallpaperManager.getWallpaperFile(
110                WallpaperManager.FLAG_LOCK, lockWallpaperUserId);
111
112        if (fd != null) {
113            try {
114                BitmapFactory.Options options = new BitmapFactory.Options();
115                return LoaderResult.success(BitmapFactory.decodeFileDescriptor(
116                        fd.getFileDescriptor(), null, options));
117            } catch (OutOfMemoryError e) {
118                Log.w(TAG, "Can't decode file", e);
119                return LoaderResult.fail();
120            } finally {
121                IoUtils.closeQuietly(fd);
122            }
123        } else {
124            if (selectedUser != null) {
125                // Show the selected user's static wallpaper.
126                return LoaderResult.success(
127                        mWallpaperManager.getBitmapAsUser(selectedUser.getIdentifier()));
128
129            } else {
130                // When there is no selected user, show the system wallpaper
131                return LoaderResult.success(null);
132            }
133        }
134    }
135
136    public void setCurrentUser(int user) {
137        if (user != mCurrentUserId) {
138            if (mSelectedUser == null || user != mSelectedUser.getIdentifier()) {
139                mCached = false;
140            }
141            mCurrentUserId = user;
142        }
143    }
144
145    public void setSelectedUser(UserHandle selectedUser) {
146        if (Objects.equals(selectedUser, mSelectedUser)) {
147            return;
148        }
149        mSelectedUser = selectedUser;
150        postUpdateWallpaper();
151    }
152
153    @Override
154    public void onWallpaperChanged() {
155        // Called on Binder thread.
156        postUpdateWallpaper();
157    }
158
159    private void postUpdateWallpaper() {
160        mH.removeCallbacks(this);
161        mH.post(this);
162    }
163
164    @Override
165    public void run() {
166        // Called in response to onWallpaperChanged on the main thread.
167
168        if (mLoader != null) {
169            mLoader.cancel(false /* interrupt */);
170        }
171
172        final int currentUser = mCurrentUserId;
173        final UserHandle selectedUser = mSelectedUser;
174        mLoader = new AsyncTask<Void, Void, LoaderResult>() {
175            @Override
176            protected LoaderResult doInBackground(Void... params) {
177                return loadBitmap(currentUser, selectedUser);
178            }
179
180            @Override
181            protected void onPostExecute(LoaderResult result) {
182                super.onPostExecute(result);
183                if (isCancelled()) {
184                    return;
185                }
186                if (result.success) {
187                    mCached = true;
188                    mCache = result.bitmap;
189                    mUpdateMonitor.setHasLockscreenWallpaper(result.bitmap != null);
190                    mBar.updateMediaMetaData(
191                            true /* metaDataChanged */, true /* allowEnterAnimation */);
192                }
193                mLoader = null;
194            }
195        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
196    }
197
198    private static class LoaderResult {
199        public final boolean success;
200        public final Bitmap bitmap;
201
202        LoaderResult(boolean success, Bitmap bitmap) {
203            this.success = success;
204            this.bitmap = bitmap;
205        }
206
207        static LoaderResult success(Bitmap b) {
208            return new LoaderResult(true, b);
209        }
210
211        static LoaderResult fail() {
212            return new LoaderResult(false, null);
213        }
214    }
215
216    /**
217     * Drawable that aligns left horizontally and center vertically (like ImageWallpaper).
218     */
219    public static class WallpaperDrawable extends DrawableWrapper {
220
221        private final ConstantState mState;
222        private final Rect mTmpRect = new Rect();
223
224        public WallpaperDrawable(Resources r, Bitmap b) {
225            this(r, new ConstantState(b));
226        }
227
228        private WallpaperDrawable(Resources r, ConstantState state) {
229            super(new BitmapDrawable(r, state.mBackground));
230            mState = state;
231        }
232
233        @Override
234        public void setXfermode(@Nullable Xfermode mode) {
235            // DrawableWrapper does not call this for us.
236            getDrawable().setXfermode(mode);
237        }
238
239        @Override
240        public int getIntrinsicWidth() {
241            return -1;
242        }
243
244        @Override
245        public int getIntrinsicHeight() {
246            return -1;
247        }
248
249        @Override
250        protected void onBoundsChange(Rect bounds) {
251            int vwidth = getBounds().width();
252            int vheight = getBounds().height();
253            int dwidth = mState.mBackground.getWidth();
254            int dheight = mState.mBackground.getHeight();
255            float scale;
256            float dx = 0, dy = 0;
257
258            if (dwidth * vheight > vwidth * dheight) {
259                scale = (float) vheight / (float) dheight;
260            } else {
261                scale = (float) vwidth / (float) dwidth;
262            }
263
264            if (scale <= 1f) {
265                scale = 1f;
266            }
267            dy = (vheight - dheight * scale) * 0.5f;
268
269            mTmpRect.set(
270                    bounds.left,
271                    bounds.top + Math.round(dy),
272                    bounds.left + Math.round(dwidth * scale),
273                    bounds.top + Math.round(dheight * scale + dy));
274
275            super.onBoundsChange(mTmpRect);
276        }
277
278        @Override
279        public ConstantState getConstantState() {
280            return mState;
281        }
282
283        static class ConstantState extends Drawable.ConstantState {
284
285            private final Bitmap mBackground;
286
287            ConstantState(Bitmap background) {
288                mBackground = background;
289            }
290
291            @Override
292            public Drawable newDrawable() {
293                return newDrawable(null);
294            }
295
296            @Override
297            public Drawable newDrawable(@Nullable Resources res) {
298                return new WallpaperDrawable(res, this);
299            }
300
301            @Override
302            public int getChangingConfigurations() {
303                // DrawableWrapper already handles this for us.
304                return 0;
305            }
306        }
307    }
308}
309