diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 90b42f41cbe..ca963287c18 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -140,7 +140,7 @@ - + diff --git a/aconfig/settings_wifi_flag_declarations.aconfig b/aconfig/settings_wifi_flag_declarations.aconfig new file mode 100644 index 00000000000..cb8007f2e93 --- /dev/null +++ b/aconfig/settings_wifi_flag_declarations.aconfig @@ -0,0 +1,12 @@ +package: "com.android.settings.flags" +container: "system_ext" + +# NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors. + +flag { + name: "enable_wifi_sharing_runtime_fragment" + namespace: "prism_qr" + description: "Use WifiFeatureProvider to get the instance of WifiDppQrCodeGeneratorFragment." + bug: "329012096" +} + diff --git a/res/layout/zen_mode_type_item.xml b/res/layout/zen_mode_type_item.xml new file mode 100644 index 00000000000..841ca0066f8 --- /dev/null +++ b/res/layout/zen_mode_type_item.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index f92fd2aca84..386ece3caf4 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -7927,6 +7927,19 @@ Only get notified by important people and apps + + Select activation type + + + Time + + Ex. \"9:30 – 5:00 PM\" + + + Calendar + + Ex. \"Personal calendar\" + Limit interruptions diff --git a/src/com/android/settings/ResetNetworkRequest.java b/src/com/android/settings/ResetNetworkRequest.java index 7632ea01d71..8df67e771b2 100644 --- a/src/com/android/settings/ResetNetworkRequest.java +++ b/src/com/android/settings/ResetNetworkRequest.java @@ -271,12 +271,12 @@ public class ResetNetworkRequest { builder.resetIms(mSubscriptionIdToResetIms); } // Reset phone process and RILD may impact above components, keep them at the end - if ((mResetOptions & RESET_PHONE_PROCESS) != 0) { - builder.restartPhoneProcess(); - } if ((mResetOptions & RESET_RILD) != 0) { builder.restartRild(); } + if ((mResetOptions & RESET_PHONE_PROCESS) != 0) { + builder.restartPhoneProcess(); + } return builder; } } diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaService.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaService.java index 6d297f4cd7c..f812e06912a 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaService.java +++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaService.java @@ -75,6 +75,10 @@ public class AudioStreamMediaService extends Service { public void onSourceLost(int broadcastId) { super.onSourceLost(broadcastId); if (broadcastId == mBroadcastId) { + Log.d(TAG, "onSourceLost() : stopSelf"); + if (mNotificationManager != null) { + mNotificationManager.cancel(NOTIFICATION_ID); + } stopSelf(); } } @@ -86,6 +90,10 @@ public class AudioStreamMediaService extends Service { && mAudioStreamsHelper.getAllConnectedSources().stream() .map(BluetoothLeBroadcastReceiveState::getBroadcastId) .noneMatch(id -> id == mBroadcastId)) { + Log.d(TAG, "onSourceRemoved() : stopSelf"); + if (mNotificationManager != null) { + mNotificationManager.cancel(NOTIFICATION_ID); + } stopSelf(); } } @@ -96,6 +104,10 @@ public class AudioStreamMediaService extends Service { @Override public void onBluetoothStateChanged(int bluetoothState) { if (BluetoothAdapter.STATE_OFF == bluetoothState) { + Log.d(TAG, "onBluetoothStateChanged() : stopSelf"); + if (mNotificationManager != null) { + mNotificationManager.cancel(NOTIFICATION_ID); + } stopSelf(); } } @@ -120,6 +132,10 @@ public class AudioStreamMediaService extends Service { }); } if (mDevices == null || mDevices.isEmpty()) { + Log.d(TAG, "onProfileConnectionStateChanged() : stopSelf"); + if (mNotificationManager != null) { + mNotificationManager.cancel(NOTIFICATION_ID); + } stopSelf(); } } @@ -246,21 +262,27 @@ public class AudioStreamMediaService extends Service { @Override public void onDestroy() { + Log.d(TAG, "onDestroy()"); super.onDestroy(); if (!AudioSharingUtils.isFeatureEnabled()) { + Log.d(TAG, "onDestroy() : skip due to feature not enabled"); return; } if (mLocalBtManager != null) { + Log.d(TAG, "onDestroy() : unregister mBluetoothCallback"); mLocalBtManager.getEventManager().unregisterCallback(mBluetoothCallback); } if (mLeBroadcastAssistant != null) { + Log.d(TAG, "onDestroy() : unregister mBroadcastAssistantCallback"); mLeBroadcastAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback); } if (mVolumeControl != null) { + Log.d(TAG, "onDestroy() : unregister mVolumeControlCallback"); mVolumeControl.unregisterCallback(mVolumeControlCallback); } if (mLocalSession != null) { + Log.d(TAG, "onDestroy() : release mLocalSession"); mLocalSession.release(); mLocalSession = null; } @@ -273,6 +295,9 @@ public class AudioStreamMediaService extends Service { mBroadcastId = intent != null ? intent.getIntExtra(BROADCAST_ID, -1) : -1; if (mBroadcastId == -1) { Log.w(TAG, "Invalid broadcast ID. Service will not start."); + if (mNotificationManager != null) { + mNotificationManager.cancel(NOTIFICATION_ID); + } stopSelf(); return START_NOT_STICKY; } @@ -282,6 +307,9 @@ public class AudioStreamMediaService extends Service { } if (mDevices == null || mDevices.isEmpty()) { Log.w(TAG, "No device. Service will not start."); + if (mNotificationManager != null) { + mNotificationManager.cancel(NOTIFICATION_ID); + } stopSelf(); return START_NOT_STICKY; } diff --git a/src/com/android/settings/fuelgauge/batteryusage/bugreport/BugReportContentProvider.java b/src/com/android/settings/fuelgauge/batteryusage/bugreport/BugReportContentProvider.java index 7613d9ab097..baae10959c3 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/bugreport/BugReportContentProvider.java +++ b/src/com/android/settings/fuelgauge/batteryusage/bugreport/BugReportContentProvider.java @@ -60,6 +60,7 @@ public final class BugReportContentProvider extends ContentProvider { LogUtils.dumpBatteryUsageSlotDatabaseHist(context, writer); LogUtils.dumpBatteryEventDatabaseHist(context, writer); LogUtils.dumpBatteryStateDatabaseHist(context, writer); + LogUtils.dumpBatteryReattributeDatabaseHist(context, writer); } @Override diff --git a/src/com/android/settings/fuelgauge/batteryusage/bugreport/LogUtils.java b/src/com/android/settings/fuelgauge/batteryusage/bugreport/LogUtils.java index b2300308fd4..d2f3adcd9d7 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/bugreport/LogUtils.java +++ b/src/com/android/settings/fuelgauge/batteryusage/bugreport/LogUtils.java @@ -19,6 +19,8 @@ package com.android.settings.fuelgauge.batteryusage.bugreport; import android.content.Context; import android.util.Log; +import androidx.annotation.VisibleForTesting; + import com.android.settings.fuelgauge.BatteryUtils; import com.android.settings.fuelgauge.batteryusage.AppOptModeSharedPreferencesUtils; import com.android.settings.fuelgauge.batteryusage.AppOptimizationModeEvent; @@ -29,6 +31,8 @@ import com.android.settings.fuelgauge.batteryusage.db.AppUsageEventDao; import com.android.settings.fuelgauge.batteryusage.db.AppUsageEventEntity; import com.android.settings.fuelgauge.batteryusage.db.BatteryEventDao; import com.android.settings.fuelgauge.batteryusage.db.BatteryEventEntity; +import com.android.settings.fuelgauge.batteryusage.db.BatteryReattributeDao; +import com.android.settings.fuelgauge.batteryusage.db.BatteryReattributeEntity; import com.android.settings.fuelgauge.batteryusage.db.BatteryState; import com.android.settings.fuelgauge.batteryusage.db.BatteryStateDao; import com.android.settings.fuelgauge.batteryusage.db.BatteryStateDatabase; @@ -127,6 +131,24 @@ public final class LogUtils { dumpListItems(writer, entities, entity -> entity); } + static void dumpBatteryReattributeDatabaseHist(Context context, PrintWriter writer) { + dumpBatteryReattributeDatabaseHist( + BatteryStateDatabase.getInstance(context).batteryReattributeDao(), + writer); + } + + @VisibleForTesting + static void dumpBatteryReattributeDatabaseHist( + BatteryReattributeDao batteryReattributeDao, PrintWriter writer) { + writer.println("\n\tBatteryReattribute DatabaseHistory:"); + final List entities = + batteryReattributeDao.getAllAfter( + Clock.systemUTC().millis() - DUMP_TIME_OFFSET.toMillis()); + if (entities != null && !entities.isEmpty()) { + dumpListItems(writer, entities, entity -> entity); + } + } + private static void dumpListItems( PrintWriter writer, List itemList, Function itemConverter) { final AtomicInteger counter = new AtomicInteger(0); diff --git a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryReattributeEntity.java b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryReattributeEntity.java index 6abfb81bf83..53678496d16 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryReattributeEntity.java +++ b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryReattributeEntity.java @@ -62,6 +62,7 @@ public class BatteryReattributeEntity { .append("\nBatteryReattributeEntity{") .append("\n\t" + utcToLocalTimeForLogging(timestampStart)) .append("\n\t" + utcToLocalTimeForLogging(timestampEnd)) + .append("\n\t" + ConvertUtils.decodeBatteryReattribute(reattributeData)) .append("\n}"); return builder.toString(); } diff --git a/src/com/android/settings/network/ResetNetworkOperationBuilder.java b/src/com/android/settings/network/ResetNetworkOperationBuilder.java index 6f36074d145..47c06d4480d 100644 --- a/src/com/android/settings/network/ResetNetworkOperationBuilder.java +++ b/src/com/android/settings/network/ResetNetworkOperationBuilder.java @@ -18,6 +18,7 @@ package com.android.settings.network; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothManager; +import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.Context; import android.net.ConnectivityManager; @@ -28,11 +29,14 @@ import android.net.wifi.WifiManager; import android.net.wifi.p2p.WifiP2pManager; import android.os.Looper; import android.os.RecoverySystem; +import android.os.RemoteException; import android.os.SystemClock; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.util.Log; +import androidx.annotation.Nullable; + import com.android.internal.annotations.VisibleForTesting; import com.android.settings.R; import com.android.settings.ResetNetworkRequest; @@ -257,15 +261,15 @@ public class ResetNetworkOperationBuilder { */ public ResetNetworkOperationBuilder restartPhoneProcess() { Runnable runnable = () -> { - try { - mContext.getContentResolver().call( - getResetTelephonyContentProviderAuthority(), - METHOD_RESTART_PHONE_PROCESS, - /* arg= */ null, - /* extras= */ null); - Log.i(TAG, "Phone process was restarted."); - } catch (IllegalArgumentException iae) { - Log.w(TAG, "Fail to restart phone process: " + iae); + // Unstable content provider can avoid us getting killed together with phone process + try (ContentProviderClient client = getUnstableTelephonyContentProviderClient()) { + if (client != null) { + client.call(METHOD_RESTART_PHONE_PROCESS, /* arg= */ null, /* extra= */ null); + Log.i(TAG, "Phone process was restarted."); + } + } catch (RemoteException re) { + // It's normal to throw RE since phone process immediately dies + Log.i(TAG, "Phone process has been restarted: " + re); } }; mResetSequence.add(runnable); @@ -279,15 +283,13 @@ public class ResetNetworkOperationBuilder { */ public ResetNetworkOperationBuilder restartRild() { Runnable runnable = () -> { - try { - mContext.getContentResolver().call( - getResetTelephonyContentProviderAuthority(), - METHOD_RESTART_RILD, - /* arg= */ null, - /* extras= */ null); - Log.i(TAG, "RILD was restarted."); - } catch (IllegalArgumentException iae) { - Log.w(TAG, "Fail to restart RILD: " + iae); + try (ContentProviderClient client = getUnstableTelephonyContentProviderClient()) { + if (client != null) { + client.call(METHOD_RESTART_RILD, /* arg= */ null, /* extra= */ null); + Log.i(TAG, "RILD was restarted."); + } + } catch (RemoteException re) { + Log.w(TAG, "Fail to restart RILD: " + re); } }; mResetSequence.add(runnable); @@ -322,9 +324,18 @@ public class ResetNetworkOperationBuilder { * @return the authority of the telephony content provider that support methods * resetPhoneProcess and resetRild. */ - @VisibleForTesting - String getResetTelephonyContentProviderAuthority() { + private String getResetTelephonyContentProviderAuthority() { return mContext.getResources().getString( R.string.reset_telephony_stack_content_provider_authority); } + + /** + * @return the unstable content provider to avoid us getting killed with phone process + */ + @Nullable + @VisibleForTesting + public ContentProviderClient getUnstableTelephonyContentProviderClient() { + return mContext.getContentResolver().acquireUnstableContentProviderClient( + getResetTelephonyContentProviderAuthority()); + } } diff --git a/src/com/android/settings/network/apn/ApnEditCarrierEnabled.kt b/src/com/android/settings/network/apn/ApnEditCarrierEnabled.kt new file mode 100644 index 00000000000..bd58da873ab --- /dev/null +++ b/src/com/android/settings/network/apn/ApnEditCarrierEnabled.kt @@ -0,0 +1,40 @@ +/* + * 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. + */ + +package com.android.settings.network.apn + +import android.provider.Telephony +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.booleanResource +import androidx.compose.ui.res.stringResource +import com.android.settings.R +import com.android.settingslib.spa.widget.preference.SwitchPreference +import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel + +@Composable +fun ApnEditCarrierEnabled(apnData: ApnData, onCarrierEnabledChanged: (Boolean) -> Unit) { + SwitchPreference( + object : SwitchPreferenceModel { + override val title = stringResource(R.string.carrier_enabled) + val allowEdit = booleanResource(R.bool.config_allow_edit_carrier_enabled) + override val changeable = { + allowEdit && apnData.isFieldEnabled(Telephony.Carriers.CARRIER_ENABLED) + } + override val checked = { apnData.carrierEnabled } + override val onCheckedChange = onCarrierEnabledChanged + } + ) +} diff --git a/src/com/android/settings/network/apn/ApnEditPageProvider.kt b/src/com/android/settings/network/apn/ApnEditPageProvider.kt index 099e2faa357..544208247fa 100644 --- a/src/com/android/settings/network/apn/ApnEditPageProvider.kt +++ b/src/com/android/settings/network/apn/ApnEditPageProvider.kt @@ -235,19 +235,7 @@ fun ApnPage(apnDataInit: ApnData, apnDataCur: MutableState, uriInit: Ur enabled = apnData.isFieldEnabled(Telephony.Carriers.ROAMING_PROTOCOL), ) { apnData = apnData.copy(apnRoaming = it) } ApnNetworkTypeCheckBox(apnData) { apnData = apnData.copy(networkType = it) } - SwitchPreference( - object : SwitchPreferenceModel { - override val title = stringResource(R.string.carrier_enabled) - override val changeable = { - apnData.apnEnableEnabled && - apnData.isFieldEnabled(Telephony.Carriers.CARRIER_ENABLED) - } - override val checked = { apnData.apnEnable } - override val onCheckedChange = { newChecked: Boolean -> - apnData = apnData.copy(apnEnable = newChecked) - } - } - ) + ApnEditCarrierEnabled(apnData) { apnData = apnData.copy(carrierEnabled = it) } } } } \ No newline at end of file diff --git a/src/com/android/settings/network/apn/ApnRepository.kt b/src/com/android/settings/network/apn/ApnRepository.kt index 2d41976b239..843371501b7 100644 --- a/src/com/android/settings/network/apn/ApnRepository.kt +++ b/src/com/android/settings/network/apn/ApnRepository.kt @@ -90,7 +90,7 @@ fun getApnDataFromUri(uri: Uri, context: Context): ApnData { apnRoaming = context.convertProtocol2Options( cursor.getString(Telephony.Carriers.ROAMING_PROTOCOL) ), - apnEnable = cursor.getInt(Telephony.Carriers.CARRIER_ENABLED) == 1, + carrierEnabled = cursor.getInt(Telephony.Carriers.CARRIER_ENABLED) == 1, networkType = cursor.getLong(Telephony.Carriers.NETWORK_TYPE_BITMASK), edited = cursor.getInt(Telephony.Carriers.EDITED_STATUS), userEditable = cursor.getInt(Telephony.Carriers.USER_EDITABLE), diff --git a/src/com/android/settings/network/apn/ApnStatus.kt b/src/com/android/settings/network/apn/ApnStatus.kt index dc50452a995..6492d39417d 100644 --- a/src/com/android/settings/network/apn/ApnStatus.kt +++ b/src/com/android/settings/network/apn/ApnStatus.kt @@ -44,11 +44,10 @@ data class ApnData( val apnType: String = "", val apnProtocol: Int = -1, val apnRoaming: Int = -1, - val apnEnable: Boolean = true, + val carrierEnabled: Boolean = true, val networkType: Long = 0, val edited: Int = Telephony.Carriers.USER_EDITED, val userEditable: Int = 1, - val apnEnableEnabled: Boolean = true, val newApn: Boolean = false, val subId: Int = -1, val validEnabled: Boolean = false, @@ -72,7 +71,7 @@ data class ApnData( Telephony.Carriers.NETWORK_TYPE_BITMASK to networkType, // Copy network type into lingering network type. Telephony.Carriers.LINGERING_NETWORK_TYPE_BITMASK to networkType, - Telephony.Carriers.CARRIER_ENABLED to apnEnable, + Telephony.Carriers.CARRIER_ENABLED to carrierEnabled, Telephony.Carriers.EDITED_STATUS to Telephony.Carriers.USER_EDITED, ) @@ -134,10 +133,6 @@ fun getApnDataInit(arguments: Bundle, context: Context, uriInit: Uri, subId: Int ) } - apnDataInit = apnDataInit.copy( - apnEnableEnabled = - context.resources.getBoolean(R.bool.config_allow_edit_carrier_enabled) - ) // TODO: mIsCarrierIdApn return disableInit(apnDataInit) } diff --git a/src/com/android/settings/notification/modes/ZenMode.java b/src/com/android/settings/notification/modes/ZenMode.java index cbe915b5bab..1040d1e0021 100644 --- a/src/com/android/settings/notification/modes/ZenMode.java +++ b/src/com/android/settings/notification/modes/ZenMode.java @@ -18,6 +18,12 @@ package com.android.settings.notification.modes; import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL; import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY; +import static android.service.notification.SystemZenRules.getTriggerDescriptionForScheduleEvent; +import static android.service.notification.SystemZenRules.getTriggerDescriptionForScheduleTime; +import static android.service.notification.ZenModeConfig.tryParseEventConditionId; +import static android.service.notification.ZenModeConfig.tryParseScheduleConditionId; + +import static com.google.common.base.Preconditions.checkState; import static java.util.Objects.requireNonNull; @@ -26,7 +32,10 @@ import android.app.AutomaticZenRule; import android.app.NotificationManager; import android.content.Context; import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.service.notification.SystemZenRules; import android.service.notification.ZenDeviceEffects; +import android.service.notification.ZenModeConfig; import android.service.notification.ZenPolicy; import android.util.Log; @@ -204,6 +213,44 @@ class ZenMode { : new ZenDeviceEffects.Builder().build(); } + public void setCustomModeConditionId(Context context, Uri conditionId) { + checkState(SystemZenRules.PACKAGE_ANDROID.equals(mRule.getPackageName()), + "Trying to change condition of non-system-owned rule %s (to %s)", + mRule, conditionId); + + Uri oldCondition = mRule.getConditionId(); + mRule.setConditionId(conditionId); + + ZenModeConfig.ScheduleInfo scheduleInfo = tryParseScheduleConditionId(conditionId); + if (scheduleInfo != null) { + mRule.setType(AutomaticZenRule.TYPE_SCHEDULE_TIME); + mRule.setOwner(ZenModeConfig.getScheduleConditionProvider()); + mRule.setTriggerDescription( + getTriggerDescriptionForScheduleTime(context, scheduleInfo)); + return; + } + + ZenModeConfig.EventInfo eventInfo = tryParseEventConditionId(conditionId); + if (eventInfo != null) { + mRule.setType(AutomaticZenRule.TYPE_SCHEDULE_CALENDAR); + mRule.setOwner(ZenModeConfig.getEventConditionProvider()); + mRule.setTriggerDescription(getTriggerDescriptionForScheduleEvent(context, eventInfo)); + return; + } + + if (ZenModeConfig.isValidCustomManualConditionId(conditionId)) { + mRule.setType(AutomaticZenRule.TYPE_OTHER); + mRule.setOwner(ZenModeConfig.getCustomManualConditionProvider()); + mRule.setTriggerDescription(""); + return; + } + + Log.wtf(TAG, String.format( + "Changed condition of rule %s (%s -> %s) but cannot recognize which kind of " + + "condition it was!", + mRule, oldCondition, conditionId)); + } + public boolean canEditName() { return !isManualDnd(); } @@ -224,6 +271,15 @@ class ZenMode { return mIsActive; } + public boolean isSystemOwned() { + return SystemZenRules.PACKAGE_ANDROID.equals(mRule.getPackageName()); + } + + @AutomaticZenRule.Type + public int getType() { + return mRule.getType(); + } + @Override public boolean equals(@Nullable Object obj) { return obj instanceof ZenMode other diff --git a/src/com/android/settings/notification/modes/ZenModeFragment.java b/src/com/android/settings/notification/modes/ZenModeFragment.java index 6bda5e13c97..ee497ae74df 100644 --- a/src/com/android/settings/notification/modes/ZenModeFragment.java +++ b/src/com/android/settings/notification/modes/ZenModeFragment.java @@ -52,7 +52,7 @@ public class ZenModeFragment extends ZenModeFragmentBase { prefControllers.add(new ZenModeDisplayLinkPreferenceController( context, "mode_display_settings", mBackend, mHelperBackend)); prefControllers.add(new ZenModeSetTriggerLinkPreferenceController(context, - "zen_automatic_trigger_category", mBackend)); + "zen_automatic_trigger_category", this, mBackend)); return prefControllers; } diff --git a/src/com/android/settings/notification/modes/ZenModeScheduleChooserDialog.java b/src/com/android/settings/notification/modes/ZenModeScheduleChooserDialog.java new file mode 100644 index 00000000000..14264b7a844 --- /dev/null +++ b/src/com/android/settings/notification/modes/ZenModeScheduleChooserDialog.java @@ -0,0 +1,149 @@ +/* + * 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. + */ + +package com.android.settings.notification.modes; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import android.app.Dialog; +import android.content.Context; +import android.net.Uri; +import android.os.Bundle; +import android.service.notification.ZenModeConfig; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.appcompat.app.AlertDialog; + +import com.android.settings.R; +import com.android.settings.core.instrumentation.InstrumentedDialogFragment; +import com.android.settings.dashboard.DashboardFragment; + +import com.google.common.collect.ImmutableList; + +public class ZenModeScheduleChooserDialog extends InstrumentedDialogFragment { + + private static final String TAG = "ZenModeScheduleChooserDialog"; + + static final int OPTION_TIME = 0; + static final int OPTION_CALENDAR = 1; + + private record ScheduleOption(@StringRes int nameResId, @StringRes int exampleResId, + @DrawableRes int iconResId) {} + + private static final ImmutableList SCHEDULE_OPTIONS = ImmutableList.of( + new ScheduleOption(R.string.zen_mode_select_schedule_time, + R.string.zen_mode_select_schedule_time_example, + com.android.internal.R.drawable.ic_zen_mode_type_schedule_time), + new ScheduleOption(R.string.zen_mode_select_schedule_calendar, + R.string.zen_mode_select_schedule_calendar_example, + com.android.internal.R.drawable.ic_zen_mode_type_schedule_calendar)); + + private OnScheduleOptionListener mOptionListener; + + interface OnScheduleOptionListener { + void onScheduleSelected(Uri conditionId); + } + + @Override + public int getMetricsCategory() { + // TODO: b/332937635 - Update metrics category + return 0; + } + + static void show(DashboardFragment parent, OnScheduleOptionListener optionListener) { + ZenModeScheduleChooserDialog dialog = new ZenModeScheduleChooserDialog(); + dialog.mOptionListener = optionListener; + dialog.setTargetFragment(parent, 0); + dialog.show(parent.getParentFragmentManager(), TAG); + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + checkState(getContext() != null); + return new AlertDialog.Builder(getContext()) + .setTitle(R.string.zen_mode_choose_rule_type) + .setAdapter(new OptionsAdapter(getContext()), + (dialog, which) -> onScheduleTypeSelected(which)) + .setNegativeButton(R.string.cancel, null) + .create(); + } + + private static class OptionsAdapter extends ArrayAdapter { + + private final LayoutInflater mInflater; + + OptionsAdapter(@NonNull Context context) { + super(context, R.layout.zen_mode_type_item, SCHEDULE_OPTIONS); + mInflater = LayoutInflater.from(context); + } + + @NonNull + @Override + public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { + if (convertView == null) { + convertView = mInflater.inflate(R.layout.zen_mode_type_item, parent, false); + } + // No need for holder pattern since we have only 2 items. + ImageView imageView = checkNotNull(convertView.findViewById(R.id.icon)); + TextView title = checkNotNull(convertView.findViewById(R.id.title)); + TextView subtitle = checkNotNull(convertView.findViewById(R.id.subtitle)); + + ScheduleOption option = checkNotNull(getItem(position)); + imageView.setImageResource(option.iconResId()); + title.setText(option.nameResId()); + subtitle.setText(option.exampleResId()); + + return convertView; + } + } + + private void onScheduleTypeSelected(int whichOption) { + Uri conditionId = switch (whichOption) { + case OPTION_TIME -> getDefaultScheduleTimeCondition(); + case OPTION_CALENDAR -> getDefaultScheduleCalendarCondition(); + default -> ZenModeConfig.toCustomManualConditionId(); + }; + + mOptionListener.onScheduleSelected(conditionId); + } + + private static Uri getDefaultScheduleTimeCondition() { + ZenModeConfig.ScheduleInfo schedule = new ZenModeConfig.ScheduleInfo(); + schedule.days = ZenModeConfig.ALL_DAYS; + schedule.startHour = 9; + schedule.startMinute = 30; + schedule.endHour = 17; + return ZenModeConfig.toScheduleConditionId(schedule); + } + + private static Uri getDefaultScheduleCalendarCondition() { + ZenModeConfig.EventInfo eventInfo = new ZenModeConfig.EventInfo(); + eventInfo.calendarId = null; // All calendars of the current user. + eventInfo.reply = ZenModeConfig.EventInfo.REPLY_ANY_EXCEPT_NO; + return ZenModeConfig.toEventConditionId(eventInfo); + } +} diff --git a/src/com/android/settings/notification/modes/ZenModeSetCalendarPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeSetCalendarPreferenceController.java index 28413091a37..e87907647db 100644 --- a/src/com/android/settings/notification/modes/ZenModeSetCalendarPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeSetCalendarPreferenceController.java @@ -16,14 +16,12 @@ package com.android.settings.notification.modes; -import android.app.Flags; import android.content.Context; import android.content.pm.PackageManager; import android.database.Cursor; import android.os.UserHandle; import android.os.UserManager; import android.provider.CalendarContract; -import android.service.notification.SystemZenRules; import android.service.notification.ZenModeConfig; import androidx.annotation.NonNull; @@ -42,7 +40,7 @@ import java.util.List; import java.util.Objects; import java.util.function.Function; -public class ZenModeSetCalendarPreferenceController extends AbstractZenModePreferenceController { +class ZenModeSetCalendarPreferenceController extends AbstractZenModePreferenceController { @VisibleForTesting protected static final String KEY_CALENDAR = "calendar"; @VisibleForTesting @@ -122,11 +120,7 @@ public class ZenModeSetCalendarPreferenceController extends AbstractZenModePrefe @VisibleForTesting protected Function updateEventMode(ZenModeConfig.EventInfo event) { return (zenMode) -> { - zenMode.getRule().setConditionId(ZenModeConfig.toEventConditionId(event)); - if (Flags.modesApi() && Flags.modesUi()) { - zenMode.getRule().setTriggerDescription( - SystemZenRules.getTriggerDescriptionForScheduleEvent(mContext, event)); - } + zenMode.setCustomModeConditionId(mContext, ZenModeConfig.toEventConditionId(event)); return zenMode; }; } diff --git a/src/com/android/settings/notification/modes/ZenModeSetSchedulePreferenceController.java b/src/com/android/settings/notification/modes/ZenModeSetSchedulePreferenceController.java index a6008ccd768..3432ed5154f 100644 --- a/src/com/android/settings/notification/modes/ZenModeSetSchedulePreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeSetSchedulePreferenceController.java @@ -16,9 +16,7 @@ package com.android.settings.notification.modes; -import android.app.Flags; import android.content.Context; -import android.service.notification.SystemZenRules; import android.service.notification.ZenModeConfig; import android.text.format.DateFormat; import android.util.ArraySet; @@ -116,16 +114,13 @@ class ZenModeSetSchedulePreferenceController extends AbstractZenModePreferenceCo @VisibleForTesting protected Function updateScheduleMode(ZenModeConfig.ScheduleInfo schedule) { return (zenMode) -> { - zenMode.getRule().setConditionId(ZenModeConfig.toScheduleConditionId(schedule)); - if (Flags.modesApi() && Flags.modesUi()) { - zenMode.getRule().setTriggerDescription( - SystemZenRules.getTriggerDescriptionForScheduleTime(mContext, schedule)); - } + zenMode.setCustomModeConditionId(mContext, + ZenModeConfig.toScheduleConditionId(schedule)); return zenMode; }; } - private ZenModeTimePickerFragment.TimeSetter mStartSetter = (hour, minute) -> { + private final ZenModeTimePickerFragment.TimeSetter mStartSetter = (hour, minute) -> { if (!isValidTime(hour, minute)) { return; } @@ -137,7 +132,7 @@ class ZenModeSetSchedulePreferenceController extends AbstractZenModePreferenceCo saveMode(updateScheduleMode(mSchedule)); }; - private ZenModeTimePickerFragment.TimeSetter mEndSetter = (hour, minute) -> { + private final ZenModeTimePickerFragment.TimeSetter mEndSetter = (hour, minute) -> { if (!isValidTime(hour, minute)) { return; } diff --git a/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java index 14d5d59a19d..fd27958db95 100644 --- a/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java @@ -13,15 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.android.settings.notification.modes; import static android.app.AutomaticZenRule.TYPE_SCHEDULE_CALENDAR; import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME; -import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID; - import android.content.Context; -import android.os.Bundle; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; @@ -29,7 +27,7 @@ import androidx.preference.Preference; import androidx.preference.PreferenceCategory; import com.android.settings.R; -import com.android.settings.core.SubSettingLauncher; +import com.android.settings.dashboard.DashboardFragment; import com.android.settingslib.PrimarySwitchPreference; /** @@ -39,9 +37,13 @@ class ZenModeSetTriggerLinkPreferenceController extends AbstractZenModePreferenc @VisibleForTesting protected static final String AUTOMATIC_TRIGGER_PREF_KEY = "zen_automatic_trigger_settings"; + private final DashboardFragment mFragment; + ZenModeSetTriggerLinkPreferenceController(Context context, String key, + DashboardFragment fragment, ZenModesBackend backend) { super(context, key, backend); + mFragment = fragment; } @Override @@ -54,46 +56,52 @@ class ZenModeSetTriggerLinkPreferenceController extends AbstractZenModePreferenc // This controller is expected to govern a preference category so that it controls the // availability of the entire preference category if the mode doesn't have a way to // automatically trigger (such as manual DND). - Preference switchPref = ((PreferenceCategory) preference).findPreference( + PrimarySwitchPreference switchPref = ((PreferenceCategory) preference).findPreference( AUTOMATIC_TRIGGER_PREF_KEY); if (switchPref == null) { return; } - ((PrimarySwitchPreference) switchPref).setChecked(zenMode.getRule().isEnabled()); + switchPref.setChecked(zenMode.getRule().isEnabled()); switchPref.setOnPreferenceChangeListener(mSwitchChangeListener); - Bundle bundle = new Bundle(); - bundle.putString(MODE_ID, zenMode.getId()); + switchPref.setSummary(zenMode.getRule().getTriggerDescription()); + switchPref.setIcon(null); + switchPref.setOnPreferenceClickListener(null); + switchPref.setIntent(null); - // TODO: b/341961712 - direct preference to app-owned intent if available - switch (zenMode.getRule().getType()) { - case TYPE_SCHEDULE_TIME: - switchPref.setTitle(R.string.zen_mode_set_schedule_link); - switchPref.setSummary(zenMode.getRule().getTriggerDescription()); - switchPref.setIntent(new SubSettingLauncher(mContext) - .setDestination(ZenModeSetScheduleFragment.class.getName()) - // TODO: b/332937635 - set correct metrics category - .setSourceMetricsCategory(0) - .setArguments(bundle) - .toIntent()); - break; - case TYPE_SCHEDULE_CALENDAR: - switchPref.setTitle(R.string.zen_mode_set_calendar_link); - switchPref.setSummary(zenMode.getRule().getTriggerDescription()); - switchPref.setIntent(new SubSettingLauncher(mContext) - .setDestination(ZenModeSetCalendarFragment.class.getName()) - // TODO: b/332937635 - set correct metrics category - .setSourceMetricsCategory(0) - .setArguments(bundle) - .toIntent()); - break; - default: - // TODO: b/342156843 - change this to allow adding a trigger condition for system - // rules that don't yet have a type selected - switchPref.setTitle("not implemented"); + if (zenMode.isSystemOwned() && zenMode.getType() == TYPE_SCHEDULE_TIME) { + switchPref.setTitle(R.string.zen_mode_set_schedule_link); + // TODO: b/332937635 - set correct metrics category + switchPref.setIntent(ZenSubSettingLauncher.forModeFragment(mContext, + ZenModeSetScheduleFragment.class, zenMode.getId(), 0).toIntent()); + } else if (zenMode.isSystemOwned() && zenMode.getType() == TYPE_SCHEDULE_CALENDAR) { + switchPref.setTitle(R.string.zen_mode_set_calendar_link); + switchPref.setIcon(null); + // TODO: b/332937635 - set correct metrics category + switchPref.setIntent(ZenSubSettingLauncher.forModeFragment(mContext, + ZenModeSetCalendarFragment.class, zenMode.getId(), 0).toIntent()); + } else if (zenMode.isSystemOwned()) { + switchPref.setTitle(R.string.zen_mode_select_schedule); + switchPref.setIcon(R.drawable.ic_add_24dp); + switchPref.setSummary(""); + // TODO: b/342156843 - Hide the switch (needs support in SettingsLib). + switchPref.setOnPreferenceClickListener(clickedPreference -> { + ZenModeScheduleChooserDialog.show(mFragment, mOnScheduleOptionListener); + return true; + }); + } else { + // TODO: b/341961712 - direct preference to app-owned intent if available + switchPref.setTitle("not implemented"); } } + @VisibleForTesting + final ZenModeScheduleChooserDialog.OnScheduleOptionListener mOnScheduleOptionListener = + conditionId -> saveMode(mode -> { + mode.setCustomModeConditionId(mContext, conditionId); + return mode; + }); + @VisibleForTesting protected Preference.OnPreferenceChangeListener mSwitchChangeListener = (p, newValue) -> { final boolean newEnabled = (Boolean) newValue; @@ -103,5 +111,6 @@ class ZenModeSetTriggerLinkPreferenceController extends AbstractZenModePreferenc } return zenMode; }); + // TODO: b/342156843 - Do we want to jump to the corresponding schedule editing screen? }; } diff --git a/src/com/android/settings/notification/modes/ZenModesBackend.java b/src/com/android/settings/notification/modes/ZenModesBackend.java index b58e3107934..4f86778cf63 100644 --- a/src/com/android/settings/notification/modes/ZenModesBackend.java +++ b/src/com/android/settings/notification/modes/ZenModesBackend.java @@ -24,7 +24,6 @@ import android.content.Context; import android.net.Uri; import android.provider.Settings; import android.service.notification.Condition; -import android.service.notification.SystemZenRules; import android.service.notification.ZenModeConfig; import android.util.Log; @@ -194,19 +193,11 @@ class ZenModesBackend { */ @Nullable ZenMode addCustomMode(String name) { - ZenModeConfig.ScheduleInfo schedule = new ZenModeConfig.ScheduleInfo(); - schedule.days = ZenModeConfig.ALL_DAYS; - schedule.startHour = 22; - schedule.endHour = 7; - - // TODO: b/326442408 - Create as "manual" (i.e. no trigger) instead of schedule-time. AutomaticZenRule rule = new AutomaticZenRule.Builder(name, - ZenModeConfig.toScheduleConditionId(schedule)) - .setPackage(ZenModeConfig.getScheduleConditionProvider().getPackageName()) - .setType(AutomaticZenRule.TYPE_SCHEDULE_CALENDAR) - .setOwner(ZenModeConfig.getScheduleConditionProvider()) - .setTriggerDescription(SystemZenRules.getTriggerDescriptionForScheduleTime( - mContext, schedule)) + ZenModeConfig.toCustomManualConditionId()) + .setPackage(ZenModeConfig.getCustomManualConditionProvider().getPackageName()) + .setType(AutomaticZenRule.TYPE_OTHER) + .setOwner(ZenModeConfig.getCustomManualConditionProvider()) .setManualInvocationAllowed(true) .build(); diff --git a/src/com/android/settings/notification/modes/ZenSubSettingLauncher.java b/src/com/android/settings/notification/modes/ZenSubSettingLauncher.java index 11f3492f36d..aa66e6c5186 100644 --- a/src/com/android/settings/notification/modes/ZenSubSettingLauncher.java +++ b/src/com/android/settings/notification/modes/ZenSubSettingLauncher.java @@ -29,7 +29,7 @@ class ZenSubSettingLauncher { SettingsEnums.NOTIFICATION_ZEN_MODE_AUTOMATION); } - private static SubSettingLauncher forModeFragment(Context context, + static SubSettingLauncher forModeFragment(Context context, Class fragmentClass, String modeId, int sourceMetricsCategory) { Bundle bundle = new Bundle(); diff --git a/src/com/android/settings/print/PrintSettingsPageProvider.kt b/src/com/android/settings/print/PrintSettingsPageProvider.kt index aac0a5d0cf4..f28f0bcd6f4 100644 --- a/src/com/android/settings/print/PrintSettingsPageProvider.kt +++ b/src/com/android/settings/print/PrintSettingsPageProvider.kt @@ -17,16 +17,32 @@ package com.android.settings.print import android.app.settings.SettingsEnums +import android.content.Context +import android.content.Intent +import android.net.Uri import android.os.Bundle +import android.provider.Settings import androidx.annotation.VisibleForTesting import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Add +import androidx.compose.material.icons.outlined.Print +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp import androidx.core.os.bundleOf import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.settings.R @@ -36,13 +52,18 @@ import com.android.settings.print.PrintSettingsFragment.EXTRA_CHECKED import com.android.settings.print.PrintSettingsFragment.EXTRA_SERVICE_COMPONENT_NAME import com.android.settings.print.PrintSettingsFragment.EXTRA_TITLE import com.android.settingslib.spa.framework.common.SettingsPageProvider +import com.android.settingslib.spa.framework.compose.rememberContext import com.android.settingslib.spa.framework.compose.rememberDrawablePainter import com.android.settingslib.spa.framework.theme.SettingsDimension +import com.android.settingslib.spa.framework.theme.SettingsOpacity import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.PreferenceModel import com.android.settingslib.spa.widget.scaffold.RegularScaffold import com.android.settingslib.spa.widget.ui.Category +import com.android.settingslib.spa.widget.ui.SettingsIcon +import com.android.settingslib.spaprivileged.settingsprovider.settingsSecureStringFlow import com.android.settingslib.spaprivileged.template.common.UserProfilePager +import kotlinx.coroutines.flow.Flow object PrintSettingsPageProvider : SettingsPageProvider { override val name = "PrintSettings" @@ -52,51 +73,101 @@ object PrintSettingsPageProvider : SettingsPageProvider { RegularScaffold(title = stringResource(R.string.print_settings)) { val context = LocalContext.current val printRepository = remember(context) { PrintRepository(context) } - UserProfilePager { - PrintServices(printRepository) - } + UserProfilePager { PrintServices(printRepository) } } } @Composable private fun PrintServices(printRepository: PrintRepository) { - val printServiceDisplayInfos by remember { - printRepository.printServiceDisplayInfosFlow() - }.collectAsStateWithLifecycle(initialValue = emptyList()) - Category(title = stringResource(R.string.print_settings_title)) { - for (printServiceDisplayInfo in printServiceDisplayInfos) { - PrintService(printServiceDisplayInfo) + val printServiceDisplayInfos by + remember { printRepository.printServiceDisplayInfosFlow() } + .collectAsStateWithLifecycle(initialValue = emptyList()) + if (printServiceDisplayInfos.isEmpty()) { + NoServicesInstalled() + } else { + Category(title = stringResource(R.string.print_settings_title)) { + for (printServiceDisplayInfo in printServiceDisplayInfos) { + PrintService(printServiceDisplayInfo) + } } } + AddPrintService() + } + + @Composable + private fun NoServicesInstalled() { + Column( + modifier = Modifier.fillMaxSize().padding(SettingsDimension.itemPaddingAround), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Icon( + imageVector = Icons.Outlined.Print, + contentDescription = null, + modifier = + Modifier.size(110.dp) + .padding(SettingsDimension.itemPaddingAround) + .alpha(SettingsOpacity.SurfaceTone), + ) + Text( + text = stringResource(R.string.print_no_services_installed), + style = MaterialTheme.typography.titleLarge, + ) + } } @VisibleForTesting @Composable fun PrintService(displayInfo: PrintServiceDisplayInfo) { val context = LocalContext.current - Preference(model = object : PreferenceModel { - override val title = displayInfo.title - override val summary = { displayInfo.summary } - override val icon: @Composable () -> Unit = { - Image( - painter = rememberDrawablePainter(displayInfo.icon), - contentDescription = null, - modifier = Modifier.size(SettingsDimension.appIconItemSize), - ) - } - override val onClick = { - SubSettingLauncher(context).apply { - setDestination(PrintServiceSettingsFragment::class.qualifiedName) - setArguments( - bundleOf( - EXTRA_CHECKED to displayInfo.isEnabled, - EXTRA_TITLE to displayInfo.title, - EXTRA_SERVICE_COMPONENT_NAME to displayInfo.componentName - ) + Preference( + object : PreferenceModel { + override val title = displayInfo.title + override val summary = { displayInfo.summary } + override val icon: @Composable () -> Unit = { + Image( + painter = rememberDrawablePainter(displayInfo.icon), + contentDescription = null, + modifier = Modifier.size(SettingsDimension.appIconItemSize), ) - setSourceMetricsCategory(SettingsEnums.PRINT_SETTINGS) - }.launch() + } + override val onClick = { launchPrintServiceSettings(context, displayInfo) } } - }) + ) + } + + private fun launchPrintServiceSettings(context: Context, displayInfo: PrintServiceDisplayInfo) { + SubSettingLauncher(context) + .apply { + setDestination(PrintServiceSettingsFragment::class.qualifiedName) + setArguments( + bundleOf( + EXTRA_CHECKED to displayInfo.isEnabled, + EXTRA_TITLE to displayInfo.title, + EXTRA_SERVICE_COMPONENT_NAME to displayInfo.componentName + ) + ) + setSourceMetricsCategory(SettingsEnums.PRINT_SETTINGS) + } + .launch() + } + + @Composable + fun AddPrintService( + searchUriFlow: Flow = rememberContext { context -> + context.settingsSecureStringFlow(Settings.Secure.PRINT_SERVICE_SEARCH_URI) + }, + ) { + val context = LocalContext.current + val searchUri by searchUriFlow.collectAsStateWithLifecycle("") + if (searchUri.isEmpty()) return + Preference( + object : PreferenceModel { + override val title = stringResource(R.string.print_menu_item_add_service) + override val icon = @Composable { SettingsIcon(imageVector = Icons.Outlined.Add) } + override val onClick = { + context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(searchUri))) + } + } + ) } } diff --git a/src/com/android/settings/wifi/dpp/WifiDppConfiguratorActivity.java b/src/com/android/settings/wifi/dpp/WifiDppConfiguratorActivity.java index a7527d7332f..c7ad9ca0d8d 100644 --- a/src/com/android/settings/wifi/dpp/WifiDppConfiguratorActivity.java +++ b/src/com/android/settings/wifi/dpp/WifiDppConfiguratorActivity.java @@ -35,6 +35,8 @@ import androidx.annotation.VisibleForTesting; import androidx.fragment.app.FragmentTransaction; import com.android.settings.R; +import com.android.settings.flags.Flags; +import com.android.settings.overlay.FeatureFactory; import java.util.List; @@ -236,7 +238,12 @@ public class WifiDppConfiguratorActivity extends WifiDppBaseActivity implements WifiDppUtils.TAG_FRAGMENT_QR_CODE_GENERATOR); if (fragment == null) { - fragment = new WifiDppQrCodeGeneratorFragment(); + if (Flags.enableWifiSharingRuntimeFragment()) { + fragment = FeatureFactory.getFeatureFactory().getWifiFeatureProvider() + .getWifiDppQrCodeGeneratorFragment(); + } else { + fragment = new WifiDppQrCodeGeneratorFragment(); + } } else { if (fragment.isVisible()) { return; diff --git a/src/com/android/settings/wifi/dpp/WifiDppQrCodeGeneratorFragment.java b/src/com/android/settings/wifi/dpp/WifiDppQrCodeGeneratorFragment.java index 3d437e22e7d..1213b0dc2e3 100644 --- a/src/com/android/settings/wifi/dpp/WifiDppQrCodeGeneratorFragment.java +++ b/src/com/android/settings/wifi/dpp/WifiDppQrCodeGeneratorFragment.java @@ -56,7 +56,7 @@ public class WifiDppQrCodeGeneratorFragment extends WifiDppQrCodeBaseFragment { private static final String TAG = "WifiDppQrCodeGeneratorFragment"; private ImageView mQrCodeView; - private String mQrCode; + protected String mQrCode; private static final String CHIP_LABEL_METADATA_KEY = "android.service.chooser.chip_label"; private static final String CHIP_ICON_METADATA_KEY = "android.service.chooser.chip_icon"; @@ -258,7 +258,7 @@ public class WifiDppQrCodeGeneratorFragment extends WifiDppQrCodeBaseFragment { return button; } - private void setQrCode() { + protected void setQrCode() { try { final int qrcodeSize = getContext().getResources().getDimensionPixelSize( R.dimen.qrcode_size); diff --git a/src/com/android/settings/wifi/factory/WifiFeatureProvider.java b/src/com/android/settings/wifi/factory/WifiFeatureProvider.java index 5ab899afdc5..e5bf81a3c35 100644 --- a/src/com/android/settings/wifi/factory/WifiFeatureProvider.java +++ b/src/com/android/settings/wifi/factory/WifiFeatureProvider.java @@ -27,6 +27,7 @@ import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelStoreOwner; import com.android.settings.wifi.details.WifiNetworkDetailsViewModel; +import com.android.settings.wifi.dpp.WifiDppQrCodeGeneratorFragment; import com.android.settings.wifi.repository.SharedConnectivityRepository; import com.android.settings.wifi.repository.WifiHotspotRepository; import com.android.settings.wifi.tether.WifiHotspotSecurityViewModel; @@ -146,6 +147,15 @@ public class WifiFeatureProvider { return viewModel; } + /** + * Gets an instance of WifiDppQrCodeGeneratorFragment + */ + public WifiDppQrCodeGeneratorFragment getWifiDppQrCodeGeneratorFragment() { + WifiDppQrCodeGeneratorFragment fragment = new WifiDppQrCodeGeneratorFragment(); + verboseLog(TAG, "getWifiDppQrCodeGeneratorFragment():" + fragment); + return fragment; + } + /** * Send a {@link Log#VERBOSE} log message. * diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/bugreport/LogUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/bugreport/LogUtilsTest.java new file mode 100644 index 00000000000..bf2b191a65d --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/bugreport/LogUtilsTest.java @@ -0,0 +1,102 @@ +/* + * 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. + */ + +package com.android.settings.fuelgauge.batteryusage.bugreport; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.fuelgauge.batteryusage.BatteryReattribute; +import com.android.settings.fuelgauge.batteryusage.db.BatteryReattributeDao; +import com.android.settings.fuelgauge.batteryusage.db.BatteryReattributeEntity; +import com.android.settings.fuelgauge.batteryusage.db.BatteryStateDatabase; +import com.android.settings.testutils.BatteryTestUtils; + +import org.junit.After; +import org.junit.Before; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.MethodSorters; +import org.robolectric.RobolectricTestRunner; + +import java.io.PrintWriter; +import java.io.StringWriter; + +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@RunWith(RobolectricTestRunner.class) +public final class LogUtilsTest { + + private StringWriter mTestStringWriter; + private PrintWriter mTestPrintWriter; + private Context mContext; + private BatteryStateDatabase mDatabase; + private BatteryReattributeDao mBatteryReattributeDao; + + @Before + public void setUp() { + mContext = ApplicationProvider.getApplicationContext(); + mTestStringWriter = new StringWriter(); + mTestPrintWriter = new PrintWriter(mTestStringWriter); + mDatabase = BatteryTestUtils.setUpBatteryStateDatabase(mContext); + mBatteryReattributeDao = mDatabase.batteryReattributeDao(); + } + + @After + public void cleanUp() { + mBatteryReattributeDao.clearAll(); + } + + @Test + public void dumpBatteryReattributeDatabaseHist_noData_printExpectedResult() { + LogUtils.dumpBatteryReattributeDatabaseHist(mBatteryReattributeDao, mTestPrintWriter); + + assertThat(mTestStringWriter.toString()) + .contains("BatteryReattribute DatabaseHistory:"); + } + + @Test + public void dumpBatteryReattributeDatabaseHist_printExpectedResult() { + final long currentTimeMillis = System.currentTimeMillis(); + // Insert the first testing data. + final BatteryReattribute batteryReattribute1 = + BatteryReattribute.newBuilder() + .setTimestampStart(currentTimeMillis - 20000) + .setTimestampEnd(currentTimeMillis - 10000) + .putReattributeData(1001, 0.1f) + .putReattributeData(1002, 0.99f) + .build(); + mBatteryReattributeDao.insert(new BatteryReattributeEntity(batteryReattribute1)); + // Insert the second testing data. + final BatteryReattribute batteryReattribute2 = + BatteryReattribute.newBuilder() + .setTimestampStart(currentTimeMillis - 40000) + .setTimestampEnd(currentTimeMillis - 20000) + .putReattributeData(1003, 1f) + .build(); + mBatteryReattributeDao.insert(new BatteryReattributeEntity(batteryReattribute2)); + + LogUtils.dumpBatteryReattributeDatabaseHist(mBatteryReattributeDao, mTestPrintWriter); + + final String result = mTestStringWriter.toString(); + assertThat(result).contains("BatteryReattribute DatabaseHistory:"); + assertThat(result).contains(batteryReattribute1.toString()); + assertThat(result).contains(batteryReattribute2.toString()); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetCalendarPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetCalendarPreferenceControllerTest.java index 6b24fa21832..0ede058aed7 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetCalendarPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetCalendarPreferenceControllerTest.java @@ -34,6 +34,7 @@ import android.content.Context; import android.net.Uri; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; +import android.service.notification.SystemZenRules; import android.service.notification.ZenModeConfig; import androidx.preference.DropDownPreference; @@ -85,7 +86,9 @@ public class ZenModeSetCalendarPreferenceControllerTest { @EnableFlags({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI}) public void updateEventMode_updatesConditionAndTriggerDescription() { ZenMode mode = new ZenMode("id", - new AutomaticZenRule.Builder("name", Uri.parse("condition")).build(), + new AutomaticZenRule.Builder("name", Uri.parse("condition")) + .setPackage(SystemZenRules.PACKAGE_ANDROID) + .build(), true); // is active // Explicitly update preference controller with mode info first, which will also call @@ -99,6 +102,7 @@ public class ZenModeSetCalendarPreferenceControllerTest { // apply event mode updater to existing mode ZenMode out = mPrefController.updateEventMode(eventInfo).apply(mode); + assertThat(out.getRule().getOwner()).isEqualTo(ZenModeConfig.getEventConditionProvider()); assertThat(out.getRule().getConditionId()).isEqualTo( ZenModeConfig.toEventConditionId(eventInfo)); assertThat(out.getRule().getTriggerDescription()).isEqualTo("My events"); diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetSchedulePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetSchedulePreferenceControllerTest.java index 7cf327c983e..5f492b971a8 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetSchedulePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetSchedulePreferenceControllerTest.java @@ -29,6 +29,7 @@ import android.content.Context; import android.net.Uri; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; +import android.service.notification.SystemZenRules; import android.service.notification.ZenModeConfig; import android.view.ViewGroup; import android.widget.ToggleButton; @@ -81,7 +82,9 @@ public class ZenModeSetSchedulePreferenceControllerTest { @EnableFlags({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI}) public void updateScheduleRule_updatesConditionAndTriggerDescription() { ZenMode mode = new ZenMode("id", - new AutomaticZenRule.Builder("name", Uri.parse("condition")).build(), + new AutomaticZenRule.Builder("name", Uri.parse("condition")) + .setPackage(SystemZenRules.PACKAGE_ANDROID) + .build(), true); // is active ZenModeConfig.ScheduleInfo scheduleInfo = new ZenModeConfig.ScheduleInfo(); @@ -93,6 +96,8 @@ public class ZenModeSetSchedulePreferenceControllerTest { assertThat(out.getRule().getConditionId()) .isEqualTo(ZenModeConfig.toScheduleConditionId(scheduleInfo)); assertThat(out.getRule().getTriggerDescription()).isNotEmpty(); + assertThat(out.getRule().getOwner()).isEqualTo( + ZenModeConfig.getScheduleConditionProvider()); } @Test diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceControllerTest.java index 91de4ea8348..ff4d4a3c94c 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceControllerTest.java @@ -16,6 +16,7 @@ package com.android.settings.notification.modes; +import static android.app.AutomaticZenRule.TYPE_OTHER; import static android.app.AutomaticZenRule.TYPE_SCHEDULE_CALENDAR; import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME; import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY; @@ -31,10 +32,10 @@ import static org.mockito.Mockito.when; import android.app.AutomaticZenRule; import android.app.Flags; import android.content.Context; -import android.content.Intent; import android.net.Uri; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; +import android.service.notification.SystemZenRules; import android.service.notification.ZenModeConfig; import android.service.notification.ZenPolicy; @@ -43,6 +44,7 @@ import androidx.test.core.app.ApplicationProvider; import com.android.settings.R; import com.android.settings.SettingsActivity; +import com.android.settings.dashboard.DashboardFragment; import com.android.settingslib.PrimarySwitchPreference; import org.junit.Before; @@ -57,6 +59,7 @@ import org.robolectric.RobolectricTestRunner; import java.util.Calendar; @RunWith(RobolectricTestRunner.class) +@EnableFlags(Flags.FLAG_MODES_UI) public class ZenModeSetTriggerLinkPreferenceControllerTest { @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT); @@ -65,10 +68,13 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest { private ZenModesBackend mBackend; private Context mContext; + private PrimarySwitchPreference mPreference; + @Mock private PreferenceCategory mPrefCategory; @Mock - private PrimarySwitchPreference mPreference; + private DashboardFragment mFragment; + private ZenModeSetTriggerLinkPreferenceController mPrefController; @Before @@ -77,12 +83,12 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest { mContext = ApplicationProvider.getApplicationContext(); mPrefController = new ZenModeSetTriggerLinkPreferenceController(mContext, - "zen_automatic_trigger_category", mBackend); + "zen_automatic_trigger_category", mFragment, mBackend); + mPreference = new PrimarySwitchPreference(mContext); when(mPrefCategory.findPreference(AUTOMATIC_TRIGGER_PREF_KEY)).thenReturn(mPreference); } @Test - @EnableFlags(Flags.FLAG_MODES_UI) public void testIsAvailable() { // should not be available for manual DND ZenMode manualMode = ZenMode.manualDndMode(new AutomaticZenRule.Builder("Do Not Disturb", @@ -117,12 +123,12 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest { // Update preference controller with a zen mode that is not enabled mPrefController.updateZenMode(mPrefCategory, zenMode); - verify(mPreference).setChecked(false); + assertThat(mPreference.getCheckedState()).isFalse(); // Now with the rule enabled zenMode.getRule().setEnabled(true); mPrefController.updateZenMode(mPrefCategory, zenMode); - verify(mPreference).setChecked(true); + assertThat(mPreference.getCheckedState()).isTrue(); } @Test @@ -154,21 +160,24 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest { eventInfo.calName = "My events"; ZenMode mode = new ZenMode("id", new AutomaticZenRule.Builder("name", ZenModeConfig.toEventConditionId(eventInfo)) + .setPackage(SystemZenRules.PACKAGE_ANDROID) .setType(TYPE_SCHEDULE_CALENDAR) .setTriggerDescription("My events") .build(), true); // is active mPrefController.updateZenMode(mPrefCategory, mode); - verify(mPreference).setTitle(R.string.zen_mode_set_calendar_link); - verify(mPreference).setSummary(mode.getRule().getTriggerDescription()); + assertThat(mPreference.getTitle()).isNotNull(); + assertThat(mPreference.getTitle().toString()).isEqualTo( + mContext.getString(R.string.zen_mode_set_calendar_link)); + assertThat(mPreference.getSummary()).isNotNull(); + assertThat(mPreference.getSummary().toString()).isEqualTo( + mode.getRule().getTriggerDescription()); + assertThat(mPreference.getIcon()).isNull(); - ArgumentCaptor captor = ArgumentCaptor.forClass(Intent.class); - verify(mPreference).setIntent(captor.capture()); // Destination as written into the intent by SubSettingLauncher - assertThat( - captor.getValue().getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT)).isEqualTo( - ZenModeSetCalendarFragment.class.getName()); + assertThat(mPreference.getIntent().getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT)) + .isEqualTo(ZenModeSetCalendarFragment.class.getName()); } @Test @@ -179,20 +188,75 @@ public class ZenModeSetTriggerLinkPreferenceControllerTest { scheduleInfo.endHour = 15; ZenMode mode = new ZenMode("id", new AutomaticZenRule.Builder("name", ZenModeConfig.toScheduleConditionId(scheduleInfo)) + .setPackage(SystemZenRules.PACKAGE_ANDROID) .setType(TYPE_SCHEDULE_TIME) .setTriggerDescription("some schedule") .build(), true); // is active mPrefController.updateZenMode(mPrefCategory, mode); - verify(mPreference).setTitle(R.string.zen_mode_set_schedule_link); - verify(mPreference).setSummary(mode.getRule().getTriggerDescription()); + assertThat(mPreference.getTitle()).isNotNull(); + assertThat(mPreference.getTitle().toString()).isEqualTo( + mContext.getString(R.string.zen_mode_set_schedule_link)); + assertThat(mPreference.getSummary()).isNotNull(); + assertThat(mPreference.getSummary().toString()).isEqualTo( + mode.getRule().getTriggerDescription()); + assertThat(mPreference.getIcon()).isNull(); - ArgumentCaptor captor = ArgumentCaptor.forClass(Intent.class); - verify(mPreference).setIntent(captor.capture()); // Destination as written into the intent by SubSettingLauncher - assertThat( - captor.getValue().getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT)).isEqualTo( - ZenModeSetScheduleFragment.class.getName()); + assertThat(mPreference.getIntent().getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT)) + .isEqualTo(ZenModeSetScheduleFragment.class.getName()); + } + + @Test + public void testRuleLink_manual() { + ZenMode mode = new ZenMode("id", new AutomaticZenRule.Builder("name", + ZenModeConfig.toCustomManualConditionId()) + .setPackage(SystemZenRules.PACKAGE_ANDROID) + .setType(TYPE_OTHER) + .setTriggerDescription("Will not be shown") + .build(), + true); // is active + mPrefController.updateZenMode(mPrefCategory, mode); + + assertThat(mPreference.getTitle()).isNotNull(); + assertThat(mPreference.getTitle().toString()).isEqualTo( + mContext.getString(R.string.zen_mode_select_schedule)); + assertThat(mPreference.getIcon()).isNotNull(); + assertThat(mPreference.getSummary()).isNotNull(); + assertThat(mPreference.getSummary().toString()).isEqualTo(""); + + // Set up a click listener to open the dialog. + assertThat(mPreference.getOnPreferenceClickListener()).isNotNull(); + } + + @Test + public void onScheduleChosen_updatesMode() { + ZenMode originalMode = new ZenMode("id", + new AutomaticZenRule.Builder("name", ZenModeConfig.toCustomManualConditionId()) + .setPackage(SystemZenRules.PACKAGE_ANDROID) + .setType(TYPE_OTHER) + .setTriggerDescription("") + .build(), + false); + mPrefController.updateZenMode(mPrefCategory, originalMode); + + ZenModeConfig.ScheduleInfo scheduleInfo = new ZenModeConfig.ScheduleInfo(); + scheduleInfo.days = new int[] { Calendar.MONDAY }; + scheduleInfo.startHour = 12; + scheduleInfo.endHour = 15; + Uri scheduleUri = ZenModeConfig.toScheduleConditionId(scheduleInfo); + + mPrefController.mOnScheduleOptionListener.onScheduleSelected(scheduleUri); + + // verify the backend got asked to update the mode to be schedule-based. + ArgumentCaptor captor = ArgumentCaptor.forClass(ZenMode.class); + verify(mBackend).updateMode(captor.capture()); + ZenMode updatedMode = captor.getValue(); + assertThat(updatedMode.getType()).isEqualTo(TYPE_SCHEDULE_TIME); + assertThat(updatedMode.getRule().getConditionId()).isEqualTo(scheduleUri); + assertThat(updatedMode.getRule().getTriggerDescription()).isNotEmpty(); + assertThat(updatedMode.getRule().getOwner()).isEqualTo( + ZenModeConfig.getScheduleConditionProvider()); } } diff --git a/tests/spa_unit/src/com/android/settings/network/apn/ApnEditCarrierEnabledTest.kt b/tests/spa_unit/src/com/android/settings/network/apn/ApnEditCarrierEnabledTest.kt new file mode 100644 index 00000000000..bd97482ddfa --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/network/apn/ApnEditCarrierEnabledTest.kt @@ -0,0 +1,109 @@ +/* + * 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. + */ + +package com.android.settings.network.apn + +import android.content.Context +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.assertIsEnabled +import androidx.compose.ui.test.assertIsNotEnabled +import androidx.compose.ui.test.assertIsOff +import androidx.compose.ui.test.assertIsOn +import androidx.compose.ui.test.junit4.ComposeTestRule +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settings.R +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.spy +import org.mockito.kotlin.stub + +@RunWith(AndroidJUnit4::class) +class ApnEditCarrierEnabledTest { + + @get:Rule val composeTestRule = createComposeRule() + + private val context: Context = spy(ApplicationProvider.getApplicationContext()) {} + + private val resources = spy(context.resources) {} + + @Before + fun setUp() { + context.stub { on { resources } doReturn resources } + } + + @Test + fun carrierEnabled_displayed() { + composeTestRule.setContent { ApnEditCarrierEnabled(ApnData()) {} } + + composeTestRule.onCarrierEnabled().assertIsDisplayed() + } + + @Test + fun carrierEnabled_isChecked() { + val apnData = ApnData(carrierEnabled = true) + + composeTestRule.setContent { ApnEditCarrierEnabled(apnData) {} } + + composeTestRule.onCarrierEnabled().assertIsOn() + } + + @Test + fun carrierEnabled_allowEdit_checkChanged() { + resources.stub { on { getBoolean(R.bool.config_allow_edit_carrier_enabled) } doReturn true } + var apnData by mutableStateOf(ApnData(carrierEnabled = true)) + composeTestRule.setContent { + CompositionLocalProvider(LocalContext provides context) { + ApnEditCarrierEnabled(apnData) { apnData = apnData.copy(carrierEnabled = it) } + } + } + + composeTestRule.onCarrierEnabled().performClick() + + composeTestRule.onCarrierEnabled().assertIsEnabled().assertIsOff() + } + + @Test + fun carrierEnabled_notAllowEdit_checkNotChanged() { + resources.stub { + on { getBoolean(R.bool.config_allow_edit_carrier_enabled) } doReturn false + } + var apnData by mutableStateOf(ApnData(carrierEnabled = true)) + composeTestRule.setContent { + CompositionLocalProvider(LocalContext provides context) { + ApnEditCarrierEnabled(apnData) { apnData = apnData.copy(carrierEnabled = it) } + } + } + + composeTestRule.onCarrierEnabled().performClick() + + composeTestRule.onCarrierEnabled().assertIsNotEnabled().assertIsOn() + } + + private fun ComposeTestRule.onCarrierEnabled() = + onNodeWithText(context.getString(R.string.carrier_enabled)) +} diff --git a/tests/spa_unit/src/com/android/settings/network/apn/ApnEditPageProviderTest.kt b/tests/spa_unit/src/com/android/settings/network/apn/ApnEditPageProviderTest.kt index 3621948c9fb..d310604ccf9 100644 --- a/tests/spa_unit/src/com/android/settings/network/apn/ApnEditPageProviderTest.kt +++ b/tests/spa_unit/src/com/android/settings/network/apn/ApnEditPageProviderTest.kt @@ -21,24 +21,17 @@ import android.net.Uri import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.test.assertIsDisplayed -import androidx.compose.ui.test.assertIsOff -import androidx.compose.ui.test.assertIsOn import androidx.compose.ui.test.hasText -import androidx.compose.ui.test.isFocused import androidx.compose.ui.test.junit4.createComposeRule -import androidx.compose.ui.test.onAllNodesWithText import androidx.compose.ui.test.onChild import androidx.compose.ui.test.onChildAt -import androidx.compose.ui.test.onLast import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.onRoot -import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performScrollToNode import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settings.R import com.google.common.truth.Truth -import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -55,7 +48,6 @@ class ApnEditPageProviderTest { private val port = "port" private val apnType = context.resources.getString(R.string.apn_type) private val apnRoaming = "IPv4" - private val apnEnable = context.resources.getString(R.string.carrier_enabled) private val apnProtocolOptions = context.resources.getStringArray(R.array.apn_protocol_entries).toList() private val passwordTitle = context.resources.getString(R.string.apn_password) @@ -65,7 +57,6 @@ class ApnEditPageProviderTest { port = port, apnType = apnType, apnRoaming = apnProtocolOptions.indexOf(apnRoaming), - apnEnable = true ) private val apnData = mutableStateOf( apnInit @@ -133,39 +124,6 @@ class ApnEditPageProviderTest { composeTestRule.onNodeWithText(apnRoaming, true).assertIsDisplayed() } - @Ignore("b/342374681") - @Test - fun carrier_enabled_displayed() { - composeTestRule.setContent { - ApnPage(apnInit, remember { apnData }, uri) - } - composeTestRule.onRoot().onChild().onChildAt(0) - .performScrollToNode(hasText(apnEnable, true)) - composeTestRule.onNodeWithText(apnEnable, true).assertIsDisplayed() - } - - @Test - fun carrier_enabled_isChecked() { - composeTestRule.setContent { - ApnPage(apnInit, remember { apnData }, uri) - } - composeTestRule.onRoot().onChild().onChildAt(0) - .performScrollToNode(hasText(apnEnable, true)) - composeTestRule.onNodeWithText(apnEnable, true).assertIsOn() - } - - @Ignore("b/342374681") - @Test - fun carrier_enabled_checkChanged() { - composeTestRule.setContent { - ApnPage(apnInit, remember { apnData }, uri) - } - composeTestRule.onRoot().onChild().onChildAt(0) - .performScrollToNode(hasText(apnEnable, true)) - composeTestRule.onNodeWithText(apnEnable, true).performClick() - composeTestRule.onNodeWithText(apnEnable, true).assertIsOff() - } - @Test fun password_displayed() { composeTestRule.setContent { diff --git a/tests/spa_unit/src/com/android/settings/print/PrintSettingsPageProviderTest.kt b/tests/spa_unit/src/com/android/settings/print/PrintSettingsPageProviderTest.kt index 746816b52c8..25714063d15 100644 --- a/tests/spa_unit/src/com/android/settings/print/PrintSettingsPageProviderTest.kt +++ b/tests/spa_unit/src/com/android/settings/print/PrintSettingsPageProviderTest.kt @@ -17,6 +17,7 @@ package com.android.settings.print import android.content.Context +import android.net.Uri import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.test.isDisplayed @@ -31,7 +32,9 @@ import com.android.settings.print.PrintRepository.PrintServiceDisplayInfo import com.android.settings.print.PrintSettingsFragment.EXTRA_CHECKED import com.android.settings.print.PrintSettingsFragment.EXTRA_SERVICE_COMPONENT_NAME import com.android.settings.print.PrintSettingsFragment.EXTRA_TITLE +import com.android.settings.print.PrintSettingsPageProvider.AddPrintService import com.android.settings.print.PrintSettingsPageProvider.PrintService +import kotlinx.coroutines.flow.flowOf import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -44,35 +47,32 @@ import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) class PrintSettingsPageProviderTest { - @get:Rule - val composeTestRule = createComposeRule() + @get:Rule val composeTestRule = createComposeRule() - private val context: Context = spy(ApplicationProvider.getApplicationContext()) { - doNothing().whenever(mock).startActivity(any()) - } + private val context: Context = + spy(ApplicationProvider.getApplicationContext()) { + doNothing().whenever(mock).startActivity(any()) + } - private val displayInfo = PrintServiceDisplayInfo( - title = TITLE, - isEnabled = true, - summary = SUMMARY, - icon = context.getDrawable(R.drawable.ic_settings_print)!!, - componentName = "ComponentName", - ) + private val displayInfo = + PrintServiceDisplayInfo( + title = TITLE, + isEnabled = true, + summary = SUMMARY, + icon = context.getDrawable(R.drawable.ic_settings_print)!!, + componentName = "ComponentName", + ) @Test fun printService_titleDisplayed() { - composeTestRule.setContent { - PrintService(displayInfo) - } + composeTestRule.setContent { PrintService(displayInfo) } composeTestRule.onNodeWithText(TITLE).isDisplayed() } @Test fun printService_summaryDisplayed() { - composeTestRule.setContent { - PrintService(displayInfo) - } + composeTestRule.setContent { PrintService(displayInfo) } composeTestRule.onNodeWithText(SUMMARY).isDisplayed() } @@ -80,25 +80,43 @@ class PrintSettingsPageProviderTest { @Test fun printService_onClick() { composeTestRule.setContent { - CompositionLocalProvider(LocalContext provides context) { - PrintService(displayInfo) - } + CompositionLocalProvider(LocalContext provides context) { PrintService(displayInfo) } } composeTestRule.onNodeWithText(TITLE).performClick() - verify(context).startActivity(argThat { - val fragment = getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT) - val arguments = getBundleExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS)!! - fragment == PrintServiceSettingsFragment::class.qualifiedName && - arguments.getBoolean(EXTRA_CHECKED) == displayInfo.isEnabled && - arguments.getString(EXTRA_TITLE) == displayInfo.title && - arguments.getString(EXTRA_SERVICE_COMPONENT_NAME) == displayInfo.componentName - }) + verify(context) + .startActivity( + argThat { + val fragment = getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT) + val arguments = getBundleExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS)!! + fragment == PrintServiceSettingsFragment::class.qualifiedName && + arguments.getBoolean(EXTRA_CHECKED) == displayInfo.isEnabled && + arguments.getString(EXTRA_TITLE) == displayInfo.title && + arguments.getString(EXTRA_SERVICE_COMPONENT_NAME) == + displayInfo.componentName + } + ) + } + + @Test + fun addPrintService_onClick() { + composeTestRule.setContent { + CompositionLocalProvider(LocalContext provides context) { + AddPrintService(flowOf(SEARCH_URI)) + } + } + + composeTestRule + .onNodeWithText(context.getString(R.string.print_menu_item_add_service)) + .performClick() + + verify(context).startActivity(argThat { data == Uri.parse(SEARCH_URI) }) } private companion object { const val TITLE = "Title" const val SUMMARY = "Summary" + const val SEARCH_URI = "search.uri" } } diff --git a/tests/unit/src/com/android/settings/network/ResetNetworkOperationBuilderTest.java b/tests/unit/src/com/android/settings/network/ResetNetworkOperationBuilderTest.java index 5f544064924..7f1c475dbfd 100644 --- a/tests/unit/src/com/android/settings/network/ResetNetworkOperationBuilderTest.java +++ b/tests/unit/src/com/android/settings/network/ResetNetworkOperationBuilderTest.java @@ -16,20 +16,16 @@ package com.android.settings.network; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import android.content.ContentProvider; -import android.content.ContentResolver; +import android.content.ContentProviderClient; import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkPolicyManager; @@ -67,7 +63,7 @@ public class ResetNetworkOperationBuilderTest { @Mock private NetworkPolicyManager mNetworkPolicyManager; @Mock - private ContentProvider mContentProvider;; + private ContentProviderClient mContentProviderClient; private Context mContext; @@ -77,9 +73,8 @@ public class ResetNetworkOperationBuilderTest { public void setUp() { MockitoAnnotations.initMocks(this); mContext = spy(ApplicationProvider.getApplicationContext()); - doReturn(ContentResolver.wrap(mContentProvider)).when(mContext).getContentResolver(); - mBuilder = spy(new ResetNetworkOperationBuilder(mContext)); + doReturn(mContentProviderClient).when(mBuilder).getUnstableTelephonyContentProviderClient(); } @Test @@ -184,38 +179,38 @@ public class ResetNetworkOperationBuilderTest { } @Test - public void restartPhoneProcess_withoutTelephonyContentProvider_shouldNotCrash() { - doThrow(new IllegalArgumentException()).when(mContentProvider).call( - anyString(), anyString(), anyString(), any()); + public void restartPhoneProcess_withoutTelephonyContentProvider_shouldNotCrash() + throws Exception { + doReturn(null).when(mBuilder).getUnstableTelephonyContentProviderClient(); mBuilder.restartPhoneProcess().build().run(); } @Test - public void restartRild_withoutTelephonyContentProvider_shouldNotCrash() { - doThrow(new IllegalArgumentException()).when(mContentProvider).call( - anyString(), anyString(), anyString(), any()); + public void restartRild_withoutTelephonyContentProvider_shouldNotCrash() + throws Exception { + doReturn(null).when(mBuilder).getUnstableTelephonyContentProviderClient(); mBuilder.restartRild().build().run(); } @Test - public void restartPhoneProcess_withTelephonyContentProvider_shouldCallRestartPhoneProcess() { + public void restartPhoneProcess_withTelephonyContentProvider_shouldCallRestartPhoneProcess() + throws Exception { mBuilder.restartPhoneProcess().build().run(); - verify(mContentProvider).call( - eq(mBuilder.getResetTelephonyContentProviderAuthority()), + verify(mContentProviderClient).call( eq(ResetNetworkOperationBuilder.METHOD_RESTART_PHONE_PROCESS), isNull(), isNull()); } @Test - public void restartRild_withTelephonyContentProvider_shouldCallRestartRild() { + public void restartRild_withTelephonyContentProvider_shouldCallRestartRild() + throws Exception { mBuilder.restartRild().build().run(); - verify(mContentProvider).call( - eq(mBuilder.getResetTelephonyContentProviderAuthority()), + verify(mContentProviderClient).call( eq(ResetNetworkOperationBuilder.METHOD_RESTART_RILD), isNull(), isNull());