• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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