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

Commit 5be926fb authored by Evan Chen's avatar Evan Chen
Browse files

Apply bullet permission UI to multiple device dialog

Sepreate to two dialogs for non-null profile.
One for the device chooser and another one for
bullet permission dialog.

Test: Cts
Fix: 271311544, 267646302
Change-Id: I8229ce00f17281736aa91a1a9cceea9aaeee35ee
parent c77e0e6f
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -144,7 +144,7 @@
                        android:visibility="gone"
                        android:duplicateParentState="true"
                        android:clickable="false"
                        android:text="@string/consent_no" />
                        android:text="@string/consent_cancel" />

                </LinearLayout>

+11 −14
Original line number Diff line number Diff line
@@ -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>

+8 −4
Original line number Diff line number Diff line
@@ -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>
+67 −67
Original line number Diff line number Diff line
@@ -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;

        if (!SUPPORTED_PROFILES.contains(deviceProfile)) {
            throw new RuntimeException("Unsupported profile " + deviceProfile);
        }
        final Drawable profileIcon = getIcon(this, PROFILE_ICON.get(deviceProfile));

        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));
        updatePermissionUi();

        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);

+2 −12
Original line number Diff line number Diff line
@@ -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;