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