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