diff --git a/aconfig/settings_bluetooth_declarations.aconfig b/aconfig/settings_bluetooth_declarations.aconfig
index 7aa989bb0d3..4d2528a71db 100644
--- a/aconfig/settings_bluetooth_declarations.aconfig
+++ b/aconfig/settings_bluetooth_declarations.aconfig
@@ -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
+ }
+}
diff --git a/res/drawable/ic_bluetooth_share_info.xml b/res/drawable/ic_bluetooth_share_info.xml
new file mode 100644
index 00000000000..860c5536f0f
--- /dev/null
+++ b/res/drawable/ic_bluetooth_share_info.xml
@@ -0,0 +1,25 @@
+
+
+
+
diff --git a/res/layout/nearby_sharing_suggestion_card.xml b/res/layout/nearby_sharing_suggestion_card.xml
new file mode 100644
index 00000000000..6c9d310e439
--- /dev/null
+++ b/res/layout/nearby_sharing_suggestion_card.xml
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 3679ba403ab..c22ce6ce790 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -198,6 +198,10 @@
Route sounds to your hearing device or phone speaker
Related
+
+ Try sharing with %s
+
+ The fastest way to send files to nearby Android devices
Ringtone and alarms
diff --git a/res/xml/device_picker.xml b/res/xml/device_picker.xml
index 6f8d267cd66..5e7667dc80e 100644
--- a/res/xml/device_picker.xml
+++ b/res/xml/device_picker.xml
@@ -15,12 +15,21 @@
-->
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto">
+
+
diff --git a/res/xml/storage_category_fragment.xml b/res/xml/storage_category_fragment.xml
index 8af66396bd5..1742832df2a 100644
--- a/res/xml/storage_category_fragment.xml
+++ b/res/xml/storage_category_fragment.xml
@@ -19,7 +19,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:title="@string/storage_settings">
- (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(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
+ }
+}
diff --git a/src/com/android/settings/connecteddevice/usb/OWNERS b/src/com/android/settings/connecteddevice/usb/OWNERS
index bb3b8fcef67..60abfebf7c4 100644
--- a/src/com/android/settings/connecteddevice/usb/OWNERS
+++ b/src/com/android/settings/connecteddevice/usb/OWNERS
@@ -1,5 +1,4 @@
# Default reviewers for this and subdirectories.
-zhangjerry@google.com
badhri@google.com
hughchen@google.com
timhypeng@google.com
diff --git a/src/com/android/settings/datetime/DateTimeLaunchUtils.java b/src/com/android/settings/datetime/DateTimeLaunchUtils.java
index aef0ff2080b..f8cf0be2047 100644
--- a/src/com/android/settings/datetime/DateTimeLaunchUtils.java
+++ b/src/com/android/settings/datetime/DateTimeLaunchUtils.java
@@ -15,7 +15,7 @@
*/
package com.android.settings.datetime;
-import static android.provider.DeviceConfig.NAMESPACE_SETTINGS_UI;
+import static android.provider.DeviceConfig.NAMESPACE_SYSTEM_TIME;
import android.provider.DeviceConfig;
@@ -47,6 +47,6 @@ final class DateTimeLaunchUtils {
private static boolean isFeedbackFeatureSupportedOnThisDevice() {
boolean defaultIsSupported = false;
return DeviceConfig.getBoolean(
- NAMESPACE_SETTINGS_UI, KEY_HELP_AND_FEEDBACK_FEATURE_SUPPORTED, defaultIsSupported);
+ NAMESPACE_SYSTEM_TIME, KEY_HELP_AND_FEEDBACK_FEATURE_SUPPORTED, defaultIsSupported);
}
}
diff --git a/src/com/android/settings/security/screenlock/AutoPinConfirmPreferenceController.java b/src/com/android/settings/security/screenlock/AutoPinConfirmPreferenceController.java
index 560f3134d66..673ff522116 100644
--- a/src/com/android/settings/security/screenlock/AutoPinConfirmPreferenceController.java
+++ b/src/com/android/settings/security/screenlock/AutoPinConfirmPreferenceController.java
@@ -95,7 +95,6 @@ public class AutoPinConfirmPreferenceController extends AbstractPreferenceContro
.setRequestCode(newState
? ScreenLockSettings.AUTO_PIN_SETTING_ENABLING_REQUEST_CODE
: ScreenLockSettings.AUTO_PIN_SETTING_DISABLING_REQUEST_CODE)
- .setTitle(mContext.getString(R.string.lock_screen_auto_pin_confirm_title))
.setDescription(newState
? mContext.getString(R.string.auto_confirm_on_pin_verify_description)
: mContext.getString(R.string.auto_confirm_off_pin_verify_description))
diff --git a/src/com/android/settings/spa/SettingsSpaEnvironment.kt b/src/com/android/settings/spa/SettingsSpaEnvironment.kt
index 7702db6bcde..754b30b5ab7 100644
--- a/src/com/android/settings/spa/SettingsSpaEnvironment.kt
+++ b/src/com/android/settings/spa/SettingsSpaEnvironment.kt
@@ -64,6 +64,7 @@ import com.android.settingslib.spa.framework.common.SpaLogger
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListProvider
import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListTemplate
+import com.android.settingslib.widget.theme.flags.Flags
open class SettingsSpaEnvironment(context: Context) : SpaEnvironment(context) {
open fun getTogglePermissionAppListProviders(): List {
@@ -132,4 +133,8 @@ open class SettingsSpaEnvironment(context: Context) : SpaEnvironment(context) {
)
) SpaLogMetricsProvider // ToDo: Implement 'SpaLogProvider' for SPA settings.
else object : SpaLogger {}
+
+ override val isSpaExpressiveEnabled by lazy {
+ super.isSpaExpressiveEnabled || Flags.isExpressiveDesignEnabled()
+ }
}
diff --git a/tests/robotests/src/com/android/settings/bluetooth/NearbySharePreferenceControllerTest.kt b/tests/robotests/src/com/android/settings/bluetooth/NearbySharePreferenceControllerTest.kt
new file mode 100644
index 00000000000..2055e886c4e
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/bluetooth/NearbySharePreferenceControllerTest.kt
@@ -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(R.id.card_container).performClick()
+
+ verify(context).startActivity(intent)
+ }
+
+ private companion object {
+ const val COMPONENT_NAME = "com.example/.BComponent"
+ const val PREF_KEY = "key"
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/datetime/TimeFeedbackPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/datetime/TimeFeedbackPreferenceControllerTest.java
index 196aa36d8de..6cbb14f2142 100644
--- a/tests/robotests/src/com/android/settings/datetime/TimeFeedbackPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/datetime/TimeFeedbackPreferenceControllerTest.java
@@ -16,7 +16,7 @@
package com.android.settings.datetime;
-import static android.provider.DeviceConfig.NAMESPACE_SETTINGS_UI;
+import static android.provider.DeviceConfig.NAMESPACE_SYSTEM_TIME;
import static com.android.settings.core.BasePreferenceController.AVAILABLE;
import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE;
@@ -93,7 +93,7 @@ public class TimeFeedbackPreferenceControllerTest {
@Test
@EnableFlags({Flags.FLAG_DATETIME_FEEDBACK})
public void validIntentUri_targetHandlerNotFound_returnsConditionallyUnavailable() {
- DeviceConfig.setProperty(NAMESPACE_SETTINGS_UI,
+ DeviceConfig.setProperty(NAMESPACE_SYSTEM_TIME,
DateTimeLaunchUtils.KEY_HELP_AND_FEEDBACK_FEATURE_SUPPORTED, "true", true);
when(mMockPackageManager.resolveActivity(any(), anyInt())).thenReturn(null);
@@ -107,7 +107,7 @@ public class TimeFeedbackPreferenceControllerTest {
@Test
@EnableFlags({Flags.FLAG_DATETIME_FEEDBACK})
public void validIntentUri_targetHandlerAvailable_returnsAvailable() {
- DeviceConfig.setProperty(NAMESPACE_SETTINGS_UI,
+ DeviceConfig.setProperty(NAMESPACE_SYSTEM_TIME,
DateTimeLaunchUtils.KEY_HELP_AND_FEEDBACK_FEATURE_SUPPORTED, "true", true);
when(mMockPackageManager.resolveActivity(any(), anyInt())).thenReturn(
createDummyResolveInfo());