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