Merge "Apply bullet permission UI to multiple device dialog" into udc-qpr-dev

This commit is contained in:
Treehugger Robot
2023-05-15 18:47:07 +00:00
committed by Android (Google) Code Review
5 changed files with 89 additions and 98 deletions

View File

@@ -144,7 +144,7 @@
android:visibility="gone"
android:duplicateParentState="true"
android:clickable="false"
android:text="@string/consent_no" />
android:text="@string/consent_cancel" />
</LinearLayout>

View File

@@ -28,13 +28,13 @@
<string name="profile_name_watch">watch</string>
<!-- Title of the device selection dialog. -->
<string name="chooser_title">Choose a <xliff:g id="profile_name" example="watch">%1$s</xliff:g> to be managed by &lt;strong&gt;<xliff:g id="app_name" example="Android Wear">%2$s</xliff:g>&lt;/strong&gt;</string>
<string name="chooser_title_non_profile">Choose a device to be managed by &lt;strong&gt;<xliff:g id="app_name" example="Android Wear">%1$s</xliff:g>&lt;/strong&gt;</string>
<!-- Description of the privileges the application will get if associated with the companion device of WATCH profile (type) [CHAR LIMIT=NONE] -->
<string name="summary_watch">This app is needed to manage your <xliff:g id="device_name" example="My Watch">%1$s</xliff:g>. <xliff:g id="app_name" example="Android Wear">%2$s</xliff:g> will be allowed to sync info, like the name of someone calling, interact with your notifications and access your Phone, SMS, Contacts, Calendar, Call logs and Nearby devices permissions.</string>
<!-- Tile of the multiple devices' dialog. -->
<string name="chooser_title">Choose a <xliff:g id="profile_name" example="watch">%1$s</xliff:g> to set up</string>
<!-- Description of the privileges the application will get if associated with the companion device of WATCH profile for singleDevice(type) [CHAR LIMIT=NONE] -->
<string name="summary_watch_single_device">This app will be allowed to sync info, like the name of someone calling, and access these permissions on your <xliff:g id="device_type" example="phone">%1$s</xliff:g></string>
<!-- Description of the privileges the application will get if associated with the companion device of WATCH profile [CHAR LIMIT=NONE] -->
<string name="summary_watch">This app will be allowed to sync info, like the name of someone calling, and access these permissions on your <xliff:g id="device_name" example="phone">%1$s</xliff:g></string>
<!-- ================= DEVICE_PROFILE_GLASSES ================= -->
@@ -42,13 +42,10 @@
<string name="confirmation_title_glasses">Allow &lt;strong&gt;<xliff:g id="app_name" example="Android Wear">%1$s</xliff:g>&lt;/strong&gt; to manage &lt;strong&gt;<xliff:g id="device_name" example="Glasses">%2$s</xliff:g>&lt;/strong&gt;?</string>
<!-- The name of the "glasses" device type [CHAR LIMIT=30] -->
<string name="profile_name_glasses">glasses</string>
<string name="profile_name_glasses">device</string>
<!-- Description of the privileges the application will get if associated with the companion device of GLASSES profile (type) [CHAR LIMIT=NONE] -->
<string name="summary_glasses_multi_device">This app is needed to manage <xliff:g id="device_name" example="My Glasses">%1$s</xliff:g>. <xliff:g id="app_name" example="Glasses">%2$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Microphone and Nearby devices permissions.</string>
<!-- Description of the privileges the application will get if associated with the companion device of GLASSES profile for singleDevice(type) [CHAR LIMIT=NONE] -->
<string name="summary_glasses_single_device">This app will be allowed to access these permissions on your <xliff:g id="device_type" example="phone">%1$s</xliff:g></string>
<!-- Description of the privileges the application will get if associated with the companion device of GLASSES profile [CHAR LIMIT=NONE] -->
<string name="summary_glasses">This app will be allowed to access these permissions on your <xliff:g id="device_name" example="phone">%1$s</xliff:g></string>
<!-- ================= DEVICE_PROFILE_APP_STREAMING ================= -->
@@ -96,9 +93,6 @@
<!-- A noun for a companion device with unspecified profile (type) [CHAR LIMIT=30] -->
<string name="profile_name_generic">device</string>
<!-- Description of the privileges the application will get if associated with the companion device of unspecified profile (type) [CHAR LIMIT=NONE] -->
<string name="summary_generic_single_device">This app will be able to sync info, like the name of someone calling, between your phone and <xliff:g id="device_name" example="My Watch">%1$s</xliff:g></string>
<!-- Description of the privileges the application will get if associated with the companion device of unspecified profile (type) [CHAR LIMIT=NONE] -->
<string name="summary_generic">This app will be able to sync info, like the name of someone calling, between your phone and the chosen device</string>
@@ -110,6 +104,9 @@
<!-- Negative button for the device-app association consent dialog [CHAR LIMIT=30] -->
<string name="consent_no">Don\u2019t allow</string>
<!-- Cancel button for the device chooser dialog [CHAR LIMIT=30] -->
<string name="consent_cancel">Cancel</string>
<!-- Back button for the helper consent dialog [CHAR LIMIT=30] -->
<string name="consent_back">Back</string>

View File

@@ -69,11 +69,13 @@
<style name="PositiveButton"
parent="@android:style/Widget.Material.Button.Borderless.Colored">
<item name="android:layout_width">300dp</item>
<item name="android:layout_height">56dp</item>
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_marginBottom">2dp</item>
<item name="android:textAllCaps">false</item>
<item name="android:textSize">14sp</item>
<item name="android:layout_marginStart">32dp</item>
<item name="android:layout_marginEnd">32dp</item>
<item name="android:textColor">@android:color/system_neutral1_900</item>
<item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item>
<item name="android:background">@drawable/btn_positive_bottom</item>
@@ -81,11 +83,13 @@
<style name="NegativeButton"
parent="@android:style/Widget.Material.Button.Borderless.Colored">
<item name="android:layout_width">300dp</item>
<item name="android:layout_height">56dp</item>
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_marginTop">2dp</item>
<item name="android:textAllCaps">false</item>
<item name="android:textSize">14sp</item>
<item name="android:layout_marginStart">32dp</item>
<item name="android:layout_marginEnd">32dp</item>
<item name="android:textColor">@android:color/system_neutral1_900</item>
<item name="android:layout_marginTop">4dp</item>
<item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item>

View File

@@ -27,10 +27,8 @@ import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTE
import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService.DiscoveryState;
import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService.DiscoveryState.FINISHED_TIMEOUT;
import static com.android.companiondevicemanager.CompanionDeviceResources.MULTI_DEVICES_SUMMARIES;
import static com.android.companiondevicemanager.CompanionDeviceResources.PERMISSION_TYPES;
import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILES_NAME;
import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILES_NAME_MULTI;
import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_ICON;
import static com.android.companiondevicemanager.CompanionDeviceResources.SUMMARIES;
import static com.android.companiondevicemanager.CompanionDeviceResources.SUPPORTED_PROFILES;
@@ -121,6 +119,9 @@ public class CompanionDeviceActivity extends FragmentActivity implements
private IAssociationRequestCallback mAppCallback;
private ResultReceiver mCdmServiceReceiver;
// Present for application's name.
private CharSequence mAppLabel;
// Always present widgets.
private TextView mTitle;
private TextView mSummary;
@@ -165,8 +166,7 @@ public class CompanionDeviceActivity extends FragmentActivity implements
private @Nullable RecyclerView mDeviceListRecyclerView;
private @Nullable DeviceListAdapter mDeviceAdapter;
// The recycler view is only shown for selfManaged and singleDevice association request.
// The recycler view is shown for non-null profile association request.
private @Nullable RecyclerView mPermissionListRecyclerView;
private @Nullable PermissionListAdapter mPermissionListAdapter;
@@ -178,8 +178,6 @@ public class CompanionDeviceActivity extends FragmentActivity implements
// onActivityResult() after the association is created.
private @Nullable DeviceFilterPair<?> mSelectedDevice;
private @Nullable List<Integer> mPermissionTypes;
private LinearLayoutManager mPermissionsLayoutManager = new LinearLayoutManager(this);
@Override
@@ -302,6 +300,8 @@ public class CompanionDeviceActivity extends FragmentActivity implements
setContentView(R.layout.activity_confirmation);
mAppLabel = appLabel;
mConstraintList = findViewById(R.id.constraint_list);
mAssociationConfirmationDialog = findViewById(R.id.association_confirmation);
mVendorHeader = findViewById(R.id.vendor_header);
@@ -322,7 +322,6 @@ public class CompanionDeviceActivity extends FragmentActivity implements
mMultipleDeviceSpinner = findViewById(R.id.spinner_multiple_device);
mSingleDeviceSpinner = findViewById(R.id.spinner_single_device);
mDeviceAdapter = new DeviceListAdapter(this, this::onListItemClick);
mPermissionListRecyclerView = findViewById(R.id.permission_list);
mPermissionListAdapter = new PermissionListAdapter(this);
@@ -468,8 +467,6 @@ public class CompanionDeviceActivity extends FragmentActivity implements
throw new RuntimeException("Unsupported profile " + deviceProfile);
}
mPermissionTypes = new ArrayList<>();
try {
vendorIcon = getVendorHeaderIcon(this, packageName, userId);
vendorName = getVendorHeaderName(this, packageName, userId);
@@ -486,17 +483,13 @@ public class CompanionDeviceActivity extends FragmentActivity implements
}
title = getHtmlFromResources(this, TITLES.get(deviceProfile), deviceName);
mPermissionTypes.addAll(PERMISSION_TYPES.get(deviceProfile));
setupPermissionList(deviceProfile);
// Summary is not needed for selfManaged dialog.
mSummary.setVisibility(View.GONE);
setupPermissionList();
mTitle.setText(title);
mVendorHeaderName.setText(vendorName);
mVendorHeader.setVisibility(View.VISIBLE);
mVendorHeader.setVisibility(View.VISIBLE);
mProfileIcon.setVisibility(View.GONE);
mDeviceListRecyclerView.setVisibility(View.GONE);
// Top and bottom borders should be gone for selfManaged dialog.
@@ -509,7 +502,9 @@ public class CompanionDeviceActivity extends FragmentActivity implements
final String deviceProfile = mRequest.getDeviceProfile();
mPermissionTypes = new ArrayList<>();
if (!SUPPORTED_PROFILES.contains(deviceProfile)) {
throw new RuntimeException("Unsupported profile " + deviceProfile);
}
CompanionDeviceDiscoveryService.getScanResult().observe(this,
deviceFilterPairs -> updateSingleDeviceUi(
@@ -529,75 +524,40 @@ public class CompanionDeviceActivity extends FragmentActivity implements
if (deviceFilterPairs.isEmpty()) return;
mSelectedDevice = requireNonNull(deviceFilterPairs.get(0));
// No need to show user consent dialog if it is a singleDevice
// and isSkipPrompt(true) AssociationRequest.
// See AssociationRequestsProcessor#mayAssociateWithoutPrompt.
if (mRequest.isSkipPrompt()) {
mSingleDeviceSpinner.setVisibility(View.GONE);
onUserSelectedDevice(mSelectedDevice);
return;
}
final String deviceName = mSelectedDevice.getDisplayName();
final Spanned title;
final Spanned summary;
final Drawable profileIcon;
final Drawable profileIcon = getIcon(this, PROFILE_ICON.get(deviceProfile));
if (!SUPPORTED_PROFILES.contains(deviceProfile)) {
throw new RuntimeException("Unsupported profile " + deviceProfile);
}
updatePermissionUi();
if (deviceProfile == null) {
summary = getHtmlFromResources(this, SUMMARIES.get(null), deviceName);
mConstraintList.setVisibility(View.GONE);
} else {
summary = getHtmlFromResources(
this, SUMMARIES.get(deviceProfile), getString(R.string.device_type));
mPermissionTypes.addAll(PERMISSION_TYPES.get(deviceProfile));
setupPermissionList();
}
title = getHtmlFromResources(this, TITLES.get(deviceProfile), appLabel, deviceName);
profileIcon = getIcon(this, PROFILE_ICON.get(deviceProfile));
mTitle.setText(title);
mSummary.setText(summary);
mProfileIcon.setImageDrawable(profileIcon);
mSingleDeviceSpinner.setVisibility(View.GONE);
mAssociationConfirmationDialog.setVisibility(View.VISIBLE);
mSingleDeviceSpinner.setVisibility(View.GONE);
}
private void initUiForMultipleDevices(CharSequence appLabel) {
if (DEBUG) Log.i(TAG, "initUiFor_MultipleDevices()");
final String deviceProfile = mRequest.getDeviceProfile();
final String profileName;
final String profileNameMulti;
final Spanned summary;
final Drawable profileIcon;
final int summaryResourceId;
final Spanned title;
final String deviceProfile = mRequest.getDeviceProfile();
if (!SUPPORTED_PROFILES.contains(deviceProfile)) {
throw new RuntimeException("Unsupported profile " + deviceProfile);
}
profileName = getString(PROFILES_NAME.get(deviceProfile));
profileNameMulti = getString(PROFILES_NAME_MULTI.get(deviceProfile));
profileIcon = getIcon(this, PROFILE_ICON.get(deviceProfile));
summaryResourceId = MULTI_DEVICES_SUMMARIES.get(deviceProfile);
if (deviceProfile == null) {
summary = getHtmlFromResources(this, summaryResourceId);
title = getHtmlFromResources(this, R.string.chooser_title_non_profile, appLabel);
mButtonNotAllowMultipleDevices.setText(R.string.consent_no);
} else {
summary = getHtmlFromResources(this, summaryResourceId, profileName, appLabel);
title = getHtmlFromResources(this,
R.string.chooser_title, getString(PROFILES_NAME.get(deviceProfile)));
}
final Spanned title = getHtmlFromResources(
this, R.string.chooser_title, profileNameMulti, appLabel);
mDeviceAdapter = new DeviceListAdapter(this, this::onDeviceClicked);
mTitle.setText(title);
mSummary.setText(summary);
mProfileIcon.setImageDrawable(profileIcon);
mDeviceListRecyclerView.setAdapter(mDeviceAdapter);
@@ -613,6 +573,7 @@ public class CompanionDeviceActivity extends FragmentActivity implements
mDeviceAdapter.setDevices(deviceFilterPairs);
});
mSummary.setVisibility(View.GONE);
// "Remove" consent button: users would need to click on the list item.
mButtonAllow.setVisibility(View.GONE);
mButtonNotAllow.setVisibility(View.GONE);
@@ -623,11 +584,9 @@ public class CompanionDeviceActivity extends FragmentActivity implements
mMultipleDeviceSpinner.setVisibility(View.VISIBLE);
}
private void onListItemClick(int position) {
if (DEBUG) Log.d(TAG, "onListItemClick() " + position);
private void onDeviceClicked(int position) {
final DeviceFilterPair<?> selectedDevice = mDeviceAdapter.getItem(position);
// To prevent double tap on the selected device.
if (mSelectedDevice != null) {
if (DEBUG) Log.w(TAG, "Already selected.");
return;
@@ -637,7 +596,47 @@ public class CompanionDeviceActivity extends FragmentActivity implements
mSelectedDevice = requireNonNull(selectedDevice);
onUserSelectedDevice(selectedDevice);
Log.d(TAG, "onDeviceClicked(): " + mSelectedDevice.toShortString());
updatePermissionUi();
mSummary.setVisibility(View.VISIBLE);
mButtonAllow.setVisibility(View.VISIBLE);
mButtonNotAllow.setVisibility(View.VISIBLE);
mDeviceListRecyclerView.setVisibility(View.GONE);
mNotAllowMultipleDevicesLayout.setVisibility(View.GONE);
}
private void updatePermissionUi() {
final String deviceProfile = mRequest.getDeviceProfile();
final int summaryResourceId = SUMMARIES.get(deviceProfile);
final String remoteDeviceName = mSelectedDevice.getDisplayName();
final Spanned title = getHtmlFromResources(
this, TITLES.get(deviceProfile), mAppLabel, remoteDeviceName);
final Spanned summary;
// No need to show permission consent dialog if it is a isSkipPrompt(true)
// AssociationRequest. See AssociationRequestsProcessor#mayAssociateWithoutPrompt.
if (mRequest.isSkipPrompt()) {
mSingleDeviceSpinner.setVisibility(View.GONE);
onUserSelectedDevice(mSelectedDevice);
return;
}
if (deviceProfile == null && mRequest.isSingleDevice()) {
summary = getHtmlFromResources(this, summaryResourceId, remoteDeviceName);
mConstraintList.setVisibility(View.GONE);
} else if (deviceProfile == null) {
onUserSelectedDevice(mSelectedDevice);
return;
} else {
summary = getHtmlFromResources(
this, summaryResourceId, getString(R.string.device_type));
setupPermissionList(deviceProfile);
}
mTitle.setText(title);
mSummary.setText(summary);
}
private void onPositiveButtonClick(View v) {
@@ -680,8 +679,9 @@ public class CompanionDeviceActivity extends FragmentActivity implements
// initiate the layoutManager for the recyclerview, add listeners for monitoring the scrolling
// and when mPermissionListRecyclerView is fully populated.
// Lastly, disable the Allow and Don't allow buttons.
private void setupPermissionList() {
mPermissionListAdapter.setPermissionType(mPermissionTypes);
private void setupPermissionList(String deviceProfile) {
final List<Integer> permissionTypes = new ArrayList<>(PERMISSION_TYPES.get(deviceProfile));
mPermissionListAdapter.setPermissionType(permissionTypes);
mPermissionListRecyclerView.setAdapter(mPermissionListAdapter);
mPermissionListRecyclerView.setLayoutManager(mPermissionsLayoutManager);

View File

@@ -84,23 +84,13 @@ final class CompanionDeviceResources {
}
static final Map<String, Integer> SUMMARIES;
static {
final Map<String, Integer> map = new ArrayMap<>();
map.put(DEVICE_PROFILE_WATCH, R.string.summary_watch_single_device);
map.put(DEVICE_PROFILE_GLASSES, R.string.summary_glasses_single_device);
map.put(null, R.string.summary_generic_single_device);
SUMMARIES = unmodifiableMap(map);
}
static final Map<String, Integer> MULTI_DEVICES_SUMMARIES;
static {
final Map<String, Integer> map = new ArrayMap<>();
map.put(DEVICE_PROFILE_WATCH, R.string.summary_watch);
map.put(DEVICE_PROFILE_GLASSES, R.string.summary_glasses_multi_device);
map.put(DEVICE_PROFILE_GLASSES, R.string.summary_glasses);
map.put(null, R.string.summary_generic);
MULTI_DEVICES_SUMMARIES = unmodifiableMap(map);
SUMMARIES = unmodifiableMap(map);
}
static final Map<String, Integer> PROFILES_NAME;