diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index 69d3af8ff9835..390a9e65c1b4a 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -156,6 +156,11 @@ public class ScreenDecorations extends SystemUI implements Tunable { * @param visible whether the handles should be shown */ public void setAssistHintVisible(boolean visible) { + if (!mHandler.getLooper().isCurrentThread()) { + mHandler.post(() -> setAssistHintVisible(visible)); + return; + } + if (mAssistHintVisible != visible) { mAssistHintVisible = visible; diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehavior.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehavior.java new file mode 100644 index 0000000000000..e2c731314246d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehavior.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2019 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.assist; + + +import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.assist.AssistHandleBehaviorController.BehaviorController; + +public enum AssistHandleBehavior { + + TEST(new AssistHandleOffBehavior()), + OFF(new AssistHandleOffBehavior()), + LIKE_HOME(new AssistHandleLikeHomeBehavior()), + REMINDER_EXP(new AssistHandleReminderExpBehavior()); + + private BehaviorController mController; + + AssistHandleBehavior(BehaviorController controller) { + mController = controller; + } + + BehaviorController getController() { + return mController; + } + + @VisibleForTesting + void setTestController(BehaviorController controller) { + if (this.equals(TEST)) { + mController = controller; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java new file mode 100644 index 0000000000000..0203d9ed23763 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2019 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.assist; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Build; +import android.os.Handler; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.Dependency; +import com.android.systemui.ScreenDecorations; +import com.android.systemui.SysUiServiceProvider; +import com.android.systemui.shared.system.QuickStepContract; +import com.android.systemui.statusbar.phone.NavigationModeController; + +import java.util.Locale; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +/** + * A class for managing Assistant handle logic. + * + * Controls when visual handles for Assistant gesture affordance should be shown or hidden using an + * {@link AssistHandleBehavior}. + */ +public final class AssistHandleBehaviorController implements AssistHandleCallbacks { + + private static final String TAG = "AssistHandleBehavior"; + private static final boolean IS_DEBUG_DEVICE = + Build.TYPE.toLowerCase(Locale.ROOT).contains("debug") + || Build.TYPE.toLowerCase(Locale.ROOT).equals("eng"); + + private static final String SHOWN_FREQUENCY_THRESHOLD_KEY = + "ASSIST_HANDLES_SHOWN_FREQUENCY_THRESHOLD_MS"; + private static final long DEFAULT_SHOWN_FREQUENCY_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(10); + private static final String SHOW_AND_GO_DURATION_KEY = "ASSIST_HANDLES_SHOW_AND_GO_DURATION_MS"; + private static final long DEFAULT_SHOW_AND_GO_DURATION_MS = TimeUnit.SECONDS.toMillis(3); + private static final String BEHAVIOR_KEY = "behavior"; + private static final String SET_BEHAVIOR_ACTION = + "com.android.systemui.SET_ASSIST_HANDLE_BEHAVIOR"; + + private final Context mContext; + private final Handler mHandler; + private final Runnable mHideHandles = this::hideHandles; + private final Supplier mScreenDecorationsSupplier; + + private boolean mHandlesShowing = false; + private long mHandlesLastHiddenAt; + private AssistHandleBehavior mCurrentBehavior = AssistHandleBehavior.OFF; + private boolean mInGesturalMode; + + AssistHandleBehaviorController(Context context, Handler handler) { + this(context, handler, () -> + SysUiServiceProvider.getComponent(context, ScreenDecorations.class)); + } + + @VisibleForTesting + AssistHandleBehaviorController( + Context context, + Handler handler, + Supplier screenDecorationsSupplier) { + mContext = context; + mHandler = handler; + mScreenDecorationsSupplier = screenDecorationsSupplier; + + mInGesturalMode = QuickStepContract.isGesturalMode( + Dependency.get(NavigationModeController.class) + .addListener(this::handleNavigationModeChange)); + + if (IS_DEBUG_DEVICE) { + context.registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String behaviorString = intent.getExtras().getString(BEHAVIOR_KEY); + try { + setBehavior(AssistHandleBehavior.valueOf(behaviorString)); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Invalid behavior identifier: " + behaviorString); + } + } + }, new IntentFilter(SET_BEHAVIOR_ACTION)); + } + } + + @Override + public void hide() { + mHandler.removeCallbacks(mHideHandles); + mHandler.post(mHideHandles); + } + + @Override + public void showAndGo() { + mHandler.removeCallbacks(mHideHandles); + mHandler.post(() -> { + maybeShowHandles(/* ignoreThreshold = */ false); + mHandler.postDelayed(mHideHandles, getShowAndGoDuration()); + }); + } + + @Override + public void showAndStay() { + mHandler.removeCallbacks(mHideHandles); + mHandler.post(() -> maybeShowHandles(/* ignoreThreshold = */ true)); + } + + void setBehavior(AssistHandleBehavior behavior) { + if (mCurrentBehavior == behavior) { + return; + } + + if (mInGesturalMode) { + mCurrentBehavior.getController().onModeDeactivated(); + behavior.getController().onModeActivated(mContext, this); + } + + mCurrentBehavior = behavior; + } + + private static long getShownFrequencyThreshold() { + return SystemProperties.getLong( + SHOWN_FREQUENCY_THRESHOLD_KEY, DEFAULT_SHOWN_FREQUENCY_THRESHOLD_MS); + } + + private static long getShowAndGoDuration() { + return SystemProperties.getLong(SHOW_AND_GO_DURATION_KEY, DEFAULT_SHOW_AND_GO_DURATION_MS); + } + + private void maybeShowHandles(boolean ignoreThreshold) { + if (mHandlesShowing) { + return; + } + + long timeSinceHidden = SystemClock.elapsedRealtime() - mHandlesLastHiddenAt; + if (ignoreThreshold || timeSinceHidden > getShownFrequencyThreshold()) { + mHandlesShowing = true; + ScreenDecorations screenDecorations = mScreenDecorationsSupplier.get(); + if (screenDecorations == null) { + Log.w(TAG, "Couldn't show handles, ScreenDecorations unavailable"); + } else { + screenDecorations.setAssistHintVisible(true); + } + } + } + + private void hideHandles() { + if (!mHandlesShowing) { + return; + } + + mHandlesShowing = false; + mHandlesLastHiddenAt = SystemClock.elapsedRealtime(); + ScreenDecorations screenDecorations = mScreenDecorationsSupplier.get(); + if (screenDecorations == null) { + Log.w(TAG, "Couldn't hide handles, ScreenDecorations unavailable"); + } else { + screenDecorations.setAssistHintVisible(false); + } + } + + private void handleNavigationModeChange(int navigationMode) { + boolean inGesturalMode = QuickStepContract.isGesturalMode(navigationMode); + if (mInGesturalMode == inGesturalMode) { + return; + } + + mInGesturalMode = inGesturalMode; + if (mInGesturalMode) { + mCurrentBehavior.getController().onModeActivated(mContext, this); + } else { + mCurrentBehavior.getController().onModeDeactivated(); + hide(); + } + } + + @VisibleForTesting + void setInGesturalModeForTest(boolean inGesturalMode) { + mInGesturalMode = inGesturalMode; + } + + interface BehaviorController { + void onModeActivated(Context context, AssistHandleCallbacks callbacks); + void onModeDeactivated(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleCallbacks.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleCallbacks.java new file mode 100644 index 0000000000000..0577d37217e29 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleCallbacks.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2019 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.assist; + +/** Callback for controlling Assistant handle behavior. */ +public interface AssistHandleCallbacks { + + /** Hide the Assistant handles. */ + void hide(); + + /** + * Show the Assistant handles for the configured duration and then hide them. + * + * Won't show if the handles have been shown within the configured timeout. + */ + void showAndGo(); + + /** Show the Assistant handles. */ + void showAndStay(); +} diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleLikeHomeBehavior.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleLikeHomeBehavior.java new file mode 100644 index 0000000000000..e89e93c0adf07 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleLikeHomeBehavior.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2019 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.assist; + +import android.app.StatusBarManager; +import android.content.Context; + +import androidx.annotation.Nullable; + +import com.android.systemui.Dependency; +import com.android.systemui.SysUiServiceProvider; +import com.android.systemui.assist.AssistHandleBehaviorController.BehaviorController; +import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.NavigationBarController; +import com.android.systemui.statusbar.phone.NavigationBarFragment; + +/** + * Assistant Handle behavior that makes Assistant handles show/hide when the home handle is + * shown/hidden, respectively. + */ +final class AssistHandleLikeHomeBehavior implements BehaviorController { + + private final CommandQueue.Callbacks mCallbacks = new CommandQueue.Callbacks() { + @Override + public void setWindowState(int displayId, int window, int state) { + if (mNavBarDisplayId == displayId + && window == StatusBarManager.WINDOW_NAVIGATION_BAR) { + handleWindowStateChanged(state); + } + } + }; + + private CommandQueue mCommandQueue; + private int mNavBarDisplayId; + private boolean mIsNavBarWindowVisible; + + @Nullable private AssistHandleCallbacks mAssistHandleCallbacks; + + @Override + public void onModeActivated(Context context, AssistHandleCallbacks callbacks) { + mAssistHandleCallbacks = callbacks; + NavigationBarFragment navigationBarFragment = + Dependency.get(NavigationBarController.class).getDefaultNavigationBarFragment(); + mNavBarDisplayId = navigationBarFragment.mDisplayId; + mIsNavBarWindowVisible = navigationBarFragment.isNavBarWindowVisible(); + mCommandQueue = SysUiServiceProvider.getComponent(context, CommandQueue.class); + mCommandQueue.addCallback(mCallbacks); + callbackForCurrentState(); + } + + @Override + public void onModeDeactivated() { + mAssistHandleCallbacks = null; + mCommandQueue.removeCallback(mCallbacks); + } + + private void handleWindowStateChanged(int state) { + boolean newVisibility = state == StatusBarManager.WINDOW_STATE_SHOWING; + if (mIsNavBarWindowVisible == newVisibility) { + return; + } + + mIsNavBarWindowVisible = newVisibility; + callbackForCurrentState(); + } + + private void callbackForCurrentState() { + if (mAssistHandleCallbacks == null) { + return; + } + + if (mIsNavBarWindowVisible) { + mAssistHandleCallbacks.showAndStay(); + } else { + mAssistHandleCallbacks.hide(); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleOffBehavior.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleOffBehavior.java new file mode 100644 index 0000000000000..ebf15ae8755ce --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleOffBehavior.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2019 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.assist; + +import android.content.Context; + +import com.android.systemui.assist.AssistHandleBehaviorController.BehaviorController; + +/** Assistant handle behavior that hides the Assistant handles. */ +final class AssistHandleOffBehavior implements BehaviorController { + + @Override + public void onModeActivated(Context context, AssistHandleCallbacks callbacks) { + callbacks.hide(); + } + + @Override + public void onModeDeactivated() { + // Do nothing + } +} diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java new file mode 100644 index 0000000000000..4a5e0e85107e0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2019 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.assist; + +import android.content.ComponentName; +import android.content.Context; +import android.graphics.Rect; +import android.view.View; +import android.view.WindowManager; + +import androidx.annotation.Nullable; + +import com.android.systemui.Dependency; +import com.android.systemui.SysUiServiceProvider; +import com.android.systemui.assist.AssistHandleBehaviorController.BehaviorController; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.shared.system.TaskStackChangeListener; +import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.StatusBarState; + +/** + * Assistant handle behavior that hides the handles when the phone is dozing or in immersive mode, + * shows the handles when on lockscreen, and shows the handles temporarily when changing tasks or + * entering overview. + */ +final class AssistHandleReminderExpBehavior implements BehaviorController { + + private final StatusBarStateController.StateListener mStatusBarStateListener = + new StatusBarStateController.StateListener() { + @Override + public void onStateChanged(int newState) { + handleStatusBarStateChanged(newState); + } + + @Override + public void onDozingChanged(boolean isDozing) { + handleDozingChanged(isDozing); + } + }; + private final TaskStackChangeListener mTaskStackChangeListener = + new TaskStackChangeListener() { + @Override + public void onTaskMovedToFront(int taskId) { + handleTaskStackTopChanged(taskId); + } + + @Override + public void onTaskCreated(int taskId, ComponentName componentName) { + handleTaskStackTopChanged(taskId); + } + }; + private final CommandQueue.Callbacks mCallbacks = new CommandQueue.Callbacks() { + @Override + public void setSystemUiVisibility(int displayId, int vis, + int fullscreenStackVis, int dockedStackVis, int mask, + Rect fullscreenStackBounds, Rect dockedStackBounds, + boolean navbarColorManagedByIme) { + if (mStatusBarDisplayId == displayId) { + handleSystemUiVisibilityChange(vis, mask); + } + } + }; + private final OverviewProxyService.OverviewProxyListener mOverviewProxyListener = + new OverviewProxyService.OverviewProxyListener() { + @Override + public void onOverviewShown(boolean fromHome) { + handleOverviewShown(); + } + }; + + private StatusBarStateController mStatusBarStateController; + private ActivityManagerWrapper mActivityManagerWrapper; + private OverviewProxyService mOverviewProxyService; + private int mStatusBarDisplayId; + private CommandQueue mCommandQueue; + private boolean mOnLockscreen; + private boolean mIsDozing; + private int mRunningTaskId; + private boolean mIsImmersive; + + @Nullable private AssistHandleCallbacks mAssistHandleCallbacks; + + @Override + public void onModeActivated(Context context, AssistHandleCallbacks callbacks) { + mAssistHandleCallbacks = callbacks; + mStatusBarStateController = Dependency.get(StatusBarStateController.class); + mOnLockscreen = onLockscreen(mStatusBarStateController.getState()); + mIsDozing = mStatusBarStateController.isDozing(); + mStatusBarStateController.addCallback(mStatusBarStateListener); + mActivityManagerWrapper = ActivityManagerWrapper.getInstance(); + mRunningTaskId = mActivityManagerWrapper.getRunningTask().taskId; + mActivityManagerWrapper.registerTaskStackListener(mTaskStackChangeListener); + mStatusBarDisplayId = + ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)) + .getDefaultDisplay().getDisplayId(); + mCommandQueue = SysUiServiceProvider.getComponent(context, CommandQueue.class); + mCommandQueue.addCallback(mCallbacks); + mOverviewProxyService = Dependency.get(OverviewProxyService.class); + mOverviewProxyService.addCallback(mOverviewProxyListener); + callbackForCurrentState(); + } + + @Override + public void onModeDeactivated() { + mAssistHandleCallbacks = null; + mStatusBarStateController.removeCallback(mStatusBarStateListener); + mActivityManagerWrapper.unregisterTaskStackListener(mTaskStackChangeListener); + mCommandQueue.removeCallback(mCallbacks); + mOverviewProxyService.removeCallback(mOverviewProxyListener); + } + + private void handleStatusBarStateChanged(int newState) { + boolean onLockscreen = onLockscreen(newState); + if (mOnLockscreen == onLockscreen) { + return; + } + + mOnLockscreen = onLockscreen; + callbackForCurrentState(); + } + + private void handleDozingChanged(boolean isDozing) { + if (mIsDozing == isDozing) { + return; + } + + mIsDozing = isDozing; + callbackForCurrentState(); + } + + private void handleTaskStackTopChanged(int taskId) { + if (mRunningTaskId == taskId) { + return; + } + + mRunningTaskId = taskId; + callbackForCurrentState(); + } + + private void handleSystemUiVisibilityChange(int vis, int mask) { + boolean isImmersive = isImmersive(vis, mask); + if (mIsImmersive == isImmersive) { + return; + } + + mIsImmersive = isImmersive; + callbackForCurrentState(); + } + + private void handleOverviewShown() { + callbackForCurrentState(); + } + + private boolean onLockscreen(int statusBarState) { + return statusBarState == StatusBarState.KEYGUARD + || statusBarState == StatusBarState.SHADE_LOCKED; + } + + private boolean isImmersive(int vis, int mask) { + return ((vis & mask) + & (View.SYSTEM_UI_FLAG_IMMERSIVE | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)) != 0; + } + + private void callbackForCurrentState() { + if (mAssistHandleCallbacks == null) { + return; + } + + if (mIsDozing || mIsImmersive) { + mAssistHandleCallbacks.hide(); + } else if (mOnLockscreen) { + mAssistHandleCallbacks.showAndStay(); + } else { + mAssistHandleCallbacks.showAndGo(); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java index f0c38fb7534fa..2c38e513d7de5 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java @@ -75,6 +75,7 @@ public class AssistManager implements ConfigurationChangedReceiver { private final AssistDisclosure mAssistDisclosure; private final InterestingConfigChanges mInterestingConfigChanges; private final PhoneStateMonitor mPhoneStateMonitor; + private final AssistHandleBehaviorController mHandleController; private AssistOrbContainer mView; private final DeviceProvisionedController mDeviceProvisionedController; @@ -110,6 +111,7 @@ public class AssistManager implements ConfigurationChangedReceiver { mAssistUtils = new AssistUtils(context); mAssistDisclosure = new AssistDisclosure(context, new Handler()); mPhoneStateMonitor = new PhoneStateMonitor(context); + mHandleController = new AssistHandleBehaviorController(context, new Handler()); registerVoiceInteractionSessionListener(); mInterestingConfigChanges = new InterestingConfigChanges(ActivityInfo.CONFIG_ORIENTATION @@ -352,6 +354,10 @@ public class AssistManager implements ConfigurationChangedReceiver { v.setImageDrawable(null); } + protected AssistHandleBehaviorController getHandleBehaviorController() { + return mHandleController; + } + @Nullable public ComponentName getAssistInfoForUser(int userId) { return mAssistUtils.getAssistComponentForUser(userId); diff --git a/packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java b/packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java index ea08c335b31d4..1fd3089aaa41f 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java +++ b/packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 The Android Open Source Project + * Copyright (C) 2019 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. diff --git a/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java new file mode 100644 index 0000000000000..13c92b605c034 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2019 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.assist; + +import static org.mockito.AdditionalAnswers.answerVoid; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import android.os.Handler; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper.RunWithLooper; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.ScreenDecorations; +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@RunWithLooper +public class AssistHandleBehaviorControllerTest extends SysuiTestCase { + + private final AssistHandleBehavior mTestBehavior = AssistHandleBehavior.TEST; + + private AssistHandleBehaviorController mAssistHandleBehaviorController; + + @Mock private ScreenDecorations mMockScreenDecorations; + @Mock private Handler mMockHandler; + @Mock private AssistHandleBehaviorController.BehaviorController mMockBehaviorController; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + doAnswer(answerVoid(Runnable::run)).when(mMockHandler).post(any(Runnable.class)); + doAnswer(answerVoid(Runnable::run)).when(mMockHandler) + .postDelayed(any(Runnable.class), anyLong()); + mTestBehavior.setTestController(mMockBehaviorController); + mAssistHandleBehaviorController = + new AssistHandleBehaviorController( + mContext, mMockHandler, () -> mMockScreenDecorations); + } + + @Test + public void hide_hidesHandlesWhenShowing() { + // Arrange + mAssistHandleBehaviorController.showAndStay(); + reset(mMockScreenDecorations); + + // Act + mAssistHandleBehaviorController.hide(); + + // Assert + verify(mMockScreenDecorations).setAssistHintVisible(false); + verifyNoMoreInteractions(mMockScreenDecorations); + } + + @Test + public void hide_doesNothingWhenHiding() { + // Arrange + mAssistHandleBehaviorController.hide(); + reset(mMockScreenDecorations); + + // Act + mAssistHandleBehaviorController.hide(); + + // Assert + verifyNoMoreInteractions(mMockScreenDecorations); + } + + @Test + public void showAndStay_showsHandlesWhenHiding() { + // Arrange + mAssistHandleBehaviorController.hide(); + reset(mMockScreenDecorations); + + // Act + mAssistHandleBehaviorController.showAndStay(); + + // Assert + verify(mMockScreenDecorations).setAssistHintVisible(true); + verifyNoMoreInteractions(mMockScreenDecorations); + } + + @Test + public void showAndStay_doesNothingWhenShowing() { + // Arrange + mAssistHandleBehaviorController.showAndStay(); + reset(mMockScreenDecorations); + + // Act + mAssistHandleBehaviorController.showAndStay(); + + // Assert + verifyNoMoreInteractions(mMockScreenDecorations); + } + + @Test + public void showAndGo_showsThenHidesHandlesWhenHiding() { + // Arrange + mAssistHandleBehaviorController.hide(); + reset(mMockScreenDecorations); + + // Act + mAssistHandleBehaviorController.showAndGo(); + + // Assert + InOrder inOrder = inOrder(mMockScreenDecorations); + inOrder.verify(mMockScreenDecorations).setAssistHintVisible(true); + inOrder.verify(mMockScreenDecorations).setAssistHintVisible(false); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void showAndGo_hidesHandlesAfterTimeoutWhenShowing() { + // Arrange + mAssistHandleBehaviorController.showAndStay(); + reset(mMockScreenDecorations); + + // Act + mAssistHandleBehaviorController.showAndGo(); + + // Assert + verify(mMockScreenDecorations).setAssistHintVisible(false); + verifyNoMoreInteractions(mMockScreenDecorations); + } + + @Test + public void showAndGo_doesNothingIfRecentlyHidden() { + // Arrange + mAssistHandleBehaviorController.showAndGo(); + reset(mMockScreenDecorations); + + // Act + mAssistHandleBehaviorController.showAndGo(); + + // Assert + verifyNoMoreInteractions(mMockScreenDecorations); + } + + @Test + public void setBehavior_activatesTheBehaviorWhenInGesturalMode() { + // Arrange + mAssistHandleBehaviorController.setInGesturalModeForTest(true); + + // Act + mAssistHandleBehaviorController.setBehavior(mTestBehavior); + + // Assert + verify(mMockBehaviorController).onModeActivated(mContext, mAssistHandleBehaviorController); + verifyNoMoreInteractions(mMockBehaviorController); + } + + @Test + public void setBehavior_deactivatesThePreviousBehaviorWhenInGesturalMode() { + // Arrange + mAssistHandleBehaviorController.setBehavior(mTestBehavior); + mAssistHandleBehaviorController.setInGesturalModeForTest(true); + + // Act + mAssistHandleBehaviorController.setBehavior(AssistHandleBehavior.OFF); + + // Assert + verify(mMockBehaviorController).onModeDeactivated(); + verifyNoMoreInteractions(mMockBehaviorController); + } + + @Test + public void setBehavior_doesNothingWhenNotInGesturalMode() { + // Arrange + mAssistHandleBehaviorController.setInGesturalModeForTest(false); + + // Act + mAssistHandleBehaviorController.setBehavior(mTestBehavior); + + // Assert + verifyNoMoreInteractions(mMockBehaviorController); + } +}