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 .setLocalOnly(true) 96 .setColor(context.getResources().getColor( 97 com.android.internal.R.color.system_notification_accent_color)); 98 99 NotificationManager.from(context).notify(TAG, 0, builder.build()); 100 101 // Clean up older bugreports in background 102 final PendingResult result = goAsync(); 103 new AsyncTask<Void, Void, Void>() { 104 @Override 105 protected Void doInBackground(Void... params) { 106 FileUtils.deleteOlderFiles( 107 bugreportFile.getParentFile(), MIN_KEEP_COUNT, MIN_KEEP_AGE); 108 result.finish(); 109 return null; 110 } 111 }.execute(); 112 } 113 114 private static Intent buildWarningIntent(Context context, Intent sendIntent) { 115 final Intent intent = new Intent(context, BugreportWarningActivity.class); 116 intent.putExtra(Intent.EXTRA_INTENT, sendIntent); 117 return intent; 118 } 119 120 /** 121 * Build {@link Intent} that can be used to share the given bugreport. 122 */ 123 private static Intent buildSendIntent(Context context, Uri bugreportUri, Uri screenshotUri) { 124 final Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE); 125 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 126 intent.addCategory(Intent.CATEGORY_DEFAULT); 127 intent.setType("application/vnd.android.bugreport"); 128 129 intent.putExtra(Intent.EXTRA_SUBJECT, bugreportUri.getLastPathSegment()); 130 intent.putExtra(Intent.EXTRA_TEXT, SystemProperties.get("ro.build.description")); 131 132 final ArrayList<Uri> attachments = Lists.newArrayList(bugreportUri, screenshotUri); 133 intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments); 134 135 final Account sendToAccount = findSendToAccount(context); 136 if (sendToAccount != null) { 137 intent.putExtra(Intent.EXTRA_EMAIL, new String[] { sendToAccount.name }); 138 } 139 140 return intent; 141 } 142 143 /** 144 * Find the best matching {@link Account} based on build properties. 145 */ 146 private static Account findSendToAccount(Context context) { 147 final AccountManager am = (AccountManager) context.getSystemService( 148 Context.ACCOUNT_SERVICE); 149 150 String preferredDomain = SystemProperties.get("sendbug.preferred.domain"); 151 if (!preferredDomain.startsWith("@")) { 152 preferredDomain = "@" + preferredDomain; 153 } 154 155 final Account[] accounts = am.getAccounts(); 156 Account foundAccount = null; 157 for (Account account : accounts) { 158 if (Patterns.EMAIL_ADDRESS.matcher(account.name).matches()) { 159 if (!preferredDomain.isEmpty()) { 160 // if we have a preferred domain and it matches, return; otherwise keep 161 // looking 162 if (account.name.endsWith(preferredDomain)) { 163 return account; 164 } else { 165 foundAccount = account; 166 } 167 // if we don't have a preferred domain, just return since it looks like 168 // an email address 169 } else { 170 return account; 171 } 172 } 173 } 174 return foundAccount; 175 } 176 177 private static File getFileExtra(Intent intent, String key) { 178 final String path = intent.getStringExtra(key); 179 if (path != null) { 180 return new File(path); 181 } else { 182 return null; 183 } 184 } 185} 186