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

Commit 9c196d38 authored by Lorenzo Lucena Maguire's avatar Lorenzo Lucena Maguire
Browse files

Create Wallet Footer for Double Tap Power gesture settings

Add a footer explaining why the gesture does not work for wallet as a
target action if Quick Access Wallet Service is not available for the
current wallet role holder. Prompt the user to change their default
wallet role holder via a deeplink to the default wallet app permission
control page.

Android Settings Feature Request: b/380287172

Bug: 407619230
Test: atest tests/robotests/src/com/android/settings/gestures/DoubleTapPowerForWalletPreferenceControllerTest
Test: atest tests/robotests/src/com/android/settings/gestures/DoubleTapPowerWalletFooterPreferenceControllerTest
Test: Manually Tested.
Flag: android.service.quickaccesswallet.launch_wallet_option_on_power_double_tap

Change-Id: I097648d7c96d6cb61c38e1e17cee8a5394054cb6
parent bb434258
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -11580,6 +11580,10 @@ Data usage charges may apply.</string>
    <string name="double_tap_power_lockscreen_shortcut_tip_title">Change lock screen shortcut?</string>
    <!-- Description text for tip suggesting user to modify lockscreen shortcut. [CHAR LIMIT=200]-->
    <string name="double_tap_power_lockscreen_shortcut_tip_description"><xliff:g id="double_tap_power_target_action" example="Camera">%1$s</xliff:g> is also your lock screen shortcut. Want to change it?</string>
    <!-- Title for footer explaining why wallet is not available for the double tap power gesture. [CHAR LIMIT=200]-->
    <string name="double_tap_power_wallet_footer_title">Wallet is not available. Your current default wallet app does not support the gesture.</string>
    <!-- Summary for footer explaining why wallet is not available for the double tap power gesture. [CHAR LIMIT=100]-->
    <string name="double_tap_power_wallet_footer_learn_more_text">Tap here to change it.</string>
    <!-- Title text for double twist for camera mode [CHAR LIMIT=60]-->
+5 −0
Original line number Diff line number Diff line
@@ -47,4 +47,9 @@
        android:title="@string/double_tap_power_lockscreen_shortcut_tip_title"
        android:icon="@drawable/ic_double_tap_power_lockscreen_shortcut_tip_icon"
        settings:controller="com.android.settings.gestures.DoubleTapPowerLockscreenTipPreferenceController" />
    <com.android.settingslib.widget.FooterPreference
        android:key="gesture_double_tap_power_wallet_footer"
        android:title="@string/double_tap_power_wallet_footer_title"
        settings:searchable="false"
        settings:controller="com.android.settings.gestures.DoubleTapPowerWalletFooterPreferenceController" />
</PreferenceScreen>
+48 −5
Original line number Diff line number Diff line
@@ -16,14 +16,19 @@

package com.android.settings.gestures;

import android.app.role.OnRoleHoldersChangedListener;
import android.app.role.RoleManager;
import android.content.Context;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
import android.service.quickaccesswallet.QuickAccessWalletClient;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;

@@ -36,7 +41,9 @@ import com.android.settingslib.widget.SelectorWithWidgetPreference;
public class DoubleTapPowerForWalletPreferenceController extends BasePreferenceController
        implements LifecycleObserver, OnStart, OnStop {

    @Nullable private Preference mPreference;
    @Nullable private final RoleManager mRoleManager;
    @NonNull private QuickAccessWalletClient mQuickAccessWalletClient;
    @Nullable private SelectorWithWidgetPreference mPreference;
    private final ContentObserver mSettingsObserver =
            new ContentObserver(new Handler(Looper.getMainLooper())) {
                @Override
@@ -48,19 +55,41 @@ public class DoubleTapPowerForWalletPreferenceController extends BasePreferenceC
                            DoubleTapPowerSettingsUtils
                                    .DOUBLE_TAP_POWER_BUTTON_GESTURE_ENABLED_URI)) {
                        mPreference.setEnabled(
                                DoubleTapPowerSettingsUtils.isDoubleTapPowerButtonGestureEnabled(
                                        mContext));
                                DoubleTapPowerSettingsUtils
                                        .isDoubleTapPowerButtonGestureEnabled(mContext));
                    } else if (uri.equals(
                            DoubleTapPowerSettingsUtils
                                    .DOUBLE_TAP_POWER_BUTTON_GESTURE_TARGET_ACTION_URI)) {
                        updateState(mPreference);
                        mPreference.setChecked(
                                !DoubleTapPowerSettingsUtils
                                       .isDoubleTapPowerButtonGestureForCameraLaunchEnabled(
                                               mContext));
                    }
                }
            };
    private final OnRoleHoldersChangedListener mOnRoleHoldersChangedListener = (roleName, user) -> {
        if (!roleName.equals(RoleManager.ROLE_WALLET) || mPreference == null
                || user.getIdentifier() != UserHandle.myUserId()) {
            return;
        }
        mQuickAccessWalletClient = QuickAccessWalletClient.create(mContext);
        mPreference.setEnabled(mQuickAccessWalletClient.isWalletServiceAvailable());
    };

    public DoubleTapPowerForWalletPreferenceController(
            @NonNull Context context, @NonNull String preferenceKey) {
        super(context, preferenceKey);
        mRoleManager = mContext.getSystemService(RoleManager.class);
        mQuickAccessWalletClient = QuickAccessWalletClient.create(context);
    }

    @VisibleForTesting
    public DoubleTapPowerForWalletPreferenceController(
            @NonNull Context context, @NonNull String preferenceKey,
            @NonNull QuickAccessWalletClient quickAccessWalletClient) {
        super(context, preferenceKey);
        mRoleManager = mContext.getSystemService(RoleManager.class);
        mQuickAccessWalletClient = quickAccessWalletClient;
    }

    @Override
@@ -69,7 +98,7 @@ public class DoubleTapPowerForWalletPreferenceController extends BasePreferenceC
                .isMultiTargetDoubleTapPowerButtonGestureAvailable(mContext)) {
            return UNSUPPORTED_ON_DEVICE;
        }
        return DoubleTapPowerSettingsUtils.isDoubleTapPowerButtonGestureEnabled(mContext)
        return isPreferenceEnabled()
                ? AVAILABLE
                : DISABLED_DEPENDENT_SETTING;
    }
@@ -83,6 +112,7 @@ public class DoubleTapPowerForWalletPreferenceController extends BasePreferenceC
    @Override
    public void updateState(@NonNull Preference preference) {
        super.updateState(preference);
        preference.setEnabled(isPreferenceEnabled());
        if (preference instanceof SelectorWithWidgetPreference) {
            ((SelectorWithWidgetPreference) preference)
                    .setChecked(
@@ -106,10 +136,23 @@ public class DoubleTapPowerForWalletPreferenceController extends BasePreferenceC
    @Override
    public void onStart() {
        DoubleTapPowerSettingsUtils.registerObserver(mContext, mSettingsObserver);
        if (mRoleManager != null) {
            mRoleManager.addOnRoleHoldersChangedListenerAsUser(mContext.getMainExecutor(),
                    mOnRoleHoldersChangedListener, UserHandle.of(UserHandle.myUserId()));
        }
    }

    @Override
    public void onStop() {
        DoubleTapPowerSettingsUtils.unregisterObserver(mContext, mSettingsObserver);
        if (mRoleManager != null) {
            mRoleManager.removeOnRoleHoldersChangedListenerAsUser(mOnRoleHoldersChangedListener,
                    UserHandle.of(UserHandle.myUserId()));
        }
    }

    private boolean isPreferenceEnabled() {
        return DoubleTapPowerSettingsUtils.isDoubleTapPowerButtonGestureEnabled(mContext)
                && mQuickAccessWalletClient.isWalletServiceAvailable();
    }
}
+145 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.settings.gestures;

import static com.android.settings.gestures.DoubleTapPowerSettingsUtils.DOUBLE_TAP_POWER_BUTTON_GESTURE_ENABLED_URI;

import android.app.role.OnRoleHoldersChangedListener;
import android.app.role.RoleManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
import android.service.quickaccesswallet.QuickAccessWalletClient;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;

import com.android.settings.core.BasePreferenceController;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.core.lifecycle.events.OnStop;
import com.android.settingslib.widget.FooterPreference;

public class DoubleTapPowerWalletFooterPreferenceController extends BasePreferenceController
        implements LifecycleObserver, OnStart, OnStop {

    private final Handler mHandler = new Handler(Looper.getMainLooper());

    @Nullable
    private final RoleManager mRoleManager;
    @NonNull private QuickAccessWalletClient mQuickAccessWalletClient;
    @Nullable private FooterPreference mPreference;
    private final ContentObserver mSettingsObserver =
            new ContentObserver(mHandler) {
                @Override
                public void onChange(boolean selfChange, @Nullable Uri uri) {
                    if (mPreference != null) {
                        mPreference.setEnabled(
                                DoubleTapPowerSettingsUtils.isDoubleTapPowerButtonGestureEnabled(
                                        mContext)
                        );
                    }
                }
            };
    private final OnRoleHoldersChangedListener mOnRoleHoldersChangedListener = (roleName, user) -> {
        if (!roleName.equals(RoleManager.ROLE_WALLET) || mPreference == null
                || user.getIdentifier() != UserHandle.myUserId()) {
            return;
        }
        mQuickAccessWalletClient = QuickAccessWalletClient.create(mContext);
        mPreference.setVisible(!mQuickAccessWalletClient.isWalletServiceAvailable());
    };

    public DoubleTapPowerWalletFooterPreferenceController(
            @NonNull Context context, @NonNull String preferenceKey) {
        super(context, preferenceKey);
        mRoleManager = mContext.getSystemService(RoleManager.class);
        mQuickAccessWalletClient = QuickAccessWalletClient.create(context);
    }

    @VisibleForTesting
    public DoubleTapPowerWalletFooterPreferenceController(
            @NonNull Context context, @NonNull String preferenceKey,
            @NonNull QuickAccessWalletClient quickAccessWalletClient) {
        super(context, preferenceKey);
        mRoleManager = mContext.getSystemService(RoleManager.class);
        mQuickAccessWalletClient = quickAccessWalletClient;
    }

    @Override
    public int getAvailabilityStatus() {
        if (!DoubleTapPowerSettingsUtils
                .isMultiTargetDoubleTapPowerButtonGestureAvailable(mContext)) {
            return UNSUPPORTED_ON_DEVICE;
        }
        return DoubleTapPowerSettingsUtils.isDoubleTapPowerButtonGestureEnabled(mContext)
                ? AVAILABLE_UNSEARCHABLE
                : DISABLED_DEPENDENT_SETTING;
    }

    @Override
    public void displayPreference(PreferenceScreen screen) {
        super.displayPreference(screen);
        mPreference = screen.findPreference(getPreferenceKey());
        if (mPreference != null) {
            mPreference.setLearnMoreText(mContext.getString(
                    com.android.settings.R.string.double_tap_power_wallet_footer_learn_more_text));
            mPreference.setLearnMoreAction(v -> {
                final Intent intent = new Intent(Intent.ACTION_MANAGE_DEFAULT_APP);
                intent.putExtra(Intent.EXTRA_ROLE_NAME, RoleManager.ROLE_WALLET);
                mContext.startActivity(intent);
            });
        }
    }

    @Override
    public void updateState(@NonNull Preference preference) {
        super.updateState(preference);
        preference.setVisible(!mQuickAccessWalletClient.isWalletServiceAvailable());
        preference.setEnabled(
                DoubleTapPowerSettingsUtils.isDoubleTapPowerButtonGestureEnabled(mContext)
        );
    }

    @Override
    public void onStart() {
        final ContentResolver resolver = mContext.getContentResolver();
        resolver.registerContentObserver(
                DOUBLE_TAP_POWER_BUTTON_GESTURE_ENABLED_URI, true, mSettingsObserver);
        if (mRoleManager != null) {
            mRoleManager.addOnRoleHoldersChangedListenerAsUser(mContext.getMainExecutor(),
                    mOnRoleHoldersChangedListener, UserHandle.of(UserHandle.myUserId()));
        }
    }

    @Override
    public void onStop() {
        DoubleTapPowerSettingsUtils.unregisterObserver(mContext, mSettingsObserver);
        if (mRoleManager != null) {
            mRoleManager.removeOnRoleHoldersChangedListenerAsUser(mOnRoleHoldersChangedListener,
                    UserHandle.of(UserHandle.myUserId()));
        }
    }
}
+48 −1
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import static org.mockito.Mockito.when;

import android.content.Context;
import android.content.res.Resources;
import android.service.quickaccesswallet.QuickAccessWalletClient;

import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -45,15 +46,22 @@ public class DoubleTapPowerForWalletPreferenceControllerTest {
    private static final String KEY = "gesture_double_power_tap_launch_wallet";
    private Context mContext;
    private Resources mResources;
    private final QuickAccessWalletClient mQuickAccessWalletClient = mock(
            QuickAccessWalletClient.class);

    private DoubleTapPowerForWalletPreferenceController mController;
    private SelectorWithWidgetPreference mPreference;


    @Before
    public void setUp() {
        mContext = spy(ApplicationProvider.getApplicationContext());
        mResources = mock(Resources.class);
        when(mContext.getResources()).thenReturn(mResources);
        mController = new DoubleTapPowerForWalletPreferenceController(mContext, KEY);
        when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(true);

        mController = new DoubleTapPowerForWalletPreferenceController(mContext, KEY,
                mQuickAccessWalletClient);
        mPreference = new SelectorWithWidgetPreference(mContext);
    }

@@ -75,6 +83,34 @@ public class DoubleTapPowerForWalletPreferenceControllerTest {
        assertThat(mPreference.isChecked()).isFalse();
    }

    @Test
    public void updateState_quickAccessWalletNotAvailable_preferenceDisabled() {
        DoubleTapPowerSettingsUtils.setDoubleTapPowerButtonGestureEnabled(mContext, true);
        when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(false);

        mController.updateState(mPreference);

        assertThat(mPreference.isEnabled()).isFalse();
    }

    @Test
    public void updateState_doubleTapPowerGestureDisabled_preferenceDisabled() {
        DoubleTapPowerSettingsUtils.setDoubleTapPowerButtonGestureEnabled(mContext, false);

        mController.updateState(mPreference);

        assertThat(mPreference.isEnabled()).isFalse();
    }

    @Test
    public void updateState_quickAccessWalletAndDoubleTapPowerGestureEnabled_preferenceEnabled() {
        DoubleTapPowerSettingsUtils.setDoubleTapPowerButtonGestureEnabled(mContext, true);

        mController.updateState(mPreference);

        assertThat(mPreference.isEnabled()).isTrue();
    }

    @Test
    public void getAvailabilityStatus_setDoubleTapPowerGestureNotAvailable_preferenceUnsupported() {
        when(mResources.getInteger(R.integer.config_doubleTapPowerGestureMode)).thenReturn(
@@ -94,6 +130,17 @@ public class DoubleTapPowerForWalletPreferenceControllerTest {
                .isEqualTo(BasePreferenceController.DISABLED_DEPENDENT_SETTING);
    }

    @Test
    public void getAvailabilityStatus_quickAccessWalletNotAvailable_preferenceDisabled() {
        when(mResources.getInteger(R.integer.config_doubleTapPowerGestureMode)).thenReturn(
                DOUBLE_TAP_POWER_MULTI_TARGET_MODE);
        DoubleTapPowerSettingsUtils.setDoubleTapPowerButtonGestureEnabled(mContext, true);
        when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(false);

        assertThat(mController.getAvailabilityStatus())
                .isEqualTo(BasePreferenceController.DISABLED_DEPENDENT_SETTING);
    }

    @Test
    public void getAvailabilityStatus_setDoubleTapPowerWalletLaunchEnabled_preferenceEnabled() {
        when(mResources.getInteger(R.integer.config_doubleTapPowerGestureMode)).thenReturn(
Loading