• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }