1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.quickstep.interaction;
17 
18 import static com.android.launcher3.Utilities.mapBoundToRange;
19 import static com.android.launcher3.Utilities.mapRange;
20 import static com.android.launcher3.anim.Interpolators.LINEAR;
21 
22 import android.animation.Animator;
23 import android.app.Activity;
24 import android.app.ActivityManager.RunningTaskInfo;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.res.Configuration;
28 import android.graphics.Canvas;
29 import android.graphics.Color;
30 import android.graphics.ColorFilter;
31 import android.graphics.ColorMatrix;
32 import android.graphics.ColorMatrixColorFilter;
33 import android.graphics.Matrix;
34 import android.graphics.Paint;
35 import android.graphics.PixelFormat;
36 import android.graphics.PointF;
37 import android.graphics.RadialGradient;
38 import android.graphics.Rect;
39 import android.graphics.Shader.TileMode;
40 import android.graphics.drawable.Drawable;
41 import android.os.Bundle;
42 import android.os.VibrationEffect;
43 import android.os.Vibrator;
44 import android.util.Log;
45 import android.view.View;
46 import android.view.View.AccessibilityDelegate;
47 import android.view.accessibility.AccessibilityNodeInfo;
48 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
49 import android.widget.ImageView;
50 import android.widget.TextView;
51 
52 import androidx.annotation.Nullable;
53 import androidx.core.graphics.ColorUtils;
54 
55 import com.android.launcher3.R;
56 import com.android.launcher3.Utilities;
57 import com.android.quickstep.AnimatedFloat;
58 import com.android.quickstep.GestureState;
59 import com.android.quickstep.TouchInteractionService.TISBinder;
60 import com.android.quickstep.util.TISBindHelper;
61 
62 import com.airbnb.lottie.LottieAnimationView;
63 
64 import java.net.URISyntaxException;
65 
66 /**
67  * A page shows after SUW flow to hint users to swipe up from the bottom of the screen to go home
68  * for the gestural system navigation.
69  */
70 public class AllSetActivity extends Activity {
71 
72     private static final String LOG_TAG = "AllSetActivity";
73     private static final String URI_SYSTEM_NAVIGATION_SETTING =
74             "#Intent;action=com.android.settings.SEARCH_RESULT_TRAMPOLINE;S.:settings:fragment_args_key=gesture_system_navigation_input_summary;S.:settings:show_fragment=com.android.settings.gestures.SystemNavigationGestureSettings;end";
75     private static final String EXTRA_ACCENT_COLOR_DARK_MODE = "suwColorAccentDark";
76     private static final String EXTRA_ACCENT_COLOR_LIGHT_MODE = "suwColorAccentLight";
77 
78     private static final float HINT_BOTTOM_FACTOR = 1 - .94f;
79 
80     private TISBindHelper mTISBindHelper;
81     private TISBinder mBinder;
82 
83     private final AnimatedFloat mSwipeProgress = new AnimatedFloat(this::onSwipeProgressUpdate);
84     private BgDrawable mBackground;
85     private View mContentView;
86     private float mSwipeUpShift;
87 
88     @Nullable private Vibrator mVibrator;
89     private LottieAnimationView mAnimatedBackground;
90     private Animator.AnimatorListener mBackgroundAnimatorListener;
91 
92     @Override
onCreate(@ullable Bundle savedInstanceState)93     protected void onCreate(@Nullable Bundle savedInstanceState) {
94         super.onCreate(savedInstanceState);
95         setContentView(R.layout.activity_allset);
96         findViewById(R.id.root_view).setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
97                 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
98                 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
99 
100         int mode = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
101         boolean isDarkTheme = mode == Configuration.UI_MODE_NIGHT_YES;
102         int accentColor = getIntent().getIntExtra(
103                 isDarkTheme ? EXTRA_ACCENT_COLOR_DARK_MODE : EXTRA_ACCENT_COLOR_LIGHT_MODE,
104                 isDarkTheme ? Color.WHITE : Color.BLACK);
105 
106         ((ImageView) findViewById(R.id.icon)).getDrawable().mutate().setTint(accentColor);
107 
108         mBackground = new BgDrawable(this);
109         findViewById(R.id.root_view).setBackground(mBackground);
110         mContentView = findViewById(R.id.content_view);
111         mSwipeUpShift = getResources().getDimension(R.dimen.allset_swipe_up_shift);
112 
113         TextView tv = findViewById(R.id.navigation_settings);
114         tv.setTextColor(accentColor);
115         tv.setOnClickListener(v -> {
116             try {
117                 startActivityForResult(
118                         Intent.parseUri(URI_SYSTEM_NAVIGATION_SETTING, 0), 0);
119             } catch (URISyntaxException e) {
120                 Log.e(LOG_TAG, "Failed to parse system nav settings intent", e);
121             }
122             finish();
123         });
124 
125         findViewById(R.id.hint).setAccessibilityDelegate(new SkipButtonAccessibilityDelegate());
126         mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
127 
128         mVibrator = getSystemService(Vibrator.class);
129         mAnimatedBackground = findViewById(R.id.animated_background);
130         startBackgroundAnimation();
131     }
132 
startBackgroundAnimation()133     private void startBackgroundAnimation() {
134         if (Utilities.ATLEAST_S && mVibrator != null && mVibrator.areAllPrimitivesSupported(
135                 VibrationEffect.Composition.PRIMITIVE_THUD)) {
136             if (mBackgroundAnimatorListener == null) {
137                 mBackgroundAnimatorListener =
138                         new Animator.AnimatorListener() {
139                             @Override
140                             public void onAnimationStart(Animator animation) {
141                                 mVibrator.vibrate(getVibrationEffect());
142                             }
143 
144                             @Override
145                             public void onAnimationRepeat(Animator animation) {
146                                 mVibrator.vibrate(getVibrationEffect());
147                             }
148 
149                             @Override
150                             public void onAnimationEnd(Animator animation) {
151                                 mVibrator.cancel();
152                             }
153 
154                             @Override
155                             public void onAnimationCancel(Animator animation) {
156                                 mVibrator.cancel();
157                             }
158                         };
159             }
160             mAnimatedBackground.addAnimatorListener(mBackgroundAnimatorListener);
161         }
162         mAnimatedBackground.playAnimation();
163     }
164 
165     /**
166      * Sets up the vibration effect for the next round of animation. The parameters vary between
167      * different illustrations.
168      */
getVibrationEffect()169     private VibrationEffect getVibrationEffect() {
170         return VibrationEffect.startComposition()
171                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD, 1.0f, 50)
172                 .compose();
173     }
174 
175     @Override
onResume()176     protected void onResume() {
177         super.onResume();
178         if (mBinder != null) {
179             mBinder.getTaskbarManager().setSetupUIVisible(true);
180             mBinder.setSwipeUpProxy(this::createSwipeUpProxy);
181         }
182     }
183 
onTISConnected(TISBinder binder)184     private void onTISConnected(TISBinder binder) {
185         mBinder = binder;
186         mBinder.getTaskbarManager().setSetupUIVisible(isResumed());
187         mBinder.setSwipeUpProxy(isResumed() ? this::createSwipeUpProxy : null);
188     }
189 
190     @Override
onPause()191     protected void onPause() {
192         super.onPause();
193         clearBinderOverride();
194         if (mSwipeProgress.value >= 1) {
195             finishAndRemoveTask();
196         }
197     }
198 
clearBinderOverride()199     private void clearBinderOverride() {
200         if (mBinder != null) {
201             mBinder.getTaskbarManager().setSetupUIVisible(false);
202             mBinder.setSwipeUpProxy(null);
203         }
204     }
205 
206     @Override
onDestroy()207     protected void onDestroy() {
208         super.onDestroy();
209         mTISBindHelper.onDestroy();
210         clearBinderOverride();
211         if (mBackgroundAnimatorListener != null) {
212             mAnimatedBackground.removeAnimatorListener(mBackgroundAnimatorListener);
213         }
214     }
215 
createSwipeUpProxy(GestureState state)216     private AnimatedFloat createSwipeUpProxy(GestureState state) {
217         if (!state.getHomeIntent().getComponent().getPackageName().equals(getPackageName())) {
218             return null;
219         }
220         RunningTaskInfo rti = state.getRunningTask();
221         if (rti == null || !rti.topActivity.equals(getComponentName())) {
222             return null;
223         }
224         mSwipeProgress.updateValue(0);
225         return mSwipeProgress;
226     }
227 
onSwipeProgressUpdate()228     private void onSwipeProgressUpdate() {
229         mBackground.setProgress(mSwipeProgress.value);
230         float alpha = Utilities.mapBoundToRange(mSwipeProgress.value, 0, HINT_BOTTOM_FACTOR,
231                 1, 0, LINEAR);
232         mContentView.setAlpha(alpha);
233         mContentView.setTranslationY((alpha - 1) * mSwipeUpShift);
234 
235         if (alpha == 0f) {
236             mAnimatedBackground.pauseAnimation();
237         } else if (!mAnimatedBackground.isAnimating()) {
238             mAnimatedBackground.resumeAnimation();
239         }
240     }
241 
242     /**
243      * Accessibility delegate which exposes a click event without making the view
244      * clickable in touch mode
245      */
246     private class SkipButtonAccessibilityDelegate extends AccessibilityDelegate {
247 
248         @Override
createAccessibilityNodeInfo(View host)249         public AccessibilityNodeInfo createAccessibilityNodeInfo(View host) {
250             AccessibilityNodeInfo info = super.createAccessibilityNodeInfo(host);
251             info.addAction(AccessibilityAction.ACTION_CLICK);
252             info.setClickable(true);
253             return info;
254         }
255 
256         @Override
performAccessibilityAction(View host, int action, Bundle args)257         public boolean performAccessibilityAction(View host, int action, Bundle args) {
258             if (action == AccessibilityAction.ACTION_CLICK.getId()) {
259                 startActivity(Utilities.createHomeIntent());
260                 finish();
261                 return true;
262             }
263             return super.performAccessibilityAction(host, action, args);
264         }
265     }
266 
267     private static class BgDrawable extends Drawable {
268 
269         private static final float START_SIZE_FACTOR = .5f;
270         private static final float END_SIZE_FACTOR = 2;
271         private static final float GRADIENT_END_PROGRESS = .5f;
272 
273         private final Paint mPaint = new Paint();
274         private final RadialGradient mMaskGrad;
275         private final Matrix mMatrix = new Matrix();
276 
277         private final ColorMatrix mColorMatrix = new ColorMatrix();
278         private final ColorMatrixColorFilter mColorFilter =
279                 new ColorMatrixColorFilter(mColorMatrix);
280 
281         private final int mColor;
282         private float mProgress = 0;
283 
BgDrawable(Context context)284         BgDrawable(Context context) {
285             mColor = context.getColor(R.color.all_set_page_background);
286             mMaskGrad = new RadialGradient(0, 0, 1,
287                     new int[] {ColorUtils.setAlphaComponent(mColor, 0), mColor},
288                     new float[]{0, 1}, TileMode.CLAMP);
289 
290             mPaint.setShader(mMaskGrad);
291             mPaint.setColorFilter(mColorFilter);
292         }
293 
294         @Override
draw(Canvas canvas)295         public void draw(Canvas canvas) {
296             if (mProgress <= 0) {
297                 canvas.drawColor(mColor);
298                 return;
299             }
300 
301             // Update the progress to half the size only.
302             float progress = mapBoundToRange(mProgress,
303                     0, GRADIENT_END_PROGRESS, 0, 1, LINEAR);
304             Rect bounds = getBounds();
305             float x = bounds.exactCenterX();
306             float height = bounds.height();
307 
308             float size = PointF.length(x, height);
309             float radius = size * mapRange(progress, START_SIZE_FACTOR, END_SIZE_FACTOR);
310             float y = mapRange(progress, height + radius , height / 2);
311             mMatrix.setTranslate(x, y);
312             mMatrix.postScale(radius, radius, x, y);
313             mMaskGrad.setLocalMatrix(mMatrix);
314 
315             // Change the alpha-addition-component (index 19) so that every pixel is updated
316             // accordingly
317             mColorMatrix.getArray()[19] = mapBoundToRange(mProgress, 0, 1, 0, -255, LINEAR);
318             mColorFilter.setColorMatrix(mColorMatrix);
319 
320             canvas.drawPaint(mPaint);
321         }
322 
setProgress(float progress)323         public void setProgress(float progress) {
324             if (mProgress != progress) {
325                 mProgress = progress;
326                 invalidateSelf();
327             }
328         }
329 
330         @Override
getOpacity()331         public int getOpacity() {
332             return PixelFormat.TRANSLUCENT;
333         }
334 
335         @Override
setAlpha(int i)336         public void setAlpha(int i) { }
337 
338         @Override
setColorFilter(ColorFilter colorFilter)339         public void setColorFilter(ColorFilter colorFilter) { }
340     }
341 }
342