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

Commit ac64ce51 authored by Guojing Yuan's avatar Guojing Yuan Committed by Android (Google) Code Review
Browse files

Merge "[CDM] Add Permissions Sync toggle" into main

parents b9acbfac 756d23ca
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -12162,6 +12162,11 @@
    <!-- The summary of the head tracking [CHAR LIMIT=none] -->
    <string name="bluetooth_details_head_tracking_summary">Audio changes as you move your head to sound more natural</string>
    <!-- The title of CDM Permissions Sync -->
    <string name="bluetooth_details_permissions_sync_title">Sync permissions from phone</string>
    <!-- The summary of CDM Permissions Sync -->
    <string name="bluetooth_details_permissions_sync_summary">Give your watch the same app permissions that you’ve allowed on this phone</string>
    <!-- The title of the bluetooth audio device type selection [CHAR LIMIT=none] -->
    <string name="bluetooth_details_audio_device_types_title">Audio Device Type</string>
    <!-- The audio device type corresponding to unknown device type [CHAR LIMIT=none] -->
+3 −0
Original line number Diff line number Diff line
@@ -92,6 +92,9 @@
            settings:controller="com.android.settings.accessibility.LiveCaptionPreferenceController"/>
    </PreferenceCategory>

    <PreferenceCategory
        android:key="data_sync_group"/>

    <Preference
        android:key="keyboard_settings"
        android:persistent="false"
+137 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.bluetooth;

import android.companion.AssociationInfo;
import android.companion.CompanionDeviceManager;
import android.companion.datatransfer.PermissionSyncRequest;
import android.content.Context;

import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceScreen;
import androidx.preference.SwitchPreference;

import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.core.lifecycle.Lifecycle;

import com.google.common.base.Objects;

import java.util.Comparator;

/**
 * The controller of the CDM data sync in the bluetooth detail settings.
 */
public class BluetoothDetailsDataSyncController extends BluetoothDetailsController
        implements Preference.OnPreferenceClickListener {

    private static final int DUMMY_ASSOCIATION_ID = -1;
    private static final String TAG = "BTDataSyncController";
    private static final String KEY_DATA_SYNC_GROUP = "data_sync_group";
    private static final String KEY_PERM_SYNC = "perm_sync";

    @VisibleForTesting
    PreferenceCategory mPreferenceCategory;
    @VisibleForTesting
    int mAssociationId = DUMMY_ASSOCIATION_ID;

    private CachedBluetoothDevice mCachedDevice;
    private CompanionDeviceManager mCompanionDeviceManager;

    public BluetoothDetailsDataSyncController(Context context,
            PreferenceFragmentCompat fragment,
            CachedBluetoothDevice device,
            Lifecycle lifecycle) {
        super(context, fragment, device, lifecycle);
        mCachedDevice = device;
        mCompanionDeviceManager = context.getSystemService(CompanionDeviceManager.class);

        mCompanionDeviceManager.getAllAssociations().stream().filter(
                a -> Objects.equal(mCachedDevice.getAddress(),
                        a.getDeviceMacAddress().toString().toUpperCase())).max(
                Comparator.comparingLong(AssociationInfo::getTimeApprovedMs)).ifPresent(
                a -> mAssociationId = a.getId());
    }

    @Override
    public boolean isAvailable() {
        if (mAssociationId == DUMMY_ASSOCIATION_ID) {
            return false;
        }
        return true;
    }

    @Override
    public boolean onPreferenceClick(Preference preference) {
        SwitchPreference switchPreference = (SwitchPreference) preference;
        String key = switchPreference.getKey();
        if (key.equals(KEY_PERM_SYNC)) {
            if (switchPreference.isChecked()) {
                mCompanionDeviceManager.enablePermissionsSync(mAssociationId);
            } else {
                mCompanionDeviceManager.disablePermissionsSync(mAssociationId);
            }
        }
        return false;
    }

    @Override
    public String getPreferenceKey() {
        return KEY_DATA_SYNC_GROUP;
    }

    @Override
    protected void init(PreferenceScreen screen) {
        mPreferenceCategory = screen.findPreference(getPreferenceKey());
        refresh();
    }

    @Override
    protected void refresh() {
        SwitchPreference permSyncPref = mPreferenceCategory.findPreference(KEY_PERM_SYNC);
        if (permSyncPref == null) {
            permSyncPref = createPermSyncPreference(mPreferenceCategory.getContext());
            mPreferenceCategory.addPreference(permSyncPref);
        }

        boolean visible = false;
        boolean checked = false;
        PermissionSyncRequest request = mCompanionDeviceManager.getPermissionSyncRequest(
                mAssociationId);
        if (request != null) {
            visible = true;
            if (request.isUserConsented()) {
                checked = true;
            }
        }
        permSyncPref.setVisible(visible);
        permSyncPref.setChecked(checked);
    }

    @VisibleForTesting
    SwitchPreference createPermSyncPreference(Context context) {
        SwitchPreference pref = new SwitchPreference(context);
        pref.setKey(KEY_PERM_SYNC);
        pref.setTitle(context.getString(R.string.bluetooth_details_permissions_sync_title));
        pref.setSummary(context.getString(R.string.bluetooth_details_permissions_sync_summary));
        pref.setOnPreferenceClickListener(this);
        return pref;
    }
}
+2 −0
Original line number Diff line number Diff line
@@ -316,6 +316,8 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment
                    lifecycle));
            controllers.add(new BluetoothDetailsHearingDeviceControlsController(context, this,
                    mCachedDevice, lifecycle));
            controllers.add(new BluetoothDetailsDataSyncController(context, this,
                    mCachedDevice, lifecycle));
        }
        return controllers;
    }
+121 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.bluetooth;

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

import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;

import android.companion.CompanionDeviceManager;
import android.companion.datatransfer.PermissionSyncRequest;

import androidx.preference.PreferenceCategory;
import androidx.preference.SwitchPreference;

import com.android.settingslib.core.lifecycle.Lifecycle;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;

import java.util.Collections;

@RunWith(RobolectricTestRunner.class)
public class BluetoothDetailsDataSyncControllerTest extends BluetoothDetailsControllerTestBase {

    private static final String MAC_ADDRESS = "AA:BB:CC:DD:EE:FF";
    private static final int DUMMY_ASSOCIATION_ID = -1;
    private static final int ASSOCIATION_ID = 1;
    private static final String KEY_PERM_SYNC = "perm_sync";

    private BluetoothDetailsDataSyncController mController;
    @Mock
    private Lifecycle mLifecycle;
    @Mock
    private PreferenceCategory mPreferenceCategory;
    @Mock
    private CompanionDeviceManager mCompanionDeviceManager;

    private PermissionSyncRequest mPermissionSyncRequest;
    private SwitchPreference mPermSyncPreference;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);

        mContext = spy(RuntimeEnvironment.application);
        when(mContext.getSystemService(CompanionDeviceManager.class)).thenReturn(
                mCompanionDeviceManager);
        when(mCachedDevice.getAddress()).thenReturn(MAC_ADDRESS);
        when(mCompanionDeviceManager.getAllAssociations()).thenReturn(Collections.emptyList());
        mPermissionSyncRequest = new PermissionSyncRequest(ASSOCIATION_ID);
        when(mCompanionDeviceManager.getPermissionSyncRequest(ASSOCIATION_ID)).thenReturn(
                mPermissionSyncRequest);

        mController = new BluetoothDetailsDataSyncController(mContext, mFragment,
                mCachedDevice, mLifecycle);
        mController.mAssociationId = ASSOCIATION_ID;
        mController.mPreferenceCategory = mPreferenceCategory;

        mPermSyncPreference = mController.createPermSyncPreference(mContext);
        when(mPreferenceCategory.findPreference(KEY_PERM_SYNC)).thenReturn(mPermSyncPreference);
    }

    @Test
    public void isAvailable_noAssociations_returnsFalse() {
        mController.mAssociationId = DUMMY_ASSOCIATION_ID;
        assertThat(mController.isAvailable()).isFalse();
    }

    @Test
    public void isAvailable_hasAssociations_returnTrue() {
        assertThat(mController.isAvailable()).isTrue();
    }

    @Test
    public void refresh_permSyncNull_checkPreferenceInvisible() {
        mPermissionSyncRequest = null;
        when(mCompanionDeviceManager.getPermissionSyncRequest(ASSOCIATION_ID)).thenReturn(
                mPermissionSyncRequest);
        mController.refresh();

        assertThat(mPermSyncPreference.isVisible()).isFalse();
    }

    @Test
    public void refresh_permSyncEnabled_checkPreferenceOn() {
        mPermissionSyncRequest.setUserConsented(true);
        mController.refresh();

        assertThat(mPermSyncPreference.isVisible()).isTrue();
        assertThat(mPermSyncPreference.isChecked()).isTrue();
    }

    @Test
    public void refresh_permSyncDisabled_checkPreferenceOff() {
        mPermissionSyncRequest.setUserConsented(false);
        mController.refresh();

        assertThat(mPermSyncPreference.isVisible()).isTrue();
        assertThat(mPermSyncPreference.isChecked()).isFalse();
    }
}