/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.systemui.statusbar.phone; import android.annotation.Nullable; import android.app.IWallpaperManager; import android.app.IWallpaperManagerCallback; import android.app.WallpaperColors; import android.app.WallpaperManager; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Rect; import android.graphics.Xfermode; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.DrawableWrapper; import android.os.AsyncTask; import android.os.Handler; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.UserHandle; import android.util.Log; import androidx.annotation.NonNull; import com.android.internal.util.IndentingPrintWriter; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.CoreStartable; import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.user.data.model.SelectedUserModel; import com.android.systemui.user.data.model.SelectionStatus; import com.android.systemui.user.data.repository.UserRepository; import com.android.systemui.util.kotlin.JavaAdapter; import libcore.io.IoUtils; import java.io.PrintWriter; import java.util.Objects; import javax.inject.Inject; /** * Manages the lockscreen wallpaper. */ @SysUISingleton public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implements Runnable, Dumpable, CoreStartable { private static final String TAG = "LockscreenWallpaper"; // TODO(b/253507223): temporary; remove this private static final String DISABLED_ERROR_MESSAGE = "Methods from LockscreenWallpaper.java " + "should not be called in this version. The lock screen wallpaper should be " + "managed by the WallpaperManagerService and not by this class."; private final NotificationMediaManager mMediaManager; private final WallpaperManager mWallpaperManager; private final KeyguardUpdateMonitor mUpdateMonitor; private final Handler mH; private final JavaAdapter mJavaAdapter; private final UserRepository mUserRepository; private boolean mCached; private Bitmap mCache; private int mCurrentUserId; // The user selected in the UI, or null if no user is selected or UI doesn't support selecting // users. private UserHandle mSelectedUser; private AsyncTask mLoader; @Inject public LockscreenWallpaper(WallpaperManager wallpaperManager, @Nullable IWallpaperManager iWallpaperManager, KeyguardUpdateMonitor keyguardUpdateMonitor, DumpManager dumpManager, NotificationMediaManager mediaManager, @Main Handler mainHandler, JavaAdapter javaAdapter, UserRepository userRepository, UserTracker userTracker) { dumpManager.registerDumpable(getClass().getSimpleName(), this); mWallpaperManager = wallpaperManager; mCurrentUserId = userTracker.getUserId(); mUpdateMonitor = keyguardUpdateMonitor; mMediaManager = mediaManager; mH = mainHandler; mJavaAdapter = javaAdapter; mUserRepository = userRepository; if (iWallpaperManager != null && !mWallpaperManager.isLockscreenLiveWallpaperEnabled()) { // Service is disabled on some devices like Automotive try { iWallpaperManager.setLockWallpaperCallback(this); } catch (RemoteException e) { Log.e(TAG, "System dead?" + e); } } } @Override public void start() { if (!isLockscreenLiveWallpaperEnabled()) { mJavaAdapter.alwaysCollectFlow( mUserRepository.getSelectedUser(), this::setSelectedUser); } } public Bitmap getBitmap() { assertLockscreenLiveWallpaperNotEnabled(); if (mCached) { return mCache; } if (!mWallpaperManager.isWallpaperSupported()) { mCached = true; mCache = null; return null; } LoaderResult result = loadBitmap(mCurrentUserId, mSelectedUser); if (result.success) { mCached = true; mCache = result.bitmap; } return mCache; } public LoaderResult loadBitmap(int currentUserId, UserHandle selectedUser) { // May be called on any thread - only use thread safe operations. assertLockscreenLiveWallpaperNotEnabled(); if (!mWallpaperManager.isWallpaperSupported()) { // When wallpaper is not supported, show the system wallpaper return LoaderResult.success(null); } // Prefer the selected user (when specified) over the current user for the FLAG_SET_LOCK // wallpaper. final int lockWallpaperUserId = selectedUser != null ? selectedUser.getIdentifier() : currentUserId; ParcelFileDescriptor fd = mWallpaperManager.getWallpaperFile( WallpaperManager.FLAG_LOCK, lockWallpaperUserId); if (fd != null) { try { BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Bitmap.Config.HARDWARE; return LoaderResult.success(BitmapFactory.decodeFileDescriptor( fd.getFileDescriptor(), null, options)); } catch (OutOfMemoryError e) { Log.w(TAG, "Can't decode file", e); return LoaderResult.fail(); } finally { IoUtils.closeQuietly(fd); } } else { if (selectedUser != null) { // Show the selected user's static wallpaper. return LoaderResult.success(mWallpaperManager.getBitmapAsUser( selectedUser.getIdentifier(), true /* hardware */)); } else { // When there is no selected user, show the system wallpaper return LoaderResult.success(null); } } } private void setSelectedUser(SelectedUserModel selectedUserModel) { assertLockscreenLiveWallpaperNotEnabled(); if (selectedUserModel.getSelectionStatus().equals(SelectionStatus.SELECTION_IN_PROGRESS)) { // Wait until the selection has finished before updating. return; } int user = selectedUserModel.getUserInfo().id; if (user != mCurrentUserId) { if (mSelectedUser == null || user != mSelectedUser.getIdentifier()) { mCached = false; } mCurrentUserId = user; } } public void setSelectedUser(UserHandle selectedUser) { assertLockscreenLiveWallpaperNotEnabled(); if (Objects.equals(selectedUser, mSelectedUser)) { return; } mSelectedUser = selectedUser; postUpdateWallpaper(); } @Override public void onWallpaperChanged() { assertLockscreenLiveWallpaperNotEnabled(); // Called on Binder thread. postUpdateWallpaper(); } @Override public void onWallpaperColorsChanged(WallpaperColors colors, int which, int userId) { assertLockscreenLiveWallpaperNotEnabled(); } private void postUpdateWallpaper() { assertLockscreenLiveWallpaperNotEnabled(); if (mH == null) { Log.wtfStack(TAG, "Trying to use LockscreenWallpaper before initialization."); return; } mH.removeCallbacks(this); mH.post(this); } @Override public void run() { // Called in response to onWallpaperChanged on the main thread. assertLockscreenLiveWallpaperNotEnabled(); if (mLoader != null) { mLoader.cancel(false /* interrupt */); } final int currentUser = mCurrentUserId; final UserHandle selectedUser = mSelectedUser; mLoader = new AsyncTask() { @Override protected LoaderResult doInBackground(Void... params) { return loadBitmap(currentUser, selectedUser); } @Override protected void onPostExecute(LoaderResult result) { super.onPostExecute(result); if (isCancelled()) { return; } if (result.success) { mCached = true; mCache = result.bitmap; mMediaManager.updateMediaMetaData( true /* metaDataChanged */, true /* allowEnterAnimation */); } mLoader = null; } }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } // TODO(b/273443374): remove public boolean isLockscreenLiveWallpaperEnabled() { return mWallpaperManager.isLockscreenLiveWallpaperEnabled(); } @Override public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { pw.println(getClass().getSimpleName() + ":"); IndentingPrintWriter iPw = new IndentingPrintWriter(pw, " ").increaseIndent(); iPw.println("mCached=" + mCached); iPw.println("mCache=" + mCache); iPw.println("mCurrentUserId=" + mCurrentUserId); iPw.println("mSelectedUser=" + mSelectedUser); } private static class LoaderResult { public final boolean success; public final Bitmap bitmap; LoaderResult(boolean success, Bitmap bitmap) { this.success = success; this.bitmap = bitmap; } static LoaderResult success(Bitmap b) { return new LoaderResult(true, b); } static LoaderResult fail() { return new LoaderResult(false, null); } } /** * Drawable that aligns left horizontally and center vertically (like ImageWallpaper). * *

Aligns to the center when showing on the smaller internal display of a multi display * device. */ public static class WallpaperDrawable extends DrawableWrapper { private final ConstantState mState; private final Rect mTmpRect = new Rect(); private boolean mIsOnSmallerInternalDisplays; public WallpaperDrawable(Resources r, Bitmap b, boolean isOnSmallerInternalDisplays) { this(r, new ConstantState(b), isOnSmallerInternalDisplays); } private WallpaperDrawable(Resources r, ConstantState state, boolean isOnSmallerInternalDisplays) { super(new BitmapDrawable(r, state.mBackground)); mState = state; mIsOnSmallerInternalDisplays = isOnSmallerInternalDisplays; } @Override public void setXfermode(@Nullable Xfermode mode) { // DrawableWrapper does not call this for us. getDrawable().setXfermode(mode); } @Override public int getIntrinsicWidth() { return -1; } @Override public int getIntrinsicHeight() { return -1; } @Override protected void onBoundsChange(Rect bounds) { int vwidth = getBounds().width(); int vheight = getBounds().height(); int dwidth = mState.mBackground.getWidth(); int dheight = mState.mBackground.getHeight(); float scale; float dx = 0, dy = 0; if (dwidth * vheight > vwidth * dheight) { scale = (float) vheight / (float) dheight; } else { scale = (float) vwidth / (float) dwidth; } if (scale <= 1f) { scale = 1f; } dy = (vheight - dheight * scale) * 0.5f; int offsetX = 0; // Offset to show the center area of the wallpaper on a smaller display for multi // display device if (mIsOnSmallerInternalDisplays) { offsetX = bounds.centerX() - (Math.round(dwidth * scale) / 2); } mTmpRect.set( bounds.left + offsetX, bounds.top + Math.round(dy), bounds.left + Math.round(dwidth * scale) + offsetX, bounds.top + Math.round(dheight * scale + dy)); super.onBoundsChange(mTmpRect); } @Override public ConstantState getConstantState() { return mState; } /** * Update bounds when the hosting display or the display size has changed. * * @param isOnSmallerInternalDisplays true if the drawable is on one of the internal * displays with the smaller area. */ public void onDisplayUpdated(boolean isOnSmallerInternalDisplays) { mIsOnSmallerInternalDisplays = isOnSmallerInternalDisplays; onBoundsChange(getBounds()); } static class ConstantState extends Drawable.ConstantState { private final Bitmap mBackground; ConstantState(Bitmap background) { mBackground = background; } @Override public Drawable newDrawable() { return newDrawable(null); } @Override public Drawable newDrawable(@Nullable Resources res) { return new WallpaperDrawable(res, this, /* isOnSmallerInternalDisplays= */ false); } @Override public int getChangingConfigurations() { // DrawableWrapper already handles this for us. return 0; } } } /** * Feature b/253507223 will adapt the logic to always use the * WallpaperManagerService to render the lock screen wallpaper. * Methods of this class should not be called at all if the project flag is enabled. * TODO(b/253507223) temporary assertion; remove this */ private void assertLockscreenLiveWallpaperNotEnabled() { if (mWallpaperManager.isLockscreenLiveWallpaperEnabled()) { throw new IllegalStateException(DISABLED_ERROR_MESSAGE); } } }