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

Commit 35ea195d authored by Govinda Wasserman's avatar Govinda Wasserman Committed by Android (Google) Code Review
Browse files

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

parents 71a267b6 c7495cd6
Loading
Loading
Loading
Loading
+5 −0
Original line number Original line Diff line number Diff line
@@ -156,6 +156,11 @@ public class ScreenDecorations extends SystemUI implements Tunable {
     * @param visible whether the handles should be shown
     * @param visible whether the handles should be shown
     */
     */
    public void setAssistHintVisible(boolean visible) {
    public void setAssistHintVisible(boolean visible) {
        if (!mHandler.getLooper().isCurrentThread()) {
            mHandler.post(() -> setAssistHintVisible(visible));
            return;
        }

        if (mAssistHintVisible != visible) {
        if (mAssistHintVisible != visible) {
            mAssistHintVisible = visible;
            mAssistHintVisible = visible;


+46 −0
Original line number Original line 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 Original line 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 Original line 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 Original line 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