1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.fuelgauge; 18 19 import static com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry.Action; 20 21 import android.app.Activity; 22 import android.app.settings.SettingsEnums; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.os.Bundle; 26 import android.os.UserHandle; 27 import android.text.TextUtils; 28 import android.util.Log; 29 import android.view.View; 30 import android.widget.CompoundButton; 31 import android.widget.CompoundButton.OnCheckedChangeListener; 32 33 import androidx.annotation.VisibleForTesting; 34 35 import com.android.settings.R; 36 import com.android.settings.core.SubSettingLauncher; 37 import com.android.settings.dashboard.DashboardFragment; 38 import com.android.settings.fuelgauge.batteryusage.AppOptModeSharedPreferencesUtils; 39 import com.android.settings.overlay.FeatureFactory; 40 import com.android.settings.widget.EntityHeaderController; 41 import com.android.settingslib.HelpUtils; 42 import com.android.settingslib.applications.AppUtils; 43 import com.android.settingslib.applications.ApplicationsState; 44 import com.android.settingslib.core.AbstractPreferenceController; 45 import com.android.settingslib.widget.FooterPreference; 46 import com.android.settingslib.widget.LayoutPreference; 47 import com.android.settingslib.widget.MainSwitchPreference; 48 import com.android.settingslib.widget.SelectorWithWidgetPreference; 49 50 import java.util.ArrayList; 51 import java.util.List; 52 import java.util.concurrent.ExecutorService; 53 import java.util.concurrent.Executors; 54 55 /** Allow background usage fragment for each app */ 56 public class PowerBackgroundUsageDetail extends DashboardFragment 57 implements SelectorWithWidgetPreference.OnClickListener, OnCheckedChangeListener { 58 private static final String TAG = "PowerBackgroundUsageDetail"; 59 60 public static final String EXTRA_UID = "extra_uid"; 61 public static final String EXTRA_PACKAGE_NAME = "extra_package_name"; 62 public static final String EXTRA_LABEL = "extra_label"; 63 public static final String EXTRA_POWER_USAGE_AMOUNT = "extra_power_usage_amount"; 64 public static final String EXTRA_ICON_ID = "extra_icon_id"; 65 private static final String KEY_PREF_HEADER = "header_view"; 66 private static final String KEY_PREF_UNRESTRICTED = "unrestricted_preference"; 67 private static final String KEY_PREF_OPTIMIZED = "optimized_preference"; 68 private static final String KEY_ALLOW_BACKGROUND_USAGE = "allow_background_usage"; 69 private static final String KEY_FOOTER_PREFERENCE = "app_usage_footer_preference"; 70 71 private final ExecutorService mExecutor = Executors.newSingleThreadExecutor(); 72 73 @VisibleForTesting LayoutPreference mHeaderPreference; 74 @VisibleForTesting ApplicationsState mState; 75 @VisibleForTesting ApplicationsState.AppEntry mAppEntry; 76 @VisibleForTesting BatteryOptimizeUtils mBatteryOptimizeUtils; 77 @VisibleForTesting SelectorWithWidgetPreference mOptimizePreference; 78 @VisibleForTesting SelectorWithWidgetPreference mUnrestrictedPreference; 79 @VisibleForTesting MainSwitchPreference mMainSwitchPreference; 80 @VisibleForTesting FooterPreference mFooterPreference; 81 @VisibleForTesting StringBuilder mLogStringBuilder; 82 83 @VisibleForTesting @BatteryOptimizeUtils.OptimizationMode 84 int mOptimizationMode = BatteryOptimizeUtils.MODE_UNKNOWN; 85 86 @Override onAttach(Activity activity)87 public void onAttach(Activity activity) { 88 super.onAttach(activity); 89 90 mState = ApplicationsState.getInstance(getActivity().getApplication()); 91 } 92 93 @Override onCreate(Bundle icicle)94 public void onCreate(Bundle icicle) { 95 super.onCreate(icicle); 96 97 final String packageName = getArguments().getString(EXTRA_PACKAGE_NAME); 98 onCreateBackgroundUsageState(packageName); 99 mHeaderPreference = findPreference(KEY_PREF_HEADER); 100 101 if (packageName != null) { 102 mAppEntry = mState.getEntry(packageName, UserHandle.myUserId()); 103 } 104 } 105 106 @Override onResume()107 public void onResume() { 108 super.onResume(); 109 initHeader(); 110 mOptimizationMode = mBatteryOptimizeUtils.getAppOptimizationMode(); 111 initFooter(); 112 mLogStringBuilder = new StringBuilder("onResume mode = ").append(mOptimizationMode); 113 } 114 115 @Override onPause()116 public void onPause() { 117 super.onPause(); 118 119 final int currentOptimizeMode = mBatteryOptimizeUtils.getAppOptimizationMode(); 120 mLogStringBuilder.append(", onPause mode = ").append(currentOptimizeMode); 121 logMetricCategory(currentOptimizeMode); 122 123 mExecutor.execute( 124 () -> { 125 if (currentOptimizeMode != mOptimizationMode) { 126 AppOptModeSharedPreferencesUtils.deleteAppOptimizationModeEventByUid( 127 getContext(), mBatteryOptimizeUtils.getUid()); 128 } 129 BatteryOptimizeLogUtils.writeLog( 130 getContext().getApplicationContext(), 131 Action.LEAVE, 132 BatteryOptimizeLogUtils.getPackageNameWithUserId( 133 mBatteryOptimizeUtils.getPackageName(), UserHandle.myUserId()), 134 mLogStringBuilder.toString()); 135 }); 136 Log.d(TAG, "Leave with mode: " + currentOptimizeMode); 137 } 138 139 @Override onRadioButtonClicked(SelectorWithWidgetPreference selected)140 public void onRadioButtonClicked(SelectorWithWidgetPreference selected) { 141 final String selectedKey = selected == null ? null : selected.getKey(); 142 updateSelectorPreferenceState(mUnrestrictedPreference, selectedKey); 143 updateSelectorPreferenceState(mOptimizePreference, selectedKey); 144 mBatteryOptimizeUtils.setAppUsageState(getSelectedPreference(), Action.APPLY); 145 } 146 147 @Override onCheckedChanged(CompoundButton buttonView, boolean isChecked)148 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 149 mMainSwitchPreference.setChecked(isChecked); 150 updateSelectorPreference(isChecked); 151 } 152 153 @Override getMetricsCategory()154 public int getMetricsCategory() { 155 return SettingsEnums.FUELGAUGE_POWER_USAGE_MANAGE_BACKGROUND; 156 } 157 158 @Override createPreferenceControllers(Context context)159 protected List<AbstractPreferenceController> createPreferenceControllers(Context context) { 160 final List<AbstractPreferenceController> controllers = new ArrayList<>(); 161 final Bundle bundle = getArguments(); 162 final int uid = bundle.getInt(EXTRA_UID, 0); 163 final String packageName = bundle.getString(EXTRA_PACKAGE_NAME); 164 165 controllers.add(new AllowBackgroundPreferenceController(context, uid, packageName)); 166 controllers.add(new OptimizedPreferenceController(context, uid, packageName)); 167 controllers.add(new UnrestrictedPreferenceController(context, uid, packageName)); 168 169 return controllers; 170 } 171 172 @Override getPreferenceScreenResId()173 protected int getPreferenceScreenResId() { 174 return R.xml.power_background_usage_detail; 175 } 176 177 @Override getLogTag()178 protected String getLogTag() { 179 return TAG; 180 } 181 182 @VisibleForTesting updateSelectorPreference(boolean isEnabled)183 void updateSelectorPreference(boolean isEnabled) { 184 mOptimizePreference.setEnabled(isEnabled); 185 mUnrestrictedPreference.setEnabled(isEnabled); 186 onRadioButtonClicked(isEnabled ? mOptimizePreference : null); 187 } 188 189 @VisibleForTesting getSelectedPreference()190 int getSelectedPreference() { 191 if (!mMainSwitchPreference.isChecked()) { 192 return BatteryOptimizeUtils.MODE_RESTRICTED; 193 } else if (mUnrestrictedPreference.isChecked()) { 194 return BatteryOptimizeUtils.MODE_UNRESTRICTED; 195 } else if (mOptimizePreference.isChecked()) { 196 return BatteryOptimizeUtils.MODE_OPTIMIZED; 197 } else { 198 return BatteryOptimizeUtils.MODE_UNKNOWN; 199 } 200 } 201 startPowerBackgroundUsageDetailPage(Context context, Bundle args)202 static void startPowerBackgroundUsageDetailPage(Context context, Bundle args) { 203 new SubSettingLauncher(context) 204 .setDestination(PowerBackgroundUsageDetail.class.getName()) 205 .setArguments(args) 206 .setSourceMetricsCategory(SettingsEnums.FUELGAUGE_POWER_USAGE_MANAGE_BACKGROUND) 207 .launch(); 208 } 209 210 @VisibleForTesting initHeader()211 void initHeader() { 212 final View appSnippet = mHeaderPreference.findViewById(R.id.entity_header); 213 final Activity context = getActivity(); 214 final Bundle bundle = getArguments(); 215 EntityHeaderController controller = 216 EntityHeaderController.newInstance(context, this, appSnippet) 217 .setButtonActions( 218 EntityHeaderController.ActionType.ACTION_NONE, 219 EntityHeaderController.ActionType.ACTION_NONE); 220 221 if (mAppEntry == null) { 222 controller.setLabel(bundle.getString(EXTRA_LABEL)); 223 224 final int iconId = bundle.getInt(EXTRA_ICON_ID, 0); 225 if (iconId == 0) { 226 controller.setIcon(context.getPackageManager().getDefaultActivityIcon()); 227 } else { 228 controller.setIcon(context.getDrawable(bundle.getInt(EXTRA_ICON_ID))); 229 } 230 } else { 231 mState.ensureIcon(mAppEntry); 232 controller.setLabel(mAppEntry); 233 controller.setIcon(mAppEntry); 234 controller.setIsInstantApp(AppUtils.isInstant(mAppEntry.info)); 235 } 236 237 controller.done(true /* rebindActions */); 238 } 239 240 @VisibleForTesting initFooter()241 void initFooter() { 242 final String stateString; 243 final String footerString; 244 final Context context = getContext(); 245 246 if (mBatteryOptimizeUtils.isDisabledForOptimizeModeOnly()) { 247 // Present optimized only string when the package name is invalid. 248 stateString = context.getString(R.string.manager_battery_usage_optimized_only); 249 footerString = 250 context.getString(R.string.manager_battery_usage_footer_limited, stateString); 251 } else if (mBatteryOptimizeUtils.isSystemOrDefaultApp()) { 252 // Present unrestricted only string when the package is system or default active app. 253 stateString = context.getString(R.string.manager_battery_usage_unrestricted_only); 254 footerString = 255 context.getString(R.string.manager_battery_usage_footer_limited, stateString); 256 } else { 257 // Present default string to normal app. 258 footerString = context.getString(R.string.manager_battery_usage_footer); 259 } 260 mFooterPreference.setTitle(footerString); 261 final Intent helpIntent = 262 HelpUtils.getHelpIntent( 263 context, 264 context.getString(R.string.help_url_app_usage_settings), 265 /* backupContext= */ ""); 266 if (helpIntent != null) { 267 mFooterPreference.setLearnMoreAction( 268 v -> startActivityForResult(helpIntent, /* requestCode= */ 0)); 269 mFooterPreference.setLearnMoreText( 270 context.getString(R.string.manager_battery_usage_link_a11y)); 271 } 272 } 273 onCreateBackgroundUsageState(String packageName)274 private void onCreateBackgroundUsageState(String packageName) { 275 mOptimizePreference = findPreference(KEY_PREF_OPTIMIZED); 276 mUnrestrictedPreference = findPreference(KEY_PREF_UNRESTRICTED); 277 mMainSwitchPreference = findPreference(KEY_ALLOW_BACKGROUND_USAGE); 278 mFooterPreference = findPreference(KEY_FOOTER_PREFERENCE); 279 280 mOptimizePreference.setOnClickListener(this); 281 mUnrestrictedPreference.setOnClickListener(this); 282 mMainSwitchPreference.addOnSwitchChangeListener(this); 283 284 mBatteryOptimizeUtils = 285 new BatteryOptimizeUtils( 286 getContext(), getArguments().getInt(EXTRA_UID), packageName); 287 } 288 updateSelectorPreferenceState( SelectorWithWidgetPreference preference, String selectedKey)289 private void updateSelectorPreferenceState( 290 SelectorWithWidgetPreference preference, String selectedKey) { 291 preference.setChecked(TextUtils.equals(selectedKey, preference.getKey())); 292 } 293 logMetricCategory(int currentOptimizeMode)294 private void logMetricCategory(int currentOptimizeMode) { 295 if (currentOptimizeMode == mOptimizationMode) { 296 return; 297 } 298 int metricCategory = 0; 299 switch (currentOptimizeMode) { 300 case BatteryOptimizeUtils.MODE_UNRESTRICTED: 301 metricCategory = SettingsEnums.ACTION_APP_BATTERY_USAGE_UNRESTRICTED; 302 break; 303 case BatteryOptimizeUtils.MODE_OPTIMIZED: 304 metricCategory = SettingsEnums.ACTION_APP_BATTERY_USAGE_OPTIMIZED; 305 break; 306 case BatteryOptimizeUtils.MODE_RESTRICTED: 307 metricCategory = SettingsEnums.ACTION_APP_BATTERY_USAGE_RESTRICTED; 308 break; 309 } 310 if (metricCategory == 0) { 311 return; 312 } 313 int finalMetricCategory = metricCategory; 314 mExecutor.execute( 315 () -> { 316 String packageName = 317 BatteryUtils.getLoggingPackageName( 318 getContext(), mBatteryOptimizeUtils.getPackageName()); 319 FeatureFactory.getFeatureFactory() 320 .getMetricsFeatureProvider() 321 .action( 322 /* attribution */ SettingsEnums 323 .LEAVE_POWER_USAGE_MANAGE_BACKGROUND, 324 /* action */ finalMetricCategory, 325 /* pageId */ SettingsEnums 326 .FUELGAUGE_POWER_USAGE_MANAGE_BACKGROUND, 327 packageName, 328 getArguments().getInt(EXTRA_POWER_USAGE_AMOUNT)); 329 }); 330 } 331 } 332