Merge "Make bugreport activity distraction optimized." into qt-qpr1-dev
diff --git a/car-lib/src/android/car/settings/CarSettings.java b/car-lib/src/android/car/settings/CarSettings.java
index ab7c906..d81b7ad 100644
--- a/car-lib/src/android/car/settings/CarSettings.java
+++ b/car-lib/src/android/car/settings/CarSettings.java
@@ -153,5 +153,14 @@
          */
         public static final String KEY_ENABLE_INITIAL_NOTICE_SCREEN_TO_USER =
                 "android.car.ENABLE_INITIAL_NOTICE_SCREEN_TO_USER";
+
+        /**
+         * Key to indicate Setup Wizard is in progress. It differs from USER_SETUP_COMPLETE in
+         * that this flag can be reset to 0 in deferred Setup Wizard flow.
+         * The value is boolean (1 or 0).
+         * @hide
+         */
+        public static final String KEY_SETUP_WIZARD_IN_PROGRESS =
+                "android.car.SETUP_WIZARD_IN_PROGRESS";
     }
 }
diff --git a/tests/BugReportApp/AndroidManifest.xml b/tests/BugReportApp/AndroidManifest.xml
index cace9f9..46fb757 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;
-    }
 }