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

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

Implement Timer for one handed to handle auto-exit (8/N)

1) The timeout values defined in Settings
2) When user dragging in displayArea reset timer
3) When stop one handed remove timer

Test: atest OneHandedDisplayAreaOrganizerTest
Test: atest OneHandedManagerImplTest
Test: atest OneHandedUITest
Test: atest OneHandedSettingsUtilTest
Test: atest SystemUITests
Test: atest DisplayAreaPolicyBuilderTest
Bug: 150747909
Change-Id: I10ff6e574cd806dfab69f64732017e90c4a8d0e4
parent a20e0f19
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -48,6 +48,7 @@ public class OneHandedManagerImpl implements OneHandedManager, Dumpable {
    private float mOffSetFraction;
    private DisplayController mDisplayController;
    private OneHandedDisplayAreaOrganizer mDisplayAreaOrganizer;
    private OneHandedTimeoutHandler mTimeoutHandler;
    private OneHandedTransitionCallback mTransitionCallback;
    private SysUiState mSysUiFlagContainer;

@@ -67,6 +68,7 @@ public class OneHandedManagerImpl implements OneHandedManager, Dumpable {
                context.getResources().getFraction(R.fraction.config_one_handed_offset, 1, 1);
        mIsOneHandedEnabled = OneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
                context.getContentResolver());
        mTimeoutHandler = OneHandedTimeoutHandler.get();
        updateOneHandedEnabled();
        setupGestures();
    }
@@ -87,6 +89,7 @@ public class OneHandedManagerImpl implements OneHandedManager, Dumpable {
        if (!mDisplayAreaOrganizer.isInOneHanded() && mIsOneHandedEnabled) {
            final int yOffSet = Math.round(getDisplaySize().y * mOffSetFraction);
            mDisplayAreaOrganizer.scheduleOffset(0, yOffSet);
            mTimeoutHandler.resetTimer();
        }
    }

@@ -97,6 +100,7 @@ public class OneHandedManagerImpl implements OneHandedManager, Dumpable {
    public void stopOneHanded() {
        if (mDisplayAreaOrganizer.isInOneHanded()) {
            mDisplayAreaOrganizer.scheduleOffset(0, 0);
            mTimeoutHandler.removeTimer();
        }
    }

+167 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.onehanded;

import static com.android.systemui.onehanded.OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS;

import android.os.Handler;
import android.os.Looper;
import android.os.Message;

import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;

import com.android.systemui.Dumpable;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import javax.inject.Singleton;

/**
 * Timeout handler for stop one handed mode operations.
 */
@Singleton
public class OneHandedTimeoutHandler implements Dumpable {
    private static final String TAG = "OneHandedTimeoutHandler";
    private static boolean sIsDragging = false;
    // Default timeout is ONE_HANDED_TIMEOUT_MEDIUM
    private static @OneHandedSettingsUtil.OneHandedTimeout int sTimeout =
            ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS;
    private static long sTimeoutMs = TimeUnit.SECONDS.toMillis(sTimeout);
    private static OneHandedTimeoutHandler sInstance;
    private static List<TimeoutListener> sListeners = new ArrayList<>();

    @VisibleForTesting
    static final int ONE_HANDED_TIMEOUT_STOP_MSG = 1;
    @VisibleForTesting
    static Handler sHandler;

    /**
     * Get the current config of timeout
     *
     * @return timeout of current config
     */
    public @OneHandedSettingsUtil.OneHandedTimeout int getTimeout() {
        return sTimeout;
    }

    /**
     * Listens for notify timeout events
     */
    public interface TimeoutListener {
        /**
         * Called whenever the config time out
         *
         * @param timeoutTime The time in seconds to trigger timeout
         */
        void onTimeout(int timeoutTime);
    }

    /**
     * Set the specific timeout of {@link OneHandedSettingsUtil.OneHandedTimeout}
     */
    public static void setTimeout(@OneHandedSettingsUtil.OneHandedTimeout int timeout) {
        sTimeout = timeout;
        sTimeoutMs = TimeUnit.SECONDS.toMillis(sTimeout);
        resetTimer();
    }

    /**
     * Reset the timer when one handed trigger or user is operating in some conditions
     */
    public static void removeTimer() {
        sHandler.removeMessages(ONE_HANDED_TIMEOUT_STOP_MSG);
    }

    /**
     * Reset the timer when one handed trigger or user is operating in some conditions
     */
    public static void resetTimer() {
        removeTimer();
        if (sTimeout == OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_NEVER) {
            return;
        }
        if (sTimeout != OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_NEVER) {
            sHandler.sendEmptyMessageDelayed(ONE_HANDED_TIMEOUT_STOP_MSG, sTimeoutMs);
        }
    }

    /**
     * Register timeout listener to receive time out events
     *
     * @param listener the listener be sent events when times up
     */
    public static void registerTimeoutListener(TimeoutListener listener) {
        sListeners.add(listener);
    }

    /**
     * Private constructor due to Singleton pattern
     */
    private OneHandedTimeoutHandler() {
    }

    /**
     * Singleton pattern to get {@link OneHandedTimeoutHandler} instance
     *
     * @return the static update thread instance
     */
    public static OneHandedTimeoutHandler get() {
        synchronized (OneHandedTimeoutHandler.class) {
            if (sInstance == null) {
                sInstance = new OneHandedTimeoutHandler();
            }
            if (sHandler == null) {
                sHandler = new Handler(Looper.myLooper()) {
                    @Override
                    public void handleMessage(Message msg) {
                        if (msg.what == ONE_HANDED_TIMEOUT_STOP_MSG) {
                            onStop();
                        }
                    }
                };
                if (sTimeout != OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_NEVER) {
                    sHandler.sendEmptyMessageDelayed(ONE_HANDED_TIMEOUT_STOP_MSG, sTimeoutMs);
                }
            }
            return sInstance;
        }
    }

    private static void onStop() {
        // TODO (b/149366439) UIEvent metrics add here, timeout stop  one handed
        for (int i = sListeners.size() - 1; i >= 0; i--) {
            final TimeoutListener listener = sListeners.get(i);
            listener.onTimeout(sTimeout);
        }
    }

    @Override
    public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
        final String innerPrefix = "  ";
        pw.println(TAG + "states: ");
        pw.print(innerPrefix + "sTimeout=");
        pw.println(sTimeout);
        pw.print(innerPrefix + "sListeners=");
        pw.println(sListeners);
    }

}
+18 −3
Original line number Diff line number Diff line
@@ -44,6 +44,7 @@ public class OneHandedUI extends SystemUI implements CommandQueue.Callbacks, Dum
    private final CommandQueue mCommandQueue;
    private final Handler mMainHandler = new Handler(Looper.getMainLooper());
    private final OneHandedSettingsUtil mSettingUtil;
    private final OneHandedTimeoutHandler mTimeoutHandler;

    private final ContentObserver mEnabledObserver = new ContentObserver(mMainHandler) {
        @Override
@@ -61,9 +62,11 @@ public class OneHandedUI extends SystemUI implements CommandQueue.Callbacks, Dum
        @Override
        public void onChange(boolean selfChange) {
            // TODO (b/149366439) UIEvent metrics add here, user config timeout in settings
            final boolean enabled = OneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
            if (mTimeoutHandler != null) {
                final int newTimeout = OneHandedSettingsUtil.getSettingsOneHandedModeTimeout(
                        mContext.getContentResolver());
            mOneHandedManager.setOneHandedEnabled(enabled);
                mTimeoutHandler.setTimeout(newTimeout);
            }
        }
    };

@@ -88,6 +91,7 @@ public class OneHandedUI extends SystemUI implements CommandQueue.Callbacks, Dum
            if (!supportOneHanded) return; */
        mOneHandedManager = oneHandedManager;
        mSettingUtil =  settingsUtil;
        mTimeoutHandler = OneHandedTimeoutHandler.get();
    }

    @Override
@@ -97,9 +101,14 @@ public class OneHandedUI extends SystemUI implements CommandQueue.Callbacks, Dum
            if (!supportOneHanded) return; */
        mCommandQueue.addCallback(this);
        setupSettingObservers();
        setupTimeoutListener();
        updateSettings();
    }

    private void setupTimeoutListener() {
        mTimeoutHandler.registerTimeoutListener(timeoutTime -> stopOneHanded());
    }

    private void setupSettingObservers() {
        mSettingUtil.registerSettingsKeyObserver(Settings.Secure.ONE_HANDED_MODE_ENABLED,
                mContext.getContentResolver(), mEnabledObserver);
@@ -112,6 +121,8 @@ public class OneHandedUI extends SystemUI implements CommandQueue.Callbacks, Dum
    private void updateSettings() {
        mOneHandedManager.setOneHandedEnabled(
                mSettingUtil.getSettingsOneHandedModeEnabled(mContext.getContentResolver()));
        mTimeoutHandler.setTimeout(
                mSettingUtil.getSettingsOneHandedModeTimeout(mContext.getContentResolver()));
    }

    /**
@@ -141,6 +152,10 @@ public class OneHandedUI extends SystemUI implements CommandQueue.Callbacks, Dum
            ((OneHandedManagerImpl) mOneHandedManager).dump(fd, pw, args);
        }

        if (mTimeoutHandler != null) {
            mTimeoutHandler.dump(fd, pw, args);
        }

        if (mSettingUtil != null) {
            mSettingUtil.dump(pw, innerPrefix, mContext.getContentResolver());
        }
+12 −1
Original line number Diff line number Diff line
@@ -38,14 +38,16 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;

@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class OneHandedManagerImplTest extends OneHandedTestCase {

    OneHandedManagerImpl mOneHandedManagerImpl;
    OneHandedTimeoutHandler mTimeoutHandler;

    @Mock
    DisplayController mMockDisplayController;
    @Mock
@@ -62,6 +64,7 @@ public class OneHandedManagerImplTest extends OneHandedTestCase {
                mMockDisplayController,
                mMockDisplayAreaOrganizer,
                mMockSysUiState);
        mTimeoutHandler = Mockito.spy(OneHandedTimeoutHandler.get());

        when(mMockDisplayAreaOrganizer.isInOneHanded()).thenReturn(false);
    }
@@ -105,4 +108,12 @@ public class OneHandedManagerImplTest extends OneHandedTestCase {
        verify(mMockDisplayAreaOrganizer, atLeastOnce()).registerTransitionCallback(any());
    }

    @Test
    public void testStopOneHanded_shouldRemoveTimer() {
        mOneHandedManagerImpl.stopOneHanded();

        verify(mTimeoutHandler, times(1)).removeTimer();
    }


}
+105 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.onehanded;

import static com.android.systemui.onehanded.OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_LONG_IN_SECONDS;
import static com.android.systemui.onehanded.OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS;
import static com.android.systemui.onehanded.OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_NEVER;
import static com.android.systemui.onehanded.OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_SHORT_IN_SECONDS;
import static com.android.systemui.onehanded.OneHandedTimeoutHandler.ONE_HANDED_TIMEOUT_STOP_MSG;

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.Mockito.verify;

import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;

import androidx.test.filters.SmallTest;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;

@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class OneHandedTimeoutHandlerTest extends OneHandedTestCase {
    OneHandedTimeoutHandler mTimeoutHandler;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        mTimeoutHandler = Mockito.spy(OneHandedTimeoutHandler.get());
    }

    @Test
    public void testTimeoutHandler_isNotNull() {
        assertThat(OneHandedTimeoutHandler.get()).isNotNull();
    }

    @Test
    public void testTimeoutHandler_getTimeout_defaultMedium() {
        assertThat(OneHandedTimeoutHandler.get().getTimeout()).isEqualTo(
                ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS);
    }

    @Test
    public void testTimeoutHandler_setNewTime_resetTimer() {
        mTimeoutHandler.setTimeout(ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS);
        verify(mTimeoutHandler).resetTimer();
        assertThat(mTimeoutHandler.sHandler.hasMessages(ONE_HANDED_TIMEOUT_STOP_MSG)).isNotNull();
    }

    @Test
    public void testSetTimeoutNever_neverResetTimer() {
        mTimeoutHandler.setTimeout(ONE_HANDED_TIMEOUT_NEVER);
        assertThat(!mTimeoutHandler.sHandler.hasMessages(ONE_HANDED_TIMEOUT_STOP_MSG)).isNotNull();
    }

    @Test
    public void testSetTimeoutShort() {
        mTimeoutHandler.setTimeout(ONE_HANDED_TIMEOUT_SHORT_IN_SECONDS);
        verify(mTimeoutHandler).resetTimer();
        assertThat(mTimeoutHandler.sHandler.hasMessages(ONE_HANDED_TIMEOUT_STOP_MSG)).isNotNull();
    }

    @Test
    public void testSetTimeoutMedium() {
        mTimeoutHandler.setTimeout(ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS);
        verify(mTimeoutHandler).resetTimer();
        assertThat(mTimeoutHandler.sHandler.hasMessages(
                ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS)).isNotNull();
    }

    @Test
    public void testSetTimeoutLong() {
        mTimeoutHandler.setTimeout(ONE_HANDED_TIMEOUT_LONG_IN_SECONDS);
        assertThat(mTimeoutHandler.getTimeout()).isEqualTo(ONE_HANDED_TIMEOUT_LONG_IN_SECONDS);
    }

    @Test
    public void testDragging_shouldRemoveAndSendEmptyMessageDelay() {
        final boolean isDragging = true;
        mTimeoutHandler.setTimeout(ONE_HANDED_TIMEOUT_LONG_IN_SECONDS);
        mTimeoutHandler.resetTimer();
        TestableLooper.get(this).processAllMessages();
        assertThat(mTimeoutHandler.sHandler.hasMessages(ONE_HANDED_TIMEOUT_STOP_MSG)).isNotNull();
    }
}
Loading