Merge "[RESTRICT AUTOMERGE] Add starting silent bugreporting" into qt-qpr1-dev
diff --git a/tests/BugReportApp/AndroidManifest.xml b/tests/BugReportApp/AndroidManifest.xml
index af9ee25..68bbc0c 100644
--- a/tests/BugReportApp/AndroidManifest.xml
+++ b/tests/BugReportApp/AndroidManifest.xml
@@ -16,8 +16,8 @@
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.google.android.car.bugreport"
-          android:versionCode="9"
-          android:versionName="1.6.1">
+          android:versionCode="10"
+          android:versionName="1.6.2">
 
     <uses-permission android:name="android.car.permission.CAR_DRIVING_STATE"/>
     <uses-permission android:name="android.permission.INTERNET"/>
@@ -41,6 +41,7 @@
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
                 <action android:name="android.intent.action.VIEW"/>
+                <action android:name="com.google.android.car.bugreport.action.START_SILENT"/>
             </intent-filter>
         </activity>
 
diff --git a/tests/BugReportApp/README.md b/tests/BugReportApp/README.md
index 2dac8e9..acd4347 100644
--- a/tests/BugReportApp/README.md
+++ b/tests/BugReportApp/README.md
@@ -48,6 +48,17 @@
 BugReport app uses `res/raw/gcs_credentials.json` for authentication and
 `res/values/configs.xml` for obtaining GCS bucket name.
 
+## Starting bugreporting
+
+The app supports following intents:
+
+1. `adb shell am start com.google.android.car.bugreport/com.google.android.car.bugreport.BugReportActivity`
+    - generates `MetaBugReport.Type.INTERACTIVE` bug report, with audio message, shows a
+    SUBMIT/CANCEL dialog.
+2. `adb shell am start -n com.google.android.car.bugreport/com.google.android.car.bugreport.BugReportActivity -a com.google.android.car.bugreport.action.START_SILENT`
+    - generates `MetaBugReport.Type.SILENT` bug report, without audio message. The app doesn't
+    auto-upload these bugreports.
+
 ## Testing
 
 ### Manually testing the app using the test script
diff --git a/tests/BugReportApp/res/layout/bug_info_view.xml b/tests/BugReportApp/res/layout/bug_info_view.xml
index d4c573f..089acb5 100644
--- a/tests/BugReportApp/res/layout/bug_info_view.xml
+++ b/tests/BugReportApp/res/layout/bug_info_view.xml
@@ -30,6 +30,7 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_gravity="left"
+            android:textColor="@*android:color/car_yellow_500"
             android:textSize="@dimen/bug_report_default_text_size" />
 
         <LinearLayout
@@ -53,7 +54,6 @@
             android:id="@+id/bug_info_row_message"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:textColor="@*android:color/car_yellow_500"
             android:visibility="gone"
             android:textSize="@dimen/bug_report_default_text_size" />
 
diff --git a/tests/BugReportApp/res/values/strings.xml b/tests/BugReportApp/res/values/strings.xml
index 9aca56e..6244691 100644
--- a/tests/BugReportApp/res/values/strings.xml
+++ b/tests/BugReportApp/res/values/strings.xml
@@ -30,7 +30,7 @@
     <string name="bugreport_dialog_in_progress_title" translatable="false">A bug report is already being collected</string>
     <string name="bugreport_dialog_in_progress_title_finished" translatable="false">A bug report has been collected</string>
     <string name="bugreport_move_button_text" translatable="false">Move to USB</string>
-    <string name="bugreport_upload_button_text" translatable="false">Upload</string>
+    <string name="bugreport_upload_button_text" translatable="false">Upload to Google</string>
 
     <string name="toast_permissions_denied" translatable="false">Please grant permissions</string>
     <string name="toast_bug_report_in_progress" translatable="false">Bug report already being collected</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 0e01027..07084fc 100644
--- a/tests/BugReportApp/src/com/google/android/car/bugreport/BugInfoAdapter.java
+++ b/tests/BugReportApp/src/com/google/android/car/bugreport/BugInfoAdapter.java
@@ -26,6 +26,12 @@
 import java.util.ArrayList;
 import java.util.List;
 
+/**
+ * Shows bugreport title, status, status message and user action buttons. "Upload to Google" button
+ * is enabled when the status is {@link Status#STATUS_PENDING_USER_ACTION}, "Move to USB" button is
+ * enabled only when status is  {@link Status#STATUS_PENDING_USER_ACTION} and USB device is plugged
+ * in.
+ */
 public class BugInfoAdapter extends RecyclerView.Adapter<BugInfoAdapter.BugInfoViewHolder> {
     static final int BUTTON_TYPE_UPLOAD = 0;
     static final int BUTTON_TYPE_MOVE = 1;
@@ -96,33 +102,30 @@
         holder.mTitleView.setText(bugreport.getTitle());
         holder.mStatusView.setText(Status.toString(bugreport.getStatus()));
         holder.mMessageView.setText(bugreport.getStatusMessage());
-        if (bugreport.getStatusMessage() == null || bugreport.getStatusMessage().isEmpty()) {
+        if (bugreport.getStatusMessage().isEmpty()) {
             holder.mMessageView.setVisibility(View.GONE);
         } else {
             holder.mMessageView.setVisibility(View.VISIBLE);
         }
-        if (getUserActionButtonsVisible()) {
-            holder.mMoveButton.setVisibility(View.VISIBLE);
-            holder.mUploadButton.setVisibility(View.VISIBLE);
-        } else {
-            holder.mMoveButton.setVisibility(View.GONE);
-            holder.mUploadButton.setVisibility(View.GONE);
-        }
         boolean enableUserActionButtons =
                 bugreport.getStatus() == Status.STATUS_PENDING_USER_ACTION.getValue()
                         || bugreport.getStatus() == Status.STATUS_MOVE_FAILED.getValue()
                         || bugreport.getStatus() == Status.STATUS_UPLOAD_FAILED.getValue();
         if (enableUserActionButtons) {
             holder.mMoveButton.setEnabled(true);
+            holder.mMoveButton.setVisibility(View.VISIBLE);
             holder.mMoveButton.setOnClickListener(
                     view -> mItemClickedListener.onItemClicked(BUTTON_TYPE_MOVE, bugreport,
                             holder));
             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);
         }
     }
@@ -141,12 +144,6 @@
         }
     }
 
-    /** Returns true if the upload/move buttons should be visible. */
-    private boolean getUserActionButtonsVisible() {
-        // Do not show buttons if bugreports are uploaded by default.
-        return !JobSchedulingUtils.uploadByDefault();
-    }
-
     @Override
     public long getItemId(int position) {
         return mDataset.get(position).getId();
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 456192e..d8597d8 100644
--- a/tests/BugReportApp/src/com/google/android/car/bugreport/BugReportActivity.java
+++ b/tests/BugReportApp/src/com/google/android/car/bugreport/BugReportActivity.java
@@ -25,6 +25,7 @@
 import android.car.drivingstate.CarDrivingStateEvent;
 import android.car.drivingstate.CarDrivingStateManager;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
@@ -61,10 +62,18 @@
  * submit button it initiates {@link BugReportService}.
  *
  * <p>If bug report is in-progress, it shows a progress bar.
+ *
+ * <p>If the activity is started with action {@link #ACTION_START_SILENT}, it will start
+ * bugreporting without showing dialog and recording audio message, see
+ * {@link MetaBugReport#TYPE_SILENT}.
  */
 public class BugReportActivity extends Activity {
     private static final String TAG = BugReportActivity.class.getSimpleName();
 
+    /** Starts headless (no audio message recording) bugreporting. */
+    private static final String ACTION_START_SILENT =
+            "com.google.android.car.bugreport.action.START_SILENT";
+
     private static final int VOICE_MESSAGE_MAX_DURATION_MILLIS = 60 * 1000;
     private static final int AUDIO_PERMISSIONS_REQUEST_ID = 1;
 
@@ -132,6 +141,14 @@
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
+        if (ACTION_START_SILENT.equals(getIntent().getAction())) {
+            Log.i(TAG, "Starting headless bugreport.");
+            MetaBugReport bugReport = createBugReport(this, MetaBugReport.TYPE_SILENT);
+            startBugReportingInService(this, bugReport);
+            finish();
+            return;
+        }
+
         requestWindowFeature(Window.FEATURE_NO_TITLE);
         setContentView(R.layout.bug_report_activity);
 
@@ -261,11 +278,7 @@
         mAudioRecordingStarted = true;
         showSubmitBugReportUi(/* isRecording= */ true);
 
-        Date initiatedAt = new Date();
-        String timestamp = BUG_REPORT_TIMESTAMP_DATE_FORMAT.format(initiatedAt);
-        String username = getCurrentUserName();
-        String title = BugReportTitleGenerator.generateBugReportTitle(initiatedAt, username);
-        mMetaBugReport = BugStorageUtils.createBugReport(this, title, timestamp, username);
+        mMetaBugReport = createBugReport(this, MetaBugReport.TYPE_INTERACTIVE);
 
         if (!hasRecordPermissions()) {
             requestRecordPermissions();
@@ -294,7 +307,9 @@
     }
 
     private void buttonSubmitClick(View view) {
-        startBugReportingInService();
+        stopAudioRecording();
+        startBugReportingInService(this, mMetaBugReport);
+        mBugReportServiceStarted = true;
         finish();
     }
 
@@ -314,16 +329,6 @@
         finish();
     }
 
-    private void startBugReportingInService() {
-        stopAudioRecording();
-        Bundle bundle = new Bundle();
-        bundle.putParcelable(EXTRA_META_BUG_REPORT, mMetaBugReport);
-        Intent intent = new Intent(this, BugReportService.class);
-        intent.putExtras(bundle);
-        startService(intent);
-        mBugReportServiceStarted = true;
-    }
-
     private void requestRecordPermissions() {
         requestPermissions(
                 new String[]{Manifest.permission.RECORD_AUDIO}, AUDIO_PERMISSIONS_REQUEST_ID);
@@ -430,11 +435,33 @@
         mVoiceRecordingView.setRecorder(null);
     }
 
-    private String getCurrentUserName() {
-        UserManager um = UserManager.get(this);
+    private static void startBugReportingInService(Context context, MetaBugReport bugReport) {
+        Bundle bundle = new Bundle();
+        bundle.putParcelable(EXTRA_META_BUG_REPORT, bugReport);
+        Intent intent = new Intent(context, BugReportService.class);
+        intent.putExtras(bundle);
+        context.startForegroundService(intent);
+    }
+
+    private static String getCurrentUserName(Context context) {
+        UserManager um = UserManager.get(context);
         return um.getUserName();
     }
 
+    /**
+     * Creates a {@link MetaBugReport} and saves it in a local sqlite database.
+     *
+     * @param context an Android context.
+     * @param type bug report type, {@link MetaBugReport.BugReportType}.
+     */
+    private static MetaBugReport createBugReport(Context context, int type) {
+        Date initiatedAt = new Date();
+        String timestamp = BUG_REPORT_TIMESTAMP_DATE_FORMAT.format(initiatedAt);
+        String username = getCurrentUserName(context);
+        String title = BugReportTitleGenerator.generateBugReportTitle(initiatedAt, username);
+        return BugStorageUtils.createBugReport(context, title, timestamp, username, type);
+    }
+
     /** A helper class to generate bugreport title. */
     private static final class BugReportTitleGenerator {
         /** Contains easily readable characters. */
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 f6fc651..cab945b 100644
--- a/tests/BugReportApp/src/com/google/android/car/bugreport/BugReportService.java
+++ b/tests/BugReportApp/src/com/google/android/car/bugreport/BugReportService.java
@@ -213,6 +213,9 @@
     }
 
     private Notification buildProgressNotification() {
+        Intent intent = new Intent(getApplicationContext(), BugReportInfoActivity.class);
+        PendingIntent startBugReportInfoActivity =
+                PendingIntent.getActivity(getApplicationContext(), 0, intent, 0);
         return new Notification.Builder(this, PROGRESS_CHANNEL_ID)
                 .setContentTitle(getText(R.string.notification_bugreport_in_progress))
                 .setSubText(String.format("%.1f%%", mBugReportProgress.get()))
@@ -220,6 +223,7 @@
                 .setCategory(Notification.CATEGORY_STATUS)
                 .setOngoing(true)
                 .setProgress((int) MAX_PROGRESS_VALUE, (int) mBugReportProgress.get(), false)
+                .setContentIntent(startBugReportInfoActivity)
                 .build();
     }
 
@@ -346,12 +350,16 @@
         Intent intent = new Intent(getApplicationContext(), BugReportInfoActivity.class);
         PendingIntent startBugReportInfoActivity =
                 PendingIntent.getActivity(getApplicationContext(), 0, intent, 0);
+        CharSequence contentText;
+        if (JobSchedulingUtils.autoUploadBugReport(mMetaBugReport)) {
+            contentText = getText(R.string.notification_bugreport_auto_upload_finished_text);
+        } else {
+            contentText = getText(R.string.notification_bugreport_manual_upload_finished_text);
+        }
         Notification notification = new Notification
                 .Builder(getApplicationContext(), STATUS_CHANNEL_ID)
                 .setContentTitle(getText(R.string.notification_bugreport_finished_title))
-                .setContentText(getText(JobSchedulingUtils.uploadByDefault()
-                        ? R.string.notification_bugreport_auto_upload_finished_text
-                        : R.string.notification_bugreport_manual_upload_finished_text))
+                .setContentText(contentText)
                 .setCategory(Notification.CATEGORY_STATUS)
                 .setSmallIcon(R.drawable.ic_upload)
                 .setContentIntent(startBugReportInfoActivity)
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 1ed4e81..b762cb3 100644
--- a/tests/BugReportApp/src/com/google/android/car/bugreport/BugStorageProvider.java
+++ b/tests/BugReportApp/src/com/google/android/car/bugreport/BugStorageProvider.java
@@ -35,7 +35,7 @@
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.util.List;
+import java.util.Optional;
 
 
 /**
@@ -54,6 +54,7 @@
     static final Uri BUGREPORT_CONTENT_URI =
             Uri.parse("content://" + AUTHORITY + "/" + BUG_REPORTS_TABLE);
 
+    /** See {@link MetaBugReport} for column descriptions. */
     static final String COLUMN_ID = "_ID";
     static final String COLUMN_USERNAME = "username";
     static final String COLUMN_TITLE = "title";
@@ -62,6 +63,7 @@
     static final String COLUMN_FILEPATH = "filepath";
     static final String COLUMN_STATUS = "status";
     static final String COLUMN_STATUS_MESSAGE = "message";
+    static final String COLUMN_TYPE = "type";
 
     // URL Matcher IDs.
     private static final int URL_MATCHED_BUG_REPORTS_URI = 1;
@@ -84,9 +86,11 @@
         /**
          * All changes in database versions should be recorded here.
          * 1: Initial version.
+         * 2: Add integer column details_needed.
          */
         private static final int INITIAL_VERSION = 1;
-        private static final int DATABASE_VERSION = INITIAL_VERSION;
+        private static final int TYPE_VERSION = 2;
+        private static final int DATABASE_VERSION = TYPE_VERSION;
 
         private static final String CREATE_TABLE = "CREATE TABLE " + BUG_REPORTS_TABLE + " ("
                 + COLUMN_ID + " INTEGER PRIMARY KEY,"
@@ -96,7 +100,8 @@
                 + COLUMN_DESCRIPTION + " TEXT NULL,"
                 + COLUMN_FILEPATH + " TEXT DEFAULT NULL,"
                 + COLUMN_STATUS + " INTEGER DEFAULT " + Status.STATUS_WRITE_PENDING.getValue() + ","
-                + COLUMN_STATUS_MESSAGE + " TEXT NULL"
+                + COLUMN_STATUS_MESSAGE + " TEXT NULL,"
+                + COLUMN_TYPE + " INTEGER DEFAULT " + MetaBugReport.TYPE_INTERACTIVE
                 + ");";
 
         DatabaseHelper(Context context) {
@@ -111,6 +116,10 @@
         @Override
         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
             Log.w(TAG, "Upgrading from " + oldVersion + " to " + newVersion);
+            if (oldVersion == INITIAL_VERSION && newVersion == TYPE_VERSION) {
+                db.execSQL("ALTER TABLE " + BUG_REPORTS_TABLE + " ADD COLUMN "
+                        + COLUMN_TYPE + " INTEGER DEFAULT " + MetaBugReport.TYPE_INTERACTIVE);
+            }
         }
     }
 
@@ -301,27 +310,12 @@
             throw new IllegalArgumentException("unknown uri:" + uri);
         }
 
-        Cursor c = query(uri, new String[]{COLUMN_FILEPATH}, null, null, null);
-        int count = (c != null) ? c.getCount() : 0;
-        if (count != 1) {
-            // If there is not exactly one result, throw an appropriate
-            // exception.
-            if (c != null) {
-                c.close();
-            }
-            if (count == 0) {
-                throw new FileNotFoundException("No entry for " + uri);
-            }
-            throw new FileNotFoundException("Multiple items at " + uri);
-        }
-
-        c.moveToFirst();
-        int i = c.getColumnIndex(COLUMN_FILEPATH);
-        String path = (i >= 0 ? c.getString(i) : null);
-        c.close();
-        if (path == null) {
-            throw new FileNotFoundException("Column for path not found.");
-        }
+        // Because we expect URI to be URL_MATCHED_BUG_REPORT_ID_URI.
+        int bugreportId = Integer.parseInt(uri.getLastPathSegment());
+        MetaBugReport bugReport =
+                BugStorageUtils.findBugReport(getContext(), bugreportId)
+                        .orElseThrow(() -> new FileNotFoundException("No record found for " + uri));
+        String path = bugReport.getFilePath();
 
         int modeBits = ParcelFileDescriptor.parseMode(mode);
         try {
@@ -337,8 +331,9 @@
                 Status status;
                 if (e == null) {
                     // success writing the file. Update the field to indicate bugreport
-                    // is ready for upload
-                    status = JobSchedulingUtils.uploadByDefault() ? Status.STATUS_UPLOAD_PENDING
+                    // is ready for upload.
+                    status = JobSchedulingUtils.autoUploadBugReport(bugReport)
+                            ? Status.STATUS_UPLOAD_PENDING
                             : Status.STATUS_PENDING_USER_ACTION;
                 } else {
                     // We log it and ignore it
@@ -363,16 +358,21 @@
     }
 
     private boolean deleteZipFile(int bugreportId) {
-        String selection = COLUMN_ID + " = ?";
-        String[] selectionArgs = new String[]{Integer.toString(bugreportId)};
-        List<MetaBugReport> bugs = BugStorageUtils.getBugreports(
-                getContext(), selection, selectionArgs, null);
-        if (bugs.isEmpty()) {
+        Optional<MetaBugReport> bugReport =
+                BugStorageUtils.findBugReport(getContext(), bugreportId);
+        if (!bugReport.isPresent()) {
             Log.i(TAG,
                     "Failed to delete zip file for bug " + bugreportId + ": bugreport not found.");
             return false;
         }
-        Path zipPath = new File(bugs.get(0).getFilePath()).toPath();
+        Path zipPath = new File(bugReport.get().getFilePath()).toPath();
+        // This statement is to prevent the app to print confusing full FileNotFound error stack
+        // when the user is navigating from BugReportActivity (audio message recording dialog) to
+        // BugReportInfoActivity by clicking "Show Bug Reports" button.
+        if (!Files.exists(zipPath)) {
+            Log.w(TAG, "Failed to delete " + zipPath + ". File not found.");
+            return false;
+        }
         try {
             Files.delete(zipPath);
             return true;
diff --git a/tests/BugReportApp/src/com/google/android/car/bugreport/BugStorageUtils.java b/tests/BugReportApp/src/com/google/android/car/bugreport/BugStorageUtils.java
index 3dba420..4cdc7cd 100644
--- a/tests/BugReportApp/src/com/google/android/car/bugreport/BugStorageUtils.java
+++ b/tests/BugReportApp/src/com/google/android/car/bugreport/BugStorageUtils.java
@@ -21,6 +21,7 @@
 import static com.google.android.car.bugreport.BugStorageProvider.COLUMN_STATUS_MESSAGE;
 import static com.google.android.car.bugreport.BugStorageProvider.COLUMN_TIMESTAMP;
 import static com.google.android.car.bugreport.BugStorageProvider.COLUMN_TITLE;
+import static com.google.android.car.bugreport.BugStorageProvider.COLUMN_TYPE;
 import static com.google.android.car.bugreport.BugStorageProvider.COLUMN_USERNAME;
 
 import android.annotation.NonNull;
@@ -41,6 +42,7 @@
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
+import java.util.Optional;
 
 /**
  * A class that hides details when communicating with the bug storage provider.
@@ -67,6 +69,7 @@
      * @param title     - title of the bug report.
      * @param timestamp - timestamp when the bug report was initiated.
      * @param username  - current user name. Note, it's a user name, not an account name.
+     * @param type      - bug report type, {@link MetaBugReport.BugReportType}.
      * @return an instance of {@link MetaBugReport} that was created in a database.
      */
     @NonNull
@@ -74,12 +77,14 @@
             @NonNull Context context,
             @NonNull String title,
             @NonNull String timestamp,
-            @NonNull String username) {
+            @NonNull String username,
+            @MetaBugReport.BugReportType int type) {
         // insert bug report username and title
         ContentValues values = new ContentValues();
         values.put(COLUMN_TITLE, title);
         values.put(COLUMN_TIMESTAMP, timestamp);
         values.put(COLUMN_USERNAME, username);
+        values.put(COLUMN_TYPE, type);
 
         ContentResolver r = context.getContentResolver();
         Uri uri = r.insert(BugStorageProvider.BUGREPORT_CONTENT_URI, values);
@@ -95,6 +100,7 @@
         return new MetaBugReport.Builder(id, timestamp)
                 .setTitle(title)
                 .setUserName(username)
+                .setType(type)
                 .build();
     }
 
@@ -157,7 +163,19 @@
         return getBugreports(context, null, null, COLUMN_ID + " DESC");
     }
 
-    static List<MetaBugReport> getBugreports(
+    /** Returns {@link MetaBugReport} for given bugreport id. */
+    static Optional<MetaBugReport> findBugReport(Context context, int bugreportId) {
+        String selection = COLUMN_ID + " = ?";
+        String[] selectionArgs = new String[]{Integer.toString(bugreportId)};
+        List<MetaBugReport> bugs = BugStorageUtils.getBugreports(
+                context, selection, selectionArgs, null);
+        if (bugs.isEmpty()) {
+            return Optional.empty();
+        }
+        return Optional.of(bugs.get(0));
+    }
+
+    private static List<MetaBugReport> getBugreports(
             Context context, String selection, String[] selectionArgs, String order) {
         ArrayList<MetaBugReport> bugReports = new ArrayList<>();
         String[] projection = {
@@ -167,7 +185,8 @@
                 COLUMN_TIMESTAMP,
                 COLUMN_FILEPATH,
                 COLUMN_STATUS,
-                COLUMN_STATUS_MESSAGE};
+                COLUMN_STATUS_MESSAGE,
+                COLUMN_TYPE};
         ContentResolver r = context.getContentResolver();
         Cursor c = r.query(BugStorageProvider.BUGREPORT_CONTENT_URI, projection,
                 selection, selectionArgs, order);
@@ -183,6 +202,7 @@
                     .setFilepath(getString(c, COLUMN_FILEPATH))
                     .setStatus(getInt(c, COLUMN_STATUS))
                     .setStatusMessage(getString(c, COLUMN_STATUS_MESSAGE))
+                    .setType(getInt(c, COLUMN_TYPE))
                     .build();
             bugReports.add(meta);
             c.moveToNext();
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 7a6de23..95c4685 100644
--- a/tests/BugReportApp/src/com/google/android/car/bugreport/JobSchedulingUtils.java
+++ b/tests/BugReportApp/src/com/google/android/car/bugreport/JobSchedulingUtils.java
@@ -80,7 +80,12 @@
      * 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.
      */
-    static boolean uploadByDefault() {
+    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;
+    }
 }
diff --git a/tests/BugReportApp/src/com/google/android/car/bugreport/MetaBugReport.java b/tests/BugReportApp/src/com/google/android/car/bugreport/MetaBugReport.java
index 85d3d9b..6d5e89b 100644
--- a/tests/BugReportApp/src/com/google/android/car/bugreport/MetaBugReport.java
+++ b/tests/BugReportApp/src/com/google/android/car/bugreport/MetaBugReport.java
@@ -15,11 +15,35 @@
  */
 package com.google.android.car.bugreport;
 
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.google.common.base.Strings;
+
+import java.lang.annotation.Retention;
+
 /** Represents the information that a bugreport can contain. */
 public final class MetaBugReport implements Parcelable {
+
+    /** Contains {@link #TYPE_SILENT} and audio message. */
+    static final int TYPE_INTERACTIVE = 0;
+
+    /**
+     * Contains dumpstate and screenshots.
+     *
+     * <p>Silent bugreports are not uploaded automatically. The app asks user to add audio
+     * message either through notification or {@link BugReportInfoActivity}.
+     */
+    static final int TYPE_SILENT = 1;
+
+    /** Annotation for bug report types. */
+    @Retention(SOURCE)
+    @IntDef({TYPE_INTERACTIVE, TYPE_SILENT})
+    @interface BugReportType {};
+
     private final int mId;
     private final String mTimestamp;
     private final String mTitle;
@@ -27,6 +51,8 @@
     private final String mFilePath;
     private final int mStatus;
     private final String mStatusMessage;
+    /** One of {@link BugReportType}. */
+    private final int mType;
 
     private MetaBugReport(Builder builder) {
         mId = builder.mId;
@@ -36,6 +62,7 @@
         mFilePath = builder.mFilePath;
         mStatus = builder.mStatus;
         mStatusMessage = builder.mStatusMessage;
+        mType = builder.mType;
     }
 
     /**
@@ -49,32 +76,32 @@
      * @return Username (LDAP) that created this bugreport
      */
     public String getUsername() {
-        return mUsername == null ? "" : mUsername;
+        return Strings.nullToEmpty(mUsername);
     }
 
     /**
      * @return Title of the bug.
      */
     public String getTitle() {
-        return mTitle == null ? "" : mTitle;
+        return Strings.nullToEmpty(mTitle);
     }
 
     /**
      * @return Timestamp when the bug report is initialized.
      */
     public String getTimestamp() {
-        return mTimestamp == null ? "" : mTimestamp;
+        return Strings.nullToEmpty(mTimestamp);
     }
 
     /**
      * @return path to the zip file
      */
     public String getFilePath() {
-        return mFilePath == null ? "" : mFilePath;
+        return Strings.nullToEmpty(mFilePath);
     }
 
     /**
-     * @return Status of the bug upload.
+     * @return {@link Status} of the bug upload.
      */
     public int getStatus() {
         return mStatus;
@@ -84,7 +111,14 @@
      * @return StatusMessage of the bug upload.
      */
     public String getStatusMessage() {
-        return mStatusMessage == null ? "" : mStatusMessage;
+        return Strings.nullToEmpty(mStatusMessage);
+    }
+
+    /**
+     * @return {@link BugReportType}.
+     */
+    public int getType() {
+        return mType;
     }
 
     @Override
@@ -99,7 +133,8 @@
                 .setStatus(mStatus)
                 .setStatusMessage(mStatusMessage)
                 .setTitle(mTitle)
-                .setUserName(mUsername);
+                .setUserName(mUsername)
+                .setType(mType);
     }
 
     @Override
@@ -111,6 +146,7 @@
         dest.writeString(mFilePath);
         dest.writeInt(mStatus);
         dest.writeString(mStatusMessage);
+        dest.writeInt(mType);
     }
 
     /** A creator that's used by Parcelable. */
@@ -124,12 +160,14 @@
                     String filePath = in.readString();
                     int status = in.readInt();
                     String statusMessage = in.readString();
+                    int type = in.readInt();
                     return new Builder(id, timestamp)
                             .setTitle(title)
                             .setUserName(username)
                             .setFilepath(filePath)
                             .setStatus(status)
                             .setStatusMessage(statusMessage)
+                            .setType(type)
                             .build();
                 }
 
@@ -147,6 +185,7 @@
         private String mFilePath;
         private int mStatus;
         private String mStatusMessage;
+        private int mType;
 
         /**
          * Initializes MetaBugReport.Builder.
@@ -177,7 +216,7 @@
             return this;
         }
 
-        /** Sets status. */
+        /** Sets {@link Status}. */
         public Builder setStatus(int status) {
             mStatus = status;
             return this;
@@ -189,6 +228,12 @@
             return this;
         }
 
+        /** Sets the {@link BugReportType}. */
+        public Builder setType(@BugReportType int type) {
+            mType = type;
+            return this;
+        }
+
         /** Returns a {@link MetaBugReport}. */
         public MetaBugReport build() {
             return new MetaBugReport(this);