Merge "Add Nearby share entrypoint in DevicePicker" into main
This commit is contained in:
@@ -44,3 +44,13 @@ flag {
|
||||
purpose: PURPOSE_BUGFIX
|
||||
}
|
||||
}
|
||||
|
||||
flag {
|
||||
name: "enable_nearby_share_entrypoint"
|
||||
namespace: "cross_device_experiences"
|
||||
description: "Show Nearby Share entrypoint in Bluetooth Sharing page"
|
||||
bug: "381799866"
|
||||
metadata {
|
||||
purpose: PURPOSE_BUGFIX
|
||||
}
|
||||
}
|
||||
|
25
res/drawable/ic_bluetooth_share_info.xml
Normal file
25
res/drawable/ic_bluetooth_share_info.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<!--
|
||||
~ Copyright (C) 2025 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:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="20"
|
||||
android:tint="?android:attr/colorAccent">
|
||||
<path
|
||||
android:pathData="M9,15H11V9H9V15ZM10,7C10.283,7 10.517,6.908 10.7,6.725C10.9,6.525 11,6.283 11,6C11,5.717 10.9,5.483 10.7,5.3C10.517,5.1 10.283,5 10,5C9.717,5 9.475,5.1 9.275,5.3C9.092,5.483 9,5.717 9,6C9,6.283 9.092,6.525 9.275,6.725C9.475,6.908 9.717,7 10,7ZM10,20C8.617,20 7.317,19.742 6.1,19.225C4.883,18.692 3.825,17.975 2.925,17.075C2.025,16.175 1.308,15.117 0.775,13.9C0.258,12.683 0,11.383 0,10C0,8.617 0.258,7.317 0.775,6.1C1.308,4.883 2.025,3.825 2.925,2.925C3.825,2.025 4.883,1.317 6.1,0.8C7.317,0.267 8.617,-0 10,-0C11.383,-0 12.683,0.267 13.9,0.8C15.117,1.317 16.175,2.025 17.075,2.925C17.975,3.825 18.683,4.883 19.2,6.1C19.733,7.317 20,8.617 20,10C20,11.383 19.733,12.683 19.2,13.9C18.683,15.117 17.975,16.175 17.075,17.075C16.175,17.975 15.117,18.692 13.9,19.225C12.683,19.742 11.383,20 10,20Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
93
res/layout/nearby_sharing_suggestion_card.xml
Normal file
93
res/layout/nearby_sharing_suggestion_card.xml
Normal file
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2025 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.
|
||||
-->
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/nearby_sharing_suggestion_card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:paddingVertical="@dimen/settingslib_expressive_space_medium3">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/card_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:baselineAligned="false"
|
||||
android:paddingHorizontal="@dimen/settingslib_expressive_space_small1"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:background="@drawable/settingslib_card_preference_background">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top|center_horizontal"
|
||||
android:paddingTop="@dimen/settingslib_expressive_space_small1">
|
||||
<ImageView
|
||||
android:layout_width="@dimen/settingslib_expressive_space_medium4"
|
||||
android:layout_height="@dimen/settingslib_expressive_space_medium4"
|
||||
android:layout_gravity="center"
|
||||
android:padding="@dimen/settingslib_expressive_space_extrasmall2"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/circle"
|
||||
android:tint="@color/settingslib_materialColorPrimary"
|
||||
android:importantForAccessibility="no"/>
|
||||
<ImageView
|
||||
android:layout_width="@dimen/settingslib_expressive_space_small3"
|
||||
android:layout_height="@dimen/settingslib_expressive_space_small3"
|
||||
android:layout_gravity="center"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_bluetooth_share_info"
|
||||
android:tint="@color/settingslib_materialColorPrimaryContainer"
|
||||
android:importantForAccessibility="no"/>
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/text_container"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:paddingHorizontal="@dimen/settingslib_expressive_space_small1"
|
||||
android:paddingTop="@dimen/settingslib_expressive_space_small1"
|
||||
android:paddingBottom="@dimen/settingslib_expressive_space_small4"
|
||||
android:orientation="vertical">
|
||||
<TextView
|
||||
android:id="@+id/nearby_sharing_suggestion_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.CardTitle.SettingsLib" />
|
||||
<TextView
|
||||
android:id="@+id/nearby_sharing_suggestion_summary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="@dimen/settingslib_expressive_space_extrasmall2"
|
||||
android:textAppearance="@style/TextAppearance.CardSummary.SettingsLib"
|
||||
android:text="@string/bluetooth_try_nearby_share_summary"/>
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="@dimen/settingslib_expressive_space_small4"
|
||||
android:layout_height="@dimen/settingslib_expressive_space_small4"
|
||||
android:layout_gravity="center"
|
||||
android:src="@drawable/ic_chevron_right_24dp"
|
||||
android:tint="@color/settingslib_materialColorPrimary"
|
||||
android:importantForAccessibility="no"
|
||||
android:contentDescription="@null"/>
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
@@ -198,6 +198,10 @@
|
||||
<string name="bluetooth_audio_routing_summary">Route sounds to your hearing device or phone speaker</string>
|
||||
<!-- Title for related tools section. This section will list related tools below. [CHAR LIMIT=15] -->
|
||||
<string name="bluetooth_screen_related">Related</string>
|
||||
<!-- Title for trying Nearby Sharing in Bluetooth Sharing screen. -->
|
||||
<string name="bluetooth_try_nearby_share_title">Try sharing with <xliff:g id="Nearby Sharing app label" example="Nearby Sharing">%s</xliff:g></string>
|
||||
<!-- Summary for trying Nearby Sharing in Bluetooth Sharing screen. -->
|
||||
<string name="bluetooth_try_nearby_share_summary">The fastest way to send files to nearby Android devices</string>
|
||||
|
||||
<!-- Bluetooth audio output settings. Title of the option managing ringtone and alarms audio path. [CHAR LIMIT=30] -->
|
||||
<string name="bluetooth_ringtone_title">Ringtone and alarms</string>
|
||||
|
@@ -15,12 +15,21 @@
|
||||
-->
|
||||
|
||||
<PreferenceScreen
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:settings="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<com.android.settings.bluetooth.BluetoothProgressCategory
|
||||
android:key="bt_device_list"
|
||||
android:orderingFromXml="false"
|
||||
android:title="@string/bluetooth_preference_found_media_devices" />
|
||||
|
||||
<com.android.settingslib.widget.LayoutPreference
|
||||
android:key="nearby_share_key"
|
||||
android:layout="@layout/nearby_sharing_suggestion_card"
|
||||
android:selectable="false"
|
||||
settings:allowDividerBelow="true"
|
||||
settings:searchable="false"
|
||||
settings:controller="com.android.settings.bluetooth.NearbySharePreferenceController" />
|
||||
|
||||
</PreferenceScreen>
|
||||
|
||||
|
@@ -32,9 +32,11 @@ import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.flags.Flags;
|
||||
import com.android.settings.password.PasswordUtils;
|
||||
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
|
||||
import com.android.settingslib.core.AbstractPreferenceController;
|
||||
@@ -48,6 +50,8 @@ import java.util.List;
|
||||
public final class DevicePickerFragment extends DeviceListPreferenceFragment {
|
||||
private static final String KEY_BT_DEVICE_LIST = "bt_device_list";
|
||||
private static final String TAG = "DevicePickerFragment";
|
||||
private static final String EXTRA_ORIGINAL_SEND_INTENT =
|
||||
"android.bluetooth.extra.DEVICE_PICKER_ORIGINAL_SEND_INTENT";
|
||||
|
||||
@VisibleForTesting
|
||||
BluetoothProgressCategory mAvailableDevicesCategory;
|
||||
@@ -104,6 +108,23 @@ public final class DevicePickerFragment extends DeviceListPreferenceFragment {
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
if (Flags.enableNearbyShareEntrypoint()) {
|
||||
initNearbySharingController();
|
||||
}
|
||||
}
|
||||
|
||||
private void initNearbySharingController() {
|
||||
Intent sendIntent =
|
||||
getIntent().getParcelableExtra(EXTRA_ORIGINAL_SEND_INTENT, Intent.class);
|
||||
if (sendIntent == null) {
|
||||
return;
|
||||
}
|
||||
use(NearbySharePreferenceController.class).init(sendIntent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
|
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (C) 2025 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.app.settings.SettingsEnums
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.PackageManager.NameNotFoundException
|
||||
import android.provider.Settings
|
||||
import android.text.TextUtils
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.preference.PreferenceScreen
|
||||
import com.android.settings.R
|
||||
import com.android.settings.core.BasePreferenceController
|
||||
import com.android.settings.overlay.FeatureFactory
|
||||
import com.android.settingslib.widget.LayoutPreference
|
||||
|
||||
/** Preference controller for Nearby Share. */
|
||||
class NearbySharePreferenceController(private val context: Context, key: String) :
|
||||
BasePreferenceController(context, key) {
|
||||
private lateinit var intent: Intent
|
||||
private var nearbyComponentName: ComponentName? = null
|
||||
private var nearbyLabel: CharSequence? = null
|
||||
|
||||
fun init(sendIntent: Intent) {
|
||||
this.intent = sendIntent
|
||||
val componentString =
|
||||
Settings.Secure.getString(
|
||||
context.getContentResolver(),
|
||||
Settings.Secure.NEARBY_SHARING_COMPONENT,
|
||||
)
|
||||
if (TextUtils.isEmpty(componentString)) {
|
||||
return
|
||||
}
|
||||
nearbyComponentName = ComponentName.unflattenFromString(componentString)?.also {
|
||||
intent.setComponent(it)
|
||||
nearbyLabel = getNearbyLabel(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getAvailabilityStatus(): Int {
|
||||
if (nearbyLabel == null) {
|
||||
return CONDITIONALLY_UNAVAILABLE
|
||||
}
|
||||
return AVAILABLE
|
||||
}
|
||||
|
||||
override fun displayPreference(screen: PreferenceScreen) {
|
||||
super.displayPreference(screen)
|
||||
val preference: LayoutPreference = screen.findPreference(preferenceKey) ?: return
|
||||
|
||||
preference.findViewById<TextView>(R.id.nearby_sharing_suggestion_title).text =
|
||||
context.getString(R.string.bluetooth_try_nearby_share_title, nearbyLabel)
|
||||
FeatureFactory.featureFactory.metricsFeatureProvider.action(
|
||||
SettingsEnums.PAGE_UNKNOWN,
|
||||
SettingsEnums.ACTION_NEARBY_SHARE_ENTRYPOINT_SHOWN,
|
||||
SettingsEnums.BLUETOOTH_DEVICE_PICKER,
|
||||
"",
|
||||
0
|
||||
)
|
||||
preference.findViewById<View>(R.id.card_container).setOnClickListener {
|
||||
FeatureFactory.featureFactory.metricsFeatureProvider.clicked(
|
||||
SettingsEnums.BLUETOOTH_DEVICE_PICKER,
|
||||
preferenceKey
|
||||
)
|
||||
context.startActivity(intent)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun getNearbyLabel(componentName: ComponentName): CharSequence? =
|
||||
try {
|
||||
context.packageManager
|
||||
.getActivityInfo(componentName, PackageManager.GET_META_DATA)
|
||||
.loadLabel(context.packageManager)
|
||||
} catch(_: NameNotFoundException) {
|
||||
null
|
||||
}
|
||||
}
|
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright (C) 2025 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.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.provider.Settings
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import com.android.settings.R
|
||||
import com.android.settingslib.widget.LayoutPreference
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mock
|
||||
import org.mockito.junit.MockitoJUnit
|
||||
import org.mockito.junit.MockitoRule
|
||||
import org.mockito.kotlin.any
|
||||
import org.mockito.kotlin.doNothing
|
||||
import org.mockito.kotlin.eq
|
||||
import org.mockito.kotlin.spy
|
||||
import org.mockito.kotlin.verify
|
||||
import org.mockito.kotlin.whenever
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class NearbySharePreferenceControllerTest : BluetoothDetailsControllerTestBase() {
|
||||
@get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
|
||||
|
||||
@Mock private lateinit var intent: Intent
|
||||
@Mock private lateinit var packageManager: PackageManager
|
||||
@Mock private lateinit var activityInfo: ActivityInfo
|
||||
|
||||
private lateinit var context: Context
|
||||
private lateinit var controller: NearbySharePreferenceController
|
||||
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
context = spy(mContext)
|
||||
whenever(context.packageManager).thenReturn(packageManager)
|
||||
whenever(
|
||||
packageManager.getActivityInfo(
|
||||
eq(ComponentName.unflattenFromString(COMPONENT_NAME)!!),
|
||||
eq(PackageManager.GET_META_DATA),
|
||||
)
|
||||
)
|
||||
.thenReturn(activityInfo)
|
||||
|
||||
controller = NearbySharePreferenceController(context, PREF_KEY)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun noIntent_notAvailable() {
|
||||
Settings.Secure.putString(
|
||||
context.contentResolver,
|
||||
Settings.Secure.NEARBY_SHARING_COMPONENT,
|
||||
COMPONENT_NAME,
|
||||
)
|
||||
whenever(activityInfo.loadLabel(any())).thenReturn("App")
|
||||
|
||||
assertThat(controller.isAvailable).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun noNearbyComponent_notAvailable() {
|
||||
controller.init(intent)
|
||||
|
||||
assertThat(controller.isAvailable).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun hasIntentAndNearbyComponent_available() {
|
||||
Settings.Secure.putString(
|
||||
context.contentResolver,
|
||||
Settings.Secure.NEARBY_SHARING_COMPONENT,
|
||||
COMPONENT_NAME,
|
||||
)
|
||||
whenever(activityInfo.loadLabel(any())).thenReturn("App")
|
||||
controller.init(intent)
|
||||
|
||||
assertThat(controller.isAvailable).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun clickPreference_startActivity() {
|
||||
Settings.Secure.putString(
|
||||
context.contentResolver,
|
||||
Settings.Secure.NEARBY_SHARING_COMPONENT,
|
||||
COMPONENT_NAME,
|
||||
)
|
||||
whenever(activityInfo.loadLabel(any())).thenReturn("App")
|
||||
controller.init(intent)
|
||||
doNothing().whenever(context).startActivity(any())
|
||||
val pref =
|
||||
LayoutPreference(
|
||||
context,
|
||||
LayoutInflater.from(context).inflate(R.layout.nearby_sharing_suggestion_card, null),
|
||||
)
|
||||
pref.key = PREF_KEY
|
||||
mScreen.addPreference(pref)
|
||||
controller.displayPreference(mScreen)
|
||||
|
||||
pref.findViewById<View>(R.id.card_container).performClick()
|
||||
|
||||
verify(context).startActivity(intent)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val COMPONENT_NAME = "com.example/.BComponent"
|
||||
const val PREF_KEY = "key"
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user