/* * Copyright (C) 2020 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.launcher3.statehandlers; import static com.android.launcher3.anim.Interpolators.LINEAR; import android.os.IBinder; import android.util.FloatProperty; import android.view.View; import android.view.ViewTreeObserver; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.LauncherStateManager; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.states.StateAnimationConfig; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; import com.android.systemui.shared.system.SurfaceControlCompat; import com.android.systemui.shared.system.TransactionCompat; import com.android.systemui.shared.system.WallpaperManagerCompat; /** * Controls blur and wallpaper zoom, for the Launcher surface only. */ public class DepthController implements LauncherStateManager.StateHandler { public static final FloatProperty DEPTH = new FloatProperty("depth") { @Override public void setValue(DepthController depthController, float depth) { depthController.setDepth(depth); } @Override public Float get(DepthController depthController) { return depthController.mDepth; } }; /** * A property that updates the background blur within a given range of values (ie. even if the * animator goes beyond 0..1, the interpolated value will still be bounded). */ public static class ClampedDepthProperty extends FloatProperty { private final float mMinValue; private final float mMaxValue; public ClampedDepthProperty(float minValue, float maxValue) { super("depthClamped"); mMinValue = minValue; mMaxValue = maxValue; } @Override public void setValue(DepthController depthController, float depth) { depthController.setDepth(Utilities.boundToRange(depth, mMinValue, mMaxValue)); } @Override public Float get(DepthController depthController) { return depthController.mDepth; } } private final ViewTreeObserver.OnDrawListener mOnDrawListener = new ViewTreeObserver.OnDrawListener() { @Override public void onDraw() { View view = mLauncher.getDragLayer(); setSurface(new SurfaceControlCompat(view)); view.post(() -> view.getViewTreeObserver().removeOnDrawListener(this)); } }; private final Launcher mLauncher; /** * Blur radius when completely zoomed out, in pixels. */ private int mMaxBlurRadius; private WallpaperManagerCompat mWallpaperManager; private SurfaceControlCompat mSurface; /** * Ratio from 0 to 1, where 0 is fully zoomed out, and 1 is zoomed in. * @see android.service.wallpaper.WallpaperService.Engine#onZoomChanged(float) */ private float mDepth; public DepthController(Launcher l) { mLauncher = l; } private void ensureDependencies() { if (mWallpaperManager != null) { return; } mMaxBlurRadius = mLauncher.getResources().getInteger(R.integer.max_depth_blur_radius); mWallpaperManager = new WallpaperManagerCompat(mLauncher); } /** * Sets if the underlying activity is started or not */ public void setActivityStarted(boolean isStarted) { if (isStarted) { mLauncher.getDragLayer().getViewTreeObserver().addOnDrawListener(mOnDrawListener); } else { mLauncher.getDragLayer().getViewTreeObserver().removeOnDrawListener(mOnDrawListener); setSurface(null); } } /** * Sets the specified app target surface to apply the blur to. */ public void setSurfaceToApp(RemoteAnimationTargetCompat target) { if (target != null) { setSurface(target.leash); } else { setActivityStarted(mLauncher.isStarted()); } } private void setSurface(SurfaceControlCompat surface) { if (mSurface != surface) { mSurface = surface; if (surface != null) { setDepth(mDepth); } else { // If there is no surface, then reset the ratio setDepth(0f); } } } @Override public void setState(LauncherState toState) { if (mSurface == null) { return; } float toDepth = toState.getDepth(mLauncher); if (Float.compare(mDepth, toDepth) != 0) { setDepth(toDepth); } } @Override public void setStateWithAnimation(LauncherState toState, StateAnimationConfig config, PendingAnimation animation) { if (mSurface == null || config.onlyPlayAtomicComponent()) { return; } float toDepth = toState.getDepth(mLauncher); if (Float.compare(mDepth, toDepth) != 0) { animation.setFloat(this, DEPTH, toDepth, LINEAR); } } private void setDepth(float depth) { mDepth = depth; if (mSurface == null || !mSurface.isValid()) { return; } ensureDependencies(); IBinder windowToken = mLauncher.getRootView().getWindowToken(); if (windowToken != null) { mWallpaperManager.setWallpaperZoomOut(windowToken, mDepth); } new TransactionCompat() .setBackgroundBlurRadius(mSurface, (int) (mDepth * mMaxBlurRadius)) .apply(); } }