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.server.am;
18 
19 import static android.app.AppOpsManager.OP_NONE;
20 
21 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
22 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
23 
24 import android.app.Activity;
25 import android.app.BroadcastOptions;
26 import android.content.BroadcastReceiver;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.pm.PackageManager;
30 import android.content.pm.ResolveInfo;
31 import android.os.Binder;
32 import android.os.BugreportManager;
33 import android.os.BugreportParams;
34 import android.os.UserHandle;
35 import android.provider.Settings;
36 import android.text.TextUtils;
37 import android.util.ArraySet;
38 import android.util.Slog;
39 
40 import com.android.server.SystemConfig;
41 
42 import java.util.List;
43 
44 /**
45  * Static utility methods related to BugReportHandler.
46  */
47 public final class BugReportHandlerUtil {
48     private static final String TAG = TAG_WITH_CLASS_NAME ? "BugReportHandlerUtil" : TAG_AM;
49     private static final String SHELL_APP_PACKAGE = "com.android.shell";
50     private static final String INTENT_BUGREPORT_REQUESTED =
51             "com.android.internal.intent.action.BUGREPORT_REQUESTED";
52     private static final String INTENT_GET_BUGREPORT_HANDLER_RESPONSE =
53             "com.android.internal.intent.action.GET_BUGREPORT_HANDLER_RESPONSE";
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     static boolean isBugReportHandlerEnabled(Context context) {
62         return context.getResources().getBoolean(
63                 com.android.internal.R.bool.config_bugReportHandlerEnabled);
64     }
65 
66     /**
67      * Launches a bugreport-allowlisted app to handle a bugreport.
68      *
69      * <p>Allows a bug report handler app to take bugreports on the user's behalf. The handler can
70      * be predefined in the config, meant to be launched with the current foreground user. The user
71      * can override this with a different (or same) handler app on possibly a different
72      * user profile. This is useful for capturing bug reports from work profile, for instance.
73      * @param userContext Context of the current foreground user
74      * @return true if there is a bugreport-allow-listed app to handle a bugreport, or false
75      * otherwise
76      */
launchBugReportHandlerApp(Context userContext)77     static boolean launchBugReportHandlerApp(Context userContext) {
78         if (!isBugReportHandlerEnabled(userContext)) {
79             return false;
80         }
81 
82         String handlerApp = getCustomBugReportHandlerApp(userContext);
83         if (isShellApp(handlerApp)) {
84             return false;
85         }
86 
87         int handlerUser = getCustomBugReportHandlerUser(userContext);
88         if (!isValidBugReportHandlerApp(handlerApp)) {
89             handlerApp = getDefaultBugReportHandlerApp(userContext);
90             handlerUser = userContext.getUserId();
91         } else if (getBugReportHandlerAppReceivers(userContext, handlerApp, handlerUser)
92                 .isEmpty()) {
93             // It looks like the settings are outdated, reset outdated settings.
94             //
95             // i.e.
96             // If user chooses which profile and which bugreport-allowlisted app in that
97             // profile to handle a bugreport, then user remove the profile.
98             // === RESULT ===
99             // The chosen bugreport handler app is outdated because the profile is removed,
100             // so reset the chosen app and profile
101             handlerApp = getDefaultBugReportHandlerApp(userContext);
102             handlerUser = userContext.getUserId();
103             resetCustomBugreportHandlerAppAndUser(userContext);
104         }
105 
106         if (isShellApp(handlerApp) || !isValidBugReportHandlerApp(handlerApp)
107                 || getBugReportHandlerAppReceivers(userContext, handlerApp, handlerUser)
108                 .isEmpty()) {
109             return false;
110         }
111 
112         if (getBugReportHandlerAppResponseReceivers(userContext, handlerApp, handlerUser)
113                 .isEmpty()) {
114             // Just try to launch bugreport handler app to handle bugreport request
115             // because the bugreport handler app is old and not support to provide response to
116             // let BugReportHandlerUtil know it is available or not.
117             launchBugReportHandlerApp(userContext, handlerApp, handlerUser);
118             return true;
119         }
120 
121         Slog.i(TAG, "Getting response from bug report handler app: " + handlerApp);
122         Intent intent = new Intent(INTENT_GET_BUGREPORT_HANDLER_RESPONSE);
123         intent.setPackage(handlerApp);
124         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
125         intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
126         final long identity = Binder.clearCallingIdentity();
127         try {
128             // Handler app's BroadcastReceiver should call setResultCode(Activity.RESULT_OK) to
129             // let BugreportHandlerResponseBroadcastReceiver know the handler app is available.
130             userContext.sendOrderedBroadcastAsUser(intent,
131                     UserHandle.of(handlerUser),
132                     android.Manifest.permission.DUMP,
133                     OP_NONE, /* options= */ null,
134                     new BugreportHandlerResponseBroadcastReceiver(handlerApp, handlerUser),
135                     /* scheduler= */ null,
136                     Activity.RESULT_CANCELED,
137                     /* initialData= */ null,
138                     /* initialExtras= */ null);
139         } catch (RuntimeException e) {
140             Slog.e(TAG, "Error while trying to get response from bug report handler app.", e);
141             return false;
142         } finally {
143             Binder.restoreCallingIdentity(identity);
144         }
145         return true;
146     }
147 
launchBugReportHandlerApp(Context context, String handlerApp, int handlerUser)148     private static void launchBugReportHandlerApp(Context context, String handlerApp,
149             int handlerUser) {
150         Slog.i(TAG, "Launching bug report handler app: " + handlerApp);
151         Intent intent = new Intent(INTENT_BUGREPORT_REQUESTED);
152         intent.setPackage(handlerApp);
153         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
154         intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
155         // Send broadcast to the receiver while allowing starting activity from background
156         final BroadcastOptions options = BroadcastOptions.makeBasic();
157         options.setBackgroundActivityStartsAllowed(true);
158         final long identity = Binder.clearCallingIdentity();
159         try {
160             context.sendBroadcastAsUser(intent, UserHandle.of(handlerUser),
161                     android.Manifest.permission.DUMP,
162                     options.toBundle());
163         } catch (RuntimeException e) {
164             Slog.e(TAG, "Error while trying to launch bugreport handler app.", e);
165         } finally {
166             Binder.restoreCallingIdentity(identity);
167         }
168     }
169 
getCustomBugReportHandlerApp(Context context)170     private static String getCustomBugReportHandlerApp(Context context) {
171         // Get the package of custom bugreport handler app
172         return Settings.Secure.getStringForUser(context.getContentResolver(),
173                 Settings.Secure.CUSTOM_BUGREPORT_HANDLER_APP, context.getUserId());
174     }
175 
getCustomBugReportHandlerUser(Context context)176     private static int getCustomBugReportHandlerUser(Context context) {
177         return Settings.Secure.getIntForUser(context.getContentResolver(),
178                 Settings.Secure.CUSTOM_BUGREPORT_HANDLER_USER, UserHandle.USER_NULL,
179                 context.getUserId());
180     }
181 
isShellApp(String app)182     private static boolean isShellApp(String app) {
183         return SHELL_APP_PACKAGE.equals(app);
184     }
185 
isValidBugReportHandlerApp(String app)186     private static boolean isValidBugReportHandlerApp(String app) {
187         return !TextUtils.isEmpty(app) && isBugreportWhitelistedApp(app);
188     }
189 
isBugreportWhitelistedApp(String app)190     private static boolean isBugreportWhitelistedApp(String app) {
191         // Verify the app is bugreport-allowlisted
192         final ArraySet<String> whitelistedApps = SystemConfig.getInstance()
193                 .getBugreportWhitelistedPackages();
194         return whitelistedApps.contains(app);
195     }
196 
getBugReportHandlerAppReceivers(Context context, String handlerApp, int handlerUser)197     private static List<ResolveInfo> getBugReportHandlerAppReceivers(Context context,
198             String handlerApp, int handlerUser) {
199         // Use the app package and the user id to retrieve the receiver that can handle a
200         // broadcast of the intent.
201         Intent intent = new Intent(INTENT_BUGREPORT_REQUESTED);
202         intent.setPackage(handlerApp);
203         return context.getPackageManager()
204                 .queryBroadcastReceiversAsUser(intent, PackageManager.MATCH_SYSTEM_ONLY,
205                         handlerUser);
206     }
207 
getBugReportHandlerAppResponseReceivers(Context context, String handlerApp, int handlerUser)208     private static List<ResolveInfo> getBugReportHandlerAppResponseReceivers(Context context,
209             String handlerApp, int handlerUser) {
210         // Use the app package and the user id to retrieve the receiver that can provide response
211         Intent intent = new Intent(INTENT_GET_BUGREPORT_HANDLER_RESPONSE);
212         intent.setPackage(handlerApp);
213         return context.getPackageManager()
214                 .queryBroadcastReceiversAsUser(intent, PackageManager.MATCH_SYSTEM_ONLY,
215                         handlerUser);
216     }
217 
getDefaultBugReportHandlerApp(Context context)218     private static String getDefaultBugReportHandlerApp(Context context) {
219         return context.getResources().getString(
220                 com.android.internal.R.string.config_defaultBugReportHandlerApp);
221     }
222 
resetCustomBugreportHandlerAppAndUser(Context context)223     private static void resetCustomBugreportHandlerAppAndUser(Context context) {
224         final long identity = Binder.clearCallingIdentity();
225         try {
226             Settings.Secure.putString(context.getContentResolver(),
227                     Settings.Secure.CUSTOM_BUGREPORT_HANDLER_APP,
228                     getDefaultBugReportHandlerApp(context));
229             Settings.Secure.putInt(context.getContentResolver(),
230                     Settings.Secure.CUSTOM_BUGREPORT_HANDLER_USER, context.getUserId());
231         } finally {
232             Binder.restoreCallingIdentity(identity);
233         }
234     }
235 
236     private static class BugreportHandlerResponseBroadcastReceiver extends BroadcastReceiver {
237         private final String handlerApp;
238         private final int handlerUser;
239 
BugreportHandlerResponseBroadcastReceiver(String handlerApp, int handlerUser)240         BugreportHandlerResponseBroadcastReceiver(String handlerApp, int handlerUser) {
241             this.handlerApp = handlerApp;
242             this.handlerUser = handlerUser;
243         }
244 
245         @Override
onReceive(Context context, Intent intent)246         public void onReceive(Context context, Intent intent) {
247             if (getResultCode() == Activity.RESULT_OK) {
248                 // Try to launch bugreport handler app to handle bugreport request because the
249                 // bugreport handler app is available.
250                 launchBugReportHandlerApp(context, handlerApp, handlerUser);
251                 return;
252             }
253 
254             Slog.w(TAG, "Request bug report because no response from handler app.");
255             BugreportManager bugreportManager = context.getSystemService(BugreportManager.class);
256             bugreportManager.requestBugreport(
257                     new BugreportParams(BugreportParams.BUGREPORT_MODE_INTERACTIVE),
258                     /* shareTitle= */null, /* shareDescription= */ null);
259         }
260     }
261 }
262