1 /* 2 * Copyright (C) 2019 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 17 package com.android.systemui.assist.ui; 18 19 import static com.android.systemui.assist.AssistManager.DISMISS_REASON_INVOCATION_CANCELLED; 20 import static com.android.systemui.assist.AssistManager.INVOCATION_TYPE_GESTURE; 21 22 import android.animation.Animator; 23 import android.animation.AnimatorListenerAdapter; 24 import android.animation.ValueAnimator; 25 import android.content.Context; 26 import android.graphics.PixelFormat; 27 import android.metrics.LogMaker; 28 import android.os.Build; 29 import android.util.Log; 30 import android.view.Gravity; 31 import android.view.LayoutInflater; 32 import android.view.WindowManager; 33 import android.view.animation.PathInterpolator; 34 import android.widget.FrameLayout; 35 36 import com.android.internal.logging.MetricsLogger; 37 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 38 import com.android.systemui.R; 39 import com.android.systemui.assist.AssistLogger; 40 import com.android.systemui.assist.AssistManager; 41 import com.android.systemui.assist.AssistantSessionEvent; 42 import com.android.systemui.dagger.SysUISingleton; 43 44 import java.util.Locale; 45 46 import javax.inject.Inject; 47 48 import dagger.Lazy; 49 50 /** 51 * Default UiController implementation. Shows white edge lights along the bottom of the phone, 52 * expanding from the corners to meet in the center. 53 */ 54 @SysUISingleton 55 public class DefaultUiController implements AssistManager.UiController { 56 57 private static final String TAG = "DefaultUiController"; 58 59 private static final long ANIM_DURATION_MS = 200; 60 61 private static final boolean VERBOSE = Build.TYPE.toLowerCase(Locale.ROOT).contains("debug") 62 || Build.TYPE.toLowerCase(Locale.ROOT).equals("eng"); 63 64 protected final FrameLayout mRoot; 65 protected InvocationLightsView mInvocationLightsView; 66 protected final AssistLogger mAssistLogger; 67 68 private final WindowManager mWindowManager; 69 private final MetricsLogger mMetricsLogger; 70 private final Lazy<AssistManager> mAssistManagerLazy; 71 private final WindowManager.LayoutParams mLayoutParams; 72 private final PathInterpolator mProgressInterpolator = new PathInterpolator(.83f, 0, .84f, 1); 73 74 private boolean mAttached = false; 75 private boolean mInvocationInProgress = false; 76 private float mLastInvocationProgress = 0; 77 78 private ValueAnimator mInvocationAnimator = new ValueAnimator(); 79 80 @Inject DefaultUiController(Context context, AssistLogger assistLogger, WindowManager windowManager, MetricsLogger metricsLogger, Lazy<AssistManager> assistManagerLazy)81 public DefaultUiController(Context context, AssistLogger assistLogger, 82 WindowManager windowManager, MetricsLogger metricsLogger, 83 Lazy<AssistManager> assistManagerLazy) { 84 mAssistLogger = assistLogger; 85 mRoot = new FrameLayout(context); 86 mWindowManager = windowManager; 87 mMetricsLogger = metricsLogger; 88 mAssistManagerLazy = assistManagerLazy; 89 90 mLayoutParams = new WindowManager.LayoutParams( 91 WindowManager.LayoutParams.MATCH_PARENT, 92 WindowManager.LayoutParams.WRAP_CONTENT, 0, 0, 93 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, 94 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 95 | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS 96 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 97 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, 98 PixelFormat.TRANSLUCENT); 99 mLayoutParams.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; 100 mLayoutParams.gravity = Gravity.BOTTOM; 101 mLayoutParams.setFitInsetsTypes(0 /* types */); 102 mLayoutParams.setTitle("Assist"); 103 104 mInvocationLightsView = (InvocationLightsView) 105 LayoutInflater.from(context).inflate(R.layout.invocation_lights, mRoot, false); 106 mRoot.addView(mInvocationLightsView); 107 } 108 109 @Override // AssistManager.UiController onInvocationProgress(int type, float progress)110 public void onInvocationProgress(int type, float progress) { 111 boolean invocationWasInProgress = mInvocationInProgress; 112 113 if (progress == 1) { 114 animateInvocationCompletion(type, 0); 115 } else if (progress == 0) { 116 hide(); 117 } else { 118 if (!mInvocationInProgress) { 119 attach(); 120 mInvocationInProgress = true; 121 } 122 setProgressInternal(type, progress); 123 } 124 mLastInvocationProgress = progress; 125 126 logInvocationProgressMetrics(type, progress, invocationWasInProgress); 127 } 128 129 @Override // AssistManager.UiController onGestureCompletion(float velocity)130 public void onGestureCompletion(float velocity) { 131 animateInvocationCompletion(AssistManager.INVOCATION_TYPE_GESTURE, velocity); 132 logInvocationProgressMetrics(INVOCATION_TYPE_GESTURE, 1, mInvocationInProgress); 133 } 134 135 @Override // AssistManager.UiController hide()136 public void hide() { 137 detach(); 138 if (mInvocationAnimator.isRunning()) { 139 mInvocationAnimator.cancel(); 140 } 141 mInvocationLightsView.hide(); 142 mInvocationInProgress = false; 143 } 144 logInvocationProgressMetrics( int type, float progress, boolean invocationWasInProgress)145 protected void logInvocationProgressMetrics( 146 int type, float progress, boolean invocationWasInProgress) { 147 // Logs assistant invocation start. 148 if (progress == 1f) { 149 if (VERBOSE) { 150 Log.v(TAG, "Invocation complete: type=" + type); 151 } 152 } 153 if (!invocationWasInProgress && progress > 0.f) { 154 if (VERBOSE) { 155 Log.v(TAG, "Invocation started: type=" + type); 156 } 157 mAssistLogger.reportAssistantInvocationEventFromLegacy( 158 type, 159 /* isInvocationComplete = */ false, 160 /* assistantComponent = */ null, 161 /* legacyDeviceState = */ null); 162 mMetricsLogger.write(new LogMaker(MetricsEvent.ASSISTANT) 163 .setType(MetricsEvent.TYPE_ACTION) 164 .setSubtype(mAssistManagerLazy.get().toLoggingSubType(type))); 165 } 166 // Logs assistant invocation cancelled. 167 if ((mInvocationAnimator == null || !mInvocationAnimator.isRunning()) 168 && invocationWasInProgress && progress == 0f) { 169 if (VERBOSE) { 170 Log.v(TAG, "Invocation cancelled: type=" + type); 171 } 172 mAssistLogger.reportAssistantSessionEvent( 173 AssistantSessionEvent.ASSISTANT_SESSION_INVOCATION_CANCELLED); 174 MetricsLogger.action(new LogMaker(MetricsEvent.ASSISTANT) 175 .setType(MetricsEvent.TYPE_DISMISS) 176 .setSubtype(DISMISS_REASON_INVOCATION_CANCELLED)); 177 } 178 } 179 attach()180 private void attach() { 181 if (!mAttached) { 182 mWindowManager.addView(mRoot, mLayoutParams); 183 mAttached = true; 184 } 185 } 186 detach()187 private void detach() { 188 if (mAttached) { 189 mWindowManager.removeViewImmediate(mRoot); 190 mAttached = false; 191 } 192 } 193 setProgressInternal(int type, float progress)194 private void setProgressInternal(int type, float progress) { 195 mInvocationLightsView.onInvocationProgress( 196 mProgressInterpolator.getInterpolation(progress)); 197 } 198 animateInvocationCompletion(int type, float velocity)199 private void animateInvocationCompletion(int type, float velocity) { 200 mInvocationAnimator = ValueAnimator.ofFloat(mLastInvocationProgress, 1); 201 mInvocationAnimator.setStartDelay(1); 202 mInvocationAnimator.setDuration(ANIM_DURATION_MS); 203 mInvocationAnimator.addUpdateListener( 204 animation -> setProgressInternal(type, (float) animation.getAnimatedValue())); 205 mInvocationAnimator.addListener(new AnimatorListenerAdapter() { 206 @Override 207 public void onAnimationEnd(Animator animation) { 208 super.onAnimationEnd(animation); 209 mInvocationInProgress = false; 210 mLastInvocationProgress = 0; 211 hide(); 212 } 213 }); 214 mInvocationAnimator.start(); 215 } 216 } 217