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

Commit 5924fd74 authored by Govinda Wasserman's avatar Govinda Wasserman
Browse files

Remove Assistant corner handles

Assistant corner handles are going away for S+

Test: Tested locally
BUG: 182996954
FIX: 182996954
Change-Id: Ic79c894c76c2247396fd1f3661902f127dcdacd5
parent 1cdf7f30
Loading
Loading
Loading
Loading
+0 −16
Original line number Diff line number Diff line
@@ -19,27 +19,11 @@

<com.android.systemui.navigationbar.NavigationBarView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:systemui="http://schemas.android.com/apk/res-auto"
    android:id="@+id/navigation_bar_view"
    android:layout_height="match_parent"
    android:layout_width="match_parent"
    android:background="@drawable/system_bar_background">

    <com.android.systemui.CornerHandleView
        android:id="@+id/assist_hint_left"
        android:layout_width="36dp"
        android:layout_height="36dp"
        android:layout_gravity="left|bottom"
        android:rotation="270"
        android:visibility="gone"/>
    <com.android.systemui.CornerHandleView
        android:id="@+id/assist_hint_right"
        android:layout_width="36dp"
        android:layout_height="36dp"
        android:layout_gravity="right|bottom"
        android:rotation="180"
        android:visibility="gone"/>

    <com.android.systemui.navigationbar.NavigationBarInflaterView
        android:id="@+id/navigation_inflater"
        android:layout_width="match_parent"
+0 −191
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;

import android.animation.ArgbEvaluator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.ContextThemeWrapper;
import android.view.View;

import com.android.settingslib.Utils;

/**
 * CornerHandleView draws an inset arc intended to be displayed within the screen decoration
 * corners.
 */
public class CornerHandleView extends View {
    private static final float STROKE_DP_LARGE = 2f;
    private static final float STROKE_DP_SMALL = 1.95f;
    // Radius to use if none is available.
    private static final int FALLBACK_RADIUS_DP = 15;
    private static final float MARGIN_DP = 8;
    private static final int MAX_ARC_DEGREES = 90;
    // Arc length along the phone's perimeter used to measure the desired angle.
    private static final float ARC_LENGTH_DP = 31f;

    private Paint mPaint;
    private int mLightColor;
    private int mDarkColor;
    private Path mPath;
    private boolean mRequiresInvalidate;

    public CornerHandleView(Context context, AttributeSet attrs) {
        super(context, attrs);

        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPaint.setStrokeWidth(getStrokePx());

        final int dualToneDarkTheme = Utils.getThemeAttr(mContext, R.attr.darkIconTheme);
        final int dualToneLightTheme = Utils.getThemeAttr(mContext, R.attr.lightIconTheme);
        Context lightContext = new ContextThemeWrapper(mContext, dualToneLightTheme);
        Context darkContext = new ContextThemeWrapper(mContext, dualToneDarkTheme);
        mLightColor = Utils.getColorAttrDefaultColor(lightContext, R.attr.singleToneColor);
        mDarkColor = Utils.getColorAttrDefaultColor(darkContext, R.attr.singleToneColor);

        updatePath();
    }

    @Override
    public void setAlpha(float alpha) {
        super.setAlpha(alpha);
        if (alpha > 0f && mRequiresInvalidate) {
            mRequiresInvalidate = false;
            invalidate();
        }
    }

    private void updatePath() {
        mPath = new Path();

        float marginPx = getMarginPx();
        float radiusPx = getInnerRadiusPx();
        float halfStrokePx = getStrokePx() / 2f;
        float angle = getAngle();
        float startAngle = 180 + ((90 - angle) / 2);
        RectF circle = new RectF(marginPx + halfStrokePx,
                marginPx + halfStrokePx,
                marginPx + 2 * radiusPx - halfStrokePx,
                marginPx + 2 * radiusPx - halfStrokePx);

        if (angle >= 90f) {
            float innerCircumferenceDp = convertPixelToDp(radiusPx * 2 * (float) Math.PI,
                    mContext);
            float arcDp = innerCircumferenceDp * getAngle() / 360f;
            // Add additional "arms" to the two ends of the arc. The length computation is
            // hand-tuned.
            float lineLengthPx = convertDpToPixel((ARC_LENGTH_DP - arcDp - MARGIN_DP) / 2,
                    mContext);

            mPath.moveTo(marginPx + halfStrokePx, marginPx + radiusPx + lineLengthPx);
            mPath.lineTo(marginPx + halfStrokePx, marginPx + radiusPx);
            mPath.arcTo(circle, startAngle, angle);
            mPath.moveTo(marginPx + radiusPx, marginPx + halfStrokePx);
            mPath.lineTo(marginPx + radiusPx + lineLengthPx, marginPx + halfStrokePx);
        } else {
            mPath.arcTo(circle, startAngle, angle);
        }
    }

    /**
     * Receives an intensity from 0 (lightest) to 1 (darkest) and sets the handle color
     * appropriately. Intention is to match the home handle color.
     */
    public void updateDarkness(float darkIntensity) {
        // Handle color is same as home handle color.
        int color = (int) ArgbEvaluator.getInstance().evaluate(darkIntensity,
                mLightColor, mDarkColor);
        if (mPaint.getColor() != color) {
            mPaint.setColor(color);
            if (getVisibility() == VISIBLE && getAlpha() > 0) {
                invalidate();
            } else {
                // If we are currently invisible, then invalidate when we are next made visible
                mRequiresInvalidate = true;
            }
        }
    }

    @Override
    public void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawPath(mPath, mPaint);
    }

    private static float convertDpToPixel(float dp, Context context) {
        return dp * ((float) context.getResources().getDisplayMetrics().densityDpi
                / DisplayMetrics.DENSITY_DEFAULT);
    }

    private static float convertPixelToDp(float px, Context context) {
        return px * DisplayMetrics.DENSITY_DEFAULT
                / ((float) context.getResources().getDisplayMetrics().densityDpi);
    }

    private float getAngle() {
        // Measure a length of ARC_LENGTH_DP along the *screen's* perimeter, get the angle and cap
        // it at 90.
        float circumferenceDp = convertPixelToDp((
                getOuterRadiusPx()) * 2 * (float) Math.PI, mContext);
        float angleDeg = (ARC_LENGTH_DP / circumferenceDp) * 360;
        if (angleDeg > MAX_ARC_DEGREES) {
            angleDeg = MAX_ARC_DEGREES;
        }
        return angleDeg;
    }

    private float getMarginPx() {
        return convertDpToPixel(MARGIN_DP, mContext);
    }

    private float getInnerRadiusPx() {
        return getOuterRadiusPx() - getMarginPx();
    }

    private float getOuterRadiusPx() {
        // Attempt to get the bottom corner radius, otherwise fall back on the generic or top
        // values. If none are available, use the FALLBACK_RADIUS_DP.
        int radius = getResources().getDimensionPixelSize(
                com.android.systemui.R.dimen.config_rounded_mask_size_bottom);
        if (radius == 0) {
            radius = getResources().getDimensionPixelSize(
                    com.android.systemui.R.dimen.config_rounded_mask_size);
        }
        if (radius == 0) {
            radius = getResources().getDimensionPixelSize(
                    com.android.systemui.R.dimen.config_rounded_mask_size_top);
        }
        if (radius == 0) {
            radius = (int) convertDpToPixel(FALLBACK_RADIUS_DP, mContext);
        }
        return radius;
    }

    private float getStrokePx() {
        // Use a slightly smaller stroke if we need to cover the full corner angle.
        return convertDpToPixel((getAngle() < 90) ? STROKE_DP_LARGE : STROKE_DP_SMALL,
                getContext());
    }
}
+0 −25
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;

public enum AssistHandleBehavior {

    TEST,
    OFF,
    LIKE_HOME,
    REMINDER_EXP;
}
+0 −336
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 static com.android.systemui.assist.AssistModule.ASSIST_HANDLE_THREAD_NAME;

import android.content.ComponentName;
import android.content.Context;
import android.os.Handler;
import android.os.SystemClock;
import android.provider.Settings;
import android.util.Log;
import android.view.accessibility.AccessibilityManager;

import androidx.annotation.Nullable;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.AssistUtils;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.shared.system.QuickStepContract;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;

import dagger.Lazy;

/**
 * A class for managing Assistant handle logic.
 *
 * Controls when visual handles for Assistant gesture affordance should be shown or hidden using an
 * {@link AssistHandleBehavior}.
 */
@SysUISingleton
public final class AssistHandleBehaviorController implements AssistHandleCallbacks, Dumpable {

    private static final String TAG = "AssistHandleBehavior";

    private static final long DEFAULT_SHOWN_FREQUENCY_THRESHOLD_MS = 0;
    private static final long DEFAULT_SHOW_AND_GO_DURATION_MS = TimeUnit.SECONDS.toMillis(3);
    private static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete";

    /**
     * This is the default behavior that will be used once the system is up. It will be set once the
     * behavior dependencies are available. This ensures proper behavior lifecycle.
     */
    private static final AssistHandleBehavior DEFAULT_BEHAVIOR = AssistHandleBehavior.REMINDER_EXP;

    private final Context mContext;
    private final AssistUtils mAssistUtils;
    private final Handler mHandler;
    private final Runnable mHideHandles = this::hideHandles;
    private final Runnable mShowAndGo = this::showAndGoInternal;
    private final Provider<AssistHandleViewController> mAssistHandleViewController;
    private final DeviceConfigHelper mDeviceConfigHelper;
    private final Map<AssistHandleBehavior, BehaviorController> mBehaviorMap;
    private final Lazy<AccessibilityManager> mA11yManager;

    private boolean mHandlesShowing = false;
    private long mHandlesLastHiddenAt;
    private long mShowAndGoEndsAt;
    /**
     * This should always be initialized as {@link AssistHandleBehavior#OFF} to ensure proper
     * behavior lifecycle.
     */
    private AssistHandleBehavior mCurrentBehavior = AssistHandleBehavior.OFF;
    private boolean mInGesturalMode;

    @Inject
    AssistHandleBehaviorController(
            Context context,
            AssistUtils assistUtils,
            @Named(ASSIST_HANDLE_THREAD_NAME) Handler handler,
            Provider<AssistHandleViewController> assistHandleViewController,
            DeviceConfigHelper deviceConfigHelper,
            Map<AssistHandleBehavior, BehaviorController> behaviorMap,
            NavigationModeController navigationModeController,
            Lazy<AccessibilityManager> a11yManager,
            DumpManager dumpManager) {
        mContext = context;
        mAssistUtils = assistUtils;
        mHandler = handler;
        mAssistHandleViewController = assistHandleViewController;
        mDeviceConfigHelper = deviceConfigHelper;
        mBehaviorMap = behaviorMap;
        mA11yManager = a11yManager;

        mInGesturalMode = QuickStepContract.isGesturalMode(
                navigationModeController.addListener(this::handleNavigationModeChange));

        setBehavior(getBehaviorMode());
        mDeviceConfigHelper.addOnPropertiesChangedListener(
                mHandler::post,
                (properties) -> {
                    if (properties.getKeyset().contains(
                            SystemUiDeviceConfigFlags.ASSIST_HANDLES_BEHAVIOR_MODE)) {
                        setBehavior(properties.getString(
                                SystemUiDeviceConfigFlags.ASSIST_HANDLES_BEHAVIOR_MODE, null));
                    }
                });

        dumpManager.registerDumpable(TAG, this);
    }

    @Override // AssistHandleCallbacks
    public void hide() {
        clearPendingCommands();
        mHandler.post(mHideHandles);
    }

    @Override // AssistHandleCallbacks
    public void showAndGo() {
        clearPendingCommands();
        mHandler.post(mShowAndGo);
    }

    private void showAndGoInternal() {
        maybeShowHandles(/* ignoreThreshold = */ false);
        long showAndGoDuration = getShowAndGoDuration();
        mShowAndGoEndsAt = SystemClock.elapsedRealtime() + showAndGoDuration;
        mHandler.postDelayed(mHideHandles, showAndGoDuration);
    }

    @Override // AssistHandleCallbacks
    public void showAndGoDelayed(long delayMs, boolean hideIfShowing) {
        clearPendingCommands();
        if (hideIfShowing) {
            mHandler.post(mHideHandles);
        }
        mHandler.postDelayed(mShowAndGo, delayMs);
    }

    @Override // AssistHandleCallbacks
    public void showAndStay() {
        clearPendingCommands();
        mHandler.post(() -> maybeShowHandles(/* ignoreThreshold = */ true));
    }

    public long getShowAndGoRemainingTimeMs() {
        return Long.max(mShowAndGoEndsAt - SystemClock.elapsedRealtime(), 0);
    }

    public boolean areHandlesShowing() {
        return mHandlesShowing;
    }

    void onAssistantGesturePerformed() {
        mBehaviorMap.get(mCurrentBehavior).onAssistantGesturePerformed();
    }

    void onAssistHandlesRequested() {
        if (mInGesturalMode) {
            mBehaviorMap.get(mCurrentBehavior).onAssistHandlesRequested();
        }
    }

    void setBehavior(AssistHandleBehavior behavior) {
        if (mCurrentBehavior == behavior) {
            return;
        }

        if (!mBehaviorMap.containsKey(behavior)) {
            Log.e(TAG, "Unsupported behavior requested: " + behavior.toString());
            return;
        }

        if (mInGesturalMode) {
            mBehaviorMap.get(mCurrentBehavior).onModeDeactivated();
            mBehaviorMap.get(behavior).onModeActivated(mContext, /* callbacks = */ this);
        }

        mCurrentBehavior = behavior;
    }

    private void setBehavior(@Nullable String behavior) {
        try {
            setBehavior(AssistHandleBehavior.valueOf(behavior));
        } catch (IllegalArgumentException | NullPointerException e) {
            Log.e(TAG, "Invalid behavior: " + behavior);
        }
    }

    private boolean handlesUnblocked(boolean ignoreThreshold) {
        if (!isUserSetupComplete()) {
            return false;
        }

        long timeSinceHidden = SystemClock.elapsedRealtime() - mHandlesLastHiddenAt;
        boolean notThrottled = ignoreThreshold || timeSinceHidden >= getShownFrequencyThreshold();
        ComponentName assistantComponent =
                mAssistUtils.getAssistComponentForUser(KeyguardUpdateMonitor.getCurrentUser());
        return notThrottled && assistantComponent != null;
    }

    private long getShownFrequencyThreshold() {
        return mDeviceConfigHelper.getLong(
                SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOWN_FREQUENCY_THRESHOLD_MS,
                DEFAULT_SHOWN_FREQUENCY_THRESHOLD_MS);
    }

    private long getShowAndGoDuration() {
        long configuredTime = mDeviceConfigHelper.getLong(
                SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOW_AND_GO_DURATION_MS,
                DEFAULT_SHOW_AND_GO_DURATION_MS);
        return mA11yManager.get().getRecommendedTimeoutMillis(
                (int) configuredTime, AccessibilityManager.FLAG_CONTENT_ICONS);
    }

    private String getBehaviorMode() {
        return mDeviceConfigHelper.getString(
                SystemUiDeviceConfigFlags.ASSIST_HANDLES_BEHAVIOR_MODE,
                DEFAULT_BEHAVIOR.toString());
    }

    private void maybeShowHandles(boolean ignoreThreshold) {
        if (mHandlesShowing) {
            return;
        }

        if (handlesUnblocked(ignoreThreshold)) {
            mHandlesShowing = true;
            AssistHandleViewController assistHandleViewController =
                    mAssistHandleViewController.get();
            if (assistHandleViewController == null) {
                Log.w(TAG, "Couldn't show handles, AssistHandleViewController unavailable");
            } else {
                assistHandleViewController.setAssistHintVisible(true);
            }
        }
    }

    private void hideHandles() {
        if (!mHandlesShowing) {
            return;
        }

        mHandlesShowing = false;
        mHandlesLastHiddenAt = SystemClock.elapsedRealtime();
        AssistHandleViewController assistHandleViewController =
                mAssistHandleViewController.get();
        if (assistHandleViewController == null) {
            Log.w(TAG, "Couldn't show handles, AssistHandleViewController unavailable");
        } else {
            assistHandleViewController.setAssistHintVisible(false);
        }
    }

    private void handleNavigationModeChange(int navigationMode) {
        boolean inGesturalMode = QuickStepContract.isGesturalMode(navigationMode);
        if (mInGesturalMode == inGesturalMode) {
            return;
        }

        mInGesturalMode = inGesturalMode;
        if (mInGesturalMode) {
            mBehaviorMap.get(mCurrentBehavior).onModeActivated(mContext, /* callbacks = */ this);
        } else {
            mBehaviorMap.get(mCurrentBehavior).onModeDeactivated();
            hide();
        }
    }

    private void clearPendingCommands() {
        mHandler.removeCallbacks(mHideHandles);
        mHandler.removeCallbacks(mShowAndGo);
        mShowAndGoEndsAt = 0;
    }

    private boolean isUserSetupComplete() {
        return Settings.Secure.getInt(
                mContext.getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
    }

    @VisibleForTesting
    void setInGesturalModeForTest(boolean inGesturalMode) {
        mInGesturalMode = inGesturalMode;
    }

    @Override // Dumpable
    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        pw.println("Current AssistHandleBehaviorController State:");

        pw.println("   mHandlesShowing=" + mHandlesShowing);
        pw.println("   mHandlesLastHiddenAt=" + mHandlesLastHiddenAt);
        pw.println("   mInGesturalMode=" + mInGesturalMode);

        pw.println("   Phenotype Flags:");
        pw.println("      "
                + SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOW_AND_GO_DURATION_MS + "(a11y modded)"
                + "="
                + getShowAndGoDuration());
        pw.println("      "
                + SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOWN_FREQUENCY_THRESHOLD_MS
                + "="
                + getShownFrequencyThreshold());
        pw.println("      "
                + SystemUiDeviceConfigFlags.ASSIST_HANDLES_BEHAVIOR_MODE
                + "="
                + getBehaviorMode());

        pw.println("   mCurrentBehavior=" + mCurrentBehavior.toString());
        mBehaviorMap.get(mCurrentBehavior).dump(pw, "   ");
    }

    interface BehaviorController {
        void onModeActivated(Context context, AssistHandleCallbacks callbacks);
        default void onModeDeactivated() {}
        default void onAssistantGesturePerformed() {}
        default void onAssistHandlesRequested() {}
        default void dump(PrintWriter pw, String prefix) {}
    }
}
+0 −41
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();

    /**
     * Same as show and go, but will not do anything until a delay has elapsed.
     *
     * Will be cancelled if another command is given during the delay.
     */
    void showAndGoDelayed(long delayMs, boolean hideIfShowing);

    /** Show the Assistant handles. */
    void showAndStay();
}
Loading