1 /* 2 * Copyright (C) 2013 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.shell; 18 19 import static com.android.shell.BugreportPrefs.STATE_SHOW; 20 import static com.android.shell.BugreportPrefs.getWarningState; 21 22 import android.accounts.Account; 23 import android.accounts.AccountManager; 24 import android.app.Notification; 25 import android.app.NotificationManager; 26 import android.app.PendingIntent; 27 import android.content.BroadcastReceiver; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.net.Uri; 31 import android.os.AsyncTask; 32 import android.os.FileUtils; 33 import android.os.SystemProperties; 34 import android.support.v4.content.FileProvider; 35 import android.text.format.DateUtils; 36 import android.util.Patterns; 37 38 import com.google.android.collect.Lists; 39 40 import java.io.File; 41 import java.util.ArrayList; 42 43 /** 44 * Receiver that handles finished bugreports, usually by attaching them to an 45 * {@link Intent#ACTION_SEND}. 46 */ 47 public class BugreportReceiver extends BroadcastReceiver { 48 private static final String TAG = "Shell"; 49 50 private static final String AUTHORITY = "com.android.shell"; 51 52 private static final String EXTRA_BUGREPORT = "android.intent.extra.BUGREPORT"; 53 private static final String EXTRA_SCREENSHOT = "android.intent.extra.SCREENSHOT"; 54 55 /** 56 * Always keep the newest 8 bugreport files; 4 reports and 4 screenshots are 57 * roughly 17MB of disk space. 58 */ 59 private static final int MIN_KEEP_COUNT = 8; 60 61 /** 62 * Always keep bugreports taken in the last week. 63 */ 64 private static final long MIN_KEEP_AGE = DateUtils.WEEK_IN_MILLIS; 65 66 @Override 67 public void onReceive(Context context, Intent intent) { 68 final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT); 69 final File screenshotFile = getFileExtra(intent, EXTRA_SCREENSHOT); 70 71 // Files are kept on private storage, so turn into Uris that we can 72 // grant temporary permissions for. 73 final Uri bugreportUri = FileProvider.getUriForFile(context, AUTHORITY, bugreportFile); 74 final Uri screenshotUri = FileProvider.getUriForFile(context, AUTHORITY, screenshotFile); 75 76 Intent sendIntent = buildSendIntent(context, bugreportUri, screenshotUri); 77 Intent notifIntent; 78 79 // Send through warning dialog by default 80 if (getWarningState(context, STATE_SHOW) == STATE_SHOW) { 81 notifIntent = buildWarningIntent(context, sendIntent); 82 } else { 83 notifIntent = sendIntent; 84 } 85 notifIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 86 87 final Notification.Builder builder = new Notification.Builder(context); 88 builder.setSmallIcon(com.android.internal.R.drawable.stat_sys_adb); 89 builder.setContentTitle(context.getString(R.string.bugreport_finished_title)); 90 builder.setTicker(context.getString(R.string.bugreport_finished_title)); 91 builder.setContentText(context.getString(R.string.bugreport_finished_text)); 92 builder.setContentIntent(PendingIntent.getActivity( 93 context, 0, notifIntent, PendingIntent.FLAG_CANCEL_CURRENT)); 94 builder.setAutoCancel(true); 95 NotificationManager.from(context).notify(TAG, 0, builder.build()); 96 97 // Clean up older bugreports in background 98 final PendingResult result = goAsync(); 99 new AsyncTask<Void, Void, Void>() { 100 @Override 101 protected Void doInBackground(Void... params) { 102 FileUtils.deleteOlderFiles( 103 bugreportFile.getParentFile(), MIN_KEEP_COUNT, MIN_KEEP_AGE); 104 result.finish(); 105 return null; 106 } 107 }.execute(); 108 } 109 110 private static Intent buildWarningIntent(Context context, Intent sendIntent) { 111 final Intent intent = new Intent(context, BugreportWarningActivity.class); 112 intent.putExtra(Intent.EXTRA_INTENT, sendIntent); 113 return intent; 114 } 115 116 /** 117 * Build {@link Intent} that can be used to share the given bugreport. 118 */ 119 private static Intent buildSendIntent(Context context, Uri bugreportUri, Uri screenshotUri) { 120 final Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE); 121 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 122 intent.addCategory(Intent.CATEGORY_DEFAULT); 123 intent.setType("application/vnd.android.bugreport"); 124 125 intent.putExtra(Intent.EXTRA_SUBJECT, bugreportUri.getLastPathSegment()); 126 intent.putExtra(Intent.EXTRA_TEXT, SystemProperties.get("ro.build.description")); 127 128 final ArrayList<Uri> attachments = Lists.newArrayList(bugreportUri, screenshotUri); 129 intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments); 130 131 final Account sendToAccount = findSendToAccount(context); 132 if (sendToAccount != null) { 133 intent.putExtra(Intent.EXTRA_EMAIL, new String[] { sendToAccount.name }); 134 } 135 136 return intent; 137 } 138 139 /** 140 * Find the best matching {@link Account} based on build properties. 141 */ 142 private static Account findSendToAccount(Context context) { 143 final AccountManager am = (AccountManager) context.getSystemService( 144 Context.ACCOUNT_SERVICE); 145 146 String preferredDomain = SystemProperties.get("sendbug.preferred.domain"); 147 if (!preferredDomain.startsWith("@")) { 148 preferredDomain = "@" + preferredDomain; 149 } 150 151 final Account[] accounts = am.getAccounts(); 152 Account foundAccount = null; 153 for (Account account : accounts) { 154 if (Patterns.EMAIL_ADDRESS.matcher(account.name).matches()) { 155 if (!preferredDomain.isEmpty()) { 156 // if we have a preferred domain and it matches, return; otherwise keep 157 // looking 158 if (account.name.endsWith(preferredDomain)) { 159 return account; 160 } else { 161 foundAccount = account; 162 } 163 // if we don't have a preferred domain, just return since it looks like 164 // an email address 165 } else { 166 return account; 167 } 168 } 169 } 170 return foundAccount; 171 } 172 173 private static File getFileExtra(Intent intent, String key) { 174 final String path = intent.getStringExtra(key); 175 if (path != null) { 176 return new File(path); 177 } else { 178 return null; 179 } 180 } 181 } 182