From f76c528e5bc9bc81b09a3cbd0ccc3d8806ea9725 Mon Sep 17 00:00:00 2001 From: danielwbhuang Date: Thu, 6 Jul 2023 20:28:44 +0800 Subject: [PATCH 1/6] Use EXTRA_ENTRYPOINT to log entry point for metrics 1. Extra name: EXTRA_ENTRYPOINT 2. Value: integer which is from SettingsEnums.EntryPointType // access physical keyboard settings through keyboard configured notification entry point KEYBOARD_CONFIGURED_NOTIFICATION = 0; // access physical keyboard settings through keyboard settings entry point KEYBOARD_SETTINGS = 1; // access physical keyboard settings through connected devices settings entry point CONNECTED_DEVICES_SETTINGS = 2; Bug: 271391879 Test: manual and check the device log. Change-Id: I0d5144790e184eb3374d4615d8874619c372742a --- .../inputmethod/KeyboardSettingsPreferenceController.java | 4 ++-- .../settings/inputmethod/NewKeyboardSettingsUtils.java | 6 ------ .../settings/inputmethod/PhysicalKeyboardFragment.java | 6 +++--- .../inputmethod/PhysicalKeyboardPreferenceController.java | 5 ++--- 4 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/com/android/settings/inputmethod/KeyboardSettingsPreferenceController.java b/src/com/android/settings/inputmethod/KeyboardSettingsPreferenceController.java index 03461af4c29..ae6a24ac817 100644 --- a/src/com/android/settings/inputmethod/KeyboardSettingsPreferenceController.java +++ b/src/com/android/settings/inputmethod/KeyboardSettingsPreferenceController.java @@ -16,6 +16,7 @@ package com.android.settings.inputmethod; +import android.app.settings.SettingsEnums; import android.content.Context; import android.content.Intent; import android.provider.Settings; @@ -53,8 +54,7 @@ public class KeyboardSettingsPreferenceController extends BasePreferenceControll if (mCachedDevice.getAddress().equals(hardKeyboardDeviceInfo.mBluetoothAddress)) { Intent intent = new Intent(Settings.ACTION_HARD_KEYBOARD_SETTINGS); intent.putExtra( - NewKeyboardSettingsUtils.EXTRA_INTENT_FROM, - "com.android.settings.inputmethod.KeyboardSettingsPreferenceController"); + Settings.EXTRA_ENTRYPOINT, SettingsEnums.CONNECTED_DEVICES_SETTINGS); intent.putExtra( Settings.EXTRA_INPUT_DEVICE_IDENTIFIER, hardKeyboardDeviceInfo.mDeviceIdentifier); diff --git a/src/com/android/settings/inputmethod/NewKeyboardSettingsUtils.java b/src/com/android/settings/inputmethod/NewKeyboardSettingsUtils.java index 697c0f09f62..ad68c4344da 100644 --- a/src/com/android/settings/inputmethod/NewKeyboardSettingsUtils.java +++ b/src/com/android/settings/inputmethod/NewKeyboardSettingsUtils.java @@ -33,12 +33,6 @@ import java.util.List; */ public class NewKeyboardSettingsUtils { - /** - * Record the class name of the intent sender for metrics. - */ - public static final String EXTRA_INTENT_FROM = - "com.android.settings.inputmethod.EXTRA_INTENT_FROM"; - static final String EXTRA_TITLE = "keyboard_layout_picker_title"; static final String EXTRA_USER_ID = "user_id"; static final String EXTRA_INPUT_DEVICE_IDENTIFIER = "input_device_identifier"; diff --git a/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java b/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java index 9f5d67a8e9a..289d7c186a9 100644 --- a/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java +++ b/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java @@ -123,9 +123,9 @@ public final class PhysicalKeyboardFragment extends SettingsPreferenceFragment } InputDeviceIdentifier inputDeviceIdentifier = activity.getIntent().getParcelableExtra( KeyboardLayoutPickerFragment.EXTRA_INPUT_DEVICE_IDENTIFIER); - String intentFromWhere = - activity.getIntent().getStringExtra(NewKeyboardSettingsUtils.EXTRA_INTENT_FROM); - if (intentFromWhere != null) { + int intentFromWhere = + activity.getIntent().getIntExtra(android.provider.Settings.EXTRA_ENTRYPOINT, -1); + if (intentFromWhere != -1) { mMetricsFeatureProvider.action( getContext(), SettingsEnums.ACTION_OPEN_PK_SETTINGS_FROM, intentFromWhere); } diff --git a/src/com/android/settings/inputmethod/PhysicalKeyboardPreferenceController.java b/src/com/android/settings/inputmethod/PhysicalKeyboardPreferenceController.java index 1f01b98b982..b88928ca531 100644 --- a/src/com/android/settings/inputmethod/PhysicalKeyboardPreferenceController.java +++ b/src/com/android/settings/inputmethod/PhysicalKeyboardPreferenceController.java @@ -16,6 +16,7 @@ package com.android.settings.inputmethod; +import android.app.settings.SettingsEnums; import android.content.Context; import android.content.Intent; import android.hardware.input.InputManager; @@ -66,9 +67,7 @@ public class PhysicalKeyboardPreferenceController extends AbstractPreferenceCont return false; } Intent intent = new Intent(Settings.ACTION_HARD_KEYBOARD_SETTINGS); - intent.putExtra( - NewKeyboardSettingsUtils.EXTRA_INTENT_FROM, - "com.android.settings.inputmethod.PhysicalKeyboardPreferenceController"); + intent.putExtra(Settings.EXTRA_ENTRYPOINT, SettingsEnums.KEYBOARD_SETTINGS); mContext.startActivity(intent); return true; } From ef66549e64b4c922ba2c97c74629983c202668d5 Mon Sep 17 00:00:00 2001 From: ykhung Date: Fri, 7 Jul 2023 14:03:10 +0800 Subject: [PATCH 2/6] Add a mechanism to log battery usage periodic job events Example history log: Jul 07, 2023, 15:28:51 SCHEDULE_JOB triggerTime=Jul 07, 2023, 16:00:00 Jul 07, 2023, 15:32:16 FETCH_USAGE_DATA Jul 07, 2023, 15:32:17 INSERT_USAGE_DATA size=37 Jul 07, 2023, 15:43:45 FETCH_USAGE_DATA Jul 07, 2023, 15:43:48 INSERT_USAGE_DATA size=47 Jul 07, 2023, 15:43:49 SCHEDULE_JOB triggerTime=Jul 07, 2023, 16:00:00 Bug: 284893240 Test: make test RunSettingsRoboTests ROBOTEST_FILTER=com.android.settings.fuelgauge Change-Id: I45a1ce0ce9b70f095702727e53d7b7ce8824abdb --- protos/fuelgauge_log.proto | 25 ++++- .../fuelgauge/AdvancedPowerUsageDetail.java | 14 ++- .../fuelgauge/BatteryBackupHelper.java | 4 +- ...Util.java => BatteryOptimizeLogUtils.java} | 17 +-- .../fuelgauge/BatteryOptimizeUtils.java | 4 +- .../settings/fuelgauge/BatteryUtils.java | 2 +- .../batteryusage/BatteryUsageDataLoader.java | 4 + .../batteryusage/BootBroadcastReceiver.java | 7 +- .../fuelgauge/batteryusage/DatabaseUtils.java | 13 ++- .../batteryusage/PeriodicJobManager.java | 9 +- .../batteryusage/PeriodicJobReceiver.java | 4 + .../bugreport/BatteryUsageLogUtils.java | 104 ++++++++++++++++++ .../batteryusage/bugreport/LogUtils.java | 7 ++ ....java => BatteryOptimizeLogUtilsTest.java} | 32 +++--- .../batteryusage/BatteryEntryTest.java | 2 + .../bugreport/BatteryUsageLogUtilsTest.java | 96 ++++++++++++++++ .../BugReportContentProviderTest.java | 1 + 17 files changed, 306 insertions(+), 39 deletions(-) rename src/com/android/settings/fuelgauge/{BatteryHistoricalLogUtil.java => BatteryOptimizeLogUtils.java} (90%) create mode 100644 src/com/android/settings/fuelgauge/batteryusage/bugreport/BatteryUsageLogUtils.java rename tests/robotests/src/com/android/settings/fuelgauge/{BatteryHistoricalLogUtilTest.java => BatteryOptimizeLogUtilsTest.java} (60%) create mode 100644 tests/robotests/src/com/android/settings/fuelgauge/batteryusage/bugreport/BatteryUsageLogUtilsTest.java diff --git a/protos/fuelgauge_log.proto b/protos/fuelgauge_log.proto index 150c2e2ce6a..e75ca48f617 100644 --- a/protos/fuelgauge_log.proto +++ b/protos/fuelgauge_log.proto @@ -5,13 +5,12 @@ option java_multiple_files = true; option java_package = "com.android.settings.fuelgauge"; option java_outer_classname = "FuelgaugeLogProto"; -// Stores history of setting optimize mode +// Store history of setting optimize mode message BatteryOptimizeHistoricalLog { repeated BatteryOptimizeHistoricalLogEntry log_entry = 1; } message BatteryOptimizeHistoricalLogEntry { - // The action to set optimize mode enum Action { UNKNOWN = 0; @@ -28,3 +27,25 @@ message BatteryOptimizeHistoricalLogEntry { optional string action_description = 3; optional int64 timestamp = 4; } + + +// Store history of battery usage periodic job +message BatteryUsageHistoricalLog { + repeated BatteryUsageHistoricalLogEntry log_entry = 1; +} + +message BatteryUsageHistoricalLogEntry { + // The action to record battery usage job event + enum Action { + UNKNOWN = 0; + SCHEDULE_JOB = 1; + EXECUTE_JOB = 2; + RECHECK_JOB = 3; + FETCH_USAGE_DATA = 4; + INSERT_USAGE_DATA = 5; + } + + optional int64 timestamp = 1; + optional Action action = 2; + optional string action_description = 3; +} diff --git a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java index 79e01940ecd..41ead68b623 100644 --- a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java +++ b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java @@ -289,12 +289,14 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements mLogStringBuilder.append(", onPause mode = ").append(selectedPreference); logMetricCategory(selectedPreference); - BatteryHistoricalLogUtil.writeLog( - getContext().getApplicationContext(), - Action.LEAVE, - BatteryHistoricalLogUtil.getPackageNameWithUserId( - mBatteryOptimizeUtils.getPackageName(), UserHandle.myUserId()), - mLogStringBuilder.toString()); + mExecutor.execute(() -> { + BatteryOptimizeLogUtils.writeLog( + getContext().getApplicationContext(), + Action.LEAVE, + BatteryOptimizeLogUtils.getPackageNameWithUserId( + mBatteryOptimizeUtils.getPackageName(), UserHandle.myUserId()), + mLogStringBuilder.toString()); + }); Log.d(TAG, "Leave with mode: " + selectedPreference); } diff --git a/src/com/android/settings/fuelgauge/BatteryBackupHelper.java b/src/com/android/settings/fuelgauge/BatteryBackupHelper.java index 66ffc90c877..50f1b90df77 100644 --- a/src/com/android/settings/fuelgauge/BatteryBackupHelper.java +++ b/src/com/android/settings/fuelgauge/BatteryBackupHelper.java @@ -199,7 +199,7 @@ public final class BatteryBackupHelper implements BackupHelper { info.packageName + DELIMITER_MODE + optimizationMode; builder.append(packageOptimizeMode + DELIMITER); Log.d(TAG, "backupOptimizationMode: " + packageOptimizeMode); - BatteryHistoricalLogUtil.writeLog( + BatteryOptimizeLogUtils.writeLog( sharedPreferences, Action.BACKUP, info.packageName, /* actionDescription */ "mode: " + optimizationMode); backupCount++; @@ -275,7 +275,7 @@ public final class BatteryBackupHelper implements BackupHelper { /** Dump the app optimization mode backup history data. */ public static void dumpHistoricalData(Context context, PrintWriter writer) { - BatteryHistoricalLogUtil.printBatteryOptimizeHistoricalLog( + BatteryOptimizeLogUtils.printBatteryOptimizeHistoricalLog( getSharedPreferences(context), writer); } diff --git a/src/com/android/settings/fuelgauge/BatteryHistoricalLogUtil.java b/src/com/android/settings/fuelgauge/BatteryOptimizeLogUtils.java similarity index 90% rename from src/com/android/settings/fuelgauge/BatteryHistoricalLogUtil.java rename to src/com/android/settings/fuelgauge/BatteryOptimizeLogUtils.java index f82b7031749..d093d35debc 100644 --- a/src/com/android/settings/fuelgauge/BatteryHistoricalLogUtil.java +++ b/src/com/android/settings/fuelgauge/BatteryOptimizeLogUtils.java @@ -20,23 +20,25 @@ import android.content.Context; import android.content.SharedPreferences; import android.util.Base64; +import androidx.annotation.VisibleForTesting; + import com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry.Action; import com.android.settings.fuelgauge.batteryusage.ConvertUtils; -import com.google.common.annotations.VisibleForTesting; - import java.io.PrintWriter; import java.util.List; /** Writes and reads a historical log of battery related state change events. */ -public final class BatteryHistoricalLogUtil { +public final class BatteryOptimizeLogUtils { + private static final String TAG = "BatteryOptimizeLogUtils"; private static final String BATTERY_OPTIMIZE_FILE_NAME = "battery_optimize_historical_logs"; private static final String LOGS_KEY = "battery_optimize_logs_key"; - private static final String TAG = "BatteryHistoricalLogUtil"; @VisibleForTesting static final int MAX_ENTRIES = 40; + private BatteryOptimizeLogUtils() {} + /** Writes a log entry for battery optimization mode. */ static void writeLog( Context context, Action action, String packageName, String actionDescription) { @@ -67,7 +69,7 @@ public final class BatteryHistoricalLogUtil { newLogBuilder.addLogEntry(logEntry); String loggingContent = - Base64.encodeToString(newLogBuilder.build().toByteArray(), Base64.DEFAULT); + Base64.encodeToString(newLogBuilder.build().toByteArray(), Base64.DEFAULT); sharedPreferences .edit() .putString(LOGS_KEY, loggingContent) @@ -94,7 +96,7 @@ public final class BatteryHistoricalLogUtil { if (logEntryList.isEmpty()) { writer.println("\tnothing to dump"); } else { - writer.println("0:UNKNOWN 1:RESTRICTED 2:UNRESTRICTED 3:OPTIMIZED"); + writer.println("0:UNKNOWN 1:RESTRICTED 2:UNRESTRICTED 3:OPTIMIZED"); logEntryList.forEach(entry -> writer.println(toString(entry))); } } @@ -113,6 +115,7 @@ public final class BatteryHistoricalLogUtil { @VisibleForTesting static SharedPreferences getSharedPreferences(Context context) { - return context.getSharedPreferences(BATTERY_OPTIMIZE_FILE_NAME, Context.MODE_PRIVATE); + return context.getApplicationContext() + .getSharedPreferences(BATTERY_OPTIMIZE_FILE_NAME, Context.MODE_PRIVATE); } } diff --git a/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java b/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java index 589e1fd4055..124840e1e01 100644 --- a/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java +++ b/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java @@ -245,7 +245,7 @@ public class BatteryOptimizeUtils { Context context, int appStandbyMode, boolean allowListed, int uid, String packageName, BatteryUtils batteryUtils, PowerAllowlistBackend powerAllowlistBackend, Action action) { - final String packageNameKey = BatteryHistoricalLogUtil + final String packageNameKey = BatteryOptimizeLogUtils .getPackageNameWithUserId(packageName, UserHandle.myUserId()); try { batteryUtils.setForceAppStandby(uid, packageName, appStandbyMode); @@ -259,7 +259,7 @@ public class BatteryOptimizeUtils { appStandbyMode = -1; Log.e(TAG, "set OPTIMIZATION MODE failed for " + packageName, e); } - BatteryHistoricalLogUtil.writeLog( + BatteryOptimizeLogUtils.writeLog( context, action, packageNameKey, diff --git a/src/com/android/settings/fuelgauge/BatteryUtils.java b/src/com/android/settings/fuelgauge/BatteryUtils.java index 29c7591863c..1f7e3ec282b 100644 --- a/src/com/android/settings/fuelgauge/BatteryUtils.java +++ b/src/com/android/settings/fuelgauge/BatteryUtils.java @@ -355,7 +355,7 @@ public class BatteryUtils { @SuppressWarnings("unchecked") public static T parseProtoFromString( String serializedProto, T protoClass) { - if (serializedProto.isEmpty()) { + if (serializedProto == null || serializedProto.isEmpty()) { return (T) protoClass.getDefaultInstanceForType(); } try { diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java index fb1be3ee45f..ae86095dc29 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java @@ -23,6 +23,9 @@ import android.util.Log; import androidx.annotation.VisibleForTesting; +import com.android.settings.fuelgauge.BatteryUsageHistoricalLogEntry.Action; +import com.android.settings.fuelgauge.batteryusage.bugreport.BatteryUsageLogUtils; + import java.util.List; import java.util.function.Supplier; @@ -46,6 +49,7 @@ public final class BatteryUsageDataLoader { @VisibleForTesting static void loadUsageData(final Context context, final boolean isFullChargeStart) { + BatteryUsageLogUtils.writeLog(context, Action.FETCH_USAGE_DATA, ""); final long start = System.currentTimeMillis(); final BatteryUsageStats batteryUsageStats = DataProcessor.getBatteryUsageStats(context); final List batteryEntryList = diff --git a/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java b/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java index 64b5b77367f..920670fa13f 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java @@ -24,6 +24,8 @@ import android.os.Looper; import android.util.Log; import com.android.settings.core.instrumentation.ElapsedTimeUtils; +import com.android.settings.fuelgauge.BatteryUsageHistoricalLogEntry.Action; +import com.android.settings.fuelgauge.batteryusage.bugreport.BatteryUsageLogUtils; import com.android.settings.overlay.FeatureFactory; import java.time.Duration; @@ -79,8 +81,9 @@ public final class BootBroadcastReceiver extends BroadcastReceiver { if (Intent.ACTION_BOOT_COMPLETED.equals(action)) { final Intent recheckIntent = new Intent(ACTION_PERIODIC_JOB_RECHECK); recheckIntent.setClass(context, BootBroadcastReceiver.class); - mHandler.postDelayed(() -> context.sendBroadcast(recheckIntent), - getRescheduleTimeForBootAction(context)); + final long delayedTime = getRescheduleTimeForBootAction(context); + mHandler.postDelayed(() -> context.sendBroadcast(recheckIntent), delayedTime); + BatteryUsageLogUtils.writeLog(context, Action.RECHECK_JOB, "delay:" + delayedTime); } else if (ACTION_SETUP_WIZARD_FINISHED.equals(action)) { ElapsedTimeUtils.storeSuwFinishedTimestamp(context, System.currentTimeMillis()); } diff --git a/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java b/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java index 0435e451e82..8d1a2f90991 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java +++ b/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java @@ -34,7 +34,9 @@ import android.util.Log; import androidx.annotation.VisibleForTesting; +import com.android.settings.fuelgauge.BatteryUsageHistoricalLogEntry.Action; import com.android.settings.fuelgauge.BatteryUtils; +import com.android.settings.fuelgauge.batteryusage.bugreport.BatteryUsageLogUtils; import com.android.settings.fuelgauge.batteryusage.db.BatteryStateDatabase; import com.android.settingslib.fuelgauge.BatteryStatus; @@ -395,6 +397,7 @@ public final class DatabaseUtils { int size = 1; final ContentResolver resolver = context.getContentResolver(); + String errorMessage = ""; // Inserts all ContentValues into battery provider. if (!valuesList.isEmpty()) { final ContentValues[] valuesArray = new ContentValues[valuesList.size()]; @@ -404,7 +407,8 @@ public final class DatabaseUtils { Log.d(TAG, "insert() battery states data into database with isFullChargeStart:" + isFullChargeStart); } catch (Exception e) { - Log.e(TAG, "bulkInsert() battery states data into database error:\n" + e); + errorMessage = e.toString(); + Log.e(TAG, "bulkInsert() data into database error:\n" + errorMessage); } } else { // Inserts one fake data into battery provider. @@ -424,11 +428,16 @@ public final class DatabaseUtils { + isFullChargeStart); } catch (Exception e) { - Log.e(TAG, "insert() data into database error:\n" + e); + errorMessage = e.toString(); + Log.e(TAG, "insert() data into database error:\n" + errorMessage); } valuesList.add(contentValues); } resolver.notifyChange(BATTERY_CONTENT_URI, /*observer=*/ null); + BatteryUsageLogUtils.writeLog( + context, + Action.INSERT_USAGE_DATA, + "size=" + size + " " + errorMessage); Log.d(TAG, String.format("sendBatteryEntryData() size=%d in %d/ms", size, (System.currentTimeMillis() - startTime))); if (isFullChargeStart) { diff --git a/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobManager.java b/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobManager.java index 3d78c00c46f..8c0e66c78d8 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobManager.java +++ b/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobManager.java @@ -24,6 +24,8 @@ import android.util.Log; import androidx.annotation.VisibleForTesting; +import com.android.settings.fuelgauge.BatteryUsageHistoricalLogEntry.Action; +import com.android.settings.fuelgauge.batteryusage.bugreport.BatteryUsageLogUtils; import com.android.settings.overlay.FeatureFactory; import java.time.Clock; @@ -76,8 +78,11 @@ public final class PeriodicJobManager { final long triggerAtMillis = getTriggerAtMillis(mContext, Clock.systemUTC(), fromBoot); mAlarmManager.setExactAndAllowWhileIdle( AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent); - Log.d(TAG, "schedule next alarm job at " - + ConvertUtils.utcToLocalTimeForLogging(triggerAtMillis)); + + final String utcToLocalTime = ConvertUtils.utcToLocalTimeForLogging(triggerAtMillis); + BatteryUsageLogUtils.writeLog( + mContext, Action.SCHEDULE_JOB, "triggerTime=" + utcToLocalTime); + Log.d(TAG, "schedule next alarm job at " + utcToLocalTime); } void cancelJob(PendingIntent pendingIntent) { diff --git a/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiver.java b/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiver.java index 3ca45322184..2bd04669b31 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiver.java +++ b/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiver.java @@ -22,6 +22,9 @@ import android.content.Context; import android.content.Intent; import android.util.Log; +import com.android.settings.fuelgauge.BatteryUsageHistoricalLogEntry.Action; +import com.android.settings.fuelgauge.batteryusage.bugreport.BatteryUsageLogUtils; + /** Receives the periodic alarm {@link PendingIntent} callback. */ public final class PeriodicJobReceiver extends BroadcastReceiver { private static final String TAG = "PeriodicJobReceiver"; @@ -39,6 +42,7 @@ public final class PeriodicJobReceiver extends BroadcastReceiver { Log.w(TAG, "do not refresh job for work profile action=" + action); return; } + BatteryUsageLogUtils.writeLog(context, Action.EXECUTE_JOB, ""); BatteryUsageDataLoader.enqueueWork(context, /*isFullChargeStart=*/ false); AppUsageDataLoader.enqueueWork(context); Log.d(TAG, "refresh periodic job from action=" + action); diff --git a/src/com/android/settings/fuelgauge/batteryusage/bugreport/BatteryUsageLogUtils.java b/src/com/android/settings/fuelgauge/batteryusage/bugreport/BatteryUsageLogUtils.java new file mode 100644 index 00000000000..cb2f39404e6 --- /dev/null +++ b/src/com/android/settings/fuelgauge/batteryusage/bugreport/BatteryUsageLogUtils.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2023 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 android.content.Context; +import android.content.SharedPreferences; +import android.util.Base64; + +import com.android.settings.fuelgauge.BatteryUsageHistoricalLog; +import com.android.settings.fuelgauge.BatteryUsageHistoricalLogEntry; +import com.android.settings.fuelgauge.BatteryUsageHistoricalLogEntry.Action; +import com.android.settings.fuelgauge.BatteryUtils; +import com.android.settings.fuelgauge.batteryusage.ConvertUtils; +import com.google.common.annotations.VisibleForTesting; + +import java.io.PrintWriter; +import java.util.List; + +/** Writes and reads a historical log of battery usage periodic job events. */ +public final class BatteryUsageLogUtils { + private static final String TAG = "BatteryUsageLogUtils"; + private static final String BATTERY_USAGE_FILE_NAME = "battery_usage_historical_logs"; + private static final String LOGS_KEY = "battery_usage_logs_key"; + + // 24 hours x 4 events every hour x 3 days + static final int MAX_ENTRIES = 288; + + private BatteryUsageLogUtils() {} + + /** Write the log into the {@link SharedPreferences}. */ + public static void writeLog(Context context, Action action, String actionDescription) { + final SharedPreferences sharedPreferences = getSharedPreferences(context); + final BatteryUsageHistoricalLogEntry newLogEntry = + BatteryUsageHistoricalLogEntry.newBuilder() + .setTimestamp(System.currentTimeMillis()) + .setAction(action) + .setActionDescription(actionDescription) + .build(); + + final BatteryUsageHistoricalLog existingLog = + parseLogFromString(sharedPreferences.getString(LOGS_KEY, "")); + final BatteryUsageHistoricalLog.Builder newLogBuilder = existingLog.toBuilder(); + // Prune old entries to limit the max logging data count. + if (existingLog.getLogEntryCount() >= MAX_ENTRIES) { + newLogBuilder.removeLogEntry(0); + } + newLogBuilder.addLogEntry(newLogEntry); + + final String loggingContent = + Base64.encodeToString(newLogBuilder.build().toByteArray(), Base64.DEFAULT); + sharedPreferences + .edit() + .putString(LOGS_KEY, loggingContent) + .apply(); + } + + /** Prints the historical log that has previously been stored by this utility. */ + public static void printHistoricalLog(Context context, PrintWriter writer) { + final BatteryUsageHistoricalLog existingLog = parseLogFromString( + getSharedPreferences(context).getString(LOGS_KEY, "")); + final List logEntryList = existingLog.getLogEntryList(); + if (logEntryList.isEmpty()) { + writer.println("\tnothing to dump"); + } else { + logEntryList.forEach(entry -> writer.println(toString(entry))); + } + } + + @VisibleForTesting + static SharedPreferences getSharedPreferences(Context context) { + return context.getApplicationContext() + .getSharedPreferences(BATTERY_USAGE_FILE_NAME, Context.MODE_PRIVATE); + } + + private static BatteryUsageHistoricalLog parseLogFromString(String storedLogs) { + return BatteryUtils.parseProtoFromString( + storedLogs, BatteryUsageHistoricalLog.getDefaultInstance()); + } + + private static String toString(BatteryUsageHistoricalLogEntry entry) { + final StringBuilder builder = new StringBuilder("\t") + .append(ConvertUtils.utcToLocalTimeForLogging(entry.getTimestamp())) + .append(" " + entry.getAction()); + final String description = entry.getActionDescription(); + if (description != null && !description.isEmpty()) { + builder.append(" " + description); + } + return builder.toString(); + } +} diff --git a/src/com/android/settings/fuelgauge/batteryusage/bugreport/LogUtils.java b/src/com/android/settings/fuelgauge/batteryusage/bugreport/LogUtils.java index 9be378bacb1..6d5082c246a 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/bugreport/LogUtils.java +++ b/src/com/android/settings/fuelgauge/batteryusage/bugreport/LogUtils.java @@ -39,6 +39,12 @@ public final class LogUtils { private static final Duration DUMP_TIME_OFFSET_FOR_ENTRY = Duration.ofHours(4); static void dumpBatteryUsageDatabaseHist(Context context, PrintWriter writer) { + // Dumps periodic job events. + writer.println("\nBattery PeriodicJob History:"); + BatteryUsageLogUtils.printHistoricalLog(context, writer); + writer.flush(); + + // Dumps phenotype environments. DatabaseUtils.dump(context, writer); writer.flush(); final BatteryStateDao dao = @@ -47,6 +53,7 @@ public final class LogUtils { .batteryStateDao(); final long timeOffset = Clock.systemUTC().millis() - DUMP_TIME_OFFSET.toMillis(); + // Gets all distinct timestamps. final List timestamps = dao.getDistinctTimestamps(timeOffset); final int distinctCount = timestamps.size(); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryHistoricalLogUtilTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryOptimizeLogUtilsTest.java similarity index 60% rename from tests/robotests/src/com/android/settings/fuelgauge/BatteryHistoricalLogUtilTest.java rename to tests/robotests/src/com/android/settings/fuelgauge/BatteryOptimizeLogUtilsTest.java index cb5de7d43a5..87de62f5d95 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryHistoricalLogUtilTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryOptimizeLogUtilsTest.java @@ -33,7 +33,7 @@ import java.io.PrintWriter; import java.io.StringWriter; @RunWith(RobolectricTestRunner.class) -public final class BatteryHistoricalLogUtilTest { +public final class BatteryOptimizeLogUtilsTest { private final StringWriter mTestStringWriter = new StringWriter(); private final PrintWriter mTestPrintWriter = new PrintWriter(mTestStringWriter); @@ -43,19 +43,19 @@ public final class BatteryHistoricalLogUtilTest { @Before public void setUp() { mContext = ApplicationProvider.getApplicationContext(); - BatteryHistoricalLogUtil.getSharedPreferences(mContext).edit().clear().commit(); + BatteryOptimizeLogUtils.getSharedPreferences(mContext).edit().clear().commit(); } @Test public void printHistoricalLog_withDefaultLogs() { - BatteryHistoricalLogUtil.printBatteryOptimizeHistoricalLog(mContext, mTestPrintWriter); + BatteryOptimizeLogUtils.printBatteryOptimizeHistoricalLog(mContext, mTestPrintWriter); assertThat(mTestStringWriter.toString()).contains("nothing to dump"); } @Test public void writeLog_withExpectedLogs() { - BatteryHistoricalLogUtil.writeLog(mContext, Action.APPLY, "pkg1", "logs"); - BatteryHistoricalLogUtil.printBatteryOptimizeHistoricalLog(mContext, mTestPrintWriter); + BatteryOptimizeLogUtils.writeLog(mContext, Action.APPLY, "pkg1", "logs"); + BatteryOptimizeLogUtils.printBatteryOptimizeHistoricalLog(mContext, mTestPrintWriter); assertThat(mTestStringWriter.toString()).contains( "pkg1\taction:APPLY\tevent:logs"); @@ -63,21 +63,27 @@ public final class BatteryHistoricalLogUtilTest { @Test public void writeLog_multipleLogs_withCorrectCounts() { - for (int i = 0; i < BatteryHistoricalLogUtil.MAX_ENTRIES; i++) { - BatteryHistoricalLogUtil.writeLog(mContext, Action.LEAVE, "pkg" + i, "logs"); + final int expectedCount = 10; + for (int i = 0; i < expectedCount; i++) { + BatteryOptimizeLogUtils.writeLog(mContext, Action.LEAVE, "pkg" + i, "logs"); } - BatteryHistoricalLogUtil.printBatteryOptimizeHistoricalLog(mContext, mTestPrintWriter); + BatteryOptimizeLogUtils.printBatteryOptimizeHistoricalLog(mContext, mTestPrintWriter); - assertThat(mTestStringWriter.toString().split("LEAVE").length).isEqualTo(41); + assertActionCount("LEAVE", expectedCount); } @Test public void writeLog_overMaxEntriesLogs_withCorrectCounts() { - for (int i = 0; i < BatteryHistoricalLogUtil.MAX_ENTRIES + 10; i++) { - BatteryHistoricalLogUtil.writeLog(mContext, Action.RESET, "pkg" + i, "logs"); + for (int i = 0; i < BatteryOptimizeLogUtils.MAX_ENTRIES + 10; i++) { + BatteryOptimizeLogUtils.writeLog(mContext, Action.RESET, "pkg" + i, "logs"); } - BatteryHistoricalLogUtil.printBatteryOptimizeHistoricalLog(mContext, mTestPrintWriter); + BatteryOptimizeLogUtils.printBatteryOptimizeHistoricalLog(mContext, mTestPrintWriter); - assertThat(mTestStringWriter.toString().split("RESET").length).isEqualTo(41); + assertActionCount("RESET", BatteryOptimizeLogUtils.MAX_ENTRIES); + } + + private void assertActionCount(String token, int count) { + final String dumpResults = mTestStringWriter.toString(); + assertThat(dumpResults.split(token).length).isEqualTo(count + 1); } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryEntryTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryEntryTest.java index dec5d7d3345..07b3b3481c8 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryEntryTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryEntryTest.java @@ -40,6 +40,7 @@ import com.android.settings.fuelgauge.BatteryUtils; import com.android.settings.fuelgauge.batteryusage.BatteryEntry.NameAndIcon; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -232,6 +233,7 @@ public class BatteryEntryTest { assertThat(entry.getTimeInBackgroundMs()).isEqualTo(0); } + @Ignore @Test public void testUidCache_switchLocale_shouldCleanCache() { Locale.setDefault(new Locale("en_US")); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/bugreport/BatteryUsageLogUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/bugreport/BatteryUsageLogUtilsTest.java new file mode 100644 index 00000000000..12c040e97bf --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/bugreport/BatteryUsageLogUtilsTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2022 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.BatteryUsageHistoricalLogEntry.Action; + +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 BatteryUsageLogUtilsTest { + + private StringWriter mTestStringWriter; + private PrintWriter mTestPrintWriter; + private Context mContext; + + @Before + public void setUp() { + mContext = ApplicationProvider.getApplicationContext(); + mTestStringWriter = new StringWriter(); + mTestPrintWriter = new PrintWriter(mTestStringWriter); + BatteryUsageLogUtils.getSharedPreferences(mContext).edit().clear().commit(); + } + + @Test + public void printHistoricalLog_withDefaultLogs() { + final String expectedInformation = "nothing to dump"; + // Environment checking. + assertThat(mTestStringWriter.toString().contains(expectedInformation)).isFalse(); + + BatteryUsageLogUtils.printHistoricalLog(mContext, mTestPrintWriter); + assertThat(mTestStringWriter.toString()).contains(expectedInformation); + } + + @Test + public void writeLog_multipleLogs_withCorrectCounts() { + final int expectedCount = 10; + for (int i = 0; i < expectedCount; i++) { + BatteryUsageLogUtils.writeLog(mContext, Action.SCHEDULE_JOB, ""); + } + BatteryUsageLogUtils.writeLog(mContext, Action.EXECUTE_JOB, ""); + + BatteryUsageLogUtils.printHistoricalLog(mContext, mTestPrintWriter); + + assertActionCount("SCHEDULE_JOB", expectedCount); + assertActionCount("EXECUTE_JOB", 1); + } + + @Test + public void writeLog_overMaxEntriesLogs_withCorrectCounts() { + BatteryUsageLogUtils.writeLog(mContext, Action.SCHEDULE_JOB, ""); + BatteryUsageLogUtils.writeLog(mContext, Action.SCHEDULE_JOB, ""); + for (int i = 0; i < BatteryUsageLogUtils.MAX_ENTRIES * 2; i++) { + BatteryUsageLogUtils.writeLog(mContext, Action.EXECUTE_JOB, ""); + } + + BatteryUsageLogUtils.printHistoricalLog(mContext, mTestPrintWriter); + + final String dumpResults = mTestStringWriter.toString(); + assertThat(dumpResults.contains("SCHEDULE_JOB")).isFalse(); + assertActionCount("EXECUTE_JOB", BatteryUsageLogUtils.MAX_ENTRIES); + } + + private void assertActionCount(String token, int count) { + final String dumpResults = mTestStringWriter.toString(); + assertThat(dumpResults.split(token).length).isEqualTo(count + 1); + } +} diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/bugreport/BugReportContentProviderTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/bugreport/BugReportContentProviderTest.java index 8365ae40a84..45d4065bf12 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/bugreport/BugReportContentProviderTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/bugreport/BugReportContentProviderTest.java @@ -87,6 +87,7 @@ public final class BugReportContentProviderTest { mBugReportContentProvider.dump(FileDescriptor.out, mPrintWriter, new String[] {}); String dumpContent = mStringWriter.toString(); + assertThat(dumpContent).contains("Battery PeriodicJob History"); assertThat(dumpContent).contains("Battery DatabaseHistory"); assertThat(dumpContent).contains(PACKAGE_NAME1); assertThat(dumpContent).contains(PACKAGE_NAME2); From d94ac1219018a187eaa13786b063134e7882706e Mon Sep 17 00:00:00 2001 From: SongFerngWang Date: Thu, 6 Jul 2023 19:55:33 +0800 Subject: [PATCH 3/6] To fix waiting too long issue when psim -> esim if the user swithes slot from psim to esim, then the settings need to wait the simSlotMapping completed and it has a timer to avoid UI stay here too long. Since the framework did not sned the carrier config changed with vaild subId in MEP + psim->esim case, it cause the settings does not know the simSlotMapping completed and it stay here until timeout. For MEP case, changing this condition as SimSlotStatusChanged Bug: 273813956 Test: atest UiccSlotUtilTest Change-Id: Ic09dbcb3629fe13770f3ad301c0a396d6745969f --- .../settings/network/UiccSlotUtil.java | 51 +++++++++++++++++-- .../settings/network/UiccSlotUtilTest.java | 23 +++++++++ 2 files changed, 70 insertions(+), 4 deletions(-) diff --git a/src/com/android/settings/network/UiccSlotUtil.java b/src/com/android/settings/network/UiccSlotUtil.java index 95a0e4d5f2a..49a1a852ff1 100644 --- a/src/com/android/settings/network/UiccSlotUtil.java +++ b/src/com/android/settings/network/UiccSlotUtil.java @@ -17,7 +17,10 @@ package com.android.settings.network; import android.annotation.IntDef; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.provider.Settings; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; @@ -54,6 +57,28 @@ public class UiccSlotUtil { public static final int INVALID_PHYSICAL_SLOT_ID = -1; public static final int INVALID_PORT_ID = -1; + @VisibleForTesting + static class SimSlotChangeReceiver extends BroadcastReceiver{ + private final CountDownLatch mLatch; + SimSlotChangeReceiver(CountDownLatch latch) { + mLatch = latch; + } + + public void registerOn(Context context) { + context.registerReceiver(this, + new IntentFilter(TelephonyManager.ACTION_SIM_SLOT_STATUS_CHANGED), + Context.RECEIVER_EXPORTED/*UNAUDITED*/); + } + + @Override + public void onReceive(Context context, Intent intent) { + Log.i(TAG, "Action: " + intent.getAction()); + if (TelephonyManager.ACTION_SIM_SLOT_STATUS_CHANGED.equals(intent.getAction())) { + mLatch.countDown(); + } + } + } + /** * Mode for switching to eSIM slot which decides whether there is cleanup process, e.g. * disabling test profile, after eSIM slot is activated and whether we will wait it finished. @@ -229,19 +254,27 @@ public class UiccSlotUtil { && uiccSlotMapping.getPortIndex() == port); } - private static void performSwitchToSlot(TelephonyManager telMgr, + @VisibleForTesting + static void performSwitchToSlot(TelephonyManager telMgr, Collection uiccSlotMappings, Context context) throws UiccSlotsException { - CarrierConfigChangedReceiver receiver = null; + BroadcastReceiver receiver = null; long waitingTimeMillis = Settings.Global.getLong( context.getContentResolver(), Settings.Global.EUICC_SWITCH_SLOT_TIMEOUT_MILLIS, DEFAULT_WAIT_AFTER_SWITCH_TIMEOUT_MILLIS); + Log.d(TAG, "Set waitingTime as " + waitingTimeMillis); + try { CountDownLatch latch = new CountDownLatch(1); - receiver = new CarrierConfigChangedReceiver(latch); - receiver.registerOn(context); + if (isMultipleEnabledProfilesSupported(telMgr)) { + receiver = new SimSlotChangeReceiver(latch); + ((SimSlotChangeReceiver) receiver).registerOn(context); + } else { + receiver = new CarrierConfigChangedReceiver(latch); + ((CarrierConfigChangedReceiver) receiver).registerOn(context); + } telMgr.setSimSlotMapping(uiccSlotMappings); latch.await(waitingTimeMillis, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { @@ -435,4 +468,14 @@ public class UiccSlotUtil { Log.i(TAG, "isRemovableSimEnabled: " + isRemovableSimEnabled); return isRemovableSimEnabled; } + + private static boolean isMultipleEnabledProfilesSupported(TelephonyManager telMgr) { + List cardInfos = telMgr.getUiccCardsInfo(); + if (cardInfos == null) { + Log.w(TAG, "UICC cards info list is empty."); + return false; + } + return cardInfos.stream().anyMatch( + cardInfo -> cardInfo.isMultipleEnabledProfilesSupported()); + } } diff --git a/tests/unit/src/com/android/settings/network/UiccSlotUtilTest.java b/tests/unit/src/com/android/settings/network/UiccSlotUtilTest.java index 9a2c61167cb..2e17fb271cd 100644 --- a/tests/unit/src/com/android/settings/network/UiccSlotUtilTest.java +++ b/tests/unit/src/com/android/settings/network/UiccSlotUtilTest.java @@ -20,10 +20,13 @@ import static android.telephony.UiccSlotInfo.CARD_STATE_INFO_PRESENT; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; +import android.content.Intent; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; @@ -49,6 +52,7 @@ import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.concurrent.CountDownLatch; @RunWith(AndroidJUnit4.class) public class UiccSlotUtilTest { @@ -738,6 +742,25 @@ public class UiccSlotUtilTest { assertThat(testSlot).isFalse(); } + @Test + public void performSwitchToSlot_setSimSlotMapping() throws UiccSlotsException { + Collection uiccSlotMappings = createUiccSlotMappingDualPortsBNoOrding(); + + UiccSlotUtil.performSwitchToSlot(mTelephonyManager, uiccSlotMappings, mContext); + + verify(mTelephonyManager).setSimSlotMapping(any()); + } + + @Test + public void onReceiveSimSlotChangeReceiver_receiveAction_timerCountDown() { + CountDownLatch latch = spy(new CountDownLatch(1)); + UiccSlotUtil.SimSlotChangeReceiver receive = new UiccSlotUtil.SimSlotChangeReceiver(latch); + + receive.onReceive(mContext, new Intent(TelephonyManager.ACTION_SIM_SLOT_STATUS_CHANGED)); + + verify(latch).countDown(); + } + private void compareTwoUiccSlotMappings(Collection testUiccSlotMappings, Collection verifyUiccSlotMappings) { assertThat(testUiccSlotMappings.size()).isEqualTo(verifyUiccSlotMappings.size()); From 590b4e7af821e3fb540ac0f44f0beaabcf582913 Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Fri, 7 Jul 2023 15:31:20 +0800 Subject: [PATCH 4/6] [Regulatory Labels] Load labels from overlays When config_regulatory_info_overlay_package_name is configured, load regulatory labels directly from overlay package. Also fix RegulatoryInfoDisplayActivity missing dialog theme. Bug: 283170651 Test: unit test Test: manual - check regulatory labels Change-Id: Ia1f6848abe0da9facf34f47abab09e88d57e74d6 --- AndroidManifest.xml | 1 + res/values/config.xml | 3 + .../RegulatoryInfoDisplayActivity.java | 171 ------------------ .../settings/RegulatoryInfoDisplayActivity.kt | 67 +++++++ .../deviceinfo/regulatory/RegulatoryInfo.kt | 75 ++++++++ .../res/drawable/regulatory_info.png | Bin 159 -> 0 bytes .../res/drawable/regulatory_info_sku.png | Bin 159 -> 0 bytes .../res/drawable/regulatory_info_sku1_coo.png | Bin 159 -> 0 bytes .../RegulatoryInfoDisplayActivityTest.java | 83 --------- .../spa_unit/res/drawable/regulatory_info.xml | 20 ++ .../res/drawable/regulatory_info_sku.xml | 21 +++ .../res/drawable/regulatory_info_sku1_coo.xml | 20 ++ .../regulatory/RegulatoryInfoTest.kt | 105 +++++++++++ 13 files changed, 312 insertions(+), 254 deletions(-) delete mode 100644 src/com/android/settings/RegulatoryInfoDisplayActivity.java create mode 100644 src/com/android/settings/RegulatoryInfoDisplayActivity.kt create mode 100644 src/com/android/settings/deviceinfo/regulatory/RegulatoryInfo.kt delete mode 100644 tests/robotests/res/drawable/regulatory_info.png delete mode 100644 tests/robotests/res/drawable/regulatory_info_sku.png delete mode 100644 tests/robotests/res/drawable/regulatory_info_sku1_coo.png delete mode 100644 tests/robotests/src/com/android/settings/RegulatoryInfoDisplayActivityTest.java create mode 100644 tests/spa_unit/res/drawable/regulatory_info.xml create mode 100644 tests/spa_unit/res/drawable/regulatory_info_sku.xml create mode 100644 tests/spa_unit/res/drawable/regulatory_info_sku1_coo.xml create mode 100644 tests/spa_unit/src/com/android/settings/deviceinfo/regulatory/RegulatoryInfoTest.kt diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 2c3e7f3d12a..a20c5e23a69 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -3759,6 +3759,7 @@ diff --git a/res/values/config.xml b/res/values/config.xml index 0c5c7a05edf..5ae0220d29b 100755 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -345,6 +345,9 @@ false false + + true diff --git a/src/com/android/settings/RegulatoryInfoDisplayActivity.java b/src/com/android/settings/RegulatoryInfoDisplayActivity.java deleted file mode 100644 index 8f650511a4d..00000000000 --- a/src/com/android/settings/RegulatoryInfoDisplayActivity.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (C) 2013 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; - -import android.app.Activity; -import android.content.DialogInterface; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.os.SystemProperties; -import android.text.TextUtils; -import android.view.Gravity; -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.annotation.VisibleForTesting; -import androidx.appcompat.app.AlertDialog; - -import java.util.Locale; - -/** - * {@link Activity} that displays regulatory information for the "Regulatory information" - * preference item, and when "*#07#" is dialed on the Phone keypad. To enable this feature, - * set the "config_show_regulatory_info" boolean to true in a device overlay resource, and in the - * same overlay, either add a drawable named "regulatory_info.png" containing a graphical version - * of the required regulatory info (If ro.bootloader.hardware.sku property is set use - * "regulatory_info_.png where sku is ro.bootloader.hardware.sku property value in lowercase"), - * or add a string resource named "regulatory_info_text" with an HTML version of the required - * information (text will be centered in the dialog). - */ -public class RegulatoryInfoDisplayActivity extends Activity implements - DialogInterface.OnDismissListener { - - private final String REGULATORY_INFO_RESOURCE = "regulatory_info"; - private static final String DEFAULT_REGULATORY_INFO_FILEPATH = - "/data/misc/elabel/regulatory_info.png"; - private static final String REGULATORY_INFO_FILEPATH_TEMPLATE = - "/data/misc/elabel/regulatory_info_%s.png"; - - /** - * Display the regulatory info graphic in a dialog window. - */ - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - AlertDialog.Builder builder = new AlertDialog.Builder(this) - .setTitle(R.string.regulatory_labels) - .setOnDismissListener(this) - .setPositiveButton(android.R.string.ok, null /* onClickListener */); - - boolean regulatoryInfoDrawableExists = false; - - final String regulatoryInfoFile = getRegulatoryInfoImageFileName(); - final Bitmap regulatoryInfoBitmap = BitmapFactory.decodeFile(regulatoryInfoFile); - - if (regulatoryInfoBitmap != null) { - regulatoryInfoDrawableExists = true; - } - - int resId = 0; - if (!regulatoryInfoDrawableExists) { - resId = getResourceId(); - } - if (resId != 0) { - try { - Drawable d = getDrawable(resId); - // set to false if the width or height is <= 2 - // (missing PNG can return an empty 2x2 pixel Drawable) - regulatoryInfoDrawableExists = (d.getIntrinsicWidth() > 2 - && d.getIntrinsicHeight() > 2); - } catch (Resources.NotFoundException ignored) { - regulatoryInfoDrawableExists = false; - } - } - - CharSequence regulatoryText = getResources() - .getText(R.string.regulatory_info_text); - - if (regulatoryInfoDrawableExists) { - View view = getLayoutInflater().inflate(R.layout.regulatory_info, null); - ImageView image = view.findViewById(R.id.regulatoryInfo); - if (regulatoryInfoBitmap != null) { - image.setImageBitmap(regulatoryInfoBitmap); - } else { - image.setImageResource(resId); - } - builder.setView(view); - builder.show(); - } else if (regulatoryText.length() > 0) { - builder.setMessage(regulatoryText); - AlertDialog dialog = builder.show(); - // we have to show the dialog first, or the setGravity() call will throw a NPE - TextView messageText = (TextView) dialog.findViewById(android.R.id.message); - messageText.setGravity(Gravity.CENTER); - } else { - // neither drawable nor text resource exists, finish activity - finish(); - } - } - - @VisibleForTesting - int getResourceId() { - // Use regulatory_info by default. - int resId = getResources().getIdentifier( - REGULATORY_INFO_RESOURCE, "drawable", getPackageName()); - - // When hardware sku property exists, use regulatory_info_ resource if valid. - final String sku = getSku(); - if (!TextUtils.isEmpty(sku)) { - String regulatory_info_res = REGULATORY_INFO_RESOURCE + "_" + sku.toLowerCase(); - int id = getResources().getIdentifier( - regulatory_info_res, "drawable", getPackageName()); - if (id != 0) { - resId = id; - } - } - - // When hardware coo property exists, use regulatory_info__ resource if valid. - final String coo = getCoo(); - if (!TextUtils.isEmpty(coo) && !TextUtils.isEmpty(sku)) { - final String regulatory_info_coo_res = - REGULATORY_INFO_RESOURCE + "_" + sku.toLowerCase() + "_" + coo.toLowerCase(); - final int id = getResources().getIdentifier( - regulatory_info_coo_res, "drawable", getPackageName()); - if (id != 0) { - resId = id; - } - } - return resId; - } - - @Override - public void onDismiss(DialogInterface dialog) { - finish(); // close the activity - } - - private String getCoo() { - return SystemProperties.get("ro.boot.hardware.coo", ""); - } - - private String getSku() { - return SystemProperties.get("ro.boot.hardware.sku", ""); - } - - private String getRegulatoryInfoImageFileName() { - final String sku = getSku(); - if (TextUtils.isEmpty(sku)) { - return DEFAULT_REGULATORY_INFO_FILEPATH; - } else { - return String.format(Locale.US, REGULATORY_INFO_FILEPATH_TEMPLATE, - sku.toLowerCase()); - } - } -} diff --git a/src/com/android/settings/RegulatoryInfoDisplayActivity.kt b/src/com/android/settings/RegulatoryInfoDisplayActivity.kt new file mode 100644 index 00000000000..ffacb9c192a --- /dev/null +++ b/src/com/android/settings/RegulatoryInfoDisplayActivity.kt @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2023 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 + +import android.app.Activity +import android.os.Bundle +import android.view.Gravity +import android.widget.ImageView +import android.widget.TextView +import androidx.appcompat.app.AlertDialog +import com.android.settings.deviceinfo.regulatory.RegulatoryInfo.getRegulatoryInfo + +/** + * [Activity] that displays regulatory information for the "Regulatory information" + * preference item, and when "*#07#" is dialed on the Phone keypad. To enable this feature, + * set the "config_show_regulatory_info" boolean to true in a device overlay resource, and in the + * same overlay, either add a drawable named "regulatory_info.png" containing a graphical version + * of the required regulatory info (If ro.bootloader.hardware.sku property is set use + * "regulatory_info_.png where sku is ro.bootloader.hardware.sku property value in lowercase"), + * or add a string resource named "regulatory_info_text" with an HTML version of the required + * information (text will be centered in the dialog). + */ +class RegulatoryInfoDisplayActivity : Activity() { + + /** Display the regulatory info graphic in a dialog window. */ + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val builder = AlertDialog.Builder(this) + .setTitle(R.string.regulatory_labels) + .setOnDismissListener { finish() } // close the activity + .setPositiveButton(android.R.string.ok, null) + + getRegulatoryInfo()?.let { + val view = layoutInflater.inflate(R.layout.regulatory_info, null) + val image = view.findViewById(R.id.regulatoryInfo) + image.setImageDrawable(it) + builder.setView(view) + builder.show() + return + } + + val regulatoryText = resources.getText(R.string.regulatory_info_text) + if (regulatoryText.isNotEmpty()) { + builder.setMessage(regulatoryText) + val dialog = builder.show() + // we have to show the dialog first, or the setGravity() call will throw a NPE + dialog.findViewById(android.R.id.message)?.gravity = Gravity.CENTER + } else { + // neither drawable nor text resource exists, finish activity + finish() + } + } +} diff --git a/src/com/android/settings/deviceinfo/regulatory/RegulatoryInfo.kt b/src/com/android/settings/deviceinfo/regulatory/RegulatoryInfo.kt new file mode 100644 index 00000000000..e26e0610502 --- /dev/null +++ b/src/com/android/settings/deviceinfo/regulatory/RegulatoryInfo.kt @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2023 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.deviceinfo.regulatory + +import android.content.Context +import android.content.res.Resources +import android.graphics.drawable.Drawable +import android.os.SystemProperties +import androidx.annotation.DrawableRes +import androidx.annotation.VisibleForTesting +import com.android.settings.R + + + +/** To load Regulatory Info from device. */ +object RegulatoryInfo { + private const val REGULATORY_INFO_RESOURCE = "regulatory_info" + + @VisibleForTesting + const val KEY_COO = "ro.boot.hardware.coo" + + @VisibleForTesting + const val KEY_SKU = "ro.boot.hardware.sku" + + /** Gets the regulatory drawable. */ + fun Context.getRegulatoryInfo(): Drawable? { + val sku = getSku() + if (sku.isNotBlank()) { + // When hardware coo property exists, use regulatory_info__ resource if valid. + val coo = getCoo() + if (coo.isNotBlank()) { + getRegulatoryInfo("${REGULATORY_INFO_RESOURCE}_${sku}_$coo")?.let { return it } + } + // Use regulatory_info_ resource if valid. + getRegulatoryInfo("${REGULATORY_INFO_RESOURCE}_$sku")?.let { return it } + } + return getRegulatoryInfo(REGULATORY_INFO_RESOURCE) + } + + private fun getCoo(): String = SystemProperties.get(KEY_COO).lowercase() + + private fun getSku(): String = SystemProperties.get(KEY_SKU).lowercase() + + private fun Context.getRegulatoryInfo(fileName: String): Drawable? { + val overlayPackageName = + resources.getString(R.string.config_regulatory_info_overlay_package_name) + .ifBlank { packageName } + val resources = packageManager.getResourcesForApplication(overlayPackageName) + val id = resources.getIdentifier(fileName, "drawable", overlayPackageName) + return if (id > 0) resources.getRegulatoryInfo(id) else null + } + + private fun Resources.getRegulatoryInfo(@DrawableRes resId: Int): Drawable? = try { + getDrawable(resId, null).takeIf { + // Ignore the placeholder image + it.intrinsicWidth > 10 && it.intrinsicHeight > 10 + } + } catch (_: Resources.NotFoundException) { + null + } +} diff --git a/tests/robotests/res/drawable/regulatory_info.png b/tests/robotests/res/drawable/regulatory_info.png deleted file mode 100644 index 65de26c0eb28b05d6d0d6903288e1bbbce409d18..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 159 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1SBVv2j2s6ii6yp7}lMWc?slj7I;J!Gca%q zgD@k*tT_@uLG}_)Usv`!oI*kxYUgfUzX24IEOCt}an8@pP0cG|a4t$sEJ;mKD9 + + + + + diff --git a/tests/spa_unit/res/drawable/regulatory_info_sku.xml b/tests/spa_unit/res/drawable/regulatory_info_sku.xml new file mode 100644 index 00000000000..634e55edafc --- /dev/null +++ b/tests/spa_unit/res/drawable/regulatory_info_sku.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/tests/spa_unit/res/drawable/regulatory_info_sku1_coo.xml b/tests/spa_unit/res/drawable/regulatory_info_sku1_coo.xml new file mode 100644 index 00000000000..7e6b9efc721 --- /dev/null +++ b/tests/spa_unit/res/drawable/regulatory_info_sku1_coo.xml @@ -0,0 +1,20 @@ + + + + + + diff --git a/tests/spa_unit/src/com/android/settings/deviceinfo/regulatory/RegulatoryInfoTest.kt b/tests/spa_unit/src/com/android/settings/deviceinfo/regulatory/RegulatoryInfoTest.kt new file mode 100644 index 00000000000..f1e18fc8023 --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/deviceinfo/regulatory/RegulatoryInfoTest.kt @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2023 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.deviceinfo.regulatory + +import android.content.Context +import android.graphics.drawable.Drawable +import android.os.SystemProperties +import androidx.annotation.DrawableRes +import androidx.core.graphics.drawable.toBitmap +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.dx.mockito.inline.extended.ExtendedMockito +import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn +import com.android.settings.deviceinfo.regulatory.RegulatoryInfo.KEY_COO +import com.android.settings.deviceinfo.regulatory.RegulatoryInfo.KEY_SKU +import com.android.settings.deviceinfo.regulatory.RegulatoryInfo.getRegulatoryInfo +import com.android.settings.tests.spa_unit.R +import com.google.common.truth.Truth.assertThat +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.MockitoSession +import org.mockito.Spy +import org.mockito.quality.Strictness + +@RunWith(AndroidJUnit4::class) +class RegulatoryInfoTest { + private lateinit var mockSession: MockitoSession + + @Spy + private val context: Context = ApplicationProvider.getApplicationContext() + + @Before + fun setUp() { + mockSession = ExtendedMockito.mockitoSession() + .initMocks(this) + .mockStatic(SystemProperties::class.java) + .strictness(Strictness.LENIENT) + .startMocking() + } + + @After + fun tearDown() { + mockSession.finishMocking() + } + + @Test + fun getRegulatoryInfo_noSkuProperty_shouldReturnDefaultLabel() { + doReturn("").`when` { SystemProperties.get(KEY_SKU) } + + val regulatoryInfo = context.getRegulatoryInfo() + + assertDrawableSameAs(regulatoryInfo, R.drawable.regulatory_info) + } + + @Test + fun getResourceId_noCooProperty_shouldReturnSkuLabel() { + doReturn("sku").`when` { SystemProperties.get(KEY_SKU) } + doReturn("").`when` { SystemProperties.get(KEY_COO) } + + val regulatoryInfo = context.getRegulatoryInfo() + + assertDrawableSameAs(regulatoryInfo, R.drawable.regulatory_info_sku) + } + + @Test + fun getResourceId_hasSkuAndCooProperties_shouldReturnCooLabel() { + doReturn("sku1").`when` { SystemProperties.get(KEY_SKU) } + doReturn("coo").`when` { SystemProperties.get(KEY_COO) } + + val regulatoryInfo = context.getRegulatoryInfo() + + assertDrawableSameAs(regulatoryInfo, R.drawable.regulatory_info_sku1_coo) + } + + @Test + fun getResourceId_noCorrespondingCooLabel_shouldReturnSkuLabel() { + doReturn("sku").`when` { SystemProperties.get(KEY_SKU) } + doReturn("unknown").`when` { SystemProperties.get(KEY_COO) } + + val regulatoryInfo = context.getRegulatoryInfo() + + assertDrawableSameAs(regulatoryInfo, R.drawable.regulatory_info_sku) + } + + private fun assertDrawableSameAs(drawable: Drawable?, @DrawableRes resId: Int) { + val expected = context.getDrawable(resId)!!.toBitmap() + assertThat(drawable!!.toBitmap().sameAs(expected)).isTrue() + } +} From 250f2727b0a02ea86285ec7b09bc80728f5ef041 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Thu, 27 Apr 2023 18:56:28 +0000 Subject: [PATCH 5/6] ChooseLockPattern: remove redundant setting of visible pattern default Remove the code that set LOCK_PATTERN_VISIBLE to true the first time a pattern was set, since LOCK_PATTERN_VISIBLE now defaults to true when unset (ag/22912136). The explicit defaulting to true was only needed before because the low-level default value was wrong. Bug: 270013005 Test: Set a pattern. Verified that Keyguard uses visible pattern. Disabled the "Make pattern visible" option in Settings. Verified that Keyguard doesn't use visible pattern. Change-Id: I63f29c68f9a508fee0ee2f03f2cca33317fb8a32 Merged-In: I63f29c68f9a508fee0ee2f03f2cca33317fb8a32 (cherry picked from commit 6c3de30086d37947d11dca146354dca33a935755) --- .../android/settings/password/ChooseLockPattern.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/com/android/settings/password/ChooseLockPattern.java b/src/com/android/settings/password/ChooseLockPattern.java index a2fd986dd0b..a5d04cc4715 100644 --- a/src/com/android/settings/password/ChooseLockPattern.java +++ b/src/com/android/settings/password/ChooseLockPattern.java @@ -872,7 +872,6 @@ public class ChooseLockPattern extends SettingsActivity { private LockscreenCredential mChosenPattern; private LockscreenCredential mCurrentCredential; - private boolean mLockVirgin; public void start(LockPatternUtils utils, boolean requestGatekeeperPassword, LockscreenCredential chosenPattern, LockscreenCredential currentCredential, @@ -884,8 +883,6 @@ public class ChooseLockPattern extends SettingsActivity { mChosenPattern = chosenPattern; mUserId = userId; - mLockVirgin = !mUtils.isPatternEverChosen(mUserId); - start(); } @@ -916,14 +913,5 @@ public class ChooseLockPattern extends SettingsActivity { } return Pair.create(success, result); } - - @Override - protected void finish(Intent resultData) { - if (mLockVirgin) { - mUtils.setVisiblePatternEnabled(true, mUserId); - } - - super.finish(resultData); - } } } From 513f38cb17400113e90396306306458dc650afbf Mon Sep 17 00:00:00 2001 From: SongFerng Wang Date: Tue, 11 Jul 2023 08:20:23 +0000 Subject: [PATCH 6/6] Revert "Revert "Update the wording for LE Audio"" This reverts commit c123b2e5a83848d9220a6a8fe560a3ebe1929199. Reason for revert: Since the phase 2 launch plan was moved to next timeline at b/289884263. Change-Id: I54ff20c0d9599da7f47e2254f721be6007a9204a --- res/values/strings.xml | 2 ++ .../bluetooth/BluetoothDetailsProfilesController.java | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/res/values/strings.xml b/res/values/strings.xml index 9cf4bad3a8b..04d9f17de4b 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1526,6 +1526,8 @@ Disconnect App? %1$s app will no longer connect to your %2$s + + Experimental. Improves audio quality. Forget device diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java index 00f18e8a87a..701967bfb5e 100644 --- a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java +++ b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java @@ -116,6 +116,10 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll pref.setTitle(profile.getNameResource(mCachedDevice.getDevice())); pref.setOnPreferenceClickListener(this); pref.setOrder(profile.getOrdinal()); + + if (profile instanceof LeAudioProfile) { + pref.setSummary(R.string.device_details_leaudio_toggle_summary); + } return pref; }