Merge "[RESTRICT AUTOMERGE] Add bugreport Config." into qt-qpr1-dev
diff --git a/tests/BugReportApp/AndroidManifest.xml b/tests/BugReportApp/AndroidManifest.xml
index d879396..77cbe14 100644
--- a/tests/BugReportApp/AndroidManifest.xml
+++ b/tests/BugReportApp/AndroidManifest.xml
@@ -30,6 +30,7 @@
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.DUMP"/>
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+ <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
<application android:label="@string/app_name"
android:icon="@drawable/ic_launcher"
diff --git a/tests/BugReportApp/README.md b/tests/BugReportApp/README.md
index acd4347..dd50b26 100644
--- a/tests/BugReportApp/README.md
+++ b/tests/BugReportApp/README.md
@@ -38,10 +38,15 @@
[overlayed](https://source.android.com/setup/develop/new-device#use-resource-overlays)
for specific products.
+### Config
+
+Configs are defined in `Config.java`.
+
### System Properties
-- `android.car.bugreport.disableautoupload` - set it to `true` to disable auto-upload to Google
- Cloud, and allow users to manually upload or copy the bugreports to flash drive.
+- `android.car.bugreport.enableautoupload` - please see Config#ENABLE_AUTO_UPLOAD to learn more.
+- `android.car.bugreport.force_enable_gcs_upload` - set it `true` to enable uploading
+ bugreports to GCS for **all builds**.
### Upload configuration
diff --git a/tests/BugReportApp/res/values/strings.xml b/tests/BugReportApp/res/values/strings.xml
index 66680e2..d082488 100644
--- a/tests/BugReportApp/res/values/strings.xml
+++ b/tests/BugReportApp/res/values/strings.xml
@@ -30,14 +30,13 @@
<string name="bugreport_dialog_in_progress_title">A bug report is already being collected</string>
<string name="bugreport_dialog_in_progress_title_finished">A bug report has been collected</string>
<string name="bugreport_move_button_text">Move to USB</string>
- <string name="bugreport_upload_button_text">Upload to Google</string>
+ <string name="bugreport_upload_button_text">Upload</string>
+ <string name="bugreport_upload_gcs_button_text">Upload to GCS</string>
<string name="toast_permissions_denied">Please grant permissions</string>
<string name="toast_bug_report_in_progress">Bug report already being collected</string>
- <string name="toast_timed_out">Timed out, cancelling</string>
<string name="toast_status_failed">Bug report failed</string>
<string name="toast_status_finished">Bug report finished</string>
- <string name="toast_status_pending_upload">Bug report ready for upload</string>
<string name="toast_status_screencap_failed">Screen capture failed</string>
<string name="toast_status_dump_state_failed">Dump state failed</string>
diff --git a/tests/BugReportApp/src/com/google/android/car/bugreport/BugInfoAdapter.java b/tests/BugReportApp/src/com/google/android/car/bugreport/BugInfoAdapter.java
index 07084fc..bbb5792 100644
--- a/tests/BugReportApp/src/com/google/android/car/bugreport/BugInfoAdapter.java
+++ b/tests/BugReportApp/src/com/google/android/car/bugreport/BugInfoAdapter.java
@@ -80,10 +80,12 @@
private List<MetaBugReport> mDataset;
private final ItemClickedListener mItemClickedListener;
+ private final Config mConfig;
- BugInfoAdapter(ItemClickedListener itemClickedListener) {
+ BugInfoAdapter(ItemClickedListener itemClickedListener, Config config) {
mItemClickedListener = itemClickedListener;
mDataset = new ArrayList<>();
+ mConfig = config;
// Allow RecyclerView to efficiently update UI; getItemId() is implemented below.
setHasStableIds(true);
}
@@ -117,14 +119,20 @@
holder.mMoveButton.setOnClickListener(
view -> mItemClickedListener.onItemClicked(BUTTON_TYPE_MOVE, bugreport,
holder));
+ } else {
+ holder.mMoveButton.setEnabled(false);
+ holder.mMoveButton.setVisibility(View.GONE);
+ }
+ // TODO(b/144851443): Enable upload button only if upload destination is GCS until
+ // we create a way to allow implementing OEMs custom upload logic.
+ if (enableUserActionButtons && mConfig.isUploadDestinationGcs()) {
+ holder.mUploadButton.setText(R.string.bugreport_upload_gcs_button_text);
holder.mUploadButton.setEnabled(true);
holder.mUploadButton.setVisibility(View.VISIBLE);
holder.mUploadButton.setOnClickListener(
view -> mItemClickedListener.onItemClicked(BUTTON_TYPE_UPLOAD, bugreport,
holder));
} else {
- holder.mMoveButton.setVisibility(View.GONE);
- holder.mMoveButton.setEnabled(false);
holder.mUploadButton.setVisibility(View.GONE);
holder.mUploadButton.setEnabled(false);
}
diff --git a/tests/BugReportApp/src/com/google/android/car/bugreport/BugReportActivity.java b/tests/BugReportApp/src/com/google/android/car/bugreport/BugReportActivity.java
index d8597d8..5d60672 100644
--- a/tests/BugReportApp/src/com/google/android/car/bugreport/BugReportActivity.java
+++ b/tests/BugReportApp/src/com/google/android/car/bugreport/BugReportActivity.java
@@ -127,6 +127,8 @@
Car.CAR_DRIVING_STATE_SERVICE);
mDrivingStateManager.registerListener(
BugReportActivity.this::onCarDrivingStateChanged);
+ // Call onCarDrivingStateChanged(), because it's not called when Car is connected.
+ onCarDrivingStateChanged(mDrivingStateManager.getCurrentCarDrivingState());
} catch (CarNotConnectedException e) {
Log.w(TAG, "Failed to get CarDrivingStateManager.", e);
}
@@ -248,7 +250,6 @@
mShowBugReportsButton.setVisibility(View.GONE);
if (mDrivingStateManager != null) {
try {
- // Call onCarDrivingStateChanged(), because it's not called when Car is connected.
onCarDrivingStateChanged(mDrivingStateManager.getCurrentCarDrivingState());
} catch (CarNotConnectedException e) {
Log.e(TAG, "Failed to get current driving state.", e);
diff --git a/tests/BugReportApp/src/com/google/android/car/bugreport/BugReportInfoActivity.java b/tests/BugReportApp/src/com/google/android/car/bugreport/BugReportInfoActivity.java
index 46b5d45..91cf7ea 100644
--- a/tests/BugReportApp/src/com/google/android/car/bugreport/BugReportInfoActivity.java
+++ b/tests/BugReportApp/src/com/google/android/car/bugreport/BugReportInfoActivity.java
@@ -38,9 +38,11 @@
import androidx.recyclerview.widget.RecyclerView;
import java.io.File;
+import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
@@ -61,6 +63,7 @@
private MetaBugReport mLastSelectedBugReport;
private BugInfoAdapter.BugInfoViewHolder mLastSelectedBugInfoViewHolder;
private BugStorageObserver mBugStorageObserver;
+ private Config mConfig;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -77,7 +80,10 @@
mRecyclerView.addItemDecoration(new DividerItemDecoration(mRecyclerView.getContext(),
DividerItemDecoration.VERTICAL));
- mBugInfoAdapter = new BugInfoAdapter(this::onBugReportItemClicked);
+ mConfig = new Config();
+ mConfig.start();
+
+ mBugInfoAdapter = new BugInfoAdapter(this::onBugReportItemClicked, mConfig);
mRecyclerView.setAdapter(mBugInfoAdapter);
mBugStorageObserver = new BugStorageObserver(this, new Handler());
@@ -170,6 +176,21 @@
startActivity(intent);
}
+ /**
+ * Print the Provider's state into the given stream. This gets invoked if
+ * you run "adb shell dumpsys activity BugReportInfoActivity".
+ *
+ * @param prefix Desired prefix to prepend at each line of output.
+ * @param fd The raw file descriptor that the dump is being sent to.
+ * @param writer The PrintWriter to which you should dump your state. This will be
+ * closed for you after you return.
+ * @param args additional arguments to the dump request.
+ */
+ public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+ super.dump(prefix, fd, writer, args);
+ mConfig.dump(prefix, writer);
+ }
+
/** Observer for {@link BugStorageProvider}. */
private static class BugStorageObserver extends ContentObserver {
private final BugReportInfoActivity mInfoActivity;
@@ -251,18 +272,15 @@
} else {
Log.w(TAG, "Failed to delete the local bugreport " + mBugReport.getFilePath());
}
- BugStorageUtils.setBugReportStatus(
- mActivity, mBugReport,
- com.google.android.car.bugreport.Status.STATUS_MOVE_SUCCESSFUL, "");
+ return BugStorageUtils.setBugReportStatus(mActivity, mBugReport,
+ com.google.android.car.bugreport.Status.STATUS_MOVE_SUCCESSFUL,
+ "Moved to: " + mDestinationDirUri.getPath());
} catch (IOException e) {
Log.e(TAG, "Failed to create the bug report in the location.", e);
return BugStorageUtils.setBugReportStatus(
mActivity, mBugReport,
com.google.android.car.bugreport.Status.STATUS_MOVE_FAILED, e);
}
- return BugStorageUtils.setBugReportStatus(mActivity, mBugReport,
- com.google.android.car.bugreport.Status.STATUS_MOVE_SUCCESSFUL,
- "Moved to: " + mDestinationDirUri.getPath());
}
@Override
diff --git a/tests/BugReportApp/src/com/google/android/car/bugreport/BugReportService.java b/tests/BugReportApp/src/com/google/android/car/bugreport/BugReportService.java
index cab945b..ceba655 100644
--- a/tests/BugReportApp/src/com/google/android/car/bugreport/BugReportService.java
+++ b/tests/BugReportApp/src/com/google/android/car/bugreport/BugReportService.java
@@ -119,6 +119,7 @@
private Car mCar;
private CarBugreportManager mBugreportManager;
private CarBugreportManager.CarBugreportManagerCallback mCallback;
+ private Config mConfig;
/** A handler on the main thread. */
private Handler mHandler;
@@ -173,6 +174,8 @@
mSingleThreadExecutor = Executors.newSingleThreadScheduledExecutor();
mHandler = new BugReportHandler();
mCar = Car.createCar(this);
+ mConfig = new Config();
+ mConfig.start();
try {
mBugreportManager = (CarBugreportManager) mCar.getCarManager(Car.CAR_BUGREPORT_SERVICE);
} catch (CarNotConnectedException | NoClassDefFoundError e) {
@@ -351,7 +354,7 @@
PendingIntent startBugReportInfoActivity =
PendingIntent.getActivity(getApplicationContext(), 0, intent, 0);
CharSequence contentText;
- if (JobSchedulingUtils.autoUploadBugReport(mMetaBugReport)) {
+ if (mConfig.autoUploadBugReport(mMetaBugReport)) {
contentText = getText(R.string.notification_bugreport_auto_upload_finished_text);
} else {
contentText = getText(R.string.notification_bugreport_manual_upload_finished_text);
@@ -391,6 +394,7 @@
if (DEBUG) {
Log.d(TAG, "Service destroyed");
}
+ mCar.disconnect();
}
private static void copyBinaryStream(InputStream in, OutputStream out) throws IOException {
diff --git a/tests/BugReportApp/src/com/google/android/car/bugreport/BugStorageProvider.java b/tests/BugReportApp/src/com/google/android/car/bugreport/BugStorageProvider.java
index 92c1697..2809495 100644
--- a/tests/BugReportApp/src/com/google/android/car/bugreport/BugStorageProvider.java
+++ b/tests/BugReportApp/src/com/google/android/car/bugreport/BugStorageProvider.java
@@ -31,8 +31,10 @@
import android.util.Log;
import java.io.File;
+import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
@@ -74,6 +76,7 @@
private DatabaseHelper mDatabaseHelper;
private final UriMatcher mUriMatcher;
+ private Config mConfig;
/**
* A helper class to work with sqlite database.
@@ -147,6 +150,8 @@
public boolean onCreate() {
mDatabaseHelper = new DatabaseHelper(getContext());
mHandler = new Handler();
+ mConfig = new Config();
+ mConfig.start();
return true;
}
@@ -334,7 +339,7 @@
if (e == null) {
// success writing the file. Update the field to indicate bugreport
// is ready for upload.
- status = JobSchedulingUtils.autoUploadBugReport(bugReport)
+ status = mConfig.autoUploadBugReport(bugReport)
? Status.STATUS_UPLOAD_PENDING
: Status.STATUS_PENDING_USER_ACTION;
} else {
@@ -360,6 +365,20 @@
}
}
+ /**
+ * Print the Provider's state into the given stream. This gets invoked if
+ * you run "dumpsys activity provider com.google.android.car.bugreport/.BugStorageProvider".
+ *
+ * @param fd The raw file descriptor that the dump is being sent to.
+ * @param writer The PrintWriter to which you should dump your state. This will be
+ * closed for you after you return.
+ * @param args additional arguments to the dump request.
+ */
+ public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ writer.println("BugStorageProvider:");
+ mConfig.dump(/* prefix= */ " ", writer);
+ }
+
private boolean deleteZipFile(int bugreportId) {
Optional<MetaBugReport> bugReport =
BugStorageUtils.findBugReport(getContext(), bugreportId);
diff --git a/tests/BugReportApp/src/com/google/android/car/bugreport/Config.java b/tests/BugReportApp/src/com/google/android/car/bugreport/Config.java
new file mode 100644
index 0000000..ce474f5
--- /dev/null
+++ b/tests/BugReportApp/src/com/google/android/car/bugreport/Config.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.car.bugreport;
+
+import android.app.ActivityThread;
+import android.os.Build;
+import android.os.SystemProperties;
+import android.provider.DeviceConfig;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+
+/**
+ * Contains config for BugReport App.
+ *
+ * <p>The config is kept synchronized with {@link DeviceConfig#NAMESPACE_CAR}.
+ *
+ * <ul>To get/set the flags via adb:
+ * <li>{@code adb shell device_config get car bugreport_upload_destination}
+ * <li>{@code adb shell device_config put car bugreport_upload_destination gcs}
+ * <li>{@code adb shell device_config delete car bugreport_upload_destination}
+ * </ul>
+ */
+final class Config {
+ private static final String TAG = Config.class.getSimpleName();
+
+ /**
+ * A string flag, can be one of {@code null} or {@link #UPLOAD_DESTINATION_GCS}.
+ */
+ private static final String KEY_BUGREPORT_UPLOAD_DESTINATION = "bugreport_upload_destination";
+
+ /**
+ * A value for {@link #KEY_BUGREPORT_UPLOAD_DESTINATION}.
+ *
+ * Upload bugreports to GCS. Only works in {@code userdebug} or {@code eng} builds.
+ */
+ private static final String UPLOAD_DESTINATION_GCS = "gcs";
+
+ /**
+ * A system property to force enable uploading new bugreports to GCS.
+ * Unlike {@link #UPLOAD_DESTINATION_GCS}, it bypasses the {@code userdebug} build check.
+ */
+ private static final String PROP_FORCE_ENABLE_GCS_UPLOAD =
+ "android.car.bugreport.force_enable_gcs_upload";
+
+ /**
+ * Temporary flag to retain the old behavior.
+ *
+ * Default is {@code true}.
+ *
+ * TODO(b/143183993): Disable auto-upload to GCS after testing DeviceConfig.
+ */
+ private static final String ENABLE_AUTO_UPLOAD = "android.car.bugreport.enableautoupload";
+
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private String mUploadDestination = null;
+
+ void start() {
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_CAR,
+ ActivityThread.currentApplication().getMainExecutor(), this::onPropertiesChanged);
+ updateConstants();
+ }
+
+ private void onPropertiesChanged(DeviceConfig.Properties properties) {
+ if (properties.getKeyset().contains(KEY_BUGREPORT_UPLOAD_DESTINATION)) {
+ updateConstants();
+ }
+ }
+
+ /** If new bugreports should be scheduled for uploading. */
+ private boolean getAutoUpload() {
+ if (isTempForceAutoUploadGcsEnabled()) {
+ Log.d(TAG, "Enabling auto-upload because ENABLE_AUTO_UPLOAD is true");
+ return true;
+ }
+ // TODO(b/144851443): Enable auto-upload only if upload destination is Gcs until
+ // we create a way to allow implementing OEMs custom upload logic.
+ return isUploadDestinationGcs();
+ }
+
+ /**
+ * Returns {@link true} if bugreport upload destination is GCS.
+ */
+ boolean isUploadDestinationGcs() {
+ if (isTempForceAutoUploadGcsEnabled()) {
+ Log.d(TAG, "Setting upload dest to GCS ENABLE_AUTO_UPLOAD is true");
+ return true;
+ }
+ // NOTE: enable it only for userdebug builds, unless it's force enabled using a system
+ // property.
+ return (UPLOAD_DESTINATION_GCS.equals(getUploadDestination()) && Build.IS_DEBUGGABLE)
+ || SystemProperties.getBoolean(PROP_FORCE_ENABLE_GCS_UPLOAD, /* def= */ false);
+ }
+
+ /** Returns {@code true} if the bugreport should be auto-uploaded to a cloud. */
+ boolean autoUploadBugReport(MetaBugReport bugReport) {
+ return getAutoUpload() && bugReport.getType() == MetaBugReport.TYPE_INTERACTIVE;
+ }
+
+ private static boolean isTempForceAutoUploadGcsEnabled() {
+ return SystemProperties.getBoolean(ENABLE_AUTO_UPLOAD, /* def= */ true);
+ }
+
+ /**
+ * Returns value of a flag {@link #KEY_BUGREPORT_UPLOAD_DESTINATION}.
+ */
+ private String getUploadDestination() {
+ synchronized (mLock) {
+ return mUploadDestination;
+ }
+ }
+
+ private void updateConstants() {
+ synchronized (mLock) {
+ mUploadDestination = DeviceConfig.getString(DeviceConfig.NAMESPACE_CAR,
+ KEY_BUGREPORT_UPLOAD_DESTINATION, /* defaultValue= */ null);
+ }
+ }
+
+ void dump(String prefix, PrintWriter pw) {
+ pw.println(prefix + "car.bugreport.Config:");
+
+ pw.print(prefix + " ");
+ pw.print("getAutoUpload");
+ pw.print("=");
+ pw.println(getAutoUpload() ? "true" : "false");
+
+ pw.print(prefix + " ");
+ pw.print("getUploadDestination");
+ pw.print("=");
+ pw.println(getUploadDestination());
+
+ pw.print(prefix + " ");
+ pw.print("isUploadDestinationGcs");
+ pw.print("=");
+ pw.println(isUploadDestinationGcs());
+ }
+}
diff --git a/tests/BugReportApp/src/com/google/android/car/bugreport/JobSchedulingUtils.java b/tests/BugReportApp/src/com/google/android/car/bugreport/JobSchedulingUtils.java
index 95c4685..a20c6c6 100644
--- a/tests/BugReportApp/src/com/google/android/car/bugreport/JobSchedulingUtils.java
+++ b/tests/BugReportApp/src/com/google/android/car/bugreport/JobSchedulingUtils.java
@@ -19,7 +19,6 @@
import android.app.job.JobScheduler;
import android.content.ComponentName;
import android.content.Context;
-import android.os.SystemProperties;
import android.util.Log;
/**
@@ -32,14 +31,6 @@
private static final int RETRY_DELAY_IN_MS = 5_000;
/**
- * The system property to disable auto-upload when bug reports are collected. When auto-upload
- * is disabled, the app waits for user action on collected bug reports: user can either
- * upload to Google Cloud or copy to flash drive.
- */
- private static final String PROP_DISABLE_AUTO_UPLOAD =
- "android.car.bugreport.disableautoupload";
-
- /**
* Schedules an upload job under the current user.
*
* <p>Make sure this method is called under the primary user.
@@ -72,20 +63,4 @@
.setBackoffCriteria(RETRY_DELAY_IN_MS, JobInfo.BACKOFF_POLICY_LINEAR)
.build());
}
-
- /**
- * Returns true if collected bugreports should be uploaded automatically.
- *
- * <p>If it returns false, the app maps to an alternative workflow that requires user action
- * after bugreport is successfully written. A user then has an option to choose whether to
- * upload the bugreport or copy it to an external drive.
- */
- private static boolean uploadByDefault() {
- return !SystemProperties.getBoolean(PROP_DISABLE_AUTO_UPLOAD, false);
- }
-
- /** Returns {@code true} if bugreports has to be auto-uploaded. */
- static boolean autoUploadBugReport(MetaBugReport bugReport) {
- return uploadByDefault() && bugReport.getType() == MetaBugReport.TYPE_INTERACTIVE;
- }
}