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