Enable AOD image wallpaper and apply aod mask view.

1. Enables image wallpaper in AOD.
2. Enables a mask with 70% black scrim and vignette effects.
3. Add feature flag in developer options which is default disabled.

Bug: 111861907
Bug: 118470430
Test: Manually test the flow
Test: runtest systemui
Test: atest ImageWallpaperTransformerTest
Test: atest AodMaskViewTest

Change-Id: Iff2642d52264e88012f4759842a59aaf5bc45b38
This commit is contained in:
Ahan Wu
2018-11-07 20:39:32 +08:00
parent 266dd3bfd7
commit 723a80e4fd
14 changed files with 1010 additions and 12 deletions

View File

@@ -22,7 +22,9 @@ import android.provider.Settings;
import android.text.TextUtils;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* Util class to get feature flag information.
@@ -37,8 +39,11 @@ public class FeatureFlagUtils {
public static final String HEARING_AID_SETTINGS = "settings_bluetooth_hearing_aid";
public static final String SAFETY_HUB = "settings_safety_hub";
public static final String SCREENRECORD_LONG_PRESS = "settings_screenrecord_long_press";
public static final String AOD_IMAGEWALLPAPER_ENABLED = "settings_aod_imagewallpaper_enabled";
private static final Map<String, String> DEFAULT_FLAGS;
private static final Set<String> OBSERVABLE_FLAGS;
static {
DEFAULT_FLAGS = new HashMap<>();
DEFAULT_FLAGS.put("settings_audio_switcher", "true");
@@ -54,6 +59,10 @@ public class FeatureFlagUtils {
DEFAULT_FLAGS.put(HEARING_AID_SETTINGS, "false");
DEFAULT_FLAGS.put(SAFETY_HUB, "false");
DEFAULT_FLAGS.put(SCREENRECORD_LONG_PRESS, "false");
DEFAULT_FLAGS.put(AOD_IMAGEWALLPAPER_ENABLED, "false");
OBSERVABLE_FLAGS = new HashSet<>();
OBSERVABLE_FLAGS.add(AOD_IMAGEWALLPAPER_ENABLED);
}
/**
@@ -90,6 +99,16 @@ public class FeatureFlagUtils {
*/
public static void setEnabled(Context context, String feature, boolean enabled) {
SystemProperties.set(FFLAG_OVERRIDE_PREFIX + feature, enabled ? "true" : "false");
// Also update Settings.Global if needed so that we can observe it via observer.
if (OBSERVABLE_FLAGS.contains(feature)) {
setObservableFlag(context, feature, enabled);
}
}
private static void setObservableFlag(Context context, String feature, boolean enabled) {
Settings.Global.putString(
context.getContentResolver(), feature, enabled ? "true" : "false");
}
/**

View File

@@ -43,6 +43,14 @@
android:visibility="invisible" />
</com.android.systemui.statusbar.BackDropView>
<com.android.systemui.wallpaper.AodMaskView
android:id="@+id/aod_mask"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:importantForAccessibility="no"
android:visibility="invisible"
sysui:ignoreRightInset="true" />
<com.android.systemui.statusbar.ScrimView
android:id="@+id/scrim_behind"
android:layout_width="match_parent"

View File

@@ -109,5 +109,10 @@
<!-- Optional cancel button on Keyguard -->
<item type="id" name="cancel_button"/>
<!-- AodMaskView transition tag -->
<item type="id" name="aod_mask_transition_progress_tag" />
<item type="id" name="aod_mask_transition_progress_end_tag" />
<item type="id" name="aod_mask_transition_progress_start_tag" />
</resources>

View File

@@ -219,6 +219,14 @@ public enum ScrimState {
public void prepare(ScrimState previousState) {
}
/**
* Check if lockscreen wallpaper or music album art exists.
* @return true if lockscreen wallpaper or music album art exists.
*/
public boolean hasBackdrop() {
return mHasBackdrop;
}
public int getIndex() {
return mIndex;
}

View File

@@ -70,6 +70,7 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
@@ -98,6 +99,7 @@ import android.service.dreams.IDreamManager;
import android.service.notification.StatusBarNotification;
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.FeatureFlagUtils;
import android.util.Log;
import android.util.Slog;
import android.view.Display;
@@ -478,8 +480,13 @@ public class StatusBar extends SystemUI implements DemoMode,
WallpaperInfo info = wallpaperManager.getWallpaperInfo(UserHandle.USER_CURRENT);
final boolean deviceSupportsAodWallpaper = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_dozeSupportsAodWallpaper);
final boolean aodImageWallpaperEnabled = FeatureFlagUtils.isEnabled(mContext,
FeatureFlagUtils.AOD_IMAGEWALLPAPER_ENABLED);
updateAodMaskVisibility(deviceSupportsAodWallpaper && aodImageWallpaperEnabled);
// If WallpaperInfo is null, it must be ImageWallpaper.
final boolean supportsAmbientMode = deviceSupportsAodWallpaper
&& info != null && info.supportsAmbientMode();
&& (info == null && aodImageWallpaperEnabled
|| info != null && info.supportsAmbientMode());
mStatusBarWindowController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
mScrimController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
@@ -581,6 +588,7 @@ public class StatusBar extends SystemUI implements DemoMode,
protected NotificationPresenter mPresenter;
private NotificationActivityStarter mNotificationActivityStarter;
private boolean mPulsing;
private ContentObserver mFeatureFlagObserver;
@Override
public void onActiveStateChanged(int code, int uid, String packageName, boolean active) {
@@ -697,6 +705,9 @@ public class StatusBar extends SystemUI implements DemoMode,
mContext.registerReceiverAsUser(mWallpaperChangedReceiver, UserHandle.ALL,
wallpaperChangedFilter, null /* broadcastPermission */, null /* scheduler */);
mWallpaperChangedReceiver.onReceive(mContext, null);
mFeatureFlagObserver = new FeatureFlagObserver(
FeatureFlagUtils.AOD_IMAGEWALLPAPER_ENABLED /* feature */,
() -> mWallpaperChangedReceiver.onReceive(mContext, null) /* callback */);
// Set up the initial notification state. This needs to happen before CommandQueue.disable()
setUpPresenter();
@@ -4411,4 +4422,33 @@ public class StatusBar extends SystemUI implements DemoMode,
public @TransitionMode int getStatusBarMode() {
return mStatusBarMode;
}
private void updateAodMaskVisibility(boolean supportsAodWallpaper) {
View mask = mStatusBarWindow.findViewById(R.id.aod_mask);
if (mask != null) {
mask.setVisibility(supportsAodWallpaper ? View.VISIBLE : View.INVISIBLE);
}
}
private final class FeatureFlagObserver extends ContentObserver {
private final Runnable mCallback;
FeatureFlagObserver(String feature, Runnable callback) {
this(null, feature, callback);
}
private FeatureFlagObserver(Handler handler, String feature, Runnable callback) {
super(handler);
mCallback = callback;
mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(feature), false, this);
}
@Override
public void onChange(boolean selfChange) {
if (mCallback != null) {
mStatusBarWindow.post(mCallback);
}
}
}
}

View File

@@ -0,0 +1,222 @@
/*
* Copyright (C) 2018 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.wallpaper;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.app.WallpaperManager;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.RectF;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
import android.util.AttributeSet;
import android.util.FeatureFlagUtils;
import android.util.Log;
import android.view.Display;
import android.view.DisplayInfo;
import android.widget.ImageView;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.phone.ScrimState;
/**
* A view that draws mask upon either image wallpaper or music album art in AOD.
*/
public class AodMaskView extends ImageView implements StatusBarStateController.StateListener,
ImageWallpaperTransformer.TransformationListener {
private static final String TAG = AodMaskView.class.getSimpleName();
private static final int TRANSITION_DURATION = 1000;
private static final AnimatableProperty TRANSITION_PROGRESS = AnimatableProperty.from(
"transition_progress",
AodMaskView::setTransitionAmount,
AodMaskView::getTransitionAmount,
R.id.aod_mask_transition_progress_tag,
R.id.aod_mask_transition_progress_start_tag,
R.id.aod_mask_transition_progress_end_tag
);
private final AnimationProperties mTransitionProperties = new AnimationProperties();
private final ImageWallpaperTransformer mTransformer;
private final RectF mBounds = new RectF();
private boolean mChangingStates;
private boolean mNeedMask;
private float mTransitionAmount;
private final WallpaperManager mWallpaperManager;
private final DisplayManager mDisplayManager;
private DisplayListener mDisplayListener = new DisplayListener() {
@Override
public void onDisplayAdded(int displayId) {
}
@Override
public void onDisplayRemoved(int displayId) {
}
@Override
public void onDisplayChanged(int displayId) {
// We just support DEFAULT_DISPLAY currently.
if (displayId == Display.DEFAULT_DISPLAY) {
mTransformer.updateDisplayInfo(getDisplayInfo(displayId));
}
}
};
public AodMaskView(Context context) {
this(context, null);
}
public AodMaskView(Context context, AttributeSet attrs) {
this(context, attrs, null);
}
@VisibleForTesting
public AodMaskView(Context context, AttributeSet attrs, ImageWallpaperTransformer transformer) {
super(context, attrs);
setClickable(false);
StatusBarStateController controller = Dependency.get(StatusBarStateController.class);
if (controller != null) {
controller.addCallback(this);
} else {
Log.d(TAG, "Can not get StatusBarStateController!");
}
mDisplayManager = (DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE);
mDisplayManager.registerDisplayListener(mDisplayListener, null);
mWallpaperManager =
(WallpaperManager) getContext().getSystemService(Context.WALLPAPER_SERVICE);
if (transformer == null) {
mTransformer = new ImageWallpaperTransformer(this);
mTransformer.addFilter(new ScrimFilter());
mTransformer.addFilter(new VignetteFilter());
mTransformer.updateOffsets();
mTransformer.updateDisplayInfo(getDisplayInfo(Display.DEFAULT_DISPLAY));
mTransitionProperties.setDuration(TRANSITION_DURATION);
mTransitionProperties.setAnimationFinishListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mTransformer.setIsTransiting(false);
}
@Override
public void onAnimationStart(Animator animation) {
mTransformer.setIsTransiting(true);
}
});
} else {
// This part should only be hit by test cases.
mTransformer = transformer;
}
}
private DisplayInfo getDisplayInfo(int displayId) {
DisplayInfo displayInfo = new DisplayInfo();
mDisplayManager.getDisplay(displayId).getDisplayInfo(displayInfo);
return displayInfo;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mBounds.set(0, 0, w, h);
mTransformer.updateOffsets();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mNeedMask) {
mTransformer.drawTransformedImage(canvas, null /* target */, null /* src */, mBounds);
}
}
private boolean checkIfNeedMask() {
// We need mask for ImageWallpaper / LockScreen Wallpaper (Music album art).
return mWallpaperManager.getWallpaperInfo() == null || ScrimState.AOD.hasBackdrop();
}
@Override
public void onStatePreChange(int oldState, int newState) {
mChangingStates = oldState != newState;
mNeedMask = checkIfNeedMask();
}
@Override
public void onStatePostChange() {
mChangingStates = false;
}
@Override
public void onStateChanged(int newState) {
}
@Override
public void onDozingChanged(boolean isDozing) {
if (!mNeedMask) {
return;
}
boolean enabled = checkFeatureIsEnabled();
mTransformer.updateAmbientModeState(enabled && isDozing);
if (enabled && !mChangingStates) {
setAnimatorProperty(isDozing);
} else {
invalidate();
}
}
private boolean checkFeatureIsEnabled() {
return FeatureFlagUtils.isEnabled(
getContext(), FeatureFlagUtils.AOD_IMAGEWALLPAPER_ENABLED);
}
@VisibleForTesting
void setAnimatorProperty(boolean isDozing) {
PropertyAnimator.setProperty(
this,
TRANSITION_PROGRESS,
isDozing ? 1f : 0f /* newEndValue */,
mTransitionProperties,
true /* animated */);
}
@Override
public void onTransformationUpdated() {
invalidate();
}
private void setTransitionAmount(float amount) {
mTransitionAmount = amount;
mTransformer.updateTransitionAmount(amount);
}
private float getTransitionAmount() {
return mTransitionAmount;
}
}

View File

@@ -0,0 +1,81 @@
/*
* Copyright (C) 2018 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.wallpaper;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.RectF;
/**
* Abstract filter used by static image wallpaper.
*/
abstract class ImageWallpaperFilter {
protected static final boolean DEBUG = false;
private ImageWallpaperTransformer mTransformer;
/**
* Apply this filter to the bitmap before drawing on canvas.
* @param c The canvas that will draw to.
* @param bitmap The bitmap to apply this filter.
* @param src The subset of the bitmap to be drawn.
* @param dest The rectangle that the bitmap will be scaled/translated to fit into.
*/
public abstract void apply(@NonNull Canvas c, @Nullable Bitmap bitmap,
@Nullable Rect src, @NonNull RectF dest);
/**
* Notifies the occurrence of built-in transition of the animation.
* @param animator The animator which was animated.
*/
public abstract void onAnimatorUpdate(ValueAnimator animator);
/**
* Notifies the occurrence of another transition of the animation.
* @param amount The transition amount.
*/
public abstract void onTransitionAmountUpdate(float amount);
/**
* To set the associated transformer.
* @param transformer The transformer that is associated with this filter.
*/
public void setTransformer(ImageWallpaperTransformer transformer) {
if (transformer != null) {
mTransformer = transformer;
}
}
protected ImageWallpaperTransformer getTransformer() {
return mTransformer;
}
/**
* Notifies the changing of the offset value of the ImageWallpaper.
* @param force True to force re-evaluate offsets.
* @param xOffset X offset of the ImageWallpaper in percentage.
* @param yOffset Y offset of the ImageWallpaper in percentage.
*/
public void onOffsetsUpdate(boolean force, float xOffset, float yOffset) {
// No-op
}
}

View File

@@ -0,0 +1,173 @@
/*
* Copyright (C) 2018 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.wallpaper;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.RectF;
import android.view.DisplayInfo;
import java.util.ArrayList;
import java.util.List;
/**
* This class is used to manage the filters that will be applied.
*/
public class ImageWallpaperTransformer {
private static final String TAG = ImageWallpaperTransformer.class.getSimpleName();
private DisplayInfo mDisplayInfo;
private final List<ImageWallpaperFilter> mFilters;
private final TransformationListener mListener;
private boolean mIsInAmbientMode;
private boolean mIsTransiting;
/**
* Constructor.
* @param listener A listener to inform you the transformation has updated.
*/
public ImageWallpaperTransformer(TransformationListener listener) {
mFilters = new ArrayList<>();
mListener = listener;
}
/**
* Claim that we want to use the specified filter.
* @param filter The filter will be used.
*/
public void addFilter(ImageWallpaperFilter filter) {
if (filter != null) {
filter.setTransformer(this);
mFilters.add(filter);
}
}
/**
* Check if any transition is running.
* @return True if the transition is running, false otherwise.
*/
boolean isTransiting() {
return mIsTransiting;
}
/**
* Indicate if any transition is running. <br/>
* @param isTransiting True if the transition is running.
*/
void setIsTransiting(boolean isTransiting) {
mIsTransiting = isTransiting;
}
/**
* Check if the device is in ambient mode.
* @return True if the device is in ambient mode, false otherwise.
*/
public boolean isInAmbientMode() {
return mIsInAmbientMode;
}
/**
* Update current state of ambient mode.
* @param isInAmbientMode Current ambient mode state.
*/
public void updateAmbientModeState(boolean isInAmbientMode) {
mIsInAmbientMode = isInAmbientMode;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
int idx = 0;
for (ImageWallpaperFilter filter : mFilters) {
sb.append(idx++).append(": ").append(filter.getClass().getSimpleName()).append("\n");
}
if (sb.length() == 0) {
sb.append("No filters applied");
}
return sb.toString();
}
/**
* Set a new display info.
* @param displayInfo New display info.
*/
public void updateDisplayInfo(DisplayInfo displayInfo) {
mDisplayInfo = displayInfo;
}
/**
* To get current display info.
* @return Current display info.
*/
public DisplayInfo getDisplayInfo() {
return mDisplayInfo;
}
/**
* Update the offsets with default value.
*/
public void updateOffsets() {
this.updateOffsets(true, 0f, .5f);
}
/**
* To notify the filters that the offset of the ImageWallpaper changes.
* @param force True to force re-evaluate offsets.
* @param offsetX X offset of the ImageWallpaper in percentage.
* @param offsetY Y offset of the ImageWallpaper in percentage.
*/
public void updateOffsets(boolean force, float offsetX, float offsetY) {
mFilters.forEach(filter -> filter.onOffsetsUpdate(force, offsetX, offsetY));
}
/**
* Apply all specified filters to the bitmap then draw to the canvas.
* @param c The canvas that will draw to.
* @param target The bitmap to apply filters.
* @param src The subset of the bitmap to be drawn
* @param dest The rectangle that the bitmap will be scaled/translated to fit into.
*/
void drawTransformedImage(@NonNull Canvas c, @Nullable Bitmap target,
@Nullable Rect src, @NonNull RectF dest) {
mFilters.forEach(filter -> filter.apply(c, target, src, dest));
}
/**
* Update the transition amount. <br/>
* Must invoke this to update transition amount if not running built-in transition.
* @param amount The transition amount.
*/
void updateTransitionAmount(float amount) {
mFilters.forEach(filter -> filter.onTransitionAmountUpdate(amount));
if (mListener != null) {
mListener.onTransformationUpdated();
}
}
/**
* An interface that informs the transformation status.
*/
public interface TransformationListener {
/**
* Notifies the update of the transformation.
*/
void onTransformationUpdated();
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright (C) 2018 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.wallpaper;
import android.animation.ValueAnimator;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
/**
* A filter that implements 70% black scrim effect.
*/
public class ScrimFilter extends ImageWallpaperFilter {
private static final int MAX_ALPHA = (int) (255 * .7f);
private static final int MIN_ALPHA = 0;
private final Paint mPaint;
public ScrimFilter() {
mPaint = new Paint();
mPaint.setColor(Color.BLACK);
mPaint.setAlpha(MAX_ALPHA);
}
@Override
public void apply(Canvas c, Bitmap bitmap, Rect src, RectF dest) {
ImageWallpaperTransformer transformer = getTransformer();
// If it is not in the transition, we need to set the property according to aod state.
if (!transformer.isTransiting()) {
mPaint.setAlpha(transformer.isInAmbientMode() ? MAX_ALPHA : MIN_ALPHA);
}
c.drawRect(dest, mPaint);
}
@Override
public void onAnimatorUpdate(ValueAnimator animator) {
ImageWallpaperTransformer transformer = getTransformer();
float fraction = animator.getAnimatedFraction();
float factor = transformer.isInAmbientMode() ? fraction : 1f - fraction;
mPaint.setAlpha((int) (factor * MAX_ALPHA));
}
@Override
public void onTransitionAmountUpdate(float amount) {
mPaint.setAlpha((int) (amount * MAX_ALPHA));
}
}

View File

@@ -0,0 +1,124 @@
/*
* Copyright (C) 2018 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.wallpaper;
import android.animation.ValueAnimator;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.RadialGradient;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.util.Log;
import android.view.DisplayInfo;
import com.android.internal.annotations.VisibleForTesting;
/**
* A filter that implements vignette effect.
*/
public class VignetteFilter extends ImageWallpaperFilter {
private static final String TAG = VignetteFilter.class.getSimpleName();
private static final int MAX_ALPHA = 255;
private static final int MIN_ALPHA = 0;
private final Paint mPaint;
private final Matrix mMatrix;
private final Shader mShader;
private float mXOffset;
private float mYOffset;
private float mCenterX;
private float mCenterY;
private float mStretchX;
private float mStretchY;
private boolean mCalculateOffsetNeeded;
public VignetteFilter() {
mPaint = new Paint();
mMatrix = new Matrix();
mShader = new RadialGradient(0, 0, 1,
Color.TRANSPARENT, Color.BLACK, Shader.TileMode.CLAMP);
}
@Override
public void apply(Canvas c, Bitmap bitmap, Rect src, RectF dest) {
DisplayInfo info = getTransformer().getDisplayInfo();
if (mCalculateOffsetNeeded) {
int lw = info.logicalWidth;
int lh = info.logicalHeight;
mCenterX = lw / 2 + (dest.width() - lw) * mXOffset;
mCenterY = lh / 2 + (dest.height() - lh) * mYOffset;
mStretchX = info.logicalWidth / 2;
mStretchY = info.logicalHeight / 2;
mCalculateOffsetNeeded = false;
}
if (DEBUG) {
Log.d(TAG, "apply: lw=" + info.logicalWidth + ", lh=" + info.logicalHeight
+ ", center=(" + mCenterX + "," + mCenterY + ")"
+ ", stretch=(" + mStretchX + "," + mStretchY + ")");
}
mMatrix.reset();
mMatrix.postTranslate(mCenterX, mCenterY);
mMatrix.postScale(mStretchX, mStretchY, mCenterX, mCenterY);
mShader.setLocalMatrix(mMatrix);
mPaint.setShader(mShader);
ImageWallpaperTransformer transformer = getTransformer();
// If it is not in the transition, we need to set the property according to aod state.
if (!transformer.isTransiting()) {
mPaint.setAlpha(transformer.isInAmbientMode() ? MAX_ALPHA : MIN_ALPHA);
}
c.drawRect(dest, mPaint);
}
@Override
public void onAnimatorUpdate(ValueAnimator animator) {
ImageWallpaperTransformer transformer = getTransformer();
float fraction = animator.getAnimatedFraction();
float factor = transformer.isInAmbientMode() ? fraction : 1f - fraction;
mPaint.setAlpha((int) (factor * MAX_ALPHA));
}
@Override
public void onTransitionAmountUpdate(float amount) {
mPaint.setAlpha((int) (amount * MAX_ALPHA));
}
@Override
public void onOffsetsUpdate(boolean force, float xOffset, float yOffset) {
if (force || mXOffset != xOffset || mYOffset != yOffset) {
mXOffset = xOffset;
mYOffset = yOffset;
mCalculateOffsetNeeded = true;
}
}
@VisibleForTesting
public PointF getCenterPoint() {
return new PointF(mCenterX, mCenterY);
}
}

View File

@@ -16,17 +16,12 @@
package com.android.systemui;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.graphics.Bitmap;
@@ -58,13 +53,15 @@ public class ImageWallpaperTest extends SysuiTestCase {
@Mock private SurfaceHolder mSurfaceHolder;
@Mock private DisplayInfo mDisplayInfo;
CountDownLatch mEventCountdown;
private CountDownLatch mEventCountdown;
private CountDownLatch mAmbientEventCountdown;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mEventCountdown = new CountDownLatch(1);
mAmbientEventCountdown = new CountDownLatch(2);
mImageWallpaper = new ImageWallpaper() {
@Override
@@ -86,6 +83,11 @@ public class ImageWallpaperTest extends SysuiTestCase {
assertTrue("mFixedSizeAllowed should be true", allowed);
mEventCountdown.countDown();
}
@Override
public void onAmbientModeChanged(boolean inAmbientMode, long duration) {
mAmbientEventCountdown.countDown();
}
};
}
};
@@ -132,4 +134,23 @@ public class ImageWallpaperTest extends SysuiTestCase {
verify(mSurfaceHolder, times(1)).setFixedSize(ImageWallpaper.DrawableEngine.MIN_BACKGROUND_WIDTH, ImageWallpaper.DrawableEngine.MIN_BACKGROUND_HEIGHT);
}
@Test
public void testDeliversAmbientModeChanged() {
ImageWallpaper.DrawableEngine wallpaperEngine =
(ImageWallpaper.DrawableEngine) mImageWallpaper.onCreateEngine();
assertEquals("setFixedSizeAllowed should have been called.",
0, mEventCountdown.getCount());
wallpaperEngine.setCreated(true);
wallpaperEngine.doAmbientModeChanged(false, 1000);
assertFalse("ambient mode should be false", wallpaperEngine.isInAmbientMode());
assertEquals("onAmbientModeChanged should have been called.",
1, mAmbientEventCountdown.getCount());
wallpaperEngine.doAmbientModeChanged(true, 1000);
assertTrue("ambient mode should be true", wallpaperEngine.isInAmbientMode());
assertEquals("onAmbientModeChanged should have been called.",
0, mAmbientEventCountdown.getCount());
}
}

View File

@@ -0,0 +1,128 @@
/*
* Copyright (C) 2018 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.wallpaper;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.app.WallpaperManager;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.RectF;
import android.hardware.display.DisplayManager;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.FeatureFlagUtils;
import android.view.DisplayInfo;
import android.view.WindowManager;
import com.android.systemui.SysuiTestCase;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class AodMaskViewTest extends SysuiTestCase {
private AodMaskView mMaskView;
private DisplayInfo mDisplayInfo;
private ImageWallpaperTransformer mTransformer;
@Before
public void setUp() throws Exception {
DisplayManager displayManager =
spy((DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE));
doNothing().when(displayManager).registerDisplayListener(any(), any());
mContext.addMockSystemService(DisplayManager.class, displayManager);
WallpaperManager wallpaperManager =
spy((WallpaperManager) mContext.getSystemService(Context.WALLPAPER_SERVICE));
doReturn(null).when(wallpaperManager).getWallpaperInfo();
mContext.addMockSystemService(WallpaperManager.class, wallpaperManager);
mTransformer = spy(new ImageWallpaperTransformer(null /* listener */));
mMaskView = spy(new AodMaskView(getContext(), null /* attrs */, mTransformer));
mDisplayInfo = new DisplayInfo();
((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay().getDisplayInfo(mDisplayInfo);
FeatureFlagUtils.setEnabled(
mContext, FeatureFlagUtils.AOD_IMAGEWALLPAPER_ENABLED, true);
}
@After
public void tearDown() {
FeatureFlagUtils.setEnabled(
mContext, FeatureFlagUtils.AOD_IMAGEWALLPAPER_ENABLED, false);
}
@Test
public void testCreateMaskView_TransformerIsNotNull() {
assertNotNull("mTransformer should not be null", mTransformer);
}
@Test
public void testAodMaskView_ShouldNotClickable() {
assertFalse("MaskView should not be clickable", mMaskView.isClickable());
}
@Test
public void testAodMaskView_OnSizeChange_ShouldUpdateTransformerOffsets() {
mMaskView.onSizeChanged(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight, 0, 0);
verify(mTransformer, times(1)).updateOffsets();
}
@Test
public void testAodMaskView_OnDraw_ShouldDrawTransformedImage() {
Canvas c = new Canvas();
RectF bounds = new RectF(0, 0, mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
mMaskView.onSizeChanged(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight, 0, 0);
mMaskView.onStatePreChange(0, 1);
mMaskView.onDraw(c);
verify(mTransformer, times(1)).drawTransformedImage(c, null, null, bounds);
}
@Test
public void testAodMaskView_IsDozing_ShouldUpdateAmbientModeState() {
doNothing().when(mMaskView).setAnimatorProperty(anyBoolean());
mMaskView.onStatePreChange(0, 1);
mMaskView.onDozingChanged(true);
verify(mTransformer, times(1)).updateAmbientModeState(true);
}
@Test
public void testAodMaskView_IsDozing_ShouldDoTransitionOrDrawFinalFrame() {
doNothing().when(mMaskView).setAnimatorProperty(anyBoolean());
mMaskView.onStatePreChange(0, 1);
mMaskView.onDozingChanged(true);
mMaskView.onStatePostChange();
mMaskView.onDozingChanged(false);
verify(mMaskView, times(1)).invalidate();
verify(mMaskView, times(1)).setAnimatorProperty(false);
}
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright (C) 2009 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.wallpaper;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PointF;
import android.graphics.RectF;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import android.view.DisplayInfo;
import android.view.WindowManager;
import com.android.systemui.SysuiTestCase;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class ImageWallpaperTransformerTest extends SysuiTestCase {
private DisplayInfo mDisplayInfo;
private Bitmap mBitmap;
private Canvas mCanvas;
private RectF mDestination;
@Before
public void setUp() throws Exception {
mDisplayInfo = new DisplayInfo();
((WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay().getDisplayInfo(mDisplayInfo);
int dimension = Math.max(mDisplayInfo.logicalHeight, mDisplayInfo.logicalWidth);
mBitmap = Bitmap.createBitmap(dimension, dimension, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
mCanvas.drawColor(Color.RED);
mDestination = new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
}
@Test
public void testVignetteFilter() {
VignetteFilter vignette = new VignetteFilter();
ImageWallpaperTransformer transformer = getTransformer(vignette);
transformer.drawTransformedImage(mCanvas, mBitmap, null, mDestination);
PointF center = vignette.getCenterPoint();
int p1 = mBitmap.getPixel((int) center.x, (int) center.y);
int p2 = mBitmap.getPixel(0, 0);
int p3 = mBitmap.getPixel(mBitmap.getWidth() - 1, mBitmap.getHeight() - 1);
assertThat(p1).isEqualTo(Color.RED);
assertThat(p2 | p3).isEqualTo(Color.BLACK);
}
@Test
public void testScrimFilter() {
getTransformer(new ScrimFilter())
.drawTransformedImage(mCanvas, mBitmap, null, mDestination);
int pixel = mBitmap.getPixel(0, 0);
// 0xff4d0000 is the result of 70% alpha pre-multiplied which is 0.7*(0,0,0)+0.3*(255,0,0).
assertThat(pixel).isEqualTo(0xff4d0000);
}
private ImageWallpaperTransformer getTransformer(ImageWallpaperFilter filter) {
ImageWallpaperTransformer transformer = new ImageWallpaperTransformer(null);
transformer.addFilter(filter);
transformer.updateDisplayInfo(mDisplayInfo);
transformer.updateOffsets();
transformer.updateAmbientModeState(true);
return transformer;
}
}

View File

@@ -84,6 +84,7 @@ import android.service.wallpaper.WallpaperService;
import android.system.ErrnoException;
import android.system.Os;
import android.util.EventLog;
import android.util.FeatureFlagUtils;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -2204,8 +2205,12 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
synchronized (mLock) {
mInAmbientMode = inAmbientMode;
final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
if (data != null && data.connection != null && data.connection.mInfo != null
&& data.connection.mInfo.supportsAmbientMode()) {
final boolean hasConnection = data != null && data.connection != null;
final WallpaperInfo info = hasConnection ? data.connection.mInfo : null;
// The wallpaper info is null for image wallpaper, also use the engine in this case.
if (hasConnection && (info == null && isAodImageWallpaperEnabled()
|| info != null && info.supportsAmbientMode())) {
// TODO(multi-display) Extends this method with specific display.
engine = data.connection.getDisplayConnectorOrCreate(DEFAULT_DISPLAY).mEngine;
} else {
@@ -2222,6 +2227,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
}
private boolean isAodImageWallpaperEnabled() {
return FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.AOD_IMAGEWALLPAPER_ENABLED);
}
@Override
public boolean setLockWallpaperCallback(IWallpaperManagerCallback cb) {
checkPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW);