• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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.devicepolicy;
18 
19 import static android.app.admin.DevicePolicyManager.ACTION_BUGREPORT_SHARING_ACCEPTED;
20 import static android.app.admin.DevicePolicyManager.ACTION_BUGREPORT_SHARING_DECLINED;
21 import static android.app.admin.DevicePolicyManager.ACTION_REMOTE_BUGREPORT_DISPATCH;
22 import static android.app.admin.DevicePolicyManager.EXTRA_BUGREPORT_NOTIFICATION_TYPE;
23 import static android.app.admin.DevicePolicyManager.EXTRA_REMOTE_BUGREPORT_HASH;
24 import static android.app.admin.DevicePolicyManager.NOTIFICATION_BUGREPORT_ACCEPTED_NOT_FINISHED;
25 import static android.app.admin.DevicePolicyManager.NOTIFICATION_BUGREPORT_FINISHED_NOT_ACCEPTED;
26 import static android.app.admin.DevicePolicyManager.NOTIFICATION_BUGREPORT_STARTED;
27 
28 import static com.android.server.devicepolicy.DevicePolicyManagerService.LOG_TAG;
29 
30 import android.annotation.IntDef;
31 import android.app.Notification;
32 import android.app.PendingIntent;
33 import android.app.admin.DeviceAdminReceiver;
34 import android.app.admin.DevicePolicyManager;
35 import android.content.BroadcastReceiver;
36 import android.content.Context;
37 import android.content.Intent;
38 import android.content.IntentFilter;
39 import android.content.pm.ActivityInfo;
40 import android.content.pm.PackageManager;
41 import android.net.Uri;
42 import android.os.Bundle;
43 import android.os.Handler;
44 import android.os.RemoteException;
45 import android.os.UserHandle;
46 import android.provider.Settings;
47 import android.text.format.DateUtils;
48 import android.util.Pair;
49 
50 import com.android.internal.R;
51 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
52 import com.android.internal.notification.SystemNotificationChannels;
53 import com.android.server.utils.Slogf;
54 
55 import java.io.FileNotFoundException;
56 import java.lang.annotation.Retention;
57 import java.lang.annotation.RetentionPolicy;
58 import java.security.SecureRandom;
59 import java.util.concurrent.atomic.AtomicBoolean;
60 import java.util.concurrent.atomic.AtomicLong;
61 
62 /**
63  * Class managing bugreport collection upon device owner's request.
64  */
65 public class RemoteBugreportManager {
66 
67     static final String BUGREPORT_MIMETYPE = "application/vnd.android.bugreport";
68 
69     private static final long REMOTE_BUGREPORT_TIMEOUT_MILLIS = 10 * DateUtils.MINUTE_IN_MILLIS;
70     private static final String CTL_STOP = "ctl.stop";
71     private static final String REMOTE_BUGREPORT_SERVICE = "bugreportd";
72     private static final int NOTIFICATION_ID = SystemMessage.NOTE_REMOTE_BUGREPORT;
73 
74     @Retention(RetentionPolicy.SOURCE)
75     @IntDef({
76             NOTIFICATION_BUGREPORT_STARTED,
77             NOTIFICATION_BUGREPORT_ACCEPTED_NOT_FINISHED,
78             NOTIFICATION_BUGREPORT_FINISHED_NOT_ACCEPTED
79     })
80     @interface RemoteBugreportNotificationType {}
81     private final DevicePolicyManagerService mService;
82     private final DevicePolicyManagerService.Injector mInjector;
83 
84     private final SecureRandom mRng = new SecureRandom();
85 
86     private final AtomicLong mRemoteBugreportNonce = new AtomicLong();
87     private final AtomicBoolean mRemoteBugreportServiceIsActive = new AtomicBoolean();
88     private final AtomicBoolean mRemoteBugreportSharingAccepted = new AtomicBoolean();
89     private final Context mContext;
90 
91     private final Handler mHandler;
92 
93     private final Runnable mRemoteBugreportTimeoutRunnable = () -> {
94         if (mRemoteBugreportServiceIsActive.get()) {
95             onBugreportFailed();
96         }
97     };
98 
99     private final BroadcastReceiver mRemoteBugreportFinishedReceiver = new BroadcastReceiver() {
100         @Override
101         public void onReceive(Context context, Intent intent) {
102             if (ACTION_REMOTE_BUGREPORT_DISPATCH.equals(intent.getAction())
103                     && mRemoteBugreportServiceIsActive.get()) {
104                 onBugreportFinished(intent);
105             }
106         }
107     };
108 
109     private final BroadcastReceiver mRemoteBugreportConsentReceiver = new BroadcastReceiver() {
110 
111         @Override
112         public void onReceive(Context context, Intent intent) {
113             final String action = intent.getAction();
114             mInjector.getNotificationManager().cancel(LOG_TAG, NOTIFICATION_ID);
115             if (ACTION_BUGREPORT_SHARING_ACCEPTED.equals(action)) {
116                 onBugreportSharingAccepted();
117             } else if (ACTION_BUGREPORT_SHARING_DECLINED.equals(action)) {
118                 onBugreportSharingDeclined();
119             }
120             mContext.unregisterReceiver(mRemoteBugreportConsentReceiver);
121         }
122     };
123 
RemoteBugreportManager( DevicePolicyManagerService service, DevicePolicyManagerService.Injector injector)124     public RemoteBugreportManager(
125             DevicePolicyManagerService service, DevicePolicyManagerService.Injector injector) {
126         mService = service;
127         mInjector = injector;
128         mContext = service.mContext;
129         mHandler = service.mHandler;
130     }
131 
buildNotification(@emoteBugreportNotificationType int type)132     private Notification buildNotification(@RemoteBugreportNotificationType int type) {
133         final Intent dialogIntent = new Intent(Settings.ACTION_SHOW_REMOTE_BUGREPORT_DIALOG);
134         dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
135         dialogIntent.putExtra(EXTRA_BUGREPORT_NOTIFICATION_TYPE, type);
136 
137         // Fill the component explicitly to prevent the PendingIntent from being intercepted
138         // and fired with crafted target. b/155183624
139         final ActivityInfo targetInfo = dialogIntent.resolveActivityInfo(
140                 mContext.getPackageManager(), PackageManager.MATCH_SYSTEM_ONLY);
141         if (targetInfo != null) {
142             dialogIntent.setComponent(targetInfo.getComponentName());
143         } else {
144             Slogf.wtf(LOG_TAG, "Failed to resolve intent for remote bugreport dialog");
145         }
146 
147         // Simple notification clicks are immutable
148         final PendingIntent pendingDialogIntent = PendingIntent.getActivityAsUser(mContext, type,
149                 dialogIntent, PendingIntent.FLAG_IMMUTABLE, null, UserHandle.CURRENT);
150 
151         final Notification.Builder builder =
152                 new Notification.Builder(mContext, SystemNotificationChannels.DEVICE_ADMIN)
153                         .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
154                         .setOngoing(true)
155                         .setLocalOnly(true)
156                         .setContentIntent(pendingDialogIntent)
157                         .setColor(mContext.getColor(
158                                 com.android.internal.R.color.system_notification_accent_color))
159                         .extend(new Notification.TvExtender());
160 
161         if (type == NOTIFICATION_BUGREPORT_ACCEPTED_NOT_FINISHED) {
162             builder.setContentTitle(mContext.getString(
163                         R.string.sharing_remote_bugreport_notification_title))
164                     .setProgress(0, 0, true);
165         } else if (type == NOTIFICATION_BUGREPORT_STARTED) {
166             builder.setContentTitle(mContext.getString(
167                         R.string.taking_remote_bugreport_notification_title))
168                     .setProgress(0, 0, true);
169         } else if (type == NOTIFICATION_BUGREPORT_FINISHED_NOT_ACCEPTED) {
170             // Simple notification action button clicks are immutable
171             final PendingIntent pendingIntentAccept = PendingIntent.getBroadcast(mContext,
172                     NOTIFICATION_ID, new Intent(ACTION_BUGREPORT_SHARING_ACCEPTED),
173                     PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
174             // Simple notification action button clicks are immutable
175             final PendingIntent pendingIntentDecline = PendingIntent.getBroadcast(mContext,
176                     NOTIFICATION_ID, new Intent(ACTION_BUGREPORT_SHARING_DECLINED),
177                     PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
178             builder.addAction(new Notification.Action.Builder(null /* icon */, mContext.getString(
179                         R.string.decline_remote_bugreport_action), pendingIntentDecline).build())
180                     .addAction(new Notification.Action.Builder(null /* icon */, mContext.getString(
181                         R.string.share_remote_bugreport_action), pendingIntentAccept).build())
182                     .setContentTitle(mContext.getString(
183                         R.string.share_remote_bugreport_notification_title))
184                     .setContentText(mContext.getString(
185                         R.string.share_remote_bugreport_notification_message_finished))
186                     .setStyle(new Notification.BigTextStyle().bigText(mContext.getString(
187                         R.string.share_remote_bugreport_notification_message_finished)));
188         }
189 
190         return builder.build();
191     }
192 
193     /**
194      * Initiates bugreport collection.
195      * @return whether collection was initiated successfully.
196      */
requestBugreport()197     public boolean requestBugreport() {
198         if (mRemoteBugreportServiceIsActive.get()
199                 || (mService.getDeviceOwnerRemoteBugreportUriAndHash() != null)) {
200             Slogf.d(LOG_TAG, "Remote bugreport wasn't started because there's already one running");
201             return false;
202         }
203 
204         final long callingIdentity = mInjector.binderClearCallingIdentity();
205         try {
206             long nonce;
207             do {
208                 nonce = mRng.nextLong();
209             } while (nonce == 0);
210             mInjector.getIActivityManager().requestRemoteBugReport(nonce);
211 
212             mRemoteBugreportNonce.set(nonce);
213             mRemoteBugreportServiceIsActive.set(true);
214             mRemoteBugreportSharingAccepted.set(false);
215             registerRemoteBugreportReceivers();
216             mInjector.getNotificationManager().notifyAsUser(LOG_TAG, NOTIFICATION_ID,
217                     buildNotification(NOTIFICATION_BUGREPORT_STARTED), UserHandle.ALL);
218             mHandler.postDelayed(mRemoteBugreportTimeoutRunnable, REMOTE_BUGREPORT_TIMEOUT_MILLIS);
219             return true;
220         } catch (RemoteException re) {
221             // should never happen
222             Slogf.e(LOG_TAG, "Failed to make remote calls to start bugreportremote service", re);
223             return false;
224         } finally {
225             mInjector.binderRestoreCallingIdentity(callingIdentity);
226         }
227     }
228 
registerRemoteBugreportReceivers()229     private void registerRemoteBugreportReceivers() {
230         try {
231             final IntentFilter filterFinished =
232                     new IntentFilter(ACTION_REMOTE_BUGREPORT_DISPATCH, BUGREPORT_MIMETYPE);
233             mContext.registerReceiver(mRemoteBugreportFinishedReceiver, filterFinished,
234                     Context.RECEIVER_EXPORTED);
235         } catch (IntentFilter.MalformedMimeTypeException e) {
236             // should never happen, as setting a constant
237             Slogf.w(LOG_TAG, e, "Failed to set type %s", BUGREPORT_MIMETYPE);
238         }
239         final IntentFilter filterConsent = new IntentFilter();
240         filterConsent.addAction(ACTION_BUGREPORT_SHARING_DECLINED);
241         filterConsent.addAction(ACTION_BUGREPORT_SHARING_ACCEPTED);
242         mContext.registerReceiver(mRemoteBugreportConsentReceiver, filterConsent);
243     }
244 
onBugreportFinished(Intent intent)245     private void onBugreportFinished(Intent intent) {
246         long nonce = intent.getLongExtra(DevicePolicyManager.EXTRA_REMOTE_BUGREPORT_NONCE, 0);
247         if (nonce == 0 || mRemoteBugreportNonce.get() != nonce) {
248             Slogf.w(LOG_TAG, "Invalid nonce provided, ignoring " + nonce);
249             return;
250         }
251         mHandler.removeCallbacks(mRemoteBugreportTimeoutRunnable);
252         mRemoteBugreportServiceIsActive.set(false);
253         final Uri bugreportUri = intent.getData();
254         String bugreportUriString = null;
255         if (bugreportUri != null) {
256             bugreportUriString = bugreportUri.toString();
257         }
258         final String bugreportHash = intent.getStringExtra(EXTRA_REMOTE_BUGREPORT_HASH);
259         if (mRemoteBugreportSharingAccepted.get()) {
260             shareBugreportWithDeviceOwnerIfExists(bugreportUriString, bugreportHash);
261             mInjector.getNotificationManager().cancel(LOG_TAG,
262                     NOTIFICATION_ID);
263         } else {
264             mService.setDeviceOwnerRemoteBugreportUriAndHash(bugreportUriString, bugreportHash);
265             mInjector.getNotificationManager().notifyAsUser(LOG_TAG, NOTIFICATION_ID,
266                     buildNotification(NOTIFICATION_BUGREPORT_FINISHED_NOT_ACCEPTED),
267                     UserHandle.ALL);
268         }
269         mContext.unregisterReceiver(mRemoteBugreportFinishedReceiver);
270     }
271 
onBugreportFailed()272     private void onBugreportFailed() {
273         mRemoteBugreportServiceIsActive.set(false);
274         mInjector.systemPropertiesSet(CTL_STOP, REMOTE_BUGREPORT_SERVICE);
275         mRemoteBugreportSharingAccepted.set(false);
276         mService.setDeviceOwnerRemoteBugreportUriAndHash(null, null);
277         mInjector.getNotificationManager().cancel(LOG_TAG, NOTIFICATION_ID);
278         final Bundle extras = new Bundle();
279         extras.putInt(DeviceAdminReceiver.EXTRA_BUGREPORT_FAILURE_REASON,
280                 DeviceAdminReceiver.BUGREPORT_FAILURE_FAILED_COMPLETING);
281         mService.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_BUGREPORT_FAILED, extras);
282         mContext.unregisterReceiver(mRemoteBugreportConsentReceiver);
283         mContext.unregisterReceiver(mRemoteBugreportFinishedReceiver);
284     }
285 
onBugreportSharingAccepted()286     private void onBugreportSharingAccepted() {
287         mRemoteBugreportSharingAccepted.set(true);
288         final Pair<String, String> uriAndHash = mService.getDeviceOwnerRemoteBugreportUriAndHash();
289         if (uriAndHash != null) {
290             shareBugreportWithDeviceOwnerIfExists(uriAndHash.first, uriAndHash.second);
291         } else if (mRemoteBugreportServiceIsActive.get()) {
292             mInjector.getNotificationManager().notifyAsUser(LOG_TAG, NOTIFICATION_ID,
293                     buildNotification(NOTIFICATION_BUGREPORT_ACCEPTED_NOT_FINISHED),
294                     UserHandle.ALL);
295         }
296     }
297 
onBugreportSharingDeclined()298     private void onBugreportSharingDeclined() {
299         if (mRemoteBugreportServiceIsActive.get()) {
300             mInjector.systemPropertiesSet(CTL_STOP,
301                     REMOTE_BUGREPORT_SERVICE);
302             mRemoteBugreportServiceIsActive.set(false);
303             mHandler.removeCallbacks(mRemoteBugreportTimeoutRunnable);
304             mContext.unregisterReceiver(mRemoteBugreportFinishedReceiver);
305         }
306         mRemoteBugreportSharingAccepted.set(false);
307         mService.setDeviceOwnerRemoteBugreportUriAndHash(null, null);
308         mService.sendDeviceOwnerCommand(
309                 DeviceAdminReceiver.ACTION_BUGREPORT_SHARING_DECLINED, null);
310     }
311 
shareBugreportWithDeviceOwnerIfExists( String bugreportUriString, String bugreportHash)312     private void shareBugreportWithDeviceOwnerIfExists(
313             String bugreportUriString, String bugreportHash) {
314         try {
315             if (bugreportUriString == null) {
316                 throw new FileNotFoundException();
317             }
318             final Uri bugreportUri = Uri.parse(bugreportUriString);
319             mService.sendBugreportToDeviceOwner(bugreportUri, bugreportHash);
320         } catch (FileNotFoundException e) {
321             final Bundle extras = new Bundle();
322             extras.putInt(DeviceAdminReceiver.EXTRA_BUGREPORT_FAILURE_REASON,
323                     DeviceAdminReceiver.BUGREPORT_FAILURE_FILE_NO_LONGER_AVAILABLE);
324             mService.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_BUGREPORT_FAILED, extras);
325         } finally {
326             mRemoteBugreportSharingAccepted.set(false);
327             mService.setDeviceOwnerRemoteBugreportUriAndHash(null, null);
328         }
329     }
330 
331     /**
332      * Check if a bugreport was collected but not shared before reboot because the user didn't act
333      * upon sharing notification.
334      */
checkForPendingBugreportAfterBoot()335     public void checkForPendingBugreportAfterBoot() {
336         if (mService.getDeviceOwnerRemoteBugreportUriAndHash() == null) {
337             return;
338         }
339         final IntentFilter filterConsent = new IntentFilter();
340         filterConsent.addAction(ACTION_BUGREPORT_SHARING_DECLINED);
341         filterConsent.addAction(ACTION_BUGREPORT_SHARING_ACCEPTED);
342         mContext.registerReceiver(mRemoteBugreportConsentReceiver, filterConsent);
343         mInjector.getNotificationManager().notifyAsUser(LOG_TAG, NOTIFICATION_ID,
344                 buildNotification(NOTIFICATION_BUGREPORT_FINISHED_NOT_ACCEPTED), UserHandle.ALL);
345     }
346 }
347