1 /*
2  * Copyright (C) 2019 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.bugreporthandler;
18 
19 import android.app.ActivityManager;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.pm.ApplicationInfo;
23 import android.content.pm.PackageManager;
24 import android.content.pm.ResolveInfo;
25 import android.content.pm.UserInfo;
26 import android.os.RemoteException;
27 import android.os.UserHandle;
28 import android.os.UserManager;
29 import android.provider.Settings;
30 import android.text.TextUtils;
31 import android.util.Log;
32 import android.util.Pair;
33 import android.widget.Toast;
34 
35 import com.android.settings.R;
36 
37 import java.util.ArrayList;
38 import java.util.Collections;
39 import java.util.List;
40 import java.util.stream.Collectors;
41 
42 /**
43  * Utility methods related to BugReportHandler.
44  */
45 public class BugReportHandlerUtil {
46     private static final String TAG = "BugReportHandlerUtil";
47     private static final String INTENT_BUGREPORT_REQUESTED =
48             "com.android.internal.intent.action.BUGREPORT_REQUESTED";
49 
50     public static final String SHELL_APP_PACKAGE = "com.android.shell";
51 
BugReportHandlerUtil()52     public BugReportHandlerUtil() {
53     }
54 
55     /**
56      * Check is BugReportHandler enabled on the device.
57      *
58      * @param context Context
59      * @return true if BugReportHandler is enabled, or false otherwise
60      */
isBugReportHandlerEnabled(Context context)61     public boolean isBugReportHandlerEnabled(Context context) {
62         return context.getResources().getBoolean(
63                 com.android.internal.R.bool.config_bugReportHandlerEnabled);
64     }
65 
66     /**
67      * Fetch the package name currently used as bug report handler app and the user.
68      *
69      * @param context Context
70      * @return a pair of two values, first one is the current bug report handler app, second is
71      * the user.
72      */
getCurrentBugReportHandlerAppAndUser(Context context)73     public Pair<String, Integer> getCurrentBugReportHandlerAppAndUser(Context context) {
74 
75         String handlerApp = getCustomBugReportHandlerApp(context);
76         int handlerUser = getCustomBugReportHandlerUser(context);
77 
78         boolean needToResetOutdatedSettings = false;
79         if (!isBugreportWhitelistedApp(handlerApp)) {
80             handlerApp = getDefaultBugReportHandlerApp(context);
81             handlerUser = UserHandle.USER_SYSTEM;
82         } else if (getBugReportHandlerAppReceivers(context, handlerApp, handlerUser).isEmpty()) {
83             // It looks like the settings are outdated, need to reset outdated settings.
84             //
85             // i.e.
86             // If user chooses which profile and which bugreport-whitelisted app in that
87             // profile to handle a bugreport, then user remove the profile.
88             // === RESULT ===
89             // The chosen bugreport handler app is outdated because the profile is removed,
90             // so need to reset outdated settings
91             handlerApp = getDefaultBugReportHandlerApp(context);
92             handlerUser = UserHandle.USER_SYSTEM;
93             needToResetOutdatedSettings = true;
94         }
95 
96         if (!isBugreportWhitelistedApp(handlerApp)
97                 || getBugReportHandlerAppReceivers(context, handlerApp, handlerUser).isEmpty()) {
98             // It looks like current handler app may be too old and doesn't support to handle a
99             // bugreport, so change to let shell to handle a bugreport and need to reset
100             // settings.
101             handlerApp = SHELL_APP_PACKAGE;
102             handlerUser = UserHandle.USER_SYSTEM;
103             needToResetOutdatedSettings = true;
104         }
105 
106         if (needToResetOutdatedSettings) {
107             setBugreportHandlerAppAndUser(context, handlerApp, handlerUser);
108         }
109 
110         return Pair.create(handlerApp, handlerUser);
111     }
112 
getCustomBugReportHandlerApp(Context context)113     private String getCustomBugReportHandlerApp(Context context) {
114         // Get the package of custom bugreport handler app
115         return Settings.Global.getString(context.getContentResolver(),
116                 Settings.Global.CUSTOM_BUGREPORT_HANDLER_APP);
117     }
118 
getCustomBugReportHandlerUser(Context context)119     private int getCustomBugReportHandlerUser(Context context) {
120         return Settings.Global.getInt(context.getContentResolver(),
121                 Settings.Global.CUSTOM_BUGREPORT_HANDLER_USER, UserHandle.USER_NULL);
122     }
123 
getDefaultBugReportHandlerApp(Context context)124     private String getDefaultBugReportHandlerApp(Context context) {
125         return context.getResources().getString(
126                 com.android.internal.R.string.config_defaultBugReportHandlerApp);
127     }
128 
129     /**
130      * Change current bug report handler app and user.
131      *
132      * @param context     Context
133      * @param handlerApp  the package name of the handler app
134      * @param handlerUser the id of the handler user
135      * @return whether the change succeeded
136      */
setCurrentBugReportHandlerAppAndUser(Context context, String handlerApp, int handlerUser)137     public boolean setCurrentBugReportHandlerAppAndUser(Context context, String handlerApp,
138             int handlerUser) {
139         if (!isBugreportWhitelistedApp(handlerApp)) {
140             return false;
141         } else if (getBugReportHandlerAppReceivers(context, handlerApp, handlerUser).isEmpty()) {
142             return false;
143         }
144         setBugreportHandlerAppAndUser(context, handlerApp, handlerUser);
145         return true;
146     }
147 
148     /**
149      * Fetches ApplicationInfo objects and user ids for all currently valid BugReportHandler.
150      * A BugReportHandler is considered valid if it can receive BUGREPORT_REQUESTED intent.
151      *
152      * @param context Context
153      * @return pair objects for all currently valid BugReportHandler packages and user id
154      */
getValidBugReportHandlerInfos(Context context)155     public List<Pair<ApplicationInfo, Integer>> getValidBugReportHandlerInfos(Context context) {
156         final List<Pair<ApplicationInfo, Integer>> validBugReportHandlerApplicationInfos =
157                 new ArrayList<>();
158         List<String> bugreportWhitelistedPackages;
159         try {
160             bugreportWhitelistedPackages =
161                     ActivityManager.getService().getBugreportWhitelistedPackages();
162         } catch (RemoteException e) {
163             Log.e(TAG, "Failed to get bugreportWhitelistedPackages:", e);
164             return validBugReportHandlerApplicationInfos;
165         }
166 
167         // Add "Shell with system user" as System default preference on top of screen
168         if (bugreportWhitelistedPackages.contains(SHELL_APP_PACKAGE)
169                 && !getBugReportHandlerAppReceivers(context, SHELL_APP_PACKAGE,
170                 UserHandle.USER_SYSTEM).isEmpty()) {
171             try {
172                 validBugReportHandlerApplicationInfos.add(
173                         Pair.create(
174                                 context.getPackageManager().getApplicationInfo(SHELL_APP_PACKAGE,
175                                         PackageManager.MATCH_ANY_USER), UserHandle.USER_SYSTEM)
176                 );
177             } catch (PackageManager.NameNotFoundException e) {
178             }
179         }
180 
181         final UserManager userManager = context.getSystemService(UserManager.class);
182         final List<UserInfo> profileList = userManager.getProfiles(UserHandle.getCallingUserId());
183         // Only add non-Shell app as normal preference
184         final List<String> nonShellPackageList = bugreportWhitelistedPackages.stream()
185                 .filter(pkg -> !SHELL_APP_PACKAGE.equals(pkg)).collect(Collectors.toList());
186         Collections.sort(nonShellPackageList);
187         for (String pkg : nonShellPackageList) {
188             for (UserInfo profile : profileList) {
189                 final int userId = profile.getUserHandle().getIdentifier();
190                 if (getBugReportHandlerAppReceivers(context, pkg, userId).isEmpty()) {
191                     continue;
192                 }
193                 try {
194                     validBugReportHandlerApplicationInfos.add(
195                             Pair.create(context.getPackageManager()
196                                             .getApplicationInfo(pkg, PackageManager.MATCH_ANY_USER),
197                                     userId));
198                 } catch (PackageManager.NameNotFoundException e) {
199                 }
200             }
201         }
202         return validBugReportHandlerApplicationInfos;
203     }
204 
isBugreportWhitelistedApp(String app)205     private boolean isBugreportWhitelistedApp(String app) {
206         // Verify the app is bugreport-whitelisted
207         if (TextUtils.isEmpty(app)) {
208             return false;
209         }
210         try {
211             return ActivityManager.getService().getBugreportWhitelistedPackages().contains(app);
212         } catch (RemoteException e) {
213             Log.e(TAG, "Failed to get bugreportWhitelistedPackages:", e);
214             return false;
215         }
216     }
217 
getBugReportHandlerAppReceivers(Context context, String handlerApp, int handlerUser)218     private List<ResolveInfo> getBugReportHandlerAppReceivers(Context context, String handlerApp,
219             int handlerUser) {
220         // Use the app package and the user id to retrieve the receiver that can handle a
221         // broadcast of the intent.
222         final Intent intent = new Intent(INTENT_BUGREPORT_REQUESTED);
223         intent.setPackage(handlerApp);
224         return context.getPackageManager()
225                 .queryBroadcastReceiversAsUser(intent, PackageManager.MATCH_SYSTEM_ONLY,
226                         handlerUser);
227     }
228 
setBugreportHandlerAppAndUser(Context context, String handlerApp, int handlerUser)229     private void setBugreportHandlerAppAndUser(Context context, String handlerApp,
230             int handlerUser) {
231         Settings.Global.putString(context.getContentResolver(),
232                 Settings.Global.CUSTOM_BUGREPORT_HANDLER_APP,
233                 handlerApp);
234         Settings.Global.putInt(context.getContentResolver(),
235                 Settings.Global.CUSTOM_BUGREPORT_HANDLER_USER, handlerUser);
236     }
237 
238     /**
239      * Show a toast to explain the chosen bug report handler can no longer be chosen.
240      */
showInvalidChoiceToast(Context context)241     public void showInvalidChoiceToast(Context context) {
242         final Toast toast = Toast.makeText(context,
243                 R.string.select_invalid_bug_report_handler_toast_text, Toast.LENGTH_SHORT);
244         toast.show();
245     }
246 }
247