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