1 /* 2 * Copyright (C) 2023 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.tv.feedbackconsent; 18 19 import static com.android.tv.feedbackconsent.TvFeedbackConstants.BUGREPORT_CONSENT; 20 import static com.android.tv.feedbackconsent.TvFeedbackConstants.BUGREPORT_REQUESTED; 21 import static com.android.tv.feedbackconsent.TvFeedbackConstants.BUGREPORT_TOGGLE_ON_BY_DEFAULT; 22 import static com.android.tv.feedbackconsent.TvFeedbackConstants.CANCEL_REQUEST; 23 import static com.android.tv.feedbackconsent.TvFeedbackConstants.CONSENT_RECEIVER; 24 import static com.android.tv.feedbackconsent.TvFeedbackConstants.RESULT_CODE_OK; 25 import static com.android.tv.feedbackconsent.TvFeedbackConstants.SYSTEM_LOGS_CONSENT; 26 import static com.android.tv.feedbackconsent.TvFeedbackConstants.SYSTEM_LOGS_KEY; 27 import static com.android.tv.feedbackconsent.TvFeedbackConstants.SYSTEM_LOGS_REQUESTED; 28 29 import android.Manifest; 30 import android.app.Service; 31 import android.content.Intent; 32 import android.content.ActivityNotFoundException; 33 import android.os.Bundle; 34 import android.os.Handler; 35 import android.os.Looper; 36 import android.os.ResultReceiver; 37 import android.os.IBinder; 38 import android.os.RemoteException; 39 import android.net.Uri; 40 import android.util.Log; 41 import android.annotation.RequiresPermission; 42 import android.annotation.NonNull; 43 import android.annotation.Nullable; 44 45 import androidx.core.util.Preconditions; 46 47 import java.io.FileNotFoundException; 48 import java.io.FileOutputStream; 49 import java.io.IOException; 50 import java.io.OutputStreamWriter; 51 import java.io.Writer; 52 import java.nio.charset.StandardCharsets; 53 import java.util.List; 54 55 public final class TvFeedbackConsentService extends Service { 56 57 private static final String TAG = TvFeedbackConsentService.class.getSimpleName(); 58 59 final TvDiagnosticInformationManagerBinder tvDiagnosticInformationBinder = 60 new TvDiagnosticInformationManagerBinder(); 61 private boolean setBugreportSwitchOnByDefault; 62 63 @Override onBind(Intent intent)64 public IBinder onBind(Intent intent) { 65 setBugreportSwitchOnByDefault = 66 intent.getBooleanExtra(BUGREPORT_TOGGLE_ON_BY_DEFAULT, false); 67 return tvDiagnosticInformationBinder; 68 } 69 70 private final class TvDiagnosticInformationManagerBinder extends 71 ITvDiagnosticInformationManager.Stub { 72 73 private boolean mSystemLogsRequested; 74 private boolean mBugreportRequested; 75 private boolean mSystemLogsConsented; 76 private ITvDiagnosticInformationManagerCallback mTvFeedbackConsentCallback; 77 private Uri mSystemLogsUri; 78 private Uri mBugreportUri; 79 80 @RequiresPermission(allOf = {Manifest.permission.DUMP, Manifest.permission.READ_LOGS}) 81 @Override getDiagnosticInformation( @ullable Uri bugreportUri, @Nullable Uri systemLogsUri, @NonNull ITvDiagnosticInformationManagerCallback tvFeedbackConsentCallback)82 public void getDiagnosticInformation( 83 @Nullable Uri bugreportUri, 84 @Nullable Uri systemLogsUri, 85 @NonNull ITvDiagnosticInformationManagerCallback tvFeedbackConsentCallback) { 86 Preconditions.checkNotNull(tvFeedbackConsentCallback); 87 mTvFeedbackConsentCallback = tvFeedbackConsentCallback; 88 mBugreportRequested = false; 89 mSystemLogsRequested = false; 90 if (bugreportUri != null) { 91 mBugreportRequested = true; 92 mBugreportUri = bugreportUri; 93 } 94 if (systemLogsUri != null) { 95 mSystemLogsRequested = true; 96 mSystemLogsUri = systemLogsUri; 97 } 98 Preconditions.checkArgument(mBugreportRequested || mSystemLogsRequested, 99 "No Diagnostic information requested: " + 100 "Both bugreportUri and systemLogsUri cannot be null"); 101 102 ResultReceiver resultReceiver = createResultReceiver(); 103 displayConsentScreen(resultReceiver); 104 } 105 createResultReceiver()106 private ResultReceiver createResultReceiver() { 107 return new ResultReceiver( 108 new Handler(Looper.getMainLooper())) { 109 @Override 110 protected void onReceiveResult(int resultCode, Bundle resultData) { 111 if (mTvFeedbackConsentCallback == null) { 112 Log.w(TAG, "Diagnostic information requested without a callback"); 113 return; 114 } 115 116 if (resultData.getBoolean(CANCEL_REQUEST, false)) { 117 try { 118 Log.d(TAG, "User cancelled the request."); 119 mTvFeedbackConsentCallback.onCancelRequest(); 120 } catch (RemoteException e) { 121 throw new RuntimeException(e); 122 } 123 } 124 125 if (mBugreportRequested) { 126 TvFeedbackBugreportHelper bugreportHelper = 127 new TvFeedbackBugreportHelper( 128 /* context= */ TvFeedbackConsentService.this); 129 bugreportHelper.startBugreport( 130 resultData.getBoolean(BUGREPORT_CONSENT, false), 131 mTvFeedbackConsentCallback, 132 mBugreportUri); 133 134 } 135 if (mSystemLogsRequested) { 136 mSystemLogsConsented = resultData.getBoolean(SYSTEM_LOGS_CONSENT, 137 false); 138 List<String> systemLogs = resultData.getStringArrayList( 139 SYSTEM_LOGS_KEY); 140 141 processSystemLogs(systemLogs); 142 } 143 } 144 }; 145 } 146 147 private void processSystemLogs(List<String> systemLogs) { 148 int systemLogsResultCode; 149 if (!mSystemLogsConsented) { 150 systemLogsResultCode = 151 mTvFeedbackConsentCallback.SYSTEM_LOGS_ERROR_USER_CONSENT_DENIED; 152 Log.d(TAG, "User denied consent to share system logs"); 153 } else if (systemLogs == null || systemLogs.isEmpty()) { 154 systemLogsResultCode = mTvFeedbackConsentCallback.SYSTEM_LOGS_ERROR_RUNTIME; 155 Log.d(TAG, "Error generating system logs"); 156 } else { 157 systemLogsResultCode = copySystemLogsToUri(systemLogs); 158 } 159 160 try { 161 if (systemLogsResultCode == RESULT_CODE_OK) { 162 mTvFeedbackConsentCallback.onSystemLogsFinished(); 163 Log.d(TAG, "System logs generated and returned successfully."); 164 } else { 165 mTvFeedbackConsentCallback.onSystemLogsError(systemLogsResultCode); 166 } 167 168 } catch (RemoteException e) { 169 throw new RuntimeException(e); 170 } 171 } 172 173 private int copySystemLogsToUri(List<String> systemLogs) { 174 try ( 175 FileOutputStream out = (FileOutputStream) TvFeedbackConsentService.this 176 .getContentResolver().openOutputStream(mSystemLogsUri, "w"); 177 Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)) { 178 for (String str : systemLogs) { 179 writer.write(str); 180 writer.write(System.lineSeparator()); 181 } 182 return RESULT_CODE_OK; 183 } catch (FileNotFoundException e) { 184 Log.e(TAG, "Uri file not found", e); 185 return mTvFeedbackConsentCallback.SYSTEM_LOGS_ERROR_INVALID_INPUT; 186 } catch (IOException e) { 187 Log.e(TAG, "Error copying system logs", e); 188 return mTvFeedbackConsentCallback.SYSTEM_LOGS_ERROR_WRITE_FAILED; 189 } 190 } 191 192 private void displayConsentScreen(ResultReceiver resultReceiver) { 193 Intent consentIntent = new Intent( 194 TvFeedbackConsentService.this, TvFeedbackConsentActivity.class); 195 consentIntent.putExtra(CONSENT_RECEIVER, resultReceiver); 196 consentIntent.putExtra(BUGREPORT_REQUESTED, mBugreportRequested); 197 consentIntent.putExtra(SYSTEM_LOGS_REQUESTED, mSystemLogsRequested); 198 consentIntent.putExtra(BUGREPORT_TOGGLE_ON_BY_DEFAULT, setBugreportSwitchOnByDefault); 199 consentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 200 201 try { 202 TvFeedbackConsentService.this.startActivity(consentIntent); 203 } catch (ActivityNotFoundException e) { 204 Log.e(TAG, "Error starting activity", e); 205 } 206 } 207 } 208 }