1 /* 2 * Copyright (C) 2022 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.nfc; 18 19 import android.content.Context; 20 import android.content.Intent; 21 import android.os.Binder; 22 import android.os.BugreportManager; 23 import android.os.BugreportParams; 24 import android.os.Build; 25 import android.os.SystemClock; 26 import android.util.Log; 27 28 /** 29 * A class to trigger bugreport and other logs for Nfc related failures 30 */ 31 public class NfcDiagnostics { 32 private static final String TAG = "NfcDiagnostics"; 33 private static final int MS_IN_HOUR = 60 * 60 * 1000; 34 public static final int DEFAULT_BUG_REPORT_MIN_INTERVAL_MS = 24 * MS_IN_HOUR; 35 36 private final Context mContext; 37 private final SystemBuildProperties mSystemBuildProperties; 38 private long mLastBugReportTimeMs; 39 40 static class SystemBuildProperties { 41 /** @return if it is an eng build. */ isEngBuild()42 public boolean isEngBuild() { 43 return Build.TYPE.equals("eng"); 44 } 45 46 /** @return if it is an userdebug build. */ isUserdebugBuild()47 public boolean isUserdebugBuild() { 48 return Build.TYPE.equals("userdebug"); 49 } 50 51 /** @return if it is a normal user build. */ isUserBuild()52 public boolean isUserBuild() { 53 return Build.TYPE.equals("user"); 54 } 55 } 56 NfcDiagnostics(Context context)57 public NfcDiagnostics(Context context) { 58 mContext = context; 59 mSystemBuildProperties = new SystemBuildProperties(); 60 } 61 62 /** 63 * Take a bug report if it is in user debug build and there is no recent bug 64 * report 65 */ takeBugReport(String bugTitle, String description)66 public void takeBugReport(String bugTitle, String description) { 67 if (!mSystemBuildProperties.isUserdebugBuild()) { 68 Log.d(TAG, "Skip bugreport because it can be triggered only in userDebug build"); 69 return; 70 } 71 long currentTimeMs = getElapsedSinceBootMillis(); 72 long timeSinceLastUploadMs = currentTimeMs - mLastBugReportTimeMs; 73 74 if (timeSinceLastUploadMs < DEFAULT_BUG_REPORT_MIN_INTERVAL_MS 75 && mLastBugReportTimeMs > 0) { 76 Log.d(TAG, "Bugreport was filed recently, Skip " + bugTitle); 77 return; 78 } 79 80 if (!takeBugreportThroughBetterBug(bugTitle, description)) { 81 takeBugreportThroughBugreportManager(bugTitle, description); 82 } 83 } 84 takeBugreportThroughBetterBug(String bugTitle, String description)85 private boolean takeBugreportThroughBetterBug(String bugTitle, String description) { 86 Intent launchBetterBugIntent = new BetterBugIntentBuilder() 87 .setIssueTitle(bugTitle) 88 .setHappenedTimestamp(System.currentTimeMillis()) 89 .setAdditionalComment(description) 90 .build(); 91 boolean isIntentUnSafe = mContext 92 .getPackageManager().queryIntentActivities(launchBetterBugIntent, 0) 93 .isEmpty(); 94 if (isIntentUnSafe) { 95 Log.d(TAG, "intent is unsafe and skip bugreport from betterBug: " + bugTitle); 96 return false; 97 } 98 99 long identity = Binder.clearCallingIdentity(); 100 try { 101 launchBetterBugIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 102 mContext.startActivity(launchBetterBugIntent); 103 Log.d(TAG, "Taking the bugreport through betterBug: " + bugTitle); 104 mLastBugReportTimeMs = getElapsedSinceBootMillis(); 105 return true; 106 } catch (RuntimeException e) { 107 Log.e(TAG, "Error taking bugreport: " + e); 108 return false; 109 } finally { 110 Binder.restoreCallingIdentity(identity); 111 } 112 } 113 getElapsedSinceBootMillis()114 private long getElapsedSinceBootMillis() { 115 return SystemClock.elapsedRealtime(); 116 } 117 takeBugreportThroughBugreportManager(String bugTitle, String description)118 private boolean takeBugreportThroughBugreportManager(String bugTitle, String description) { 119 BugreportManager bugreportManager = mContext.getSystemService(BugreportManager.class); 120 BugreportParams params = new BugreportParams(BugreportParams.BUGREPORT_MODE_FULL); 121 try { 122 bugreportManager.requestBugreport(params, bugTitle, description); 123 Log.d(TAG, "Taking the bugreport through bugreportManager: " + bugTitle); 124 mLastBugReportTimeMs = getElapsedSinceBootMillis(); 125 return true; 126 } catch (RuntimeException e) { 127 Log.e(TAG, "Error taking bugreport: ", e); 128 return false; 129 } 130 } 131 132 /** 133 * @return the last time when the bug report is taken 134 */ getLastBugReportTimeMs()135 long getLastBugReportTimeMs() { 136 return mLastBugReportTimeMs; 137 } 138 139 /** 140 * Builder for communicating with the betterbug. 141 */ 142 private static class BetterBugIntentBuilder { 143 144 private static final String ACTION_FILE_BUG_DEEPLINK = 145 "com.google.android.apps.betterbug.intent.FILE_BUG_DEEPLINK"; 146 private static final boolean DEFAULT_AUTO_UPLOAD_ENABLED = false; 147 private static final boolean DEFAULT_BUGREPORT_REQUIRED = true; 148 private static final String DEFAULT_BUG_ASSIGNEE = "android-nfc-team@google.com"; 149 private static final long DEFAULT_COMPONENT_ID = 48448L; 150 151 private static final String EXTRA_DEEPLINK = "EXTRA_DEEPLINK"; 152 private static final String EXTRA_ISSUE_TITLE = "EXTRA_ISSUE_TITLE"; 153 private static final String EXTRA_DEEPLINK_SILENT = "EXTRA_DEEPLINK_SILENT"; 154 private static final String EXTRA_ADDITIONAL_COMMENT = "EXTRA_ADDITIONAL_COMMENT"; 155 private static final String EXTRA_REQUIRE_BUGREPORT = "EXTRA_REQUIRE_BUGREPORT"; 156 private static final String EXTRA_HAPPENED_TIME = "EXTRA_HAPPENED_TIME"; 157 private static final String EXTRA_BUG_ASSIGNEE = "EXTRA_BUG_ASSIGNEE"; 158 private static final String EXTRA_COMPONENT_ID = "EXTRA_COMPONENT_ID"; 159 160 private final Intent mBetterBugIntent; 161 BetterBugIntentBuilder()162 BetterBugIntentBuilder() { 163 mBetterBugIntent = new Intent().setAction(ACTION_FILE_BUG_DEEPLINK) 164 .putExtra(EXTRA_DEEPLINK, true); 165 setAutoUpload(DEFAULT_AUTO_UPLOAD_ENABLED); 166 setBugreportRequired(DEFAULT_BUGREPORT_REQUIRED); 167 setBugAssignee(DEFAULT_BUG_ASSIGNEE); 168 setComponentId(DEFAULT_COMPONENT_ID); 169 } 170 setIssueTitle(String title)171 public BetterBugIntentBuilder setIssueTitle(String title) { 172 mBetterBugIntent.putExtra(EXTRA_ISSUE_TITLE, title); 173 return this; 174 } 175 setAutoUpload(boolean autoUploadEnabled)176 public BetterBugIntentBuilder setAutoUpload(boolean autoUploadEnabled) { 177 mBetterBugIntent.putExtra(EXTRA_DEEPLINK_SILENT, autoUploadEnabled); 178 return this; 179 } 180 setComponentId(long componentId)181 public BetterBugIntentBuilder setComponentId(long componentId) { 182 mBetterBugIntent.putExtra(EXTRA_COMPONENT_ID, componentId); 183 return this; 184 } 185 setBugreportRequired(boolean isBugreportRequired)186 public BetterBugIntentBuilder setBugreportRequired(boolean isBugreportRequired) { 187 mBetterBugIntent.putExtra(EXTRA_REQUIRE_BUGREPORT, isBugreportRequired); 188 return this; 189 } 190 setHappenedTimestamp(long happenedTimeSinceEpochMs)191 public BetterBugIntentBuilder setHappenedTimestamp(long happenedTimeSinceEpochMs) { 192 mBetterBugIntent.putExtra(EXTRA_HAPPENED_TIME, happenedTimeSinceEpochMs); 193 return this; 194 } 195 setAdditionalComment(String additionalComment)196 public BetterBugIntentBuilder setAdditionalComment(String additionalComment) { 197 mBetterBugIntent.putExtra(EXTRA_ADDITIONAL_COMMENT, additionalComment); 198 return this; 199 } 200 setBugAssignee(String assignee)201 public BetterBugIntentBuilder setBugAssignee(String assignee) { 202 mBetterBugIntent.putExtra(EXTRA_BUG_ASSIGNEE, assignee); 203 return this; 204 } 205 build()206 public Intent build() { 207 return mBetterBugIntent; 208 } 209 } 210 } 211