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