Snap for 10817717 from d43d6f259a to udc-qpr1-release

Change-Id: Idf4017b1ad74cd5f8e940aa09fe8c18310e06161
This commit is contained in:
Android Build Coastguard Worker
2023-09-16 01:23:36 +00:00
14 changed files with 379 additions and 28 deletions

View File

@@ -12034,6 +12034,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</string>
<!-- The summary of CDM Permissions Sync -->
<string name="bluetooth_details_permissions_sync_summary">Give <xliff:g id="remote_device_name" example="Pixel Watch">%1$s</xliff:g> the same app permissions that youve allowed on <xliff:g id="local_device_name" example="Pixel 6">%2$s</xliff:g></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] -->
@@ -12165,6 +12170,8 @@
<string name="user_aspect_ratio_3_2">3:2</string>
<!-- [CHAR LIMIT=NONE] 4:3 aspect ratio entry -->
<string name="user_aspect_ratio_4_3">4:3</string>
<!-- [CHAR LIMIT=NONE] Aspect ratio a11y message announced to replace colon in aspect ratio entry e.g. 3 by 2 -->
<string name="user_aspect_ratio_option_a11y"><xliff:g id="numerator">%1$s</xliff:g> by <xliff:g id="denominator">%2$s</xliff:g></string>
<!-- [CHAR LIMIT=NONE] Warning description for app info aspect ratio page -->
<string name="app_aspect_ratio_footer">The app will restart when you change aspect ratio. You may lose unsaved changes. Some apps may not be optimized for certain aspect ratios.</string>

View File

@@ -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"

View File

@@ -45,17 +45,14 @@
<com.android.settings.applications.appcompat.RadioWithImagePreference
android:key="16_9_pref"
android:title="@string/user_aspect_ratio_16_9"
android:icon="@drawable/ic_app_aspect_ratio_16_9"/>
<com.android.settings.applications.appcompat.RadioWithImagePreference
android:key="4_3_pref"
android:title="@string/user_aspect_ratio_4_3"
android:icon="@drawable/ic_app_aspect_ratio_4_3"/>
<com.android.settings.applications.appcompat.RadioWithImagePreference
android:key="3_2_pref"
android:title="@string/user_aspect_ratio_3_2"
android:icon="@drawable/ic_app_aspect_ratio_3_2"/>
<com.android.settingslib.widget.FooterPreference

View File

@@ -227,8 +227,7 @@ public class UserAspectRatioDetails extends AppInfoBase implements
pref.setVisible(false);
return;
}
pref.setTitle(mUserAspectRatioManager.getUserMinAspectRatioEntry(aspectRatio,
mPackageName));
pref.setTitle(mUserAspectRatioManager.getAccessibleEntry(aspectRatio, mPackageName));
pref.setOnClickListener(this);
mAspectRatioPreferences.add(pref);
}

View File

@@ -36,6 +36,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.settings.R;
import com.android.settings.Utils;
import com.google.common.annotations.VisibleForTesting;
@@ -64,12 +65,14 @@ public class UserAspectRatioManager {
/** Apps that have launcher entry defined in manifest */
private final List<ResolveInfo> mInfoHasLauncherEntryList;
private final Map<Integer, String> mUserAspectRatioMap;
private final Map<Integer, CharSequence> mUserAspectRatioA11yMap;
public UserAspectRatioManager(@NonNull Context context) {
mContext = context;
mIPm = AppGlobals.getPackageManager();
mInfoHasLauncherEntryList = mContext.getPackageManager().queryIntentActivities(
UserAspectRatioManager.LAUNCHER_ENTRY_INTENT, PackageManager.GET_META_DATA);
mUserAspectRatioA11yMap = new ArrayMap<>();
mUserAspectRatioMap = getUserMinAspectRatioMapping();
}
@@ -106,6 +109,16 @@ public class UserAspectRatioManager {
return mUserAspectRatioMap.get(aspectRatio);
}
/**
* @return corresponding accessible string for {@link PackageManager.UserMinAspectRatio} value
*/
@NonNull
public CharSequence getAccessibleEntry(@PackageManager.UserMinAspectRatio int aspectRatio,
String packageName) {
return mUserAspectRatioA11yMap.getOrDefault(aspectRatio,
getUserMinAspectRatioEntry(aspectRatio, packageName));
}
/**
* @return corresponding aspect ratio string for package name and user
*/
@@ -185,6 +198,7 @@ public class UserAspectRatioManager {
final int aspectRatioVal = userMinAspectRatioValues[i];
final String aspectRatioString = getAspectRatioStringOrDefault(
userMinAspectRatioStrings[i], aspectRatioVal);
boolean containsColon = aspectRatioString.contains(":");
switch (aspectRatioVal) {
// Only map known values of UserMinAspectRatio and ignore unknown entries
case PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN:
@@ -194,6 +208,14 @@ public class UserAspectRatioManager {
case PackageManager.USER_MIN_ASPECT_RATIO_4_3:
case PackageManager.USER_MIN_ASPECT_RATIO_16_9:
case PackageManager.USER_MIN_ASPECT_RATIO_3_2:
if (containsColon) {
String[] aspectRatioDigits = aspectRatioString.split(":");
String accessibleString = getAccessibleOption(aspectRatioDigits[0],
aspectRatioDigits[1]);
final CharSequence accessibleSequence = Utils.createAccessibleSequence(
aspectRatioString, accessibleString);
mUserAspectRatioA11yMap.put(aspectRatioVal, accessibleSequence);
}
userMinAspectRatioMap.put(aspectRatioVal, aspectRatioString);
}
}
@@ -204,6 +226,12 @@ public class UserAspectRatioManager {
return userMinAspectRatioMap;
}
@NonNull
private String getAccessibleOption(String numerator, String denominator) {
return mContext.getResources().getString(R.string.user_aspect_ratio_option_a11y,
numerator, denominator);
}
@NonNull
private String getAspectRatioStringOrDefault(@Nullable String aspectRatioString,
@PackageManager.UserMinAspectRatio int aspectRatioVal) {

View File

@@ -0,0 +1,145 @@
/*
* 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 android.provider.Settings;
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 true;
}
@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);
}
if (mAssociationId == DUMMY_ASSOCIATION_ID) {
permSyncPref.setVisible(false);
return;
}
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,
mCachedDevice.getName(),
Settings.Global.getString(context.getContentResolver(), "device_name")));
pref.setOnPreferenceClickListener(this);
return pref;
}
}

View File

@@ -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;
}

View File

@@ -47,14 +47,4 @@ public class SettingsUIDeviceConfig {
*/
public static final String BT_LE_AUDIO_DEVICE_DETAIL_ENABLED =
"bt_le_audio_device_detail_enabled";
/**
* {@code true} if press and hold nav handle to search is enabled.
*/
public static final String PRESS_HOLD_NAV_HANDLE_TO_SEARCH =
"press_hold_nav_handle_to_search";
/**
* {@code true} if long press home button to search is enabled.
*/
public static final String LONG_PRESS_HOME_BUTTON_TO_SEARCH =
"long_press_home_button_to_search";
}

View File

@@ -61,15 +61,12 @@ public class PanelSlicesAdapter
private final List<LiveData<Slice>> mSliceLiveData;
private final int mMetricsCategory;
private final PanelFragment mPanelFragment;
private final String mSliceClickActionLabel;
public PanelSlicesAdapter(
PanelFragment fragment, Map<Uri, LiveData<Slice>> sliceLiveData, int metricsCategory) {
mPanelFragment = fragment;
mSliceLiveData = new ArrayList<>(sliceLiveData.values());
mMetricsCategory = metricsCategory;
mSliceClickActionLabel = mPanelFragment.getContext().getString(
R.string.accessibility_action_label_panel_slice);
}
@NonNull
@@ -77,7 +74,7 @@ public class PanelSlicesAdapter
public SliceRowViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
final Context context = viewGroup.getContext();
final LayoutInflater inflater = LayoutInflater.from(context);
View view;
final View view;
if (viewType == PanelContent.VIEW_TYPE_SLIDER) {
view = inflater.inflate(R.layout.panel_slice_slider_row, viewGroup, false);
} else {
@@ -189,7 +186,6 @@ public class PanelSlicesAdapter
return;
}
sliceView.setTag(ROW_VIEW_TAG, new Object());
sliceView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom,
@@ -208,15 +204,17 @@ public class PanelSlicesAdapter
* Update the action label for TalkBack to be more specific
* @param view the RowView within the Slice
*/
private void setActionLabel(View view) {
@VisibleForTesting void setActionLabel(View view) {
view.setAccessibilityDelegate(new View.AccessibilityDelegate() {
@Override
public void onInitializeAccessibilityNodeInfo(View host,
AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(host, info);
AccessibilityNodeInfo.AccessibilityAction customClick =
new AccessibilityNodeInfo.AccessibilityAction(
ACTION_CLICK, mSliceClickActionLabel);
new AccessibilityNodeInfo.AccessibilityAction(ACTION_CLICK, host
.getResources()
.getString(R.string.accessibility_action_label_panel_slice));
info.addAction(customClick);
}
});

View File

@@ -116,7 +116,8 @@ fun UserAspectRatioAppList(
override val resId = R.raw.user_aspect_ratio_education
override val resourceType = ResourceType.LOTTIE
})
}
},
noMoreOptions = true,
)
}

View File

@@ -451,8 +451,6 @@ public class WifiDetailPreferenceController2 extends AbstractPreferenceControlle
ImageView iconView = headerPref.findViewById(R.id.entity_header_icon);
iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
mEntityHeaderController.setLabel(mWifiEntry.getTitle());
}
private String getExpiryTimeSummary() {
@@ -491,6 +489,7 @@ public class WifiDetailPreferenceController2 extends AbstractPreferenceControlle
mSummaryHeaderController.updateState(mDataUsageSummaryPref);
} else {
mEntityHeaderController
.setLabel(mWifiEntry.getTitle())
.setSummary(mWifiEntry.getSummary())
.setSecondSummary(getExpiryTimeSummary())
.setRecyclerView(mFragment.getListView(), mLifecycle)

View File

@@ -0,0 +1,121 @@
/*
* 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_returnsTrue() {
assertThat(mController.isAvailable()).isTrue();
}
@Test
public void refresh_permSyncNull_preferenceVisibleFalse() {
mPermissionSyncRequest = null;
when(mCompanionDeviceManager.getPermissionSyncRequest(ASSOCIATION_ID)).thenReturn(
mPermissionSyncRequest);
mController.refresh();
assertThat(mPermSyncPreference.isVisible()).isFalse();
}
@Test
public void refresh_permSyncEnabled_preferenceCheckedTrue() {
mPermissionSyncRequest.setUserConsented(true);
mController.refresh();
assertThat(mPermSyncPreference.isVisible()).isTrue();
assertThat(mPermSyncPreference.isChecked()).isTrue();
}
@Test
public void refresh_permSyncDisabled_preferenceCheckedFalse() {
mPermissionSyncRequest.setUserConsented(false);
mController.refresh();
assertThat(mPermSyncPreference.isVisible()).isTrue();
assertThat(mPermSyncPreference.isChecked()).isFalse();
}
}

View File

@@ -33,9 +33,13 @@ import static org.mockito.Mockito.when;
import android.content.Context;
import android.net.Uri;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import androidx.lifecycle.LiveData;
import androidx.slice.Slice;
@@ -44,6 +48,7 @@ import com.android.settings.R;
import com.android.settings.panel.PanelSlicesAdapter.SliceRowViewHolder;
import com.android.settings.testutils.FakeFeatureFactory;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -94,7 +99,6 @@ public class PanelSlicesAdapterTest {
.get()
.getSupportFragmentManager()
.findFragmentById(R.id.main_content));
}
private void addTestLiveData(Uri uri) {
@@ -106,6 +110,61 @@ public class PanelSlicesAdapterTest {
mData.put(uri, liveData);
}
/**
* Edge case where fragment context is not available.
*/
@Test
public void withPanelFragmentContextNull_createAdapter_noExceptionThrown() {
when(mPanelFragment.getContext()).thenReturn(null);
final PanelSlicesAdapter adapter = spy(new PanelSlicesAdapter(mPanelFragment, mData, 0));
Assert.assertNotNull(adapter);
}
/**
* ViewHolder should load and set the action label correctly.
*/
@Test
public void setActionLabel_loadsActionLabel() {
addTestLiveData(VOLUME_NOTIFICATION_URI);
final PanelSlicesAdapter adapter = new PanelSlicesAdapter(mPanelFragment, mData, 0);
final ViewGroup view = new FrameLayout(mContext);
final SliceRowViewHolder viewHolder = adapter.onCreateViewHolder(view, VIEW_TYPE_SLIDER);
// now let's see if setActionLabel can load and set the label correctly.
LinearLayout llRow = new LinearLayout(mContext);
viewHolder.setActionLabel(llRow);
boolean isLabelSet = isActionLabelSet(llRow);
Assert.assertTrue("Action label was not set correctly.", isLabelSet);
}
/**
* @param rowView the view with id row_view
* @return whether the accessibility action label is set
*/
private boolean isActionLabelSet(View rowView) {
View.AccessibilityDelegate delegate = rowView.getAccessibilityDelegate();
if (delegate == null) {
return false;
}
AccessibilityNodeInfo node = new AccessibilityNodeInfo(rowView);
delegate.onInitializeAccessibilityNodeInfo(rowView, node);
boolean foundLabel = false;
final String expectedLabel =
mContext.getString(R.string.accessibility_action_label_panel_slice);
for (AccessibilityNodeInfo.AccessibilityAction action : node.getActionList()) {
if (action.equals(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK)
&& TextUtils.equals(action.getLabel(), expectedLabel)) {
foundLabel = true;
break;
}
}
return foundLabel;
}
@Test
public void sizeOfAdapter_shouldNotExceedMaxNum() {
for (int i = 0; i < MAX_NUM_OF_SLICES + 2; i++) {
@@ -141,7 +200,7 @@ public class PanelSlicesAdapterTest {
}
@Test
public void onCreateViewHolder_viewTypeSlider_verifyActionLabelSet() {
public void onBindViewHolder_viewTypeSlider_verifyActionLabelSet() {
addTestLiveData(VOLUME_NOTIFICATION_URI);
final PanelSlicesAdapter adapter =

View File

@@ -305,6 +305,8 @@ public class WifiDetailPreferenceController2Test {
ShadowEntityHeaderController.setUseMock(mMockHeaderController);
// builder pattern
when(mMockHeaderController.setLabel(any(CharSequence.class)))
.thenReturn(mMockHeaderController);
when(mMockHeaderController.setRecyclerView(mMockFragment.getListView(), mLifecycle))
.thenReturn(mMockHeaderController);
when(mMockHeaderController.setSummary(nullable(String.class)))