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