diff options
| author | 2025-01-03 13:31:25 -0800 | |
|---|---|---|
| committer | 2025-01-03 13:31:25 -0800 | |
| commit | 78d1bc52c8874ca6d4e877f81232669fa3eb847f (patch) | |
| tree | ef44e92fd9c5b0767209a6bb5bcba3707a6f1c19 | |
| parent | 2928afc4098e1f21a5bc46548ae3dce997beef22 (diff) | |
| parent | 05f3b231ba56fa057d3e362d82dd6a53f2541378 (diff) | |
Merge "[CDM] Refactor CDM association discovery timeout mechanism." into main
14 files changed, 454 insertions, 150 deletions
diff --git a/packages/CompanionDeviceManager/res/anim/progress_indeterminate_horizontal_rect1.xml b/packages/CompanionDeviceManager/res/anim/progress_indeterminate_horizontal_rect1.xml new file mode 100644 index 000000000000..a9fd55f77a9c --- /dev/null +++ b/packages/CompanionDeviceManager/res/anim/progress_indeterminate_horizontal_rect1.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 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. + --> +<set xmlns:android="http://schemas.android.com/apk/res/android" > + <objectAnimator + android:duration="2000" + android:propertyXName="translateX" + android:pathData="M -522.59998,0 c 48.89972,0 166.02656,0 301.21729,0 c 197.58128,0 420.9827,0 420.9827,0 " + android:interpolator="@interpolator/progress_indeterminate_horizontal_rect1_translatex" + android:repeatCount="infinite" /> + <objectAnimator + android:duration="2000" + android:propertyYName="scaleX" + android:pathData="M 0 0.1 L 1 0.826849212646 L 2 0.1" + android:interpolator="@interpolator/progress_indeterminate_horizontal_rect1_scalex" + android:repeatCount="infinite" /> +</set>
\ No newline at end of file diff --git a/packages/CompanionDeviceManager/res/anim/progress_indeterminate_horizontal_rect2.xml b/packages/CompanionDeviceManager/res/anim/progress_indeterminate_horizontal_rect2.xml new file mode 100644 index 000000000000..7b5b6a93473d --- /dev/null +++ b/packages/CompanionDeviceManager/res/anim/progress_indeterminate_horizontal_rect2.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 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. + --> +<set xmlns:android="http://schemas.android.com/apk/res/android" > + <objectAnimator + android:duration="2000" + android:propertyXName="translateX" + android:pathData="M -197.60001,0 c 14.28182,0 85.07782,0 135.54689,0 c 54.26191,0 90.42461,0 168.24331,0 c 144.72154,0 316.40982,0 316.40982,0 " + android:interpolator="@interpolator/progress_indeterminate_horizontal_rect2_translatex" + android:repeatCount="infinite" /> + <objectAnimator + android:duration="2000" + android:propertyYName="scaleX" + android:pathData="M 0.0,0.1 L 1.0,0.571379510698 L 2.0,0.909950256348 L 3.0,0.1" + android:interpolator="@interpolator/progress_indeterminate_horizontal_rect2_scalex" + android:repeatCount="infinite" /> +</set>
\ No newline at end of file diff --git a/packages/CompanionDeviceManager/res/drawable/indeterminate_progress_drawable.xml b/packages/CompanionDeviceManager/res/drawable/indeterminate_progress_drawable.xml new file mode 100644 index 000000000000..1029590796c1 --- /dev/null +++ b/packages/CompanionDeviceManager/res/drawable/indeterminate_progress_drawable.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 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. + --> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + android:drawable="@drawable/indeterminate_progress_vector" > + <target + android:name="rect2_grp" + android:animation="@anim/progress_indeterminate_horizontal_rect2" /> + <target + android:name="rect1_grp" + android:animation="@anim/progress_indeterminate_horizontal_rect1" /> +</animated-vector>
\ No newline at end of file diff --git a/packages/CompanionDeviceManager/res/drawable/indeterminate_progress_vector.xml b/packages/CompanionDeviceManager/res/drawable/indeterminate_progress_vector.xml new file mode 100644 index 000000000000..4db3356176e7 --- /dev/null +++ b/packages/CompanionDeviceManager/res/drawable/indeterminate_progress_vector.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:height="10dp" + android:width="360dp" + android:viewportHeight="10" + android:viewportWidth="360" > + <group + android:name="progress_group" + android:translateX="180" + android:translateY="5" > + <path + android:name="background_track" + android:pathData="M -180.0,-5.0 l 360.0,0 l 0,10.0 l -360.0,0 Z" + android:fillColor="@color/progress_bg" /> + <group + android:name="rect2_grp" + android:translateX="-197.60001" + android:scaleX="0.1" > + <path + android:name="rect2" + android:pathData="M -144.0,-5.0 l 288.0,0 l 0,10.0 l -288.0,0 Z" + android:fillColor="@color/progress_fg" /> + </group> + <group + android:name="rect1_grp" + android:translateX="-522.59998" + android:scaleX="0.1" > + <path + android:name="rect1" + android:pathData="M -144.0,-5.0 l 288.0,0 l 0,10.0 l -288.0,0 Z" + android:fillColor="@color/progress_fg" /> + </group> + </group> +</vector>
\ No newline at end of file diff --git a/packages/CompanionDeviceManager/res/interpolator/progress_indeterminate_horizontal_rect1_scalex.xml b/packages/CompanionDeviceManager/res/interpolator/progress_indeterminate_horizontal_rect1_scalex.xml new file mode 100644 index 000000000000..453e0928a2b6 --- /dev/null +++ b/packages/CompanionDeviceManager/res/interpolator/progress_indeterminate_horizontal_rect1_scalex.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 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. + --> +<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android" + android:pathData="M 0 0 L 0.3665 0 C 0.47252618112021,0.062409910275 0.61541608570164,0.5 0.68325,0.5 C 0.75475061236836,0.5 0.75725829093844,0.814510098964 1.0,1.0" /> diff --git a/packages/CompanionDeviceManager/res/interpolator/progress_indeterminate_horizontal_rect1_translatex.xml b/packages/CompanionDeviceManager/res/interpolator/progress_indeterminate_horizontal_rect1_translatex.xml new file mode 100644 index 000000000000..a6da0eb77fc5 --- /dev/null +++ b/packages/CompanionDeviceManager/res/interpolator/progress_indeterminate_horizontal_rect1_translatex.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 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. + --> +<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android" + android:pathData="M 0.0,0.0 L 0.2 0 C 0.3958333333336,0.0 0.474845090492,0.206797621729 0.5916666666664,0.417082932942 C 0.7151610251224,0.639379624869 0.81625,0.974556908664 1.0,1.0 " /> diff --git a/packages/CompanionDeviceManager/res/interpolator/progress_indeterminate_horizontal_rect2_scalex.xml b/packages/CompanionDeviceManager/res/interpolator/progress_indeterminate_horizontal_rect2_scalex.xml new file mode 100644 index 000000000000..785d7abfe51d --- /dev/null +++ b/packages/CompanionDeviceManager/res/interpolator/progress_indeterminate_horizontal_rect2_scalex.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 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. + --> +<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android" + android:pathData="M 0,0 C 0.06834272400867,0.01992566661414 0.19220331656133,0.15855429260523 0.33333333333333,0.34926160892842 C 0.38410433133433,0.41477913453861 0.54945792615267,0.68136029463551 0.66666666666667,0.68279962777002 C 0.752586273196,0.68179620963216 0.737253971954,0.878896194318 1,1" /> diff --git a/packages/CompanionDeviceManager/res/interpolator/progress_indeterminate_horizontal_rect2_translatex.xml b/packages/CompanionDeviceManager/res/interpolator/progress_indeterminate_horizontal_rect2_translatex.xml new file mode 100644 index 000000000000..931dff1c3236 --- /dev/null +++ b/packages/CompanionDeviceManager/res/interpolator/progress_indeterminate_horizontal_rect2_translatex.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 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. + --> +<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android" + android:pathData="M 0.0,0.0 C 0.0375,0.0 0.128764607715,0.0895380946618 0.25,0.218553507947 C 0.322410320025,0.295610602487 0.436666666667,0.417591408114 0.483333333333,0.489826169306 C 0.69,0.80972296795 0.793333333333,0.950016125212 1.0,1.0 " /> diff --git a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml index 5805332418d0..afece5fac0fb 100644 --- a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml +++ b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml @@ -71,6 +71,13 @@ android:layout_height="wrap_content" android:visibility="gone"> + <TextView + android:id="@+id/timeout_message" + android:layout_width="match_parent" + android:layout_height="100dp" + android:visibility="gone" + style="@style/TimeoutMessage" /> + <androidx.recyclerview.widget.RecyclerView android:id="@+id/device_list" android:layout_width="match_parent" @@ -87,9 +94,9 @@ app:layout_constraintHeight_max="220dp" android:visibility="gone" /> - <View - android:id="@+id/border_top" - style="@style/DeviceListBorder" /> + <ProgressBar + android:id="@+id/progress_bar" + style="@style/HorizontalProgressBar" /> <View android:id="@+id/border_bottom" @@ -98,10 +105,6 @@ </androidx.constraintlayout.widget.ConstraintLayout> - <ProgressBar - android:id="@+id/spinner_multiple_device" - android:visibility="gone" - style="@style/Spinner" /> </RelativeLayout> @@ -135,7 +138,8 @@ android:layout_marginEnd="16dp" android:layout_marginBottom="16dp"> - <!-- Do NOT change the IDs of the buttons: they are referenced in CTS tests. --> + <!-- Do NOT change the IDs of the buttons: they are referenced in CTS tests. + Legacy name before the change that added single-device dialog.--> <LinearLayout android:id="@+id/negative_multiple_devices_layout" android:layout_width="wrap_content" @@ -159,18 +163,6 @@ </LinearLayout> - <RelativeLayout - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_weight="1" - android:importantForAccessibility="noHideDescendants"> - - <ProgressBar - android:id="@+id/spinner_single_device" - android:visibility="gone" - style="@style/Spinner" /> - </RelativeLayout>> - </LinearLayout> </ScrollView>
\ No newline at end of file diff --git a/packages/CompanionDeviceManager/res/values/colors.xml b/packages/CompanionDeviceManager/res/values/colors.xml new file mode 100644 index 000000000000..8782250f3bba --- /dev/null +++ b/packages/CompanionDeviceManager/res/values/colors.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 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. + --> +<resources> + <color name="border">@android:color/system_neutral1_200</color> + <color name="progress_bg">@android:color/system_neutral1_600</color> + <color name="progress_fg">@android:color/system_neutral1_0</color> +</resources> diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml index 2a6d68d1ee35..20ede5ff91d3 100644 --- a/packages/CompanionDeviceManager/res/values/strings.xml +++ b/packages/CompanionDeviceManager/res/values/strings.xml @@ -22,6 +22,21 @@ <!-- Title of the device association confirmation dialog. --> <string name="confirmation_title">Allow the app <strong><xliff:g id="app_name" example="Android Wear">%1$s</xliff:g></strong> to access <strong><xliff:g id="device_name" example="ASUS ZenWatch 2">%2$s</xliff:g></strong>?</string> + <!-- Description of soft discovery timeout. [CHAR LIMIT= NONE] --> + <string name="message_discovery_soft_timeout">Make sure this <xliff:g id="device_type" example="phone">%1$s</xliff:g> has <xliff:g id="discovery_method" example="Bluetooth">%2$s</xliff:g> turned on, and keep your <xliff:g id="profile_name" example="watch">%3$s</xliff:g> nearby.</string> + + <!-- Description of hard discovery timeout. [CHAR LIMIT= NONE] --> + <string name="message_discovery_hard_timeout">No devices found. Please try again later.</string> + + <!-- The discovery method name for Bluetooth [CHAR LIMIT=30] --> + <string name="discovery_bluetooth">Bluetooth</string> + + <!-- The discovery method name for Wi-Fi [CHAR LIMIT=30] --> + <string name="discovery_wifi">Wi-Fi</string> + + <!-- The discovery method name for both Bluetooth and Wi-Fi [CHAR LIMIT=50] --> + <string name="discovery_mixed">Bluetooth and Wi-Fi</string> + <!-- ================= DEVICE_PROFILE_WATCH and null profile ================= --> <!-- The name of the "watch" device type [CHAR LIMIT=30] --> @@ -30,9 +45,12 @@ <!-- Title of the device selection dialog. --> <string name="chooser_title_non_profile">Choose a device to be managed by <strong><xliff:g id="app_name" example="Android Wear">%1$s</xliff:g></strong></string> - <!-- Tile of the multiple devices' dialog. --> + <!-- Title 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> + <!-- Title of the single device scan dialog. --> + <string name="single_device_title">Looking for a <xliff:g id="profile_name" example="watch">%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_type" example="phone">%1$s</xliff:g></string> diff --git a/packages/CompanionDeviceManager/res/values/styles.xml b/packages/CompanionDeviceManager/res/values/styles.xml index a161a505a0ac..30813baa6e4a 100644 --- a/packages/CompanionDeviceManager/res/values/styles.xml +++ b/packages/CompanionDeviceManager/res/values/styles.xml @@ -59,6 +59,43 @@ <item name="android:textColor">?android:attr/textColorSecondary</item> </style> + <style name="HorizontalProgressBar" + parent="@android:style/Widget.Material.ProgressBar.Horizontal"> + <item name="android:layout_width">match_parent</item> + <item name="android:layout_height">1dp</item> + <item name="android:layout_marginStart">32dp</item> + <item name="android:layout_marginEnd">32dp</item> + <item name="android:progress">100</item> + <item name="android:indeterminate">true</item> + <item name="android:indeterminateOnly">false</item> + <item name="android:progressTint">@color/border</item> + <item name="android:indeterminateDrawable">@drawable/indeterminate_progress_drawable</item> + </style> + + <style name="DeviceListBorder"> + <item name="android:layout_width">match_parent</item> + <item name="android:layout_height">1dp</item> + <item name="android:layout_marginStart">32dp</item> + <item name="android:layout_marginEnd">32dp</item> + <item name="android:background">@color/border</item> + </style> + + <style name="TimeoutMessage" + parent="@android:style/TextAppearance.DeviceDefault.Medium"> + <item name="android:layout_width">match_parent</item> + <item name="android:layout_height">wrap_content</item> + <item name="android:layout_marginStart">32dp</item> + <item name="android:layout_marginEnd">32dp</item> + <item name="android:paddingEnd">8dp</item> + <item name="android:paddingStart">8dp</item> + <item name="android:paddingTop">18dp</item> + <item name="android:paddingBottom">18dp</item> + <item name="android:textDirection">locale</item> + <item name="android:textSize">14sp</item> + <item name="android:lineSpacingExtra">2dp</item> + <item name="android:textColor">?android:attr/textColorSecondary</item> + </style> + <style name="VendorHelperBackButton" parent="@android:style/Widget.Material.Button.Borderless.Colored"> <item name="android:layout_width">wrap_content</item> @@ -111,22 +148,6 @@ <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item> </style> - <style name="DeviceListBorder"> - <item name="android:layout_width">match_parent</item> - <item name="android:layout_height">1dp</item> - <item name="android:layout_marginStart">32dp</item> - <item name="android:layout_marginEnd">32dp</item> - <item name="android:background">@android:color/system_neutral1_200</item> - </style> - - <style name="Spinner" - parent="@android:style/Widget.Material.Light.ProgressBar.Large"> - <item name="android:layout_width">56dp</item> - <item name="android:layout_height">56dp</item> - <item name="android:indeterminate">true</item> - <item name="android:layout_centerInParent">true</item> - </style> - <style name="ScrollViewStyle"> <item name="android:scrollbars">none</item> <item name="android:fillViewport">true</item> diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java index 50419f7368be..ea40e13fdcc9 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java @@ -17,14 +17,12 @@ package com.android.companiondevicemanager; import static android.companion.CompanionDeviceManager.RESULT_CANCELED; -import static android.companion.CompanionDeviceManager.RESULT_DISCOVERY_TIMEOUT; import static android.companion.CompanionDeviceManager.RESULT_INTERNAL_ERROR; import static android.companion.CompanionDeviceManager.RESULT_SECURITY_ERROR; import static android.companion.CompanionDeviceManager.RESULT_USER_REJECTED; import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService.DiscoveryState; -import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService.DiscoveryState.FINISHED_TIMEOUT; import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService.LOCK; import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService.sDiscoveryStarted; import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_ICONS; @@ -48,11 +46,13 @@ import static java.util.Objects.requireNonNull; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.StringRes; import android.annotation.SuppressLint; import android.companion.AssociatedDevice; import android.companion.AssociationInfo; import android.companion.AssociationRequest; import android.companion.CompanionDeviceManager; +import android.companion.DeviceFilter; import android.companion.Flags; import android.companion.IAssociationRequestCallback; import android.content.Intent; @@ -139,22 +139,19 @@ public class CompanionAssociationActivity extends FragmentActivity implements private TextView mVendorHeaderName; private ImageButton mVendorHeaderButton; - // Progress indicator is only shown while we are looking for the first suitable device for a - // multiple device association. - private ProgressBar mMultipleDeviceSpinner; - // Progress indicator is only shown while we are looking for the first suitable device for a - // single device association. - private ProgressBar mSingleDeviceSpinner; + // Message to be displayed when device hasn't been discovered for a certain duration + private TextView mTimeoutMessage; + + // Horizontal progress indicator is always shown as long as the scanner is searching for devices + private ProgressBar mProgressBar; // Present for self-managed association requests and "single-device" regular association // regular. private Button mButtonAllow; private Button mButtonNotAllow; - // Present for multiple devices' association requests only. - private Button mButtonNotAllowMultipleDevices; + private Button mButtonCancelScan; - // Present for top and bottom borders for permissions list and device list. - private View mBorderTop; + // Bottom border for permissions list and device list. The progress bar acts as the top border. private View mBorderBottom; private LinearLayout mAssociationConfirmationDialog; @@ -162,9 +159,9 @@ public class CompanionAssociationActivity extends FragmentActivity implements private ConstraintLayout mConstraintList; // Only present for self-managed association requests. private RelativeLayout mVendorHeader; - // A linearLayout for mButtonNotAllowMultipleDevices, user will press this layout instead + // A linearLayout for mButtonCancelScan, user will press this layout instead // of the button for accessibility. - private LinearLayout mNotAllowMultipleDevicesLayout; + private LinearLayout mCancelScanLayout; // The recycler view is only shown for multiple-device regular association request, after // at least one matching device is found. @@ -297,7 +294,6 @@ public class CompanionAssociationActivity extends FragmentActivity implements mAssociationConfirmationDialog = findViewById(R.id.association_confirmation); mVendorHeader = findViewById(R.id.vendor_header); - mBorderTop = findViewById(R.id.border_top); mBorderBottom = findViewById(R.id.border_bottom); mTitle = findViewById(R.id.title); @@ -311,43 +307,90 @@ public class CompanionAssociationActivity extends FragmentActivity implements mDeviceIcon = findViewById(R.id.device_icon); + mTimeoutMessage = findViewById(R.id.timeout_message); mDeviceListRecyclerView = findViewById(R.id.device_list); - mMultipleDeviceSpinner = findViewById(R.id.spinner_multiple_device); - mSingleDeviceSpinner = findViewById(R.id.spinner_single_device); + mProgressBar = findViewById(R.id.progress_bar); + mProgressBar.getIndeterminateDrawable().clearColorFilter(); mPermissionListRecyclerView = findViewById(R.id.permission_list); mPermissionListAdapter = new PermissionListAdapter(this); mButtonAllow = findViewById(R.id.btn_positive); mButtonNotAllow = findViewById(R.id.btn_negative); - mButtonNotAllowMultipleDevices = findViewById(R.id.btn_negative_multiple_devices); - mNotAllowMultipleDevicesLayout = findViewById(R.id.negative_multiple_devices_layout); + mButtonCancelScan = findViewById(R.id.btn_negative_multiple_devices); + mCancelScanLayout = findViewById(R.id.negative_multiple_devices_layout); mButtonAllow.setOnClickListener(this::onPositiveButtonClick); mButtonNotAllow.setOnClickListener(this::onNegativeButtonClick); - mNotAllowMultipleDevicesLayout.setOnClickListener(this::onNegativeButtonClick); + mCancelScanLayout.setOnClickListener(this::onNegativeButtonClick); mVendorHeaderButton.setOnClickListener(this::onShowHelperDialog); if (mRequest.isSelfManaged()) { initUiForSelfManagedAssociation(); - } else if (mRequest.isSingleDevice()) { - initUiForSingleDevice(); } else { - initUiForMultipleDevices(); + initUiForDeviceDiscovery(); } } private void onDiscoveryStateChanged(DiscoveryState newState) { - if (newState == FINISHED_TIMEOUT - && CompanionDeviceDiscoveryService.getScanResult().getValue().isEmpty()) { - synchronized (LOCK) { - if (sDiscoveryStarted) { - cancel(RESULT_DISCOVERY_TIMEOUT, null); + switch (newState) { + case IN_PROGRESS: { + mTimeoutMessage.setText(null); + mProgressBar.setIndeterminate(true); + break; + } + case IN_PROGRESS_EXTENDED: { + final String deviceType = getString(R.string.device_type); + final String discoveryType = getString(getDiscoveryMethod()); + final String profile = getString(PROFILE_NAMES.get(mRequest.getDeviceProfile())); + final Spanned message = getHtmlFromResources(this, + R.string.message_discovery_soft_timeout, + deviceType, discoveryType, profile); + mTimeoutMessage.setText(message); + break; + } + case FINISHED_STOPPED: { + if (CompanionDeviceDiscoveryService.getScanResult().getValue().isEmpty()) { + // If the scan times out, do NOT close the activity automatically and let the + // user manually cancel the flow. + synchronized (LOCK) { + if (sDiscoveryStarted) { + stopDiscovery(); + } + } + mTimeoutMessage.setText(getString(R.string.message_discovery_hard_timeout)); } + mProgressBar.setIndeterminate(false); + break; + } + } + } + + @StringRes + private int getDiscoveryMethod() { + // If no filter was given or at least one bluetooth filter was provided, then + // display message for Bluetooth. + // If filter is _only_ for Wi-Fi devices, then display message for Wi-Fi. + // e.g. "Make sure Bluetooth is on" vs "Make sure Wi-Fi is on" + boolean hasBluetooth = false; + boolean hasWifi = false; + for (DeviceFilter<?> filter : mRequest.getDeviceFilters()) { + if (filter.getMediumType() == DeviceFilter.MEDIUM_TYPE_BLUETOOTH + || filter.getMediumType() == DeviceFilter.MEDIUM_TYPE_BLUETOOTH_LE) { + hasBluetooth = true; + } else if (filter.getMediumType() == DeviceFilter.MEDIUM_TYPE_WIFI) { + hasWifi = true; } } + if (hasBluetooth == hasWifi) { + return R.string.discovery_mixed; + } else if (hasBluetooth) { + return R.string.discovery_bluetooth; + } else { + return R.string.discovery_wifi; + } } private void onUserSelectedDevice(@NonNull DeviceFilterPair<?> selectedDevice) { @@ -392,9 +435,7 @@ public class CompanionAssociationActivity extends FragmentActivity implements mCancelled = true; // Stop discovery service if it was used. - if (!mRequest.isSelfManaged()) { - CompanionDeviceDiscoveryService.stop(this); - } + stopDiscovery(); // First send callback to the app directly... try { @@ -408,6 +449,12 @@ public class CompanionAssociationActivity extends FragmentActivity implements setResultAndFinish(null, errorCode); } + private void stopDiscovery() { + if (!mRequest.isSelfManaged()) { + CompanionDeviceDiscoveryService.stop(this); + } + } + private void setResultAndFinish(@Nullable AssociationInfo association, int resultCode) { Slog.i(TAG, "setResultAndFinish(), association=" + (association == null ? "null" : association) @@ -479,41 +526,14 @@ public class CompanionAssociationActivity extends FragmentActivity implements mVendorHeader.setVisibility(View.VISIBLE); mProfileIcon.setVisibility(View.GONE); mDeviceListRecyclerView.setVisibility(View.GONE); - // Top and bottom borders should be gone for selfManaged dialog. - mBorderTop.setVisibility(View.GONE); + mProgressBar.setVisibility(View.GONE); mBorderBottom.setVisibility(View.GONE); } - private void initUiForSingleDevice() { - Slog.d(TAG, "initUiForSingleDevice()"); - - final String deviceProfile = mRequest.getDeviceProfile(); - - if (!SUPPORTED_PROFILES.contains(deviceProfile)) { - throw new RuntimeException("Unsupported profile " + deviceProfile); - } - - final Drawable profileIcon = getIcon(this, PROFILE_ICONS.get(deviceProfile)); - mProfileIcon.setImageDrawable(profileIcon); - - CompanionDeviceDiscoveryService.getScanResult().observe(this, deviceFilterPairs -> { - if (deviceFilterPairs.isEmpty()) { - return; - } - mSelectedDevice = requireNonNull(deviceFilterPairs.get(0)); - updateSingleDeviceUi(); - }); - - mSingleDeviceSpinner.setVisibility(View.VISIBLE); - // Hide permission list and confirmation dialog first before the - // first matched device is found. - mPermissionListRecyclerView.setVisibility(View.GONE); - mDeviceListRecyclerView.setVisibility(View.GONE); - mAssociationConfirmationDialog.setVisibility(View.GONE); - } - - private void initUiForMultipleDevices() { - Slog.d(TAG, "initUiForMultipleDevices()"); + private void initUiForDeviceDiscovery() { + Slog.d(TAG, "initUiForDeviceDiscovery() " + + "single-device=" + mRequest.isSingleDevice() + + ", profile=" + mRequest.getDeviceProfile()); final Drawable profileIcon; final Spanned title; @@ -525,41 +545,59 @@ public class CompanionAssociationActivity extends FragmentActivity implements profileIcon = getIcon(this, PROFILE_ICONS.get(deviceProfile)); - if (deviceProfile == null) { + if (mRequest.isSingleDevice()) { + title = getHtmlFromResources(this, + R.string.single_device_title, getString(PROFILE_NAMES.get(deviceProfile))); + } else if (deviceProfile == null) { title = getHtmlFromResources(this, R.string.chooser_title_non_profile, mAppLabel); - mButtonNotAllowMultipleDevices.setText(R.string.consent_no); } else { title = getHtmlFromResources(this, R.string.chooser_title, getString(PROFILE_NAMES.get(deviceProfile))); } - mDeviceAdapter = new DeviceListAdapter(this, this::onDeviceClicked); - mTitle.setText(title); mProfileIcon.setImageDrawable(profileIcon); - mDeviceListRecyclerView.setAdapter(mDeviceAdapter); - mDeviceListRecyclerView.setLayoutManager(new LinearLayoutManager(this)); - - CompanionDeviceDiscoveryService.getScanResult().observe(this, - deviceFilterPairs -> { - // Dismiss the progress bar once there's one device found for multiple devices. - if (deviceFilterPairs.size() >= 1) { - mMultipleDeviceSpinner.setVisibility(View.GONE); + if (mRequest.isSingleDevice()) { + mBorderBottom.setVisibility(View.GONE); + CompanionDeviceDiscoveryService.getScanResult().observe(this, deviceFilterPairs -> { + if (deviceFilterPairs.isEmpty()) { + return; + } + mSelectedDevice = requireNonNull(deviceFilterPairs.get(0)); + updateUiForAssociationConsent(); + }); + } else { + mDeviceAdapter = new DeviceListAdapter(this, this::onDeviceClicked); + mDeviceListRecyclerView.setAdapter(mDeviceAdapter); + mDeviceListRecyclerView.setLayoutManager(new LinearLayoutManager(this)); + + CompanionDeviceDiscoveryService.getScanResult().observe(this, deviceFilterPairs -> { + if (deviceFilterPairs.size() >= 1) { + // Dismiss the timeout message once there's at least one device found. + mTimeoutMessage.setText(null); + + // Update profile-less cancel scan button to read "Don't allow" to indicate + // that selecting a device implies user consent. + if (deviceProfile == null) { + mButtonCancelScan.setText(R.string.consent_no); } + } - mDeviceAdapter.setDevices(deviceFilterPairs); - }); + mDeviceAdapter.setDevices(deviceFilterPairs); + }); + + mDeviceListRecyclerView.setVisibility(View.VISIBLE); + } mSummary.setVisibility(View.GONE); - // "Remove" consent button: users would need to click on the list item. mButtonAllow.setVisibility(View.GONE); mButtonNotAllow.setVisibility(View.GONE); - mDeviceListRecyclerView.setVisibility(View.VISIBLE); - mButtonNotAllowMultipleDevices.setVisibility(View.VISIBLE); - mNotAllowMultipleDevicesLayout.setVisibility(View.VISIBLE); + + mTimeoutMessage.setVisibility(View.VISIBLE); + mButtonCancelScan.setVisibility(View.VISIBLE); + mCancelScanLayout.setVisibility(View.VISIBLE); mConstraintList.setVisibility(View.VISIBLE); - mMultipleDeviceSpinner.setVisibility(View.VISIBLE); } private void onDeviceClicked(int position) { @@ -586,15 +624,10 @@ public class CompanionAssociationActivity extends FragmentActivity implements } // The permission consent dialog should be displayed for the multiple device // dialog if a device profile exists. - updateSingleDeviceUi(); - mSummary.setVisibility(View.VISIBLE); - mButtonAllow.setVisibility(View.VISIBLE); - mButtonNotAllow.setVisibility(View.VISIBLE); - mDeviceListRecyclerView.setVisibility(View.GONE); - mNotAllowMultipleDevicesLayout.setVisibility(View.GONE); + updateUiForAssociationConsent(); } - private void updateSingleDeviceUi() { + private void updateUiForAssociationConsent() { // No need to show permission consent dialog if it is a isSkipPrompt(true) // AssociationRequest. See AssociationRequestsProcessor#mayAssociateWithoutPrompt. if (mRequest.isSkipPrompt()) { @@ -603,9 +636,14 @@ public class CompanionAssociationActivity extends FragmentActivity implements return; } - mSingleDeviceSpinner.setVisibility(View.GONE); mAssociationConfirmationDialog.setVisibility(View.VISIBLE); + mProgressBar.setIndeterminate(false); // Keep as border but remove animation + mBorderBottom.setVisibility(View.VISIBLE); + mTimeoutMessage.setVisibility(View.GONE); + mDeviceListRecyclerView.setVisibility(View.GONE); + mCancelScanLayout.setVisibility(View.GONE); + final String deviceProfile = mRequest.getDeviceProfile(); final int summaryResourceId = PROFILE_SUMMARIES.get(deviceProfile); final String remoteDeviceName = mSelectedDevice.getDisplayName(); @@ -624,6 +662,10 @@ public class CompanionAssociationActivity extends FragmentActivity implements mTitle.setText(title); mSummary.setText(summary); + + mSummary.setVisibility(View.VISIBLE); + mButtonAllow.setVisibility(View.VISIBLE); + mButtonNotAllow.setVisibility(View.VISIBLE); } private void onPositiveButtonClick(View v) { diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java index f586e3dedf9a..50a01b3bc7c9 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java @@ -76,7 +76,8 @@ public class CompanionDeviceDiscoveryService extends Service { private static final String TAG = "CDM_CompanionDeviceDiscoveryService"; private static final String SYS_PROP_DEBUG_TIMEOUT = "debug.cdm.discovery_timeout"; - private static final long TIMEOUT_DEFAULT = 20_000L; // 20 seconds + private static final long TIMEOUT_SOFT = 20_000L; // 20 seconds + private static final long TIMEOUT_HARD = 180_000L; // 3 minutes private static final long TIMEOUT_MIN = 1_000L; // 1 sec private static final long TIMEOUT_MAX = 60_000L; // 1 min @@ -102,7 +103,8 @@ public class CompanionDeviceDiscoveryService extends Service { private final List<DeviceFilterPair<?>> mDevicesFound = new ArrayList<>(); - private final Runnable mTimeoutRunnable = this::timeout; + private final Runnable mSoftTimeoutRunnable = this::softTimeout; + private final Runnable mHardTimeoutRunnable = this::stopDiscoveryAndFinish; private boolean mStopAfterFirstMatch; @@ -116,8 +118,8 @@ public class CompanionDeviceDiscoveryService extends Service { enum DiscoveryState { NOT_STARTED, IN_PROGRESS, + IN_PROGRESS_EXTENDED, FINISHED_STOPPED, - FINISHED_TIMEOUT } static boolean startForRequest( @@ -175,7 +177,7 @@ public class CompanionDeviceDiscoveryService extends Service { break; case ACTION_STOP_DISCOVERY: - stopDiscoveryAndFinish(/* timeout */ false); + stopDiscoveryAndFinish(); break; } return START_NOT_STICKY; @@ -223,9 +225,17 @@ public class CompanionDeviceDiscoveryService extends Service { } @MainThread - private void stopDiscoveryAndFinish(boolean timeout) { - Slog.d(TAG, "stopDiscoveryAndFinish(" + timeout + ")"); + private void softTimeout() { + // If no device is found at this point, display a message and continue discovery + if (mDevicesFound.isEmpty()) { + sStateLiveData.setValue(DiscoveryState.IN_PROGRESS_EXTENDED); + } else { + stopDiscoveryAndFinish(); + } + } + @MainThread + private void stopDiscoveryAndFinish() { synchronized (LOCK) { if (!sDiscoveryStarted) { stopSelf(); @@ -260,13 +270,10 @@ public class CompanionDeviceDiscoveryService extends Service { mBleScanner.stopScan(mBleScanCallback); } - Handler.getMain().removeCallbacks(mTimeoutRunnable); + Handler.getMain().removeCallbacks(mSoftTimeoutRunnable); + Handler.getMain().removeCallbacks(mHardTimeoutRunnable); - if (timeout) { - sStateLiveData.setValue(DiscoveryState.FINISHED_TIMEOUT); - } else { - sStateLiveData.setValue(DiscoveryState.FINISHED_STOPPED); - } + sStateLiveData.setValue(DiscoveryState.FINISHED_STOPPED); synchronized (LOCK) { sDiscoveryStarted = false; @@ -379,7 +386,7 @@ public class CompanionDeviceDiscoveryService extends Service { sScanResultsLiveData.setValue(mDevicesFound); // Stop discovery when there's one device found for singleDevice. if (mStopAfterFirstMatch) { - stopDiscoveryAndFinish(/* timeout */ false); + stopDiscoveryAndFinish(); } }); } @@ -396,20 +403,17 @@ public class CompanionDeviceDiscoveryService extends Service { } private void scheduleTimeout() { - long timeout = SystemProperties.getLong(SYS_PROP_DEBUG_TIMEOUT, -1); - if (timeout <= 0) { + long softTimeout = SystemProperties.getLong(SYS_PROP_DEBUG_TIMEOUT, -1); + if (softTimeout <= 0) { // 0 or negative values indicate that the sysprop was never set or should be ignored. - timeout = TIMEOUT_DEFAULT; + softTimeout = TIMEOUT_SOFT; } else { - timeout = min(timeout, TIMEOUT_MAX); // should be <= 1 min (TIMEOUT_MAX) - timeout = max(timeout, TIMEOUT_MIN); // should be >= 1 sec (TIMEOUT_MIN) + softTimeout = min(softTimeout, TIMEOUT_MAX); // should be <= 1 min (TIMEOUT_MAX) + softTimeout = max(softTimeout, TIMEOUT_MIN); // should be >= 1 sec (TIMEOUT_MIN) } - Handler.getMain().postDelayed(mTimeoutRunnable, timeout); - } - - private void timeout() { - stopDiscoveryAndFinish(/* timeout */ true); + Handler.getMain().postDelayed(mSoftTimeoutRunnable, softTimeout); + Handler.getMain().postDelayed(mHardTimeoutRunnable, TIMEOUT_HARD); } @Override |