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