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.ActivityManager; 21 import android.app.IWallpaperManager; 22 import android.app.IWallpaperManagerCallback; 23 import android.app.WallpaperManager; 24 import android.content.Context; 25 import android.content.res.Resources; 26 import android.graphics.Bitmap; 27 import android.graphics.BitmapFactory; 28 import android.graphics.Rect; 29 import android.graphics.Xfermode; 30 import android.graphics.drawable.BitmapDrawable; 31 import android.graphics.drawable.Drawable; 32 import android.graphics.drawable.DrawableWrapper; 33 import android.os.AsyncTask; 34 import android.os.Handler; 35 import android.os.ParcelFileDescriptor; 36 import android.os.RemoteException; 37 import android.os.ServiceManager; 38 import android.os.UserHandle; 39 import android.util.Log; 40 41 import com.android.keyguard.KeyguardUpdateMonitor; 42 43 import libcore.io.IoUtils; 44 45 import java.util.Objects; 46 47 /** 48 * Manages the lockscreen wallpaper. 49 */ 50 public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implements Runnable { 51 52 private static final String TAG = "LockscreenWallpaper"; 53 54 private final PhoneStatusBar 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, PhoneStatusBar 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 && selectedUser.getIdentifier() != currentUserId) { 125 // When selected user is different from the current user, show the selected 126 // user's static wallpaper. 127 return LoaderResult.success( 128 mWallpaperManager.getBitmapAsUser(selectedUser.getIdentifier())); 129 130 } else { 131 // When there is no selected user, or it's same as the current user, show the 132 // system (possibly dynamic) wallpaper for the selected user. 133 return LoaderResult.success(null); 134 } 135 } 136 } 137 138 public void setCurrentUser(int user) { 139 if (user != mCurrentUserId) { 140 mCached = false; 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