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 
17 package com.android.systemui.statusbar.phone;
18 
19 import android.annotation.Nullable;
20 import android.app.IWallpaperManager;
21 import android.app.IWallpaperManagerCallback;
22 import android.app.WallpaperColors;
23 import android.app.WallpaperManager;
24 import android.content.res.Resources;
25 import android.graphics.Bitmap;
26 import android.graphics.BitmapFactory;
27 import android.graphics.Rect;
28 import android.graphics.Xfermode;
29 import android.graphics.drawable.BitmapDrawable;
30 import android.graphics.drawable.Drawable;
31 import android.graphics.drawable.DrawableWrapper;
32 import android.os.AsyncTask;
33 import android.os.Handler;
34 import android.os.ParcelFileDescriptor;
35 import android.os.RemoteException;
36 import android.os.UserHandle;
37 import android.util.Log;
38 
39 import androidx.annotation.NonNull;
40 
41 import com.android.internal.util.IndentingPrintWriter;
42 import com.android.keyguard.KeyguardUpdateMonitor;
43 import com.android.systemui.CoreStartable;
44 import com.android.systemui.Dumpable;
45 import com.android.systemui.dagger.SysUISingleton;
46 import com.android.systemui.dagger.qualifiers.Main;
47 import com.android.systemui.dump.DumpManager;
48 import com.android.systemui.settings.UserTracker;
49 import com.android.systemui.statusbar.NotificationMediaManager;
50 import com.android.systemui.user.data.model.SelectedUserModel;
51 import com.android.systemui.user.data.model.SelectionStatus;
52 import com.android.systemui.user.data.repository.UserRepository;
53 import com.android.systemui.util.kotlin.JavaAdapter;
54 
55 import libcore.io.IoUtils;
56 
57 import java.io.PrintWriter;
58 import java.util.Objects;
59 
60 import javax.inject.Inject;
61 
62 /**
63  * Manages the lockscreen wallpaper.
64  */
65 @SysUISingleton
66 public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implements Runnable,
67         Dumpable, CoreStartable {
68 
69     private static final String TAG = "LockscreenWallpaper";
70 
71     // TODO(b/253507223): temporary; remove this
72     private static final String DISABLED_ERROR_MESSAGE = "Methods from LockscreenWallpaper.java "
73             + "should not be called in this version. The lock screen wallpaper should be "
74             + "managed by the WallpaperManagerService and not by this class.";
75 
76     private final NotificationMediaManager mMediaManager;
77     private final WallpaperManager mWallpaperManager;
78     private final KeyguardUpdateMonitor mUpdateMonitor;
79     private final Handler mH;
80     private final JavaAdapter mJavaAdapter;
81     private final UserRepository mUserRepository;
82 
83     private boolean mCached;
84     private Bitmap mCache;
85     private int mCurrentUserId;
86     // The user selected in the UI, or null if no user is selected or UI doesn't support selecting
87     // users.
88     private UserHandle mSelectedUser;
89     private AsyncTask<Void, Void, LoaderResult> mLoader;
90 
91     @Inject
LockscreenWallpaper(WallpaperManager wallpaperManager, @Nullable IWallpaperManager iWallpaperManager, KeyguardUpdateMonitor keyguardUpdateMonitor, DumpManager dumpManager, NotificationMediaManager mediaManager, @Main Handler mainHandler, JavaAdapter javaAdapter, UserRepository userRepository, UserTracker userTracker)92     public LockscreenWallpaper(WallpaperManager wallpaperManager,
93             @Nullable IWallpaperManager iWallpaperManager,
94             KeyguardUpdateMonitor keyguardUpdateMonitor,
95             DumpManager dumpManager,
96             NotificationMediaManager mediaManager,
97             @Main Handler mainHandler,
98             JavaAdapter javaAdapter,
99             UserRepository userRepository,
100             UserTracker userTracker) {
101         dumpManager.registerDumpable(getClass().getSimpleName(), this);
102         mWallpaperManager = wallpaperManager;
103         mCurrentUserId = userTracker.getUserId();
104         mUpdateMonitor = keyguardUpdateMonitor;
105         mMediaManager = mediaManager;
106         mH = mainHandler;
107         mJavaAdapter = javaAdapter;
108         mUserRepository = userRepository;
109 
110         if (iWallpaperManager != null && !mWallpaperManager.isLockscreenLiveWallpaperEnabled()) {
111             // Service is disabled on some devices like Automotive
112             try {
113                 iWallpaperManager.setLockWallpaperCallback(this);
114             } catch (RemoteException e) {
115                 Log.e(TAG, "System dead?" + e);
116             }
117         }
118     }
119 
120     @Override
start()121     public void start() {
122         if (!isLockscreenLiveWallpaperEnabled()) {
123             mJavaAdapter.alwaysCollectFlow(
124                     mUserRepository.getSelectedUser(), this::setSelectedUser);
125         }
126     }
127 
getBitmap()128     public Bitmap getBitmap() {
129         assertLockscreenLiveWallpaperNotEnabled();
130 
131         if (mCached) {
132             return mCache;
133         }
134         if (!mWallpaperManager.isWallpaperSupported()) {
135             mCached = true;
136             mCache = null;
137             return null;
138         }
139 
140         LoaderResult result = loadBitmap(mCurrentUserId, mSelectedUser);
141         if (result.success) {
142             mCached = true;
143             mCache = result.bitmap;
144         }
145         return mCache;
146     }
147 
loadBitmap(int currentUserId, UserHandle selectedUser)148     public LoaderResult loadBitmap(int currentUserId, UserHandle selectedUser) {
149         // May be called on any thread - only use thread safe operations.
150 
151         assertLockscreenLiveWallpaperNotEnabled();
152 
153 
154         if (!mWallpaperManager.isWallpaperSupported()) {
155             // When wallpaper is not supported, show the system wallpaper
156             return LoaderResult.success(null);
157         }
158 
159         // Prefer the selected user (when specified) over the current user for the FLAG_SET_LOCK
160         // wallpaper.
161         final int lockWallpaperUserId =
162                 selectedUser != null ? selectedUser.getIdentifier() : currentUserId;
163         ParcelFileDescriptor fd = mWallpaperManager.getWallpaperFile(
164                 WallpaperManager.FLAG_LOCK, lockWallpaperUserId);
165 
166         if (fd != null) {
167             try {
168                 BitmapFactory.Options options = new BitmapFactory.Options();
169                 options.inPreferredConfig = Bitmap.Config.HARDWARE;
170                 return LoaderResult.success(BitmapFactory.decodeFileDescriptor(
171                         fd.getFileDescriptor(), null, options));
172             } catch (OutOfMemoryError e) {
173                 Log.w(TAG, "Can't decode file", e);
174                 return LoaderResult.fail();
175             } finally {
176                 IoUtils.closeQuietly(fd);
177             }
178         } else {
179             if (selectedUser != null) {
180                 // Show the selected user's static wallpaper.
181                 return LoaderResult.success(mWallpaperManager.getBitmapAsUser(
182                         selectedUser.getIdentifier(), true /* hardware */));
183 
184             } else {
185                 // When there is no selected user, show the system wallpaper
186                 return LoaderResult.success(null);
187             }
188         }
189     }
190 
setSelectedUser(SelectedUserModel selectedUserModel)191     private void setSelectedUser(SelectedUserModel selectedUserModel) {
192         assertLockscreenLiveWallpaperNotEnabled();
193 
194         if (selectedUserModel.getSelectionStatus().equals(SelectionStatus.SELECTION_IN_PROGRESS)) {
195             // Wait until the selection has finished before updating.
196             return;
197         }
198 
199         int user = selectedUserModel.getUserInfo().id;
200         if (user != mCurrentUserId) {
201             if (mSelectedUser == null || user != mSelectedUser.getIdentifier()) {
202                 mCached = false;
203             }
204             mCurrentUserId = user;
205         }
206     }
207 
setSelectedUser(UserHandle selectedUser)208     public void setSelectedUser(UserHandle selectedUser) {
209         assertLockscreenLiveWallpaperNotEnabled();
210 
211         if (Objects.equals(selectedUser, mSelectedUser)) {
212             return;
213         }
214         mSelectedUser = selectedUser;
215         postUpdateWallpaper();
216     }
217 
218     @Override
onWallpaperChanged()219     public void onWallpaperChanged() {
220         assertLockscreenLiveWallpaperNotEnabled();
221         // Called on Binder thread.
222         postUpdateWallpaper();
223     }
224 
225     @Override
onWallpaperColorsChanged(WallpaperColors colors, int which, int userId)226     public void onWallpaperColorsChanged(WallpaperColors colors, int which, int userId) {
227         assertLockscreenLiveWallpaperNotEnabled();
228     }
229 
postUpdateWallpaper()230     private void postUpdateWallpaper() {
231         assertLockscreenLiveWallpaperNotEnabled();
232         if (mH == null) {
233             Log.wtfStack(TAG, "Trying to use LockscreenWallpaper before initialization.");
234             return;
235         }
236         mH.removeCallbacks(this);
237         mH.post(this);
238     }
239     @Override
run()240     public void run() {
241         // Called in response to onWallpaperChanged on the main thread.
242 
243         assertLockscreenLiveWallpaperNotEnabled();
244 
245         if (mLoader != null) {
246             mLoader.cancel(false /* interrupt */);
247         }
248 
249         final int currentUser = mCurrentUserId;
250         final UserHandle selectedUser = mSelectedUser;
251         mLoader = new AsyncTask<Void, Void, LoaderResult>() {
252             @Override
253             protected LoaderResult doInBackground(Void... params) {
254                 return loadBitmap(currentUser, selectedUser);
255             }
256 
257             @Override
258             protected void onPostExecute(LoaderResult result) {
259                 super.onPostExecute(result);
260                 if (isCancelled()) {
261                     return;
262                 }
263                 if (result.success) {
264                     mCached = true;
265                     mCache = result.bitmap;
266                     mMediaManager.updateMediaMetaData(
267                             true /* metaDataChanged */, true /* allowEnterAnimation */);
268                 }
269                 mLoader = null;
270             }
271         }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
272     }
273 
274     // TODO(b/273443374): remove
isLockscreenLiveWallpaperEnabled()275     public boolean isLockscreenLiveWallpaperEnabled() {
276         return mWallpaperManager.isLockscreenLiveWallpaperEnabled();
277     }
278 
279     @Override
dump(@onNull PrintWriter pw, @NonNull String[] args)280     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
281         pw.println(getClass().getSimpleName() + ":");
282         IndentingPrintWriter iPw = new IndentingPrintWriter(pw, "  ").increaseIndent();
283         iPw.println("mCached=" + mCached);
284         iPw.println("mCache=" + mCache);
285         iPw.println("mCurrentUserId=" + mCurrentUserId);
286         iPw.println("mSelectedUser=" + mSelectedUser);
287     }
288 
289     private static class LoaderResult {
290         public final boolean success;
291         public final Bitmap bitmap;
292 
LoaderResult(boolean success, Bitmap bitmap)293         LoaderResult(boolean success, Bitmap bitmap) {
294             this.success = success;
295             this.bitmap = bitmap;
296         }
297 
success(Bitmap b)298         static LoaderResult success(Bitmap b) {
299             return new LoaderResult(true, b);
300         }
301 
fail()302         static LoaderResult fail() {
303             return new LoaderResult(false, null);
304         }
305     }
306 
307     /**
308      * Drawable that aligns left horizontally and center vertically (like ImageWallpaper).
309      *
310      * <p>Aligns to the center when showing on the smaller internal display of a multi display
311      * device.
312      */
313     public static class WallpaperDrawable extends DrawableWrapper {
314 
315         private final ConstantState mState;
316         private final Rect mTmpRect = new Rect();
317         private boolean mIsOnSmallerInternalDisplays;
318 
WallpaperDrawable(Resources r, Bitmap b, boolean isOnSmallerInternalDisplays)319         public WallpaperDrawable(Resources r, Bitmap b, boolean isOnSmallerInternalDisplays) {
320             this(r, new ConstantState(b), isOnSmallerInternalDisplays);
321         }
322 
WallpaperDrawable(Resources r, ConstantState state, boolean isOnSmallerInternalDisplays)323         private WallpaperDrawable(Resources r, ConstantState state,
324                 boolean isOnSmallerInternalDisplays) {
325             super(new BitmapDrawable(r, state.mBackground));
326             mState = state;
327             mIsOnSmallerInternalDisplays = isOnSmallerInternalDisplays;
328         }
329 
330         @Override
setXfermode(@ullable Xfermode mode)331         public void setXfermode(@Nullable Xfermode mode) {
332             // DrawableWrapper does not call this for us.
333             getDrawable().setXfermode(mode);
334         }
335 
336         @Override
getIntrinsicWidth()337         public int getIntrinsicWidth() {
338             return -1;
339         }
340 
341         @Override
getIntrinsicHeight()342         public int getIntrinsicHeight() {
343             return -1;
344         }
345 
346         @Override
onBoundsChange(Rect bounds)347         protected void onBoundsChange(Rect bounds) {
348             int vwidth = getBounds().width();
349             int vheight = getBounds().height();
350             int dwidth = mState.mBackground.getWidth();
351             int dheight = mState.mBackground.getHeight();
352             float scale;
353             float dx = 0, dy = 0;
354 
355             if (dwidth * vheight > vwidth * dheight) {
356                 scale = (float) vheight / (float) dheight;
357             } else {
358                 scale = (float) vwidth / (float) dwidth;
359             }
360 
361             if (scale <= 1f) {
362                 scale = 1f;
363             }
364             dy = (vheight - dheight * scale) * 0.5f;
365 
366             int offsetX = 0;
367             // Offset to show the center area of the wallpaper on a smaller display for multi
368             // display device
369             if (mIsOnSmallerInternalDisplays) {
370                 offsetX = bounds.centerX() - (Math.round(dwidth * scale) / 2);
371             }
372 
373             mTmpRect.set(
374                     bounds.left + offsetX,
375                     bounds.top + Math.round(dy),
376                     bounds.left + Math.round(dwidth * scale) + offsetX,
377                     bounds.top + Math.round(dheight * scale + dy));
378 
379             super.onBoundsChange(mTmpRect);
380         }
381 
382         @Override
getConstantState()383         public ConstantState getConstantState() {
384             return mState;
385         }
386 
387         /**
388          * Update bounds when the hosting display or the display size has changed.
389          *
390          * @param isOnSmallerInternalDisplays true if the drawable is on one of the internal
391          *                                    displays with the smaller area.
392          */
onDisplayUpdated(boolean isOnSmallerInternalDisplays)393         public void onDisplayUpdated(boolean isOnSmallerInternalDisplays) {
394             mIsOnSmallerInternalDisplays = isOnSmallerInternalDisplays;
395             onBoundsChange(getBounds());
396         }
397 
398         static class ConstantState extends Drawable.ConstantState {
399 
400             private final Bitmap mBackground;
401 
ConstantState(Bitmap background)402             ConstantState(Bitmap background) {
403                 mBackground = background;
404             }
405 
406             @Override
newDrawable()407             public Drawable newDrawable() {
408                 return newDrawable(null);
409             }
410 
411             @Override
newDrawable(@ullable Resources res)412             public Drawable newDrawable(@Nullable Resources res) {
413                 return new WallpaperDrawable(res, this, /* isOnSmallerInternalDisplays= */ false);
414             }
415 
416             @Override
getChangingConfigurations()417             public int getChangingConfigurations() {
418                 // DrawableWrapper already handles this for us.
419                 return 0;
420             }
421         }
422     }
423 
424     /**
425      * Feature b/253507223 will adapt the logic to always use the
426      * WallpaperManagerService to render the lock screen wallpaper.
427      * Methods of this class should not be called at all if the project flag is enabled.
428      * TODO(b/253507223) temporary assertion; remove this
429      */
assertLockscreenLiveWallpaperNotEnabled()430     private void assertLockscreenLiveWallpaperNotEnabled() {
431         if (mWallpaperManager.isLockscreenLiveWallpaperEnabled()) {
432             throw new IllegalStateException(DISABLED_ERROR_MESSAGE);
433         }
434     }
435 }
436