1 /* 2 * Copyright (C) 2022 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.car.settings.applications.performance; 18 19 import static android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS; 20 21 import android.car.Car; 22 import android.car.drivingstate.CarUxRestrictions; 23 import android.car.watchdog.CarWatchdogManager; 24 import android.car.watchdog.PackageKillableState; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.pm.ApplicationInfo; 28 import android.content.pm.PackageManager; 29 import android.graphics.drawable.Drawable; 30 import android.net.Uri; 31 import android.os.Handler; 32 import android.os.Looper; 33 import android.os.UserHandle; 34 import android.util.AttributeSet; 35 36 import androidx.annotation.Nullable; 37 import androidx.annotation.VisibleForTesting; 38 import androidx.preference.PreferenceGroup; 39 40 import com.android.car.settings.R; 41 import com.android.car.settings.common.FragmentController; 42 import com.android.car.settings.common.Logger; 43 import com.android.car.settings.common.PreferenceController; 44 import com.android.car.ui.preference.CarUiTwoActionTextPreference; 45 import com.android.settingslib.Utils; 46 47 import java.io.File; 48 import java.util.List; 49 50 /** 51 * Displays the list of apps which have been disabled due to resource overuse by CarWatchdogService. 52 * 53 * <p>When a user taps the app, the app's detail setting page is shown. On the other hand, if a 54 * user presses the "Prioritize app" button, they are shown a dialog which allows them to prioritize 55 * the app, meaning the app can run in the background once again. 56 */ 57 public final class PerfImpactingAppsPreferenceController extends 58 PreferenceController<PreferenceGroup> { 59 private static final Logger LOG = new Logger(PerfImpactingAppsPreferenceController.class); 60 61 @VisibleForTesting 62 static final String TURN_ON_PRIORITIZE_APP_PERFORMANCE_DIALOG_TAG = 63 "com.android.car.settings.applications.performance.PrioritizeAppPerformanceDialogTag"; 64 65 @Nullable 66 private Car mCar; 67 @Nullable 68 private CarWatchdogManager mCarWatchdogManager; 69 @Nullable 70 private List<ApplicationInfo> mEntries; 71 PerfImpactingAppsPreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions)72 public PerfImpactingAppsPreferenceController(Context context, String preferenceKey, 73 FragmentController fragmentController, CarUxRestrictions uxRestrictions) { 74 super(context, preferenceKey, fragmentController, uxRestrictions); 75 } 76 77 @Override getPreferenceType()78 protected Class<PreferenceGroup> getPreferenceType() { 79 return PreferenceGroup.class; 80 } 81 82 @Override onCreateInternal()83 protected void onCreateInternal() { 84 connectToCar(); 85 updateEntries(); 86 } 87 88 @Override onDestroyInternal()89 protected void onDestroyInternal() { 90 if (mCar != null) { 91 mCar.disconnect(); 92 mCar = null; 93 } 94 } 95 96 @Override updateState(PreferenceGroup preference)97 protected void updateState(PreferenceGroup preference) { 98 if (mEntries == null) { 99 return; 100 } 101 preference.removeAll(); 102 for (int i = 0; i < mEntries.size(); i++) { 103 ApplicationInfo entry = mEntries.get(i); 104 PerformanceImpactingAppPreference appPreference = 105 new PerformanceImpactingAppPreference(getContext(), entry); 106 setOnPreferenceClickListeners(appPreference, entry); 107 preference.addPreference(appPreference); 108 } 109 } 110 setOnPreferenceClickListeners(PerformanceImpactingAppPreference preference, ApplicationInfo info)111 private void setOnPreferenceClickListeners(PerformanceImpactingAppPreference preference, 112 ApplicationInfo info) { 113 String packageName = info.packageName; 114 UserHandle userHandle = UserHandle.getUserHandleForUid(info.uid); 115 preference.setOnPreferenceClickListener(p -> { 116 Intent settingsIntent = new Intent(ACTION_APPLICATION_DETAILS_SETTINGS) 117 .setData(Uri.parse("package:" + packageName)); 118 getContext().startActivity(settingsIntent); 119 return true; 120 }); 121 preference.setOnSecondaryActionClickListener( 122 () -> PerfImpactingAppsUtils.showPrioritizeAppConfirmationDialog(getContext(), 123 getFragmentController(), 124 args -> prioritizeApp(packageName, userHandle), 125 TURN_ON_PRIORITIZE_APP_PERFORMANCE_DIALOG_TAG)); 126 } 127 prioritizeApp(String packageName, UserHandle userHandle)128 private void prioritizeApp(String packageName, UserHandle userHandle) { 129 if (mCarWatchdogManager == null) { 130 LOG.e("CarWatchdogManager is null. Could not prioritize '" + packageName + "'."); 131 connectToCar(); 132 return; 133 } 134 int killableState = PerfImpactingAppsUtils.getKillableState(packageName, userHandle, 135 mCarWatchdogManager); 136 if (killableState == PackageKillableState.KILLABLE_STATE_NEVER) { 137 LOG.wtf("Package '" + packageName + "' for user " + userHandle.getIdentifier() 138 + " is disabled for resource overuse but has KILLABLE_STATE_NEVER"); 139 // Given wtf might not kill the process, we enable package in order to 140 // remove from resource overuse disabled package list. 141 PackageManager packageManager = getContext().getPackageManager(); 142 packageManager.setApplicationEnabledSetting(packageName, 143 PackageManager.COMPONENT_ENABLED_STATE_ENABLED, /* flags= */ 0); 144 new Handler(Looper.getMainLooper()).postDelayed(this::updateEntries, 145 /* delayMillis= */ 1000); 146 } else { 147 mCarWatchdogManager.setKillablePackageAsUser(packageName, userHandle, 148 /* isKillable= */ false); 149 } 150 updateEntries(); 151 } 152 updateEntries()153 private void updateEntries() { 154 mEntries = PerfImpactingAppsUtils.getDisabledAppInfos(getContext()); 155 refreshUi(); 156 } 157 connectToCar()158 private void connectToCar() { 159 if (mCar != null && mCar.isConnected()) { 160 mCar.disconnect(); 161 mCar = null; 162 } 163 mCar = Car.createCar(getContext(), /* handler= */ null, Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER, 164 /* statusChangeListener= */ (car, isReady) -> mCarWatchdogManager = isReady 165 ? (CarWatchdogManager) car.getCarManager(Car.CAR_WATCHDOG_SERVICE) 166 : null); 167 } 168 169 static class PerformanceImpactingAppPreference extends CarUiTwoActionTextPreference { 170 PerformanceImpactingAppPreference(Context context, ApplicationInfo info)171 PerformanceImpactingAppPreference(Context context, ApplicationInfo info) { 172 super(context); 173 174 boolean apkExists = new File(info.sourceDir).exists(); 175 setKey(info.packageName + "|" + info.uid); 176 setTitle(getLabel(context, info, apkExists)); 177 setIcon(getIconDrawable(context, info, apkExists)); 178 setPersistent(false); 179 setSecondaryActionText(R.string.performance_impacting_apps_button_label); 180 } 181 182 @Override init(@ullable AttributeSet attrs)183 protected void init(@Nullable AttributeSet attrs) { 184 super.init(attrs); 185 186 setLayoutResourceInternal(R.layout.car_ui_preference_two_action_text_borderless); 187 } 188 getLabel(Context context, ApplicationInfo info, boolean apkExists)189 private String getLabel(Context context, ApplicationInfo info, boolean apkExists) { 190 if (!apkExists) { 191 return info.packageName; 192 } 193 CharSequence label = info.loadLabel(context.getPackageManager()); 194 return label != null ? label.toString() : info.packageName; 195 } 196 getIconDrawable(Context context, ApplicationInfo info, boolean apkExists)197 private Drawable getIconDrawable(Context context, ApplicationInfo info, 198 boolean apkExists) { 199 if (apkExists) { 200 return Utils.getBadgedIcon(context, info); 201 } 202 return context.getDrawable( 203 com.android.internal.R.drawable.sym_app_on_sd_unavailable_icon); 204 } 205 } 206 } 207