diff options
14 files changed, 1010 insertions, 12 deletions
| diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 8b97e0e4a107..0edcb3d8eb6a 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -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");      }      /** diff --git a/packages/SystemUI/res/layout/super_status_bar.xml b/packages/SystemUI/res/layout/super_status_bar.xml index 34c208ab81aa..02062bb7b2c4 100644 --- a/packages/SystemUI/res/layout/super_status_bar.xml +++ b/packages/SystemUI/res/layout/super_status_bar.xml @@ -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" diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index 59838d23278d..633f8686b804 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -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> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java index fb3c4aa16ef5..72519ba3503c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java @@ -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;      } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 6f877ba90cfa..9abd86d7088b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -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(); @@ -4415,4 +4426,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); +            } +        } +    }  } diff --git a/packages/SystemUI/src/com/android/systemui/wallpaper/AodMaskView.java b/packages/SystemUI/src/com/android/systemui/wallpaper/AodMaskView.java new file mode 100644 index 000000000000..52cabe278e2d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/wallpaper/AodMaskView.java @@ -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; +    } + +} diff --git a/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperFilter.java b/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperFilter.java new file mode 100644 index 000000000000..d457dac3e14f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperFilter.java @@ -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 +    } + +} diff --git a/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperTransformer.java b/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperTransformer.java new file mode 100644 index 000000000000..25b0b0aaf60b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperTransformer.java @@ -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(); +    } +} diff --git a/packages/SystemUI/src/com/android/systemui/wallpaper/ScrimFilter.java b/packages/SystemUI/src/com/android/systemui/wallpaper/ScrimFilter.java new file mode 100644 index 000000000000..637e48e67de6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/wallpaper/ScrimFilter.java @@ -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)); +    } + +} diff --git a/packages/SystemUI/src/com/android/systemui/wallpaper/VignetteFilter.java b/packages/SystemUI/src/com/android/systemui/wallpaper/VignetteFilter.java new file mode 100644 index 000000000000..ad0b98b67e68 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/wallpaper/VignetteFilter.java @@ -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); +    } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java index 521d5d10a621..53ad0b5132c0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java @@ -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()); +    }  } diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpaper/AodMaskViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpaper/AodMaskViewTest.java new file mode 100644 index 000000000000..c44a366e683c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/wallpaper/AodMaskViewTest.java @@ -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); +    } + +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpaper/ImageWallpaperTransformerTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpaper/ImageWallpaperTransformerTest.java new file mode 100644 index 000000000000..55b0aaefff51 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/wallpaper/ImageWallpaperTransformerTest.java @@ -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; +    } +} diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 410f864b5893..fcc828461bc3 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -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; @@ -2219,8 +2220,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 { @@ -2237,6 +2242,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); |