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 }