1 /* 2 * Copyright (C) 2020 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 17 package com.android.systemui.accessibility.floatingmenu; 18 19 import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME; 20 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.view.LayoutInflater; 24 import android.view.View; 25 import android.view.ViewGroup; 26 27 import androidx.annotation.IntDef; 28 import androidx.annotation.NonNull; 29 import androidx.annotation.VisibleForTesting; 30 import androidx.core.view.ViewCompat; 31 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; 32 import androidx.recyclerview.widget.RecyclerView; 33 import androidx.recyclerview.widget.RecyclerView.Adapter; 34 35 import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType; 36 import com.android.internal.accessibility.dialog.AccessibilityTarget; 37 import com.android.settingslib.bluetooth.HearingAidDeviceManager; 38 import com.android.settingslib.bluetooth.HearingAidDeviceManager.ConnectionStatus; 39 import com.android.systemui.accessibility.floatingmenu.AccessibilityTargetAdapter.ViewHolder; 40 import com.android.systemui.accessibility.hearingaid.HearingDeviceStatusDrawableInfo; 41 import com.android.systemui.res.R; 42 43 import java.lang.annotation.Retention; 44 import java.lang.annotation.RetentionPolicy; 45 import java.util.List; 46 47 /** 48 * An adapter which shows the set of accessibility targets that can be performed. 49 */ 50 public class AccessibilityTargetAdapter extends Adapter<ViewHolder> { 51 @VisibleForTesting static final int PAYLOAD_HEARING_STATUS_DRAWABLE = 1; 52 53 private int mIconWidthHeight; 54 private int mBadgeWidthHeight; 55 private int mItemPadding; 56 private final List<AccessibilityTarget> mTargets; 57 58 private int mHearingDeviceStatus; 59 private boolean mBadgeOnLeftSide = false; 60 61 @IntDef({ 62 ItemType.FIRST_ITEM, 63 ItemType.REGULAR_ITEM, 64 ItemType.LAST_ITEM 65 }) 66 @Retention(RetentionPolicy.SOURCE) 67 @interface ItemType { 68 int FIRST_ITEM = 0; 69 int REGULAR_ITEM = 1; 70 int LAST_ITEM = 2; 71 } 72 AccessibilityTargetAdapter(@onNull List<AccessibilityTarget> targets)73 public AccessibilityTargetAdapter(@NonNull List<AccessibilityTarget> targets) { 74 mTargets = targets; 75 } 76 77 @NonNull 78 @Override onCreateViewHolder(@onNull ViewGroup parent, @ItemType int itemType)79 public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, @ItemType int itemType) { 80 final View root = LayoutInflater.from(parent.getContext()).inflate( 81 R.layout.accessibility_floating_menu_item, parent, 82 /* attachToRoot= */ false); 83 84 if (itemType == ItemType.FIRST_ITEM) { 85 return new TopViewHolder(root); 86 } 87 88 if (itemType == ItemType.LAST_ITEM) { 89 return new BottomViewHolder(root); 90 } 91 92 return new ViewHolder(root); 93 } 94 95 @Override onBindViewHolder(@onNull ViewHolder holder, int position)96 public void onBindViewHolder(@NonNull ViewHolder holder, int position) { 97 final AccessibilityTarget target = mTargets.get(position); 98 holder.mIconView.setBackground(target.getIcon()); 99 holder.mRightBadgeView.setBackground(null); 100 holder.mLeftBadgeView.setBackground(null); 101 holder.updateIconSize(mIconWidthHeight); 102 holder.updateItemPadding(mItemPadding, getItemCount()); 103 holder.itemView.setOnClickListener((v) -> target.onSelected()); 104 holder.itemView.setStateDescription(target.getStateDescription()); 105 holder.itemView.setContentDescription(target.getLabel()); 106 107 final String clickHint = target.getFragmentType() == AccessibilityFragmentType.TOGGLE 108 ? holder.itemView.getResources().getString( 109 R.string.accessibility_floating_button_action_double_tap_to_toggle) 110 : null; 111 ViewCompat.replaceAccessibilityAction(holder.itemView, 112 AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK, 113 clickHint, /* command= */ null); 114 115 if (com.android.settingslib.flags.Flags.hearingDeviceSetConnectionStatusReport()) { 116 if (ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.equals( 117 ComponentName.unflattenFromString(target.getId()))) { 118 updateHearingDeviceStatusDrawable(holder, mHearingDeviceStatus); 119 } 120 } 121 } 122 123 @Override onBindViewHolder(@onNull ViewHolder holder, int position, @NonNull List<Object> payloads)124 public void onBindViewHolder(@NonNull ViewHolder holder, int position, 125 @NonNull List<Object> payloads) { 126 if (payloads.isEmpty()) { 127 onBindViewHolder(holder, position); 128 return; 129 } 130 131 if (com.android.settingslib.flags.Flags.hearingDeviceSetConnectionStatusReport()) { 132 payloads.forEach(payload -> { 133 if (payload instanceof Integer cmd) { 134 if (cmd == PAYLOAD_HEARING_STATUS_DRAWABLE) { 135 updateHearingDeviceStatusDrawable(holder, mHearingDeviceStatus); 136 } 137 } 138 }); 139 } 140 } 141 142 @ItemType 143 @Override getItemViewType(int position)144 public int getItemViewType(int position) { 145 // This LAST_ITEM condition should be checked before others to ensure proper padding when 146 // adding a second target via notifyItemInserted(). 147 if (position == (getItemCount() - 1)) { 148 return ItemType.LAST_ITEM; 149 } 150 151 if (position == 0) { 152 return ItemType.FIRST_ITEM; 153 } 154 155 return ItemType.REGULAR_ITEM; 156 } 157 158 @Override getItemCount()159 public int getItemCount() { 160 return mTargets.size(); 161 } 162 setIconWidthHeight(int iconWidthHeight)163 public void setIconWidthHeight(int iconWidthHeight) { 164 mIconWidthHeight = iconWidthHeight; 165 } 166 setBadgeWidthHeight(int badgeWidthHeight)167 public void setBadgeWidthHeight(int badgeWidthHeight) { 168 mBadgeWidthHeight = badgeWidthHeight; 169 } 170 setItemPadding(int itemPadding)171 public void setItemPadding(int itemPadding) { 172 mItemPadding = itemPadding; 173 } 174 setBadgeOnLeftSide(boolean leftSide)175 public void setBadgeOnLeftSide(boolean leftSide) { 176 mBadgeOnLeftSide = leftSide; 177 } 178 179 /** 180 * Notifies to update the hearing device status drawable at the given target index. 181 * 182 * @param status the connection status for hearing devices. 183 * {@link HearingAidDeviceManager.ConnectionStatus} 184 * @param targetIndex The index of the hearing aid device in the target list, or -1 if not 185 * exist. 186 */ onHearingDeviceStatusChanged(@earingAidDeviceManager.ConnectionStatus int status, int targetIndex)187 public void onHearingDeviceStatusChanged(@HearingAidDeviceManager.ConnectionStatus int status, 188 int targetIndex) { 189 mHearingDeviceStatus = status; 190 191 if (targetIndex >= 0) { 192 notifyItemChanged(targetIndex, PAYLOAD_HEARING_STATUS_DRAWABLE); 193 } 194 } 195 updateHearingDeviceStatusDrawable(ViewHolder holder, @ConnectionStatus int status)196 private void updateHearingDeviceStatusDrawable(ViewHolder holder, 197 @ConnectionStatus int status) { 198 final Context context = holder.itemView.getContext(); 199 HearingDeviceStatusDrawableInfo.StatusDrawableInfo statusDrawableInfo = 200 HearingDeviceStatusDrawableInfo.get(status); 201 final int baseDrawableId = statusDrawableInfo.baseDrawableId(); 202 final int stateDescriptionId = statusDrawableInfo.stateDescriptionId(); 203 final int indicatorDrawableId = statusDrawableInfo.indicatorDrawableId(); 204 205 holder.mIconView.setBackground( 206 (baseDrawableId != 0) ? context.getDrawable(baseDrawableId) : null); 207 holder.mRightBadgeView.setBackground( 208 (indicatorDrawableId != 0) ? context.getDrawable(indicatorDrawableId) : null); 209 holder.mLeftBadgeView.setBackground( 210 (indicatorDrawableId != 0) ? context.getDrawable(indicatorDrawableId) : null); 211 holder.itemView.setStateDescription( 212 (stateDescriptionId != 0) ? context.getString(stateDescriptionId) : null); 213 holder.updateBadgeSize(mBadgeWidthHeight); 214 215 if (mBadgeOnLeftSide) { 216 holder.mRightBadgeView.setVisibility(View.INVISIBLE); 217 holder.mLeftBadgeView.setVisibility(View.VISIBLE); 218 } else { 219 holder.mRightBadgeView.setVisibility(View.VISIBLE); 220 holder.mLeftBadgeView.setVisibility(View.INVISIBLE); 221 } 222 } 223 224 static class ViewHolder extends RecyclerView.ViewHolder { 225 final View mIconView; 226 final View mRightBadgeView; 227 final View mLeftBadgeView; 228 ViewHolder(View itemView)229 ViewHolder(View itemView) { 230 super(itemView); 231 mIconView = itemView.findViewById(R.id.icon_view); 232 mRightBadgeView = itemView.findViewById(R.id.right_badge_view); 233 mLeftBadgeView = itemView.findViewById(R.id.left_badge_view); 234 } 235 updateIconSize(int newValue)236 void updateIconSize(int newValue) { 237 final ViewGroup.LayoutParams layoutParams = mIconView.getLayoutParams(); 238 if (layoutParams.width == newValue) { 239 return; 240 } 241 layoutParams.width = newValue; 242 layoutParams.height = newValue; 243 mIconView.setLayoutParams(layoutParams); 244 } 245 updateBadgeSize(int newValue)246 void updateBadgeSize(int newValue) { 247 final ViewGroup.LayoutParams rightLayoutParams = mRightBadgeView.getLayoutParams(); 248 if (rightLayoutParams.width == newValue) { 249 return; 250 } 251 rightLayoutParams.width = newValue; 252 rightLayoutParams.height = newValue; 253 final ViewGroup.LayoutParams leftLayoutParams = mLeftBadgeView.getLayoutParams(); 254 if (leftLayoutParams.width == newValue) { 255 return; 256 } 257 leftLayoutParams.width = newValue; 258 leftLayoutParams.height = newValue; 259 260 mRightBadgeView.setLayoutParams(rightLayoutParams); 261 mLeftBadgeView.setLayoutParams(leftLayoutParams); 262 } 263 updateItemPadding(int padding, int size)264 void updateItemPadding(int padding, int size) { 265 itemView.setPaddingRelative(padding, padding, padding, 0); 266 } 267 } 268 269 static class TopViewHolder extends ViewHolder { TopViewHolder(View itemView)270 TopViewHolder(View itemView) { 271 super(itemView); 272 } 273 274 @Override updateItemPadding(int padding, int size)275 void updateItemPadding(int padding, int size) { 276 final int paddingBottom = size <= 1 ? padding : 0; 277 itemView.setPaddingRelative(padding, padding, padding, paddingBottom); 278 } 279 } 280 281 static class BottomViewHolder extends ViewHolder { BottomViewHolder(View itemView)282 BottomViewHolder(View itemView) { 283 super(itemView); 284 } 285 286 @Override updateItemPadding(int padding, int size)287 void updateItemPadding(int padding, int size) { 288 itemView.setPaddingRelative(padding, padding, padding, padding); 289 } 290 } 291 } 292