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