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

Commit ca75e7e4 authored by Bill Lin's avatar Bill Lin
Browse files

Allow one-handed mode shortcut expand notification

1) Add OneHandedEventCallback for WMShell to register
   callback, and if one handed mode settings set show
   notifications, when user tap one handed mode shortcut
   notify WMShell send KEYCODE_SYSTEM_NAVIGATION_DOWN
   through OneHandedEventCallback with SysUIMainExecutor.

2) Add isReady() flag in OneHandedDisplayAreaOrganizer
   and Simplify the flow of onActivatedActionChanged()
   to prevent potential race problem while auto-enable
   One-handed and immeditately startOneHanded(), however,
   DisplayAreaOrganizer#onDisplayAreaAppeared() may not
   ready for transtion yet, and result mState keep in
   unfinsihed STATE_ENTERING.

3) Adjust the transiton duration from 800ms to 600ms
   800ms is a little bit long to affect timing of
   apply runtime overlay package.

4) DelayExecute setEnabledGesturalOverlay() until
   transition finised and at most delay once.
   - onSwipeToNotificationEnabledChanged() : DelayExecute
   - onEnabledSettingChanged() : DelayExecute
   - Ctor setupGesturalOverlay() : No DelayExecute
   - mState.isTransitioning() : DelayExecute

Test: WMShellUnitTests
Bug: 182425480
Change-Id: I2615c3b30ad95a858510b4bcc73d8f343843fc96
parent 96d2926f
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -40,7 +40,7 @@
    <integer name="long_press_dock_anim_duration">250</integer>

    <!-- Animation duration for translating of one handed when trigger / dismiss. -->
    <integer name="config_one_handed_translate_animation_duration">800</integer>
    <integer name="config_one_handed_translate_animation_duration">600</integer>

    <!-- One handed mode default offset % of display size -->
    <fraction name="config_one_handed_offset">40%</fraction>
+5 −0
Original line number Diff line number Diff line
@@ -67,6 +67,11 @@ public interface OneHanded {
     */
    void setLockedDisabled(boolean locked, boolean enabled);

    /**
     * Registers callback to notify WMShell when user tap shortcut to expand notification.
     */
    void registerEventCallback(OneHandedEventCallback callback);

    /**
     * Registers callback to be notified after {@link OneHandedDisplayAreaOrganizer}
     * transition start or finish
+68 −10
Original line number Diff line number Diff line
@@ -43,6 +43,7 @@ import android.util.Slog;
import android.view.Surface;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -72,6 +73,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
    private static final String ONE_HANDED_MODE_GESTURAL_OVERLAY =
            "com.android.internal.systemui.onehanded.gestural";
    private static final int OVERLAY_ENABLED_DELAY_MS = 250;
    private static final int DISPLAY_AREA_READY_RETRY_MS = 10;

    static final String SUPPORT_ONE_HANDED_MODE = "ro.support_one_handed_mode";

@@ -99,6 +101,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
    private final Handler mMainHandler;
    private final OneHandedImpl mImpl = new OneHandedImpl();

    private OneHandedEventCallback mEventCallback;
    private OneHandedDisplayAreaOrganizer mDisplayAreaOrganizer;
    private OneHandedBackgroundPanelOrganizer mBackgroundPanelOrganizer;

@@ -288,7 +291,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
        mTimeoutObserver = getObserver(this::onTimeoutSettingChanged);
        mTaskChangeExitObserver = getObserver(this::onTaskChangeExitSettingChanged);
        mSwipeToNotificationEnabledObserver =
                getObserver(this::onSwipeToNotificationEnabledSettingChanged);
                getObserver(this::onSwipeToNotificationEnabledChanged);

        mDisplayController.addDisplayChangingController(mRotationController);
        setupCallback();
@@ -358,14 +361,23 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
            Slog.d(TAG, "Temporary lock disabled");
            return;
        }

        if (!mDisplayAreaOrganizer.isReady()) {
            // Must wait until DisplayAreaOrganizer is ready for transitioning.
            mMainExecutor.executeDelayed(this::startOneHanded, DISPLAY_AREA_READY_RETRY_MS);
            return;
        }

        if (mState.isTransitioning() || mState.isInOneHanded()) {
            return;
        }

        final int currentRotation = mDisplayAreaOrganizer.getDisplayLayout().rotation();
        if (currentRotation != Surface.ROTATION_0 && currentRotation != Surface.ROTATION_180) {
            Slog.w(TAG, "One handed mode only support portrait mode");
            return;
        }

        mState.setState(STATE_ENTERING);
        final int yOffSet = Math.round(
                mDisplayAreaOrganizer.getDisplayLayout().height() * mOffSetFraction);
@@ -394,6 +406,10 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
        mOneHandedUiEventLogger.writeEvent(uiEvent);
    }

    void registerEventCallback(OneHandedEventCallback callback) {
        mEventCallback = callback;
    }

    @VisibleForTesting
    void registerTransitionCallback(OneHandedTransitionCallback callback) {
        mDisplayAreaOrganizer.registerTransitionCallback(callback);
@@ -463,9 +479,30 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
        };
    }

    @VisibleForTesting
    void notifyExpandNotification() {
        mMainExecutor.execute(() -> mEventCallback.notifyExpandNotification());
    }

    @VisibleForTesting
    void notifyUserConfigChanged(boolean success) {
        if (!success) {
            return;
        }
        // TODO Check UX if popup Toast to notify user when auto-enabled one-handed is good option.
        Toast.makeText(mContext, R.string.one_handed_tutorial_title, Toast.LENGTH_LONG).show();
    }

    @VisibleForTesting
    void onActivatedActionChanged() {
        if (mState.isTransitioning() || !isOneHandedEnabled()) {
        if (!isOneHandedEnabled()) {
            final boolean success = mOneHandedSettingsUtil.setOneHandedModeEnabled(
                    mContext.getContentResolver(), 1 /* Enabled for shortcut */, mUserId);
            notifyUserConfigChanged(success);
        }

        if (isSwipeToNotificationEnabled()) {
            notifyExpandNotification();
            return;
        }

@@ -494,11 +531,9 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
        setOneHandedEnabled(enabled);

        // Also checks swipe to notification settings since they all need gesture overlay.
        // Enabled overlay package may affect the current animation(e.g:Settings switch),
        // so we delay 250ms to enabled overlay after switch animation finish
        mMainExecutor.executeDelayed(() -> setEnabledGesturalOverlay(
        setEnabledGesturalOverlay(
                enabled || mOneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
                        mContext.getContentResolver(), mUserId)), OVERLAY_ENABLED_DELAY_MS);
                        mContext.getContentResolver(), mUserId), true /* DelayExecute */);
    }

    @VisibleForTesting
@@ -542,7 +577,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
    }

    @VisibleForTesting
    void onSwipeToNotificationEnabledSettingChanged() {
    void onSwipeToNotificationEnabledChanged() {
        final boolean enabled =
                mOneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
                        mContext.getContentResolver(), mUserId);
@@ -551,7 +586,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
        // Also checks one handed mode settings since they all need gesture overlay.
        setEnabledGesturalOverlay(
                enabled || mOneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
                        mContext.getContentResolver(), mUserId));
                        mContext.getContentResolver(), mUserId), true /* DelayExecute */);
    }

    private void setupTimeoutListener() {
@@ -569,11 +604,19 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
        return mIsOneHandedEnabled;
    }

    @VisibleForTesting
    boolean isSwipeToNotificationEnabled() {
        return mIsSwipeToNotificationEnabled;
    }

    private void updateOneHandedEnabled() {
        if (mState.getState() == STATE_ENTERING || mState.getState() == STATE_ACTIVE) {
            mMainExecutor.execute(() -> stopOneHanded());
        }

        // Reset and align shortcut one_handed_mode_activated status with current mState
        notifyShortcutState(mState.getState());

        mTouchHandler.onOneHandedEnabled(mIsOneHandedEnabled);

        if (!mIsOneHandedEnabled) {
@@ -608,12 +651,19 @@ public class OneHandedController implements RemoteCallable<OneHandedController>

        if (info != null && !info.isEnabled()) {
            // Enable the default gestural one handed overlay.
            setEnabledGesturalOverlay(true);
            setEnabledGesturalOverlay(true /* enabled */, false /* delayExecute */);
        }
    }

    @VisibleForTesting
    private void setEnabledGesturalOverlay(boolean enabled) {
    private void setEnabledGesturalOverlay(boolean enabled, boolean delayExecute) {
        if (mState.isTransitioning() || delayExecute) {
            // Enabled overlay package may affect the current animation(e.g:Settings switch),
            // so we delay 250ms to enabled overlay after switch animation finish, only delay once.
            mMainExecutor.executeDelayed(() -> setEnabledGesturalOverlay(enabled, false),
                    OVERLAY_ENABLED_DELAY_MS);
            return;
        }
        try {
            mOverlayManager.setEnabled(ONE_HANDED_MODE_GESTURAL_OVERLAY, enabled, USER_CURRENT);
        } catch (RemoteException e) {
@@ -628,6 +678,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
        if (enabled == isFeatureEnabled) {
            return;
        }

        mLockedDisabled = locked && !enabled;
    }

@@ -760,6 +811,13 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
            });
        }

        @Override
        public void registerEventCallback(OneHandedEventCallback callback) {
            mMainExecutor.execute(() -> {
                OneHandedController.this.registerEventCallback(callback);
            });
        }

        @Override
        public void registerTransitionCallback(OneHandedTransitionCallback callback) {
            mMainExecutor.execute(() -> {
+10 −1
Original line number Diff line number Diff line
@@ -61,11 +61,12 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {

    private DisplayLayout mDisplayLayout = new DisplayLayout();

    private float mLastVisualOffset = 0;
    private final Rect mLastVisualDisplayBounds = new Rect();
    private final Rect mDefaultDisplayBounds = new Rect();
    private final OneHandedSettingsUtil mOneHandedSettingsUtil;

    private boolean mIsReady;
    private float mLastVisualOffset = 0;
    private int mEnterExitAnimationDurationMs;

    private ArrayMap<WindowContainerToken, SurfaceControl> mDisplayAreaTokenMap = new ArrayMap();
@@ -157,6 +158,7 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
            final DisplayAreaAppearedInfo info = displayAreaInfos.get(i);
            onDisplayAreaAppeared(info.getDisplayAreaInfo(), info.getLeash());
        }
        mIsReady = true;
        updateDisplayBounds();
        return displayAreaInfos;
    }
@@ -164,9 +166,14 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
    @Override
    public void unregisterOrganizer() {
        super.unregisterOrganizer();
        mIsReady = false;
        resetWindowsOffset();
    }

    boolean isReady() {
        return mIsReady;
    }

    /**
     * Handler for display rotation changes by {@link DisplayLayout}
     *
@@ -312,6 +319,8 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
        pw.println(mDisplayAreaTokenMap);
        pw.print(innerPrefix + "mDefaultDisplayBounds=");
        pw.println(mDefaultDisplayBounds);
        pw.print(innerPrefix + "mIsReady=");
        pw.println(mIsReady);
        pw.print(innerPrefix + "mLastVisualDisplayBounds=");
        pw.println(mLastVisualDisplayBounds);
        pw.print(innerPrefix + "mLastVisualOffset=");
+28 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.wm.shell.onehanded;

/**
 * Additional callback interface for OneHanded events.
 */
public interface OneHandedEventCallback {
    /**
     * Called to notify expand notification shade.
     */
    default void notifyExpandNotification() {
    }
}
Loading