Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 67757b66 authored by Govinda Wasserman's avatar Govinda Wasserman Committed by android-build-merger
Browse files

Merge "Adds an Assistant handle controller to the AssistManager" into qt-dev

am: 35ea195d

Change-Id: I230892998475a7fbdf4d51c6b1c76d2e4f286072
parents ffeda699 35ea195d
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -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;

+46 −0
Original line number Diff line number Diff line
/*
 * 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;
        }
    }
}
+204 −0
Original line number Diff line number Diff line
/*
 * 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<ScreenDecorations> 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<ScreenDecorations> 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();
    }
}
+34 −0
Original line number Diff line number Diff line
/*
 * 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();
}
+92 −0
Original line number Diff line number Diff line
/*
 * 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();
        }
    }
}
Loading