diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 313fc75ed668e..b33f5db90b74f 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -296,6 +296,7 @@ package android { field public static final String SET_SYSTEM_AUDIO_CAPTION = "android.permission.SET_SYSTEM_AUDIO_CAPTION"; field public static final String SET_VOLUME_KEY_LONG_PRESS_LISTENER = "android.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER"; field public static final String SET_WALLPAPER_COMPONENT = "android.permission.SET_WALLPAPER_COMPONENT"; + field public static final String SET_WALLPAPER_DIM_AMOUNT = "android.permission.SET_WALLPAPER_DIM_AMOUNT"; field public static final String SHOW_KEYGUARD_MESSAGE = "android.permission.SHOW_KEYGUARD_MESSAGE"; field public static final String SHUTDOWN = "android.permission.SHUTDOWN"; field public static final String SIGNAL_REBOOT_READINESS = "android.permission.SIGNAL_REBOOT_READINESS"; @@ -985,8 +986,10 @@ package android.app { public class WallpaperManager { method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public void clearWallpaper(int, int); + method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT) public float getWallpaperDimAmount(); method public void setDisplayOffset(android.os.IBinder, int, int); method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT) public boolean setWallpaperComponent(android.content.ComponentName); + method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT) public void setWallpaperDimAmount(@FloatRange(from=0.0f, to=1.0f) float); } } diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt index 716f43e1d68ad..9a06423563039 100644 --- a/core/api/system-lint-baseline.txt +++ b/core/api/system-lint-baseline.txt @@ -103,6 +103,16 @@ ProtectedMember: android.service.notification.NotificationAssistantService#attac +RethrowRemoteException: android.app.WallpaperManager#getWallpaperDimAmount(): + Methods calling system APIs should rethrow `RemoteException` as `RuntimeException` (but do not list it in the throws clause) +RethrowRemoteException: android.app.WallpaperManager#getWallpaperDimmingAmount(): + Methods calling system APIs should rethrow `RemoteException` as `RuntimeException` (but do not list it in the throws clause) +RethrowRemoteException: android.app.WallpaperManager#setWallpaperDimAmount(float): + Methods calling system APIs should rethrow `RemoteException` as `RuntimeException` (but do not list it in the throws clause) +RethrowRemoteException: android.app.WallpaperManager#setWallpaperDimmingAmount(float): + Methods calling system APIs should rethrow `RemoteException` as `RuntimeException` (but do not list it in the throws clause) + + SamShouldBeLast: android.accounts.AccountManager#addAccount(String, String, String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback, android.os.Handler): SamShouldBeLast: android.accounts.AccountManager#addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, android.os.Handler, boolean): diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl index 4f7c6841d6bb8..28c273ec50a6e 100644 --- a/core/java/android/app/IWallpaperManager.aidl +++ b/core/java/android/app/IWallpaperManager.aidl @@ -204,4 +204,27 @@ interface IWallpaperManager { * @hide */ void notifyGoingToSleep(int x, int y, in Bundle extras); + + /** + * Sets the wallpaper dim amount between [0f, 1f] which would be blended with the system default + * dimming. 0f doesn't add any additional dimming and 1f makes the wallpaper fully black. + * + * @hide + */ + oneway void setWallpaperDimAmount(float dimAmount); + + /** + * Gets the current additional dim amount set on the wallpaper. 0f means no application has + * added any dimming on top of the system default dim amount. + * + * @hide + */ + float getWallpaperDimAmount(); + + /** + * Whether the lock screen wallpaper is different from the system wallpaper. + * + * @hide + */ + boolean lockScreenWallpaperExists(); } diff --git a/core/java/android/app/WallpaperColors.java b/core/java/android/app/WallpaperColors.java index 7ef0a19ec44c3..067a4c3c047ea 100644 --- a/core/java/android/app/WallpaperColors.java +++ b/core/java/android/app/WallpaperColors.java @@ -16,6 +16,7 @@ package android.app; +import android.annotation.FloatRange; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -27,6 +28,7 @@ import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; +import android.util.MathUtils; import android.util.Size; import com.android.internal.graphics.ColorUtils; @@ -44,6 +46,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; /** @@ -173,6 +176,22 @@ public final class WallpaperColors implements Parcelable { if (bitmap == null) { throw new IllegalArgumentException("Bitmap can't be null"); } + return fromBitmap(bitmap, 0f /* dimAmount */); + } + + /** + * Constructs {@link WallpaperColors} from a bitmap with dimming applied. + *

+ * Main colors will be extracted from the bitmap with dimming taken into account when + * calculating dark hints. + * + * @param bitmap Source where to extract from. + * @param dimAmount Wallpaper dim amount + * @hide + */ + public static WallpaperColors fromBitmap(@NonNull Bitmap bitmap, + @FloatRange (from = 0f, to = 1f) float dimAmount) { + Objects.requireNonNull(bitmap, "Bitmap can't be null"); final int bitmapArea = bitmap.getWidth() * bitmap.getHeight(); boolean shouldRecycle = false; @@ -211,7 +230,7 @@ public final class WallpaperColors implements Parcelable { } - int hints = calculateDarkHints(bitmap); + int hints = calculateDarkHints(bitmap, dimAmount); if (shouldRecycle) { bitmap.recycle(); @@ -507,13 +526,15 @@ public final class WallpaperColors implements Parcelable { * Checks if image is bright and clean enough to support light text. * * @param source What to read. + * @param dimAmount How much wallpaper dim amount was applied. * @return Whether image supports dark text or not. */ - private static int calculateDarkHints(Bitmap source) { + private static int calculateDarkHints(Bitmap source, float dimAmount) { if (source == null) { return 0; } + dimAmount = MathUtils.saturate(dimAmount); int[] pixels = new int[source.getWidth() * source.getHeight()]; double totalLuminance = 0; final int maxDarkPixels = (int) (pixels.length * MAX_DARK_AREA); @@ -521,24 +542,37 @@ public final class WallpaperColors implements Parcelable { source.getPixels(pixels, 0 /* offset */, source.getWidth(), 0 /* x */, 0 /* y */, source.getWidth(), source.getHeight()); + // Create a new black layer with dimAmount as the alpha to be accounted for when computing + // the luminance. + int dimmingLayerAlpha = (int) (255 * dimAmount); + int blackTransparent = ColorUtils.setAlphaComponent(Color.BLACK, dimmingLayerAlpha); + // This bitmap was already resized to fit the maximum allowed area. // Let's just loop through the pixels, no sweat! float[] tmpHsl = new float[3]; for (int i = 0; i < pixels.length; i++) { - ColorUtils.colorToHSL(pixels[i], tmpHsl); - final float luminance = tmpHsl[2]; - final int alpha = Color.alpha(pixels[i]); + int pixelColor = pixels[i]; + ColorUtils.colorToHSL(pixelColor, tmpHsl); + final int alpha = Color.alpha(pixelColor); + + // Apply composite colors where the foreground is a black layer with an alpha value of + // the dim amount and the background is the wallpaper pixel color. + int compositeColors = ColorUtils.compositeColors(blackTransparent, pixelColor); + + // Calculate the adjusted luminance of the dimmed wallpaper pixel color. + double adjustedLuminance = ColorUtils.calculateLuminance(compositeColors); + // Make sure we don't have a dark pixel mass that will // make text illegible. final boolean satisfiesTextContrast = ContrastColorUtil - .calculateContrast(pixels[i], Color.BLACK) > DARK_PIXEL_CONTRAST; + .calculateContrast(pixelColor, Color.BLACK) > DARK_PIXEL_CONTRAST; if (!satisfiesTextContrast && alpha != 0) { darkPixels++; if (DEBUG_DARK_PIXELS) { pixels[i] = Color.RED; } } - totalLuminance += luminance; + totalLuminance += adjustedLuminance; } int hints = 0; diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index 772492dbfd5ef..3d1b0e5bf8653 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -16,6 +16,7 @@ package android.app; +import android.annotation.FloatRange; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -71,6 +72,7 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; +import android.util.MathUtils; import android.util.Pair; import android.view.Display; import android.view.WindowManagerGlobal; @@ -2011,6 +2013,63 @@ public class WallpaperManager { return setWallpaperComponent(name, mContext.getUserId()); } + /** + * Sets the wallpaper dim amount between [0f, 1f] which would be blended with the system default + * dimming. 0f doesn't add any additional dimming and 1f makes the wallpaper fully black. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT) + public void setWallpaperDimAmount(@FloatRange (from = 0f, to = 1f) float dimAmount) { + if (sGlobals.mService == null) { + Log.w(TAG, "WallpaperService not running"); + throw new RuntimeException(new DeadSystemException()); + } + try { + sGlobals.mService.setWallpaperDimAmount(MathUtils.saturate(dimAmount)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Gets the current additional dim amount set on the wallpaper. 0f means no application has + * added any dimming on top of the system default dim amount. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT) + public float getWallpaperDimAmount() { + if (sGlobals.mService == null) { + Log.w(TAG, "WallpaperService not running"); + throw new RuntimeException(new DeadSystemException()); + } + try { + return sGlobals.mService.getWallpaperDimAmount(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Whether the lock screen wallpaper is different from the system wallpaper. + * + * @hide + */ + public boolean lockScreenWallpaperExists() { + if (sGlobals.mService == null) { + Log.w(TAG, "WallpaperService not running"); + throw new RuntimeException(new DeadSystemException()); + } + try { + return sGlobals.mService.lockScreenWallpaperExists(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Set the live wallpaper. * diff --git a/core/java/android/service/wallpaper/IWallpaperEngine.aidl b/core/java/android/service/wallpaper/IWallpaperEngine.aidl index 81af6a2204441..93d4def2180ef 100644 --- a/core/java/android/service/wallpaper/IWallpaperEngine.aidl +++ b/core/java/android/service/wallpaper/IWallpaperEngine.aidl @@ -46,4 +46,5 @@ interface IWallpaperEngine { oneway void removeLocalColorsAreas(in List regions); oneway void addLocalColorsAreas(in List regions); SurfaceControl mirrorSurfaceControl(); + oneway void applyDimming(float dimAmount); } diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 73ffd66486d25..bff75a62b590f 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -26,6 +26,7 @@ import static android.view.SurfaceControl.METADATA_WINDOW_TYPE; import static android.view.View.SYSTEM_UI_FLAG_VISIBLE; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; +import android.animation.ValueAnimator; import android.annotation.FloatRange; import android.annotation.NonNull; import android.annotation.Nullable; @@ -159,6 +160,7 @@ public abstract class WallpaperService extends Service { private static final int MSG_ZOOM = 10100; private static final int MSG_SCALE_PREVIEW = 10110; private static final int MSG_REPORT_SHOWN = 10150; + private static final int MSG_UPDATE_DIMMING = 10200; private static final List PROHIBITED_STEPS = Arrays.asList(0f, Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY); @@ -167,6 +169,8 @@ public abstract class WallpaperService extends Service { private static final boolean ENABLE_WALLPAPER_DIMMING = SystemProperties.getBoolean("persist.debug.enable_wallpaper_dimming", true); + private static final long DIMMING_ANIMATION_DURATION_MS = 300L; + private final ArrayList mActiveEngines = new ArrayList(); @@ -221,6 +225,9 @@ public abstract class WallpaperService extends Service { boolean mOffsetsChanged; boolean mFixedSizeAllowed; boolean mShouldDim; + // Whether the wallpaper should be dimmed by default (when no additional dimming is applied) + // based on its color hints + boolean mShouldDimByDefault; int mWidth; int mHeight; int mFormat; @@ -272,6 +279,8 @@ public abstract class WallpaperService extends Service { private Context mDisplayContext; private int mDisplayState; private float mWallpaperDimAmount = 0.05f; + private float mPreviousWallpaperDimAmount = mWallpaperDimAmount; + private float mDefaultDimAmount = mWallpaperDimAmount; SurfaceControl mSurfaceControl = new SurfaceControl(); SurfaceControl mBbqSurfaceControl; @@ -861,15 +870,34 @@ public abstract class WallpaperService extends Service { return; } int colorHints = colors.getColorHints(); - boolean shouldDim = ((colorHints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) == 0 + mShouldDimByDefault = ((colorHints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) == 0 && (colorHints & WallpaperColors.HINT_SUPPORTS_DARK_THEME) == 0); - if (shouldDim != mShouldDim) { - mShouldDim = shouldDim; + + // If default dimming value changes and no additional dimming is applied + if (mShouldDimByDefault != mShouldDim && mWallpaperDimAmount == 0f) { + mShouldDim = mShouldDimByDefault; updateSurfaceDimming(); updateSurface(false, false, true); } } + /** + * Update the dim amount of the wallpaper by updating the surface. + * + * @param dimAmount Float amount between [0.0, 1.0] to dim the wallpaper. + */ + private void updateWallpaperDimming(float dimAmount) { + mPreviousWallpaperDimAmount = mWallpaperDimAmount; + + // Custom dim amount cannot be less than the default dim amount. + mWallpaperDimAmount = Math.max(mDefaultDimAmount, dimAmount); + // If dim amount is 0f (additional dimming is removed), then the wallpaper should dim + // based on its default wallpaper color hints. + mShouldDim = dimAmount != 0f || mShouldDimByDefault; + updateSurfaceDimming(); + updateSurface(false, false, true); + } + private void updateSurfaceDimming() { if (!ENABLE_WALLPAPER_DIMMING || mBbqSurfaceControl == null) { return; @@ -878,9 +906,21 @@ public abstract class WallpaperService extends Service { // preview mode. if (!isPreview() && mShouldDim) { Log.v(TAG, "Setting wallpaper dimming: " + mWallpaperDimAmount); - new SurfaceControl.Transaction() - .setAlpha(mBbqSurfaceControl, 1 - mWallpaperDimAmount) - .apply(); + SurfaceControl.Transaction surfaceControl = new SurfaceControl.Transaction(); + + // Animate dimming to gradually change the wallpaper alpha from the previous + // dim amount to the new amount only if the dim amount changed. + ValueAnimator animator = ValueAnimator.ofFloat( + mPreviousWallpaperDimAmount, mWallpaperDimAmount); + animator.setDuration(mPreviousWallpaperDimAmount == mWallpaperDimAmount + ? 0 : DIMMING_ANIMATION_DURATION_MS); + animator.addUpdateListener((ValueAnimator va) -> { + final float dimValue = (float) va.getAnimatedValue(); + surfaceControl + .setAlpha(mBbqSurfaceControl, 1 - dimValue) + .apply(); + }); + animator.start(); } else { Log.v(TAG, "Setting wallpaper dimming: " + 0); new SurfaceControl.Transaction() @@ -1332,8 +1372,10 @@ public abstract class WallpaperService extends Service { // Use window context of TYPE_WALLPAPER so client can access UI resources correctly. mDisplayContext = createDisplayContext(mDisplay) .createWindowContext(TYPE_WALLPAPER, null /* options */); - mWallpaperDimAmount = mDisplayContext.getResources().getFloat( + mDefaultDimAmount = mDisplayContext.getResources().getFloat( com.android.internal.R.dimen.config_wallpaperDimAmount); + mWallpaperDimAmount = mDefaultDimAmount; + mPreviousWallpaperDimAmount = mWallpaperDimAmount; mDisplayState = mDisplay.getState(); if (DEBUG) Log.v(TAG, "onCreate(): " + this); @@ -1647,7 +1689,7 @@ public abstract class WallpaperService extends Service { Log.e(TAG, "Error creating page local color bitmap", e); continue; } - WallpaperColors color = WallpaperColors.fromBitmap(target); + WallpaperColors color = WallpaperColors.fromBitmap(target, mWallpaperDimAmount); target.recycle(); WallpaperColors currentColor = page.getColors(area); @@ -2175,6 +2217,12 @@ public abstract class WallpaperService extends Service { mDetached.set(true); } + public void applyDimming(float dimAmount) throws RemoteException { + Message msg = mCaller.obtainMessageI(MSG_UPDATE_DIMMING, + Float.floatToIntBits(dimAmount)); + mCaller.sendMessage(msg); + } + public void scalePreview(Rect position) { Message msg = mCaller.obtainMessageO(MSG_SCALE_PREVIEW, position); mCaller.sendMessage(msg); @@ -2245,6 +2293,9 @@ public abstract class WallpaperService extends Service { case MSG_ZOOM: mEngine.setZoom(Float.intBitsToFloat(message.arg1)); break; + case MSG_UPDATE_DIMMING: + mEngine.updateWallpaperDimming(Float.intBitsToFloat(message.arg1)); + break; case MSG_SCALE_PREVIEW: mEngine.scalePreview((Rect) message.obj); break; diff --git a/core/java/com/android/internal/graphics/ColorUtils.java b/core/java/com/android/internal/graphics/ColorUtils.java index 537e797c9bac7..dff9551c0c078 100644 --- a/core/java/com/android/internal/graphics/ColorUtils.java +++ b/core/java/com/android/internal/graphics/ColorUtils.java @@ -62,7 +62,10 @@ public final class ColorUtils { return Color.argb(a, r, g, b); } - private static int compositeAlpha(int foregroundAlpha, int backgroundAlpha) { + /** + * Returns the composite alpha of the given foreground and background alpha. + */ + public static int compositeAlpha(int foregroundAlpha, int backgroundAlpha) { return 0xFF - (((0xFF - backgroundAlpha) * (0xFF - foregroundAlpha)) / 0xFF); } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index ae2052b66f9cd..9cc1fd6127e91 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -5122,6 +5122,11 @@ + + + { + float dimAmount = 0f; + if (mWallpaperManager.lockScreenWallpaperExists()) { + dimAmount = mWallpaperManager.getWallpaperDimAmount(); + } + final float scrimDimAmount = dimAmount; + mMainExecutor.execute(() -> { + mScrimController.setAdditionalScrimBehindAlphaKeyguard(scrimDimAmount); + mScrimController.applyCompositeAlphaOnScrimBehindKeyguard(); + }); + }); + // Lock wallpaper defines the color of the majority of the views, hence we'll use it // to set our default theme. final boolean lockDarkText = mColorExtractor.getNeutralColors().supportsDarkText(); final int themeResId = lockDarkText ? R.style.Theme_SystemUI_LightWallpaper : R.style.Theme_SystemUI; - if (mContext.getThemeResId() == themeResId) { - return; + if (mContext.getThemeResId() != themeResId) { + mContext.setTheme(themeResId); + mConfigurationController.notifyThemeChanged(); } - mContext.setTheme(themeResId); - mConfigurationController.notifyThemeChanged(); } private void updateDozingState() { diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 2ec42b41fc9fb..ee2cc7bd74868 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -81,7 +81,9 @@ import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteCallbackList; import android.os.RemoteException; +import android.os.ResultReceiver; import android.os.SELinux; +import android.os.ShellCallback; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; @@ -92,6 +94,7 @@ import android.service.wallpaper.IWallpaperService; import android.service.wallpaper.WallpaperService; import android.system.ErrnoException; import android.system.Os; +import android.util.ArrayMap; import android.util.EventLog; import android.util.Slog; import android.util.SparseArray; @@ -420,7 +423,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub Slog.v(TAG, "notifyWallpaperColorsChangedOnDisplay " + which); } - needsExtraction = wallpaper.primaryColors == null; + needsExtraction = wallpaper.primaryColors == null || wallpaper.mIsColorExtractedFromDim; } if (needsExtraction) { @@ -491,12 +494,17 @@ public class WallpaperManagerService extends IWallpaperManager.Stub String cropFile = null; boolean defaultImageWallpaper = false; int wallpaperId; + float dimAmount; + + synchronized (mLock) { + wallpaper.mIsColorExtractedFromDim = false; + } if (wallpaper.equals(mFallbackWallpaper)) { synchronized (mLock) { if (mFallbackWallpaper.primaryColors != null) return; } - final WallpaperColors colors = extractDefaultImageWallpaperColors(); + final WallpaperColors colors = extractDefaultImageWallpaperColors(wallpaper); synchronized (mLock) { mFallbackWallpaper.primaryColors = colors; } @@ -513,18 +521,19 @@ public class WallpaperManagerService extends IWallpaperManager.Stub defaultImageWallpaper = true; } wallpaperId = wallpaper.wallpaperId; + dimAmount = wallpaper.mWallpaperDimAmount; } WallpaperColors colors = null; if (cropFile != null) { Bitmap bitmap = BitmapFactory.decodeFile(cropFile); if (bitmap != null) { - colors = WallpaperColors.fromBitmap(bitmap); + colors = WallpaperColors.fromBitmap(bitmap, dimAmount); bitmap.recycle(); } } else if (defaultImageWallpaper) { // There is no crop and source file because this is default image wallpaper. - colors = extractDefaultImageWallpaperColors(); + colors = extractDefaultImageWallpaperColors(wallpaper); } if (colors == null) { @@ -544,11 +553,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } - private WallpaperColors extractDefaultImageWallpaperColors() { + private WallpaperColors extractDefaultImageWallpaperColors(WallpaperData wallpaper) { if (DEBUG) Slog.d(TAG, "Extract default image wallpaper colors"); + float dimAmount; synchronized (mLock) { if (mCacheDefaultImageWallpaperColors != null) return mCacheDefaultImageWallpaperColors; + dimAmount = wallpaper.mWallpaperDimAmount; } WallpaperColors colors = null; @@ -561,7 +572,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub final BitmapFactory.Options options = new BitmapFactory.Options(); final Bitmap bitmap = BitmapFactory.decodeStream(is, null, options); if (bitmap != null) { - colors = WallpaperColors.fromBitmap(bitmap); + colors = WallpaperColors.fromBitmap(bitmap, dimAmount); bitmap.recycle(); } } catch (OutOfMemoryError e) { @@ -947,6 +958,23 @@ public class WallpaperManagerService extends IWallpaperManager.Stub boolean wallpaperUpdating; WallpaperObserver wallpaperObserver; + /** + * The dim amount to be applied to the wallpaper. + */ + float mWallpaperDimAmount = 0.0f; + + /** + * A map to keep track of the dimming set by different applications. The key is the calling + * UID and the value is the dim amount. + */ + ArrayMap mUidToDimAmount = new ArrayMap<>(); + + /** + * Whether we need to extract the wallpaper colors again to calculate the dark hints + * after dimming is applied. + */ + boolean mIsColorExtractedFromDim; + /** * List of callbacks registered they should each be notified when the wallpaper is changed. */ @@ -1487,6 +1515,15 @@ public class WallpaperManagerService extends IWallpaperManager.Stub Slog.w(TAG, "Failed to register local colors areas", e); } } + + if (mWallpaper.mWallpaperDimAmount != 0f) { + try { + connector.mEngine.applyDimming(mWallpaper.mWallpaperDimAmount); + notifyWallpaperColorsChanged(mWallpaper, FLAG_SYSTEM); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to dim wallpaper", e); + } + } } } @@ -2536,6 +2573,98 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (purgeAreas.size() > 0) engine.removeLocalColorsAreas(purgeAreas); } + /** + * Returns true if the lock screen wallpaper exists (different wallpaper from the system) + */ + @Override + public boolean lockScreenWallpaperExists() { + synchronized (mLock) { + return mLockWallpaperMap.get(mCurrentUserId) != null; + } + } + + /** + * Sets wallpaper dim amount for the calling UID. This only applies to FLAG_SYSTEM wallpaper as + * the lock screen does not have a wallpaper component, so we use mWallpaperMap. + * + * @param dimAmount Dim amount which would be blended with the system default dimming. + */ + @Override + public void setWallpaperDimAmount(float dimAmount) throws RemoteException { + checkPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT); + int uid = Binder.getCallingUid(); + final long ident = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + WallpaperData wallpaper = mWallpaperMap.get(mCurrentUserId); + WallpaperData lockWallpaper = mLockWallpaperMap.get(mCurrentUserId); + + if (dimAmount == 0.0f) { + wallpaper.mUidToDimAmount.remove(uid); + } else { + wallpaper.mUidToDimAmount.put(uid, dimAmount); + } + + float maxDimAmount = getHighestDimAmountFromMap(wallpaper.mUidToDimAmount); + wallpaper.mWallpaperDimAmount = maxDimAmount; + // Also set the dim amount to the lock screen wallpaper if the lock and home screen + // do not share the same wallpaper + if (lockWallpaper != null) { + lockWallpaper.mWallpaperDimAmount = maxDimAmount; + } + + if (wallpaper.connection != null) { + wallpaper.connection.forEachDisplayConnector(connector -> { + if (connector.mEngine != null) { + try { + connector.mEngine.applyDimming(maxDimAmount); + } catch (RemoteException e) { + Slog.w(TAG, + "Can't apply dimming on wallpaper display connector", e); + } + } + }); + // Need to extract colors again to re-calculate dark hints after + // applying dimming. + wallpaper.mIsColorExtractedFromDim = true; + notifyWallpaperColorsChanged(wallpaper, FLAG_SYSTEM); + if (lockWallpaper != null) { + lockWallpaper.mIsColorExtractedFromDim = true; + notifyWallpaperColorsChanged(lockWallpaper, FLAG_LOCK); + } + saveSettingsLocked(wallpaper.userId); + } + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override + public float getWallpaperDimAmount() { + checkPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT); + synchronized (mLock) { + WallpaperData data = mWallpaperMap.get(mCurrentUserId); + return data.mWallpaperDimAmount; + } + } + + /** + * Gets the highest dim amount among all the calling UIDs that set the wallpaper dim amount. + * Return 0f as default value to indicate no application has dimmed the wallpaper. + * + * @param uidToDimAmountMap Map of UIDs to dim amounts + */ + private float getHighestDimAmountFromMap(ArrayMap uidToDimAmountMap) { + float maxDimAmount = 0.0f; + for (Map.Entry entry : uidToDimAmountMap.entrySet()) { + if (entry.getValue() > maxDimAmount) { + maxDimAmount = entry.getValue(); + } + } + return maxDimAmount; + } + @Override public WallpaperColors getWallpaperColors(int which, int userId, int displayId) throws RemoteException { @@ -2562,7 +2691,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (wallpaperData == null) { return null; } - shouldExtract = wallpaperData.primaryColors == null; + shouldExtract = wallpaperData.primaryColors == null + || wallpaperData.mIsColorExtractedFromDim; } if (shouldExtract) { @@ -2664,6 +2794,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub lockWP.cropHint.set(sysWP.cropHint); lockWP.allowBackup = sysWP.allowBackup; lockWP.primaryColors = sysWP.primaryColors; + lockWP.mWallpaperDimAmount = sysWP.mWallpaperDimAmount; // Migrate the bitmap files outright; no need to copy try { @@ -3191,6 +3322,18 @@ public class WallpaperManagerService extends IWallpaperManager.Stub out.attributeInt(null, "paddingBottom", wpdData.mPadding.bottom); } + out.attributeFloat(null, "dimAmount", wallpaper.mWallpaperDimAmount); + int dimAmountsCount = wallpaper.mUidToDimAmount.size(); + out.attributeInt(null, "dimAmountsCount", dimAmountsCount); + if (dimAmountsCount > 0) { + int index = 0; + for (Map.Entry entry : wallpaper.mUidToDimAmount.entrySet()) { + out.attributeInt(null, "dimUID" + index, entry.getKey()); + out.attributeFloat(null, "dimValue" + index, entry.getValue()); + index++; + } + } + if (wallpaper.primaryColors != null) { int colorsCount = wallpaper.primaryColors.getMainColors().size(); out.attributeInt(null, "colorsCount", colorsCount); @@ -3267,6 +3410,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return parser.getAttributeInt(null, name, defValue); } + private float getAttributeFloat(TypedXmlPullParser parser, String name, float defValue) { + return parser.getAttributeFloat(null, name, defValue); + } + /** * Sometimes it is expected the wallpaper map may not have a user's data. E.g. This could * happen during user switch. The async user switch observer may not have received @@ -3471,6 +3618,17 @@ public class WallpaperManagerService extends IWallpaperManager.Stub wpData.mPadding.top = getAttributeInt(parser, "paddingTop", 0); wpData.mPadding.right = getAttributeInt(parser, "paddingRight", 0); wpData.mPadding.bottom = getAttributeInt(parser, "paddingBottom", 0); + wallpaper.mWallpaperDimAmount = getAttributeFloat(parser, "dimAmount", 0f); + int dimAmountsCount = getAttributeInt(parser, "dimAmountsCount", 0); + if (dimAmountsCount > 0) { + ArrayMap allDimAmounts = new ArrayMap<>(dimAmountsCount); + for (int i = 0; i < dimAmountsCount; i++) { + int uid = getAttributeInt(parser, "dimUID" + i, 0); + float dimValue = getAttributeFloat(parser, "dimValue" + i, 0f); + allDimAmounts.put(uid, dimValue); + } + wallpaper.mUidToDimAmount = allDimAmounts; + } int colorsCount = getAttributeInt(parser, "colorsCount", 0); int allColorsCount = getAttributeInt(parser, "allColorsCount", 0); if (allColorsCount > 0) { @@ -3637,6 +3795,14 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return false; } + @Override // Binder call + public void onShellCommand(FileDescriptor in, FileDescriptor out, + FileDescriptor err, String[] args, ShellCallback callback, + ResultReceiver resultReceiver) { + new WallpaperManagerShellCommand(WallpaperManagerService.this).exec(this, in, out, err, + args, callback, resultReceiver); + } + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; @@ -3664,6 +3830,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub pw.print(" mName="); pw.println(wallpaper.name); pw.print(" mAllowBackup="); pw.println(wallpaper.allowBackup); pw.print(" mWallpaperComponent="); pw.println(wallpaper.wallpaperComponent); + pw.print(" mWallpaperDimAmount="); pw.println(wallpaper.mWallpaperDimAmount); + pw.print(" isColorExtracted="); pw.println(wallpaper.mIsColorExtractedFromDim); + pw.println(" mUidToDimAmount:"); + for (Map.Entry entry : wallpaper.mUidToDimAmount.entrySet()) { + pw.print(" UID="); pw.print(entry.getKey()); + pw.print(" dimAmount="); pw.println(entry.getValue()); + } if (wallpaper.connection != null) { WallpaperConnection conn = wallpaper.connection; pw.print(" Wallpaper connection "); @@ -3695,6 +3868,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub pw.print(" mCropHint="); pw.println(wallpaper.cropHint); pw.print(" mName="); pw.println(wallpaper.name); pw.print(" mAllowBackup="); pw.println(wallpaper.allowBackup); + pw.print(" mWallpaperDimAmount="); pw.println(wallpaper.mWallpaperDimAmount); } pw.println("Fallback wallpaper state:"); pw.print(" User "); pw.print(mFallbackWallpaper.userId); diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerShellCommand.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerShellCommand.java new file mode 100644 index 0000000000000..fc827b40f3a13 --- /dev/null +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerShellCommand.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2021 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.server.wallpaper; + +import android.os.RemoteException; +import android.os.ShellCommand; +import android.util.Log; + +import java.io.PrintWriter; + +/** + * Shell Command class to run adb commands on the wallpaper service + */ +public class WallpaperManagerShellCommand extends ShellCommand { + private static final String TAG = "WallpaperManagerShellCommand"; + + private final WallpaperManagerService mService; + + public WallpaperManagerShellCommand(WallpaperManagerService service) { + mService = service; + } + + @Override + public int onCommand(String cmd) { + if (cmd == null) { + onHelp(); + return 1; + } + switch(cmd) { + case "set-dim-amount": + return setWallpaperDimAmount(); + case "get-dim-amount": + return getWallpaperDimAmount(); + case "-h": + case "help": + onHelp(); + return 0; + default: + return handleDefaultCommands(cmd); + } + } + + @Override + public void onHelp() { + final PrintWriter pw = getOutPrintWriter(); + pw.println("Wallpaper manager commands:"); + pw.println(" help"); + pw.println(" Print this help text."); + pw.println(); + pw.println(" set-dim-amount DIMMING"); + pw.println(" Sets the current dimming value to DIMMING (a number between 0 and 1)."); + pw.println(); + pw.println(" get-dim-amount"); + pw.println(" Get the current wallpaper dim amount."); + } + + /** + * Sets the wallpaper dim amount between [0f, 1f] which would be blended with the system default + * dimming. 0f doesn't add any additional dimming and 1f makes the wallpaper fully black. + */ + private int setWallpaperDimAmount() { + float dimAmount = Float.parseFloat(getNextArgRequired()); + try { + mService.setWallpaperDimAmount(dimAmount); + } catch (RemoteException e) { + Log.e(TAG, "Can't set wallpaper dim amount"); + } + getOutPrintWriter().println("Dimming the wallpaper to: " + dimAmount); + return 0; + } + + /** + * Gets the current additional dim amount set on the wallpaper. 0f means no application has + * added any dimming on top of the system default dim amount. + */ + private int getWallpaperDimAmount() { + float dimAmount = mService.getWallpaperDimAmount(); + getOutPrintWriter().println("The current wallpaper dim amount is: " + dimAmount); + return 0; + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java index 966675819069c..0b488b2add8e7 100644 --- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java @@ -153,6 +153,9 @@ public class WallpaperManagerServiceTests { sContext.getTestablePermissions().setPermission( android.Manifest.permission.SET_WALLPAPER, PackageManager.PERMISSION_GRANTED); + sContext.getTestablePermissions().setPermission( + android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT, + PackageManager.PERMISSION_GRANTED); doNothing().when(sContext).sendBroadcastAsUser(any(), any()); //Wallpaper components @@ -433,6 +436,15 @@ public class WallpaperManagerServiceTests { assertTrue(timestamps[1] > timestamps[0]); } + @Test + public void testSetWallpaperDimAmount() throws RemoteException { + mService.switchUser(USER_SYSTEM, null); + float dimAmount = 0.7f; + mService.setWallpaperDimAmount(dimAmount); + assertEquals("Getting dim amount should match after setting the dim amount", + mService.getWallpaperDimAmount(), dimAmount, 0.0); + } + // Verify that after continue switch user from userId 0 to lastUserId, the wallpaper data for // non-current user must not bind to wallpaper service. private void verifyNoConnectionBeforeLastUser(int lastUserId) {