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