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