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