1 /* 2 * Copyright (C) 2018 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.car.settings.quicksettings; 18 19 import android.annotation.Nullable; 20 import android.content.Context; 21 import android.graphics.drawable.Drawable; 22 import android.text.TextUtils; 23 import android.view.LayoutInflater; 24 import android.view.MotionEvent; 25 import android.view.View; 26 import android.view.ViewGroup; 27 import android.widget.ImageView; 28 import android.widget.SeekBar; 29 import android.widget.TextView; 30 31 import androidx.recyclerview.widget.GridLayoutManager; 32 import androidx.recyclerview.widget.RecyclerView; 33 34 import com.android.car.settings.R; 35 import com.android.car.settings.common.rotary.DirectManipulationHandler; 36 import com.android.car.settings.common.rotary.DirectManipulationState; 37 import com.android.car.settings.common.rotary.SeekBarHelper; 38 39 import java.util.ArrayList; 40 import java.util.List; 41 42 /** 43 * Controls the content in quick setting grid view. 44 */ 45 public class QuickSettingGridAdapter 46 extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements StateChangedListener { 47 private static final int SEEKBAR_VIEWTYPE = 0; 48 private static final int TILE_VIEWTYPE = 1; 49 private final int mColumnCount; 50 private final Context mContext; 51 private final LayoutInflater mInflater; 52 private final List<Tile> mTiles = new ArrayList<>(); 53 private final List<SeekbarTile> mSeekbarTiles = new ArrayList<>(); 54 private final QsSpanSizeLookup mQsSpanSizeLookup = new QsSpanSizeLookup(); 55 private final DirectManipulationState mDirectManipulationMode = new DirectManipulationState(); 56 QuickSettingGridAdapter(Context context)57 public QuickSettingGridAdapter(Context context) { 58 mContext = context; 59 mInflater = LayoutInflater.from(context); 60 mColumnCount = mContext.getResources().getInteger(R.integer.quick_setting_column_count); 61 } 62 getGridLayoutManager()63 GridLayoutManager getGridLayoutManager() { 64 GridLayoutManager gridLayoutManager = new GridLayoutManager(mContext, 65 mContext.getResources().getInteger(R.integer.quick_setting_column_count)); 66 gridLayoutManager.setSpanSizeLookup(mQsSpanSizeLookup); 67 return gridLayoutManager; 68 } 69 70 /** 71 * Represents an UI tile in the quick setting grid. 72 */ 73 interface Tile extends View.OnClickListener { 74 75 /** 76 * A state to indicate how we want to render icon, this is independent of what to show 77 * in text. 78 */ 79 enum State { 80 OFF, ON 81 } 82 83 /** 84 * Called when activity owning this tile's onStart() gets called. 85 */ start()86 void start(); 87 88 /** 89 * Called when activity owning this tile's onStop() gets called. 90 */ stop()91 void stop(); 92 getIcon()93 Drawable getIcon(); 94 95 @Nullable getText()96 String getText(); 97 getState()98 State getState(); 99 100 /** 101 * Returns {@code true} if this tile should be displayed. 102 */ isAvailable()103 boolean isAvailable(); 104 105 /** 106 * Returns a listener to call when this tile is clicked and held. Returns {@code null} if 107 * no action should be performed. 108 */ 109 @Nullable getOnLongClickListener()110 View.OnLongClickListener getOnLongClickListener(); 111 } 112 113 interface SeekbarTile extends SeekBar.OnSeekBarChangeListener { 114 /** 115 * Called when activity owning this tile's onStart() gets called. 116 */ start()117 void start(); 118 119 /** 120 * Called when activity owning this tile's onStop() gets called. 121 */ stop()122 void stop(); 123 getMax()124 int getMax(); 125 getCurrent()126 int getCurrent(); 127 } 128 addSeekbarTile(SeekbarTile seekbarTile)129 QuickSettingGridAdapter addSeekbarTile(SeekbarTile seekbarTile) { 130 mSeekbarTiles.add(seekbarTile); 131 return this; 132 } 133 addTile(Tile tile)134 QuickSettingGridAdapter addTile(Tile tile) { 135 if (tile.isAvailable()) { 136 mTiles.add(tile); 137 } 138 return this; 139 } 140 start()141 void start() { 142 for (SeekbarTile tile : mSeekbarTiles) { 143 tile.start(); 144 } 145 for (Tile tile : mTiles) { 146 tile.start(); 147 } 148 } 149 stop()150 void stop() { 151 for (SeekbarTile tile : mSeekbarTiles) { 152 tile.stop(); 153 } 154 for (Tile tile : mTiles) { 155 tile.stop(); 156 } 157 } 158 159 @Override onCreateViewHolder(ViewGroup parent, int viewType)160 public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 161 switch (viewType) { 162 case SEEKBAR_VIEWTYPE: 163 return new BrightnessViewHolder(mInflater.inflate( 164 R.layout.brightness_tile, parent, /* attachToRoot= */ false)); 165 case TILE_VIEWTYPE: 166 return new TileViewHolder(mInflater.inflate( 167 R.layout.tile, parent, /* attachToRoot= */ false)); 168 default: 169 throw new RuntimeException("unknown viewType: " + viewType); 170 } 171 } 172 173 @Override onBindViewHolder(RecyclerView.ViewHolder holder, int position)174 public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { 175 switch (holder.getItemViewType()) { 176 case SEEKBAR_VIEWTYPE: 177 BrightnessViewHolder bvh = (BrightnessViewHolder) holder; 178 SeekbarTile seekbarTile = mSeekbarTiles.get(position); 179 SeekBar seekbar = bvh.mSeekBar; 180 seekbar.setMax(seekbarTile.getMax()); 181 seekbar.setProgress(seekbarTile.getCurrent()); 182 seekbar.setOnSeekBarChangeListener(seekbarTile); 183 bvh.registerDirectManipulation(); 184 break; 185 case TILE_VIEWTYPE: 186 Tile tile = mTiles.get(position - mSeekbarTiles.size()); 187 TileViewHolder vh = (TileViewHolder) holder; 188 vh.itemView.setOnClickListener(tile); 189 View.OnLongClickListener onLongClickListener = tile.getOnLongClickListener(); 190 if (onLongClickListener != null) { 191 vh.itemView.setOnLongClickListener(onLongClickListener); 192 } else { 193 vh.itemView.setOnLongClickListener(null); 194 } 195 vh.mIcon.setImageDrawable(tile.getIcon()); 196 switch (tile.getState()) { 197 case ON: 198 vh.mIcon.setEnabled(true); 199 vh.mIconBackground.setEnabled(true); 200 break; 201 case OFF: 202 vh.mIcon.setEnabled(false); 203 vh.mIconBackground.setEnabled(false); 204 break; 205 default: 206 } 207 String textString = tile.getText(); 208 if (!TextUtils.isEmpty(textString)) { 209 vh.mText.setText(textString); 210 } 211 break; 212 default: 213 } 214 } 215 216 private class BrightnessViewHolder extends RecyclerView.ViewHolder { 217 private final SeekBar mSeekBar; 218 BrightnessViewHolder(View view)219 BrightnessViewHolder(View view) { 220 super(view); 221 mSeekBar = view.findViewById(R.id.quick_settings_brightness_seekbar); 222 } 223 registerDirectManipulation()224 void registerDirectManipulation() { 225 DirectManipulationHandler.setDirectManipulationHandler(mSeekBar, 226 new DirectManipulationHandler.Builder(mDirectManipulationMode) 227 .setCenterButtonHandler(inDirectManipulationMode -> { 228 mSeekBar.setSelected(!inDirectManipulationMode); 229 return true; 230 }) 231 .setBackHandler(inDirectManipulationMode -> { 232 if (inDirectManipulationMode) { 233 mSeekBar.setSelected(false); 234 return true; 235 } 236 return false; 237 }) 238 .setRotationHandler((v, event) -> { 239 int scrollAmount = Math.round( 240 event.getAxisValue(MotionEvent.AXIS_SCROLL)); 241 mSeekBar.incrementProgressBy( 242 scrollAmount * SeekBarHelper.getScaledSeekBarIncrement( 243 mSeekBar)); 244 return true; 245 }) 246 .build()); 247 } 248 } 249 250 private class TileViewHolder extends RecyclerView.ViewHolder { 251 private final View mIconBackground; 252 private final ImageView mIcon; 253 private final TextView mText; 254 TileViewHolder(View view)255 TileViewHolder(View view) { 256 super(view); 257 mIconBackground = view.findViewById(R.id.icon_background); 258 mIcon = view.findViewById(R.id.tile_icon); 259 mText = view.findViewById(R.id.tile_text); 260 } 261 } 262 263 class QsSpanSizeLookup extends GridLayoutManager.SpanSizeLookup { 264 265 /** 266 * Each list item takes a full row, and each tile takes only 1 span. 267 */ 268 @Override getSpanSize(int position)269 public int getSpanSize(int position) { 270 return position < mSeekbarTiles.size() ? mColumnCount : 1; 271 } 272 273 @Override getSpanIndex(int position, int spanCount)274 public int getSpanIndex(int position, int spanCount) { 275 return position < mSeekbarTiles.size() 276 ? 1 : (position - mSeekbarTiles.size()) % mColumnCount; 277 } 278 } 279 280 @Override getItemViewType(int position)281 public int getItemViewType(int position) { 282 return position < mSeekbarTiles.size() ? SEEKBAR_VIEWTYPE : TILE_VIEWTYPE; 283 } 284 285 @Override getItemCount()286 public int getItemCount() { 287 return mTiles.size() + mSeekbarTiles.size(); 288 } 289 290 @Override onStateChanged()291 public void onStateChanged() { 292 notifyDataSetChanged(); 293 } 294 } 295