1 /* 2 * Copyright (C) 2019 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 package com.android.car.bugreport; 17 18 import android.os.Build; 19 import android.view.LayoutInflater; 20 import android.view.View; 21 import android.view.ViewGroup; 22 import android.widget.Button; 23 import android.widget.TextView; 24 25 import androidx.recyclerview.widget.RecyclerView; 26 27 import java.util.ArrayList; 28 import java.util.List; 29 30 /** 31 * Shows bugreport title, status, status message and user action buttons. "Upload to Google" button 32 * is enabled when the status is {@link Status#STATUS_PENDING_USER_ACTION}, "Move to USB" button is 33 * enabled only when status is {@link Status#STATUS_PENDING_USER_ACTION} and USB device is plugged 34 * in. 35 */ 36 public class BugInfoAdapter extends RecyclerView.Adapter<BugInfoAdapter.BugInfoViewHolder> { 37 static final int BUTTON_TYPE_UPLOAD = 0; 38 static final int BUTTON_TYPE_MOVE = 1; 39 static final int BUTTON_TYPE_ADD_AUDIO = 2; 40 41 /** If bugreport TTL points go below this number, show a notice message. */ 42 private static final int MIN_TTL_POINTS_TO_SHOW_NOTICE = 10; 43 44 /** Provides a handler for click events. */ 45 interface ItemClickedListener { 46 /** 47 * Handles click events differently depending on provided buttonType and 48 * uses additional information provided in metaBugReport. 49 * 50 * @param buttonType One of {@link #BUTTON_TYPE_UPLOAD}, {@link #BUTTON_TYPE_MOVE} or 51 * {@link #BUTTON_TYPE_ADD_AUDIO}. 52 * @param metaBugReport Selected bugreport. 53 * @param holder ViewHolder of the clicked item. 54 */ onItemClicked(int buttonType, MetaBugReport metaBugReport, BugInfoViewHolder holder)55 void onItemClicked(int buttonType, MetaBugReport metaBugReport, BugInfoViewHolder holder); 56 } 57 58 /** 59 * Reference to each bug report info views. 60 */ 61 static class BugInfoViewHolder extends RecyclerView.ViewHolder { 62 /** Title view */ 63 TextView mTitleView; 64 65 /** Status View */ 66 TextView mStatusView; 67 68 /** Message View */ 69 TextView mMessageView; 70 71 /** Expiration Notice View */ 72 TextView mExpirationNoticeView; 73 74 /** Move Button */ 75 Button mMoveButton; 76 77 /** Upload Button */ 78 Button mUploadButton; 79 80 /** Add Audio Button */ 81 Button mAddAudioButton; 82 BugInfoViewHolder(View v)83 BugInfoViewHolder(View v) { 84 super(v); 85 mTitleView = itemView.findViewById(R.id.bug_info_row_title); 86 mStatusView = itemView.findViewById(R.id.bug_info_row_status); 87 mMessageView = itemView.findViewById(R.id.bug_info_row_status_message); 88 mExpirationNoticeView = itemView.findViewById(R.id.bug_info_row_expiration_notice); 89 mMoveButton = itemView.findViewById(R.id.bug_info_move_button); 90 mUploadButton = itemView.findViewById(R.id.bug_info_upload_button); 91 mAddAudioButton = itemView.findViewById(R.id.bug_info_add_audio_button); 92 } 93 } 94 95 private List<MetaBugReport> mDataset; 96 private final ItemClickedListener mItemClickedListener; 97 private final Config mConfig; 98 BugInfoAdapter(ItemClickedListener itemClickedListener, Config config)99 BugInfoAdapter(ItemClickedListener itemClickedListener, Config config) { 100 mItemClickedListener = itemClickedListener; 101 mDataset = new ArrayList<>(); 102 mConfig = config; 103 // Allow RecyclerView to efficiently update UI; getItemId() is implemented below. 104 setHasStableIds(true); 105 } 106 107 @Override onCreateViewHolder(ViewGroup parent, int viewType)108 public BugInfoViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 109 // create a new view 110 View v = LayoutInflater.from(parent.getContext()) 111 .inflate(R.layout.bug_info_view, parent, false); 112 return new BugInfoViewHolder(v); 113 } 114 115 @Override onBindViewHolder(BugInfoViewHolder holder, int position)116 public void onBindViewHolder(BugInfoViewHolder holder, int position) { 117 MetaBugReport bugReport = mDataset.get(position); 118 holder.mTitleView.setText(bugReport.getTitle()); 119 holder.mStatusView.setText(Status.toString(bugReport.getStatus())); 120 121 showOrHideMessageView(bugReport, holder); 122 showOrHideExpirationNotice(bugReport, holder); 123 showOrHideUploadButton(bugReport, holder); 124 showOrHideMoveButton(bugReport, holder); 125 showOrHideAddAudioButton(bugReport, holder); 126 } 127 pendingUserAction(MetaBugReport bugReport)128 private boolean pendingUserAction(MetaBugReport bugReport) { 129 int bugReportStatus = bugReport.getStatus(); 130 return bugReportStatus == Status.STATUS_PENDING_USER_ACTION.getValue() 131 || bugReportStatus == Status.STATUS_MOVE_FAILED.getValue() 132 || bugReportStatus == Status.STATUS_UPLOAD_FAILED.getValue(); 133 } 134 pendingAudioRecording(MetaBugReport bugReport)135 private boolean pendingAudioRecording(MetaBugReport bugReport) { 136 return bugReport.getStatus() == Status.STATUS_AUDIO_PENDING.getValue(); 137 } 138 showOrHideMessageView(MetaBugReport bugReport, BugInfoViewHolder holder)139 private void showOrHideMessageView(MetaBugReport bugReport, BugInfoViewHolder holder) { 140 holder.mMessageView.setText(bugReport.getStatusMessage()); 141 if (bugReport.getStatusMessage().isEmpty()) { 142 holder.mMessageView.setVisibility(View.GONE); 143 } else { 144 holder.mMessageView.setVisibility(View.VISIBLE); 145 } 146 } 147 showOrHideExpirationNotice(MetaBugReport bugReport, BugInfoViewHolder holder)148 private void showOrHideExpirationNotice(MetaBugReport bugReport, BugInfoViewHolder holder) { 149 if (bugReport.getTtlPoints() <= MIN_TTL_POINTS_TO_SHOW_NOTICE 150 && BugStorageUtils.canBugReportBeExpired(bugReport.getStatus())) { 151 holder.mExpirationNoticeView.setVisibility(View.VISIBLE); 152 } else { 153 holder.mExpirationNoticeView.setVisibility(View.GONE); 154 } 155 } 156 showOrHideUploadButton(MetaBugReport bugReport, BugInfoViewHolder holder)157 private void showOrHideUploadButton(MetaBugReport bugReport, BugInfoViewHolder holder) { 158 // Enable the upload button only for userdebug/eng builds. 159 if (pendingUserAction(bugReport) && Build.IS_DEBUGGABLE) { 160 holder.mUploadButton.setText(R.string.bugreport_upload_gcs_button_text); 161 holder.mUploadButton.setEnabled(true); 162 holder.mUploadButton.setVisibility(View.VISIBLE); 163 holder.mUploadButton.setOnClickListener( 164 view -> mItemClickedListener.onItemClicked(BUTTON_TYPE_UPLOAD, bugReport, 165 holder)); 166 } else { 167 holder.mUploadButton.setVisibility(View.GONE); 168 holder.mUploadButton.setEnabled(false); 169 } 170 } 171 showOrHideMoveButton(MetaBugReport bugReport, BugInfoViewHolder holder)172 private void showOrHideMoveButton(MetaBugReport bugReport, BugInfoViewHolder holder) { 173 if (pendingUserAction(bugReport)) { 174 holder.mMoveButton.setEnabled(true); 175 holder.mMoveButton.setVisibility(View.VISIBLE); 176 holder.mMoveButton.setOnClickListener( 177 view -> mItemClickedListener.onItemClicked(BUTTON_TYPE_MOVE, bugReport, 178 holder)); 179 } else { 180 holder.mMoveButton.setEnabled(false); 181 holder.mMoveButton.setVisibility(View.GONE); 182 } 183 } 184 showOrHideAddAudioButton(MetaBugReport bugReport, BugInfoViewHolder holder)185 private void showOrHideAddAudioButton(MetaBugReport bugReport, BugInfoViewHolder holder) { 186 boolean showButton = true; 187 188 if (pendingAudioRecording(bugReport)) { 189 if (mConfig.isAutoUpload()) { 190 holder.mAddAudioButton.setText( 191 R.string.bugreport_add_audio_upload_button_text); 192 } else { 193 holder.mAddAudioButton.setText(R.string.bugreport_add_audio_button_text); 194 } 195 } else if (pendingUserAction(bugReport)) { 196 holder.mAddAudioButton.setText(R.string.bugreport_replace_audio_button_text); 197 } else { 198 showButton = false; 199 } 200 201 if (showButton) { 202 holder.mAddAudioButton.setEnabled(true); 203 holder.mAddAudioButton.setVisibility(View.VISIBLE); 204 holder.mAddAudioButton.setOnClickListener(view -> 205 mItemClickedListener.onItemClicked(BUTTON_TYPE_ADD_AUDIO, bugReport, holder)); 206 } else { 207 holder.mAddAudioButton.setEnabled(false); 208 holder.mAddAudioButton.setVisibility(View.GONE); 209 } 210 } 211 212 /** Sets dataSet; it copies the list, because it modifies it in this adapter. */ setDataset(List<MetaBugReport> bugReports)213 void setDataset(List<MetaBugReport> bugReports) { 214 mDataset = new ArrayList<>(bugReports); 215 notifyDataSetChanged(); 216 } 217 218 /** Update a bug report in the data set. */ updateBugReportInDataSet(MetaBugReport bugReport, int position)219 void updateBugReportInDataSet(MetaBugReport bugReport, int position) { 220 if (position != RecyclerView.NO_POSITION) { 221 mDataset.set(position, bugReport); 222 notifyItemChanged(position); 223 } 224 } 225 226 @Override getItemId(int position)227 public long getItemId(int position) { 228 return mDataset.get(position).getId(); 229 } 230 231 @Override getItemCount()232 public int getItemCount() { 233 return mDataset.size(); 234 } 235 } 236