• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.RESULT_CODE_OK;
20 
21 import android.content.Context;
22 import android.os.BugreportManager;
23 import android.os.BugreportParams;
24 import android.os.Handler;
25 import android.os.Looper;
26 import android.os.ParcelFileDescriptor;
27 import android.os.RemoteException;
28 import android.os.BugreportManager.BugreportCallback;
29 import android.net.Uri;
30 import android.util.Log;
31 
32 import androidx.annotation.Nullable;
33 
34 import java.io.File;
35 import java.io.FileNotFoundException;
36 import java.io.IOException;
37 import java.io.OutputStream;
38 import java.nio.file.Files;
39 import java.nio.file.Path;
40 
41 final class TvFeedbackBugreportHelper {
42 
43     private static final String TAG = TvFeedbackBugreportHelper.class.getSimpleName();
44 
45     public static final String BUGREPORT_FILENAME = "bugreport.zip";
46     private File mBugreportFile;
47     private final File cacheDir;
48     private final Context mContext;
49 
TvFeedbackBugreportHelper(Context context)50     TvFeedbackBugreportHelper(Context context) {
51         mContext = context;
52         cacheDir = mContext.getCacheDir();
53     }
54 
startBugreport(boolean bugreportConsented, ITvDiagnosticInformationManagerCallback callback, Uri bugreportUri)55     void startBugreport(boolean bugreportConsented,
56                         ITvDiagnosticInformationManagerCallback callback, Uri bugreportUri) {
57         BugreportCallbackImpl bugreportCallback = new BugreportCallbackImpl(callback, bugreportUri);
58 
59         if (!bugreportConsented) {
60             Log.d(TAG, "User denied consent to share bugreport");
61             bugreportCallback.onError(
62                 BugreportCallback.BUGREPORT_ERROR_USER_DENIED_CONSENT);
63             return;
64         }
65 
66         BugreportManager bugreportManager = mContext.getSystemService(BugreportManager.class);
67         if (bugreportManager == null) {
68             Log.e(TAG, "BugreportManager is not available");
69             bugreportCallback.onError(
70                 BugreportCallback.BUGREPORT_ERROR_RUNTIME);
71             return;
72         }
73 
74         ParcelFileDescriptor bugreportFd = createBugreportFile();
75         if (bugreportFd == null) {
76             Log.e(TAG, "Bugreport file descriptor could not be created.");
77             bugreportCallback.onError(
78                 BugreportCallback.BUGREPORT_ERROR_RUNTIME);
79             return;
80         }
81 
82         bugreportManager.startBugreport(bugreportFd,
83             /* screenshotFd= */ null,
84             new BugreportParams(BugreportParams.BUGREPORT_MODE_FULL),
85             runnable -> new Handler(Looper.getMainLooper()).post(runnable),
86             bugreportCallback);
87     }
88 
89     @Nullable
createBugreportFile()90     private ParcelFileDescriptor createBugreportFile() {
91         try {
92             mBugreportFile = File.createTempFile(BUGREPORT_FILENAME, null, cacheDir);
93             return ParcelFileDescriptor.open(
94                 mBugreportFile,
95                 ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_APPEND);
96         } catch (IOException e) {
97             Log.e(TAG, "Error creating file " + BUGREPORT_FILENAME, e);
98         }
99         return null;
100     }
101 
102     private final class BugreportCallbackImpl extends BugreportCallback {
103         private final ITvDiagnosticInformationManagerCallback mCallback;
104         private final Uri mBugreportUri;
105 
BugreportCallbackImpl(ITvDiagnosticInformationManagerCallback callback, Uri bugreportUri)106         BugreportCallbackImpl(ITvDiagnosticInformationManagerCallback callback, Uri bugreportUri) {
107             mCallback = callback;
108             mBugreportUri = bugreportUri;
109         }
110 
111         @Override
onError(@ugreportErrorCode int errorCode)112         public void onError(@BugreportErrorCode int errorCode) {
113             Log.e(TAG, "Error generating bugreport: " + errorCode);
114             if (mBugreportFile != null) {
115                 mBugreportFile.delete();
116             }
117             try {
118                 mCallback.onBugreportError(errorCode);
119             } catch (RemoteException ex) {
120                 throw new RuntimeException(ex);
121             }
122         }
123 
124         @Override
onFinished()125         public void onFinished() {
126             int bugreportResultCode = copyBugreportToUri(mBugreportUri);
127             if (mBugreportFile != null) {
128                 mBugreportFile.delete();
129             }
130             try {
131                 if (bugreportResultCode == RESULT_CODE_OK) {
132                     mCallback.onBugreportFinished();
133                     Log.d(TAG, "Bugreport generated and returned successfully.");
134                 } else {
135                     mCallback.onBugreportError(bugreportResultCode);
136                 }
137 
138             } catch (RemoteException e) {
139                 throw new RuntimeException(e);
140             }
141         }
142 
copyBugreportToUri(Uri bugreportUri)143         private int copyBugreportToUri(Uri bugreportUri) {
144             try (OutputStream out = mContext.getContentResolver()
145                 .openOutputStream(bugreportUri, "w")) {
146                 Files.copy(Path.of(mBugreportFile.getPath()), out);
147             } catch (FileNotFoundException e) {
148                 Log.e(TAG, "Uri file not found", e);
149                 return BugreportCallback.BUGREPORT_ERROR_INVALID_INPUT;
150             } catch (IOException e) {
151                 Log.e(TAG, "Error copying bugreport", e);
152                 return BugreportCallback.BUGREPORT_ERROR_RUNTIME;
153             }
154 
155             return RESULT_CODE_OK;
156         }
157     }
158 }