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 17package com.android.shell; 18 19import static com.android.shell.BugreportPrefs.STATE_SHOW; 20import static com.android.shell.BugreportPrefs.getWarningState; 21 22import android.accounts.Account; 23import android.accounts.AccountManager; 24import android.app.Notification; 25import android.app.NotificationManager; 26import android.app.PendingIntent; 27import android.content.BroadcastReceiver; 28import android.content.Context; 29import android.content.Intent; 30import android.net.Uri; 31import android.os.AsyncTask; 32import android.os.FileUtils; 33import android.os.SystemProperties; 34import android.support.v4.content.FileProvider; 35import android.text.format.DateUtils; 36import android.util.Patterns; 37 38import com.google.android.collect.Lists; 39 40import java.io.File; 41import java.util.ArrayList; 42 43/** 44 * Receiver that handles finished bugreports, usually by attaching them to an 45 * {@link Intent#ACTION_SEND}. 46 */ 47public 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 .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb) 89 .setContentTitle(context.getString(R.string.bugreport_finished_title)) 90 .setTicker(context.getString(R.string.bugreport_finished_title)) 91 .setContentText(context.getString(R.string.bugreport_finished_text)) 92 .setContentIntent(PendingIntent.getActivity( 93 context, 0, notifIntent, PendingIntent.FLAG_CANCEL_CURRENT)) 94 .setAutoCancel(true) 95 .setColor(context.getResources().getColor( 96 com.android.internal.R.color.system_notification_accent_color)); 97 98 NotificationManager.from(context).notify(TAG, 0, builder.build()); 99 100 // Clean up older bugreports in background 101 final PendingResult result = goAsync(); 102 new AsyncTask<Void, Void, Void>() { 103 @Override 104 protected Void doInBackground(Void... params) { 105 FileUtils.deleteOlderFiles( 106 bugreportFile.getParentFile(), MIN_KEEP_COUNT, MIN_KEEP_AGE); 107 result.finish(); 108 return null; 109 } 110 }.execute(); 111 } 112 113 private static Intent buildWarningIntent(Context context, Intent sendIntent) { 114 final Intent intent = new Intent(context, BugreportWarningActivity.class); 115 intent.putExtra(Intent.EXTRA_INTENT, sendIntent); 116 return intent; 117 } 118 119 /** 120 * Build {@link Intent} that can be used to share the given bugreport. 121 */ 122 private static Intent buildSendIntent(Context context, Uri bugreportUri, Uri screenshotUri) { 123 final Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE); 124 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 125 intent.addCategory(Intent.CATEGORY_DEFAULT); 126 intent.setType("application/vnd.android.bugreport"); 127 128 intent.putExtra(Intent.EXTRA_SUBJECT, bugreportUri.getLastPathSegment()); 129 intent.putExtra(Intent.EXTRA_TEXT, SystemProperties.get("ro.build.description")); 130 131 final ArrayList<Uri> attachments = Lists.newArrayList(bugreportUri, screenshotUri); 132 intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments); 133 134 final Account sendToAccount = findSendToAccount(context); 135 if (sendToAccount != null) { 136 intent.putExtra(Intent.EXTRA_EMAIL, new String[] { sendToAccount.name }); 137 } 138 139 return intent; 140 } 141 142 /** 143 * Find the best matching {@link Account} based on build properties. 144 */ 145 private static Account findSendToAccount(Context context) { 146 final AccountManager am = (AccountManager) context.getSystemService( 147 Context.ACCOUNT_SERVICE); 148 149 String preferredDomain = SystemProperties.get("sendbug.preferred.domain"); 150 if (!preferredDomain.startsWith("@")) { 151 preferredDomain = "@" + preferredDomain; 152 } 153 154 final Account[] accounts = am.getAccounts(); 155 Account foundAccount = null; 156 for (Account account : accounts) { 157 if (Patterns.EMAIL_ADDRESS.matcher(account.name).matches()) { 158 if (!preferredDomain.isEmpty()) { 159 // if we have a preferred domain and it matches, return; otherwise keep 160 // looking 161 if (account.name.endsWith(preferredDomain)) { 162 return account; 163 } else { 164 foundAccount = account; 165 } 166 // if we don't have a preferred domain, just return since it looks like 167 // an email address 168 } else { 169 return account; 170 } 171 } 172 } 173 return foundAccount; 174 } 175 176 private static File getFileExtra(Intent intent, String key) { 177 final String path = intent.getStringExtra(key); 178 if (path != null) { 179 return new File(path); 180 } else { 181 return null; 182 } 183 } 184} 185