blob: bdc5e7d75b4887ffded6d5a33ff493085bb16bfc [file] [log] [blame]
Jason Monk5db8a412015-10-21 15:16:23 -07001/*
Jason Monk62b63a02016-02-02 15:15:31 -05002 * Copyright (C) 2016 The Android Open Source Project
Jason Monk5db8a412015-10-21 15:16:23 -07003 *
Jason Monk62b63a02016-02-02 15:15:31 -05004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 * except in compliance with the License. You may obtain a copy of the License at
Jason Monk5db8a412015-10-21 15:16:23 -07006 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
Jason Monk62b63a02016-02-02 15:15:31 -05009 * Unless required by applicable law or agreed to in writing, software distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11 * KIND, either express or implied. See the License for the specific language governing
12 * permissions and limitations under the License.
Jason Monk5db8a412015-10-21 15:16:23 -070013 */
14
15package com.android.systemui.qs.customize;
16
Jason Monk39c98e62016-03-16 09:18:35 -040017import android.app.AlertDialog;
18import android.app.AlertDialog.Builder;
Jason Monk96defbe2016-03-29 16:51:03 -040019import android.content.ComponentName;
Jason Monk5db8a412015-10-21 15:16:23 -070020import android.content.Context;
Jason Monk39c98e62016-03-16 09:18:35 -040021import android.content.DialogInterface;
Andrew Sappersteinc11df252016-06-19 19:12:11 -070022import android.content.res.TypedArray;
Jason Monk62b63a02016-02-02 15:15:31 -050023import android.graphics.Canvas;
24import android.graphics.drawable.ColorDrawable;
Jason Monk1c2fea82016-03-11 11:33:36 -050025import android.os.Handler;
Jason Monk62b63a02016-02-02 15:15:31 -050026import android.support.v4.view.ViewCompat;
27import android.support.v7.widget.GridLayoutManager.SpanSizeLookup;
28import android.support.v7.widget.RecyclerView;
29import android.support.v7.widget.RecyclerView.ItemDecoration;
30import android.support.v7.widget.RecyclerView.State;
31import android.support.v7.widget.RecyclerView.ViewHolder;
32import android.support.v7.widget.helper.ItemTouchHelper;
Jason Monk5db8a412015-10-21 15:16:23 -070033import android.view.LayoutInflater;
34import android.view.View;
Jason Monk39c98e62016-03-16 09:18:35 -040035import android.view.View.OnClickListener;
36import android.view.View.OnLayoutChangeListener;
Jason Monk5db8a412015-10-21 15:16:23 -070037import android.view.ViewGroup;
Jason Monk39c98e62016-03-16 09:18:35 -040038import android.view.accessibility.AccessibilityManager;
Jason Monk62b63a02016-02-02 15:15:31 -050039import android.widget.FrameLayout;
Jason Monk24dbd512016-03-16 14:56:42 -040040import android.widget.TextView;
Jason Monk0c0df792016-06-17 13:15:34 -040041
Jason Monk96defbe2016-03-29 16:51:03 -040042import com.android.internal.logging.MetricsLogger;
Tamas Berghammer383db5eb2016-06-22 15:21:38 +010043import com.android.internal.logging.nano.MetricsProto;
Jason Monk5db8a412015-10-21 15:16:23 -070044import com.android.systemui.R;
Jason Monk702e2eb2017-03-03 16:53:44 -050045import com.android.systemui.qs.tileimpl.QSIconViewImpl;
Jason Monk62b63a02016-02-02 15:15:31 -050046import com.android.systemui.qs.customize.TileAdapter.Holder;
47import com.android.systemui.qs.customize.TileQueryHelper.TileInfo;
48import com.android.systemui.qs.customize.TileQueryHelper.TileStateListener;
Jason Monk96defbe2016-03-29 16:51:03 -040049import com.android.systemui.qs.external.CustomTile;
Jason Monke5b770e2017-03-03 21:49:29 -050050import com.android.systemui.qs.QSTileHost;
Jason Monk39c98e62016-03-16 09:18:35 -040051import com.android.systemui.statusbar.phone.SystemUIDialog;
Jason Monk5db8a412015-10-21 15:16:23 -070052
53import java.util.ArrayList;
Jason Monk5db8a412015-10-21 15:16:23 -070054import java.util.List;
55
Jason Monk62b63a02016-02-02 15:15:31 -050056public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileStateListener {
Jason Monk5db8a412015-10-21 15:16:23 -070057
Jason Monk62b63a02016-02-02 15:15:31 -050058 private static final long DRAG_LENGTH = 100;
59 private static final float DRAG_SCALE = 1.2f;
60 public static final long MOVE_DURATION = 150;
Jason Monk5db8a412015-10-21 15:16:23 -070061
Jason Monk62b63a02016-02-02 15:15:31 -050062 private static final int TYPE_TILE = 0;
63 private static final int TYPE_EDIT = 1;
Jason Monk39c98e62016-03-16 09:18:35 -040064 private static final int TYPE_ACCESSIBLE_DROP = 2;
Jason Monk0b349ad2016-04-04 11:18:03 -040065 private static final int TYPE_DIVIDER = 4;
66
67 private static final long EDIT_ID = 10000;
68 private static final long DIVIDER_ID = 20000;
Jason Monk62b63a02016-02-02 15:15:31 -050069
Jason Monk5db8a412015-10-21 15:16:23 -070070 private final Context mContext;
71
Jason Monk1c2fea82016-03-11 11:33:36 -050072 private final Handler mHandler = new Handler();
Jason Monk62b63a02016-02-02 15:15:31 -050073 private final List<TileInfo> mTiles = new ArrayList<>();
Jason Monkb53b6c52016-02-24 17:25:49 -050074 private final ItemTouchHelper mItemTouchHelper;
Andrew Sappersteinc11df252016-06-19 19:12:11 -070075 private final ItemDecoration mDecoration;
Jason Monk39c98e62016-03-16 09:18:35 -040076 private final AccessibilityManager mAccessibilityManager;
Jason Monk0b349ad2016-04-04 11:18:03 -040077 private int mEditIndex;
78 private int mTileDividerIndex;
Jason Monk39c98e62016-03-16 09:18:35 -040079 private boolean mNeedsFocus;
Jason Monk62b63a02016-02-02 15:15:31 -050080 private List<String> mCurrentSpecs;
81 private List<TileInfo> mOtherTiles;
82 private List<TileInfo> mAllTiles;
Jason Monk5db8a412015-10-21 15:16:23 -070083
Jason Monk62b63a02016-02-02 15:15:31 -050084 private Holder mCurrentDrag;
Jason Monk39c98e62016-03-16 09:18:35 -040085 private boolean mAccessibilityMoving;
86 private int mAccessibilityFromIndex;
Jason Monk63554ed2016-04-29 14:58:29 -040087 private QSTileHost mHost;
Jason Monk62b63a02016-02-02 15:15:31 -050088
89 public TileAdapter(Context context) {
Jason Monk5db8a412015-10-21 15:16:23 -070090 mContext = context;
Jason Monk39c98e62016-03-16 09:18:35 -040091 mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
Jason Monkb53b6c52016-02-24 17:25:49 -050092 mItemTouchHelper = new ItemTouchHelper(mCallbacks);
Andrew Sappersteinc11df252016-06-19 19:12:11 -070093 mDecoration = new TileItemDecoration(context);
Jason Monk5db8a412015-10-21 15:16:23 -070094 }
95
Jason Monk63554ed2016-04-29 14:58:29 -040096 public void setHost(QSTileHost host) {
97 mHost = host;
98 }
99
Jason Monkb53b6c52016-02-24 17:25:49 -0500100 public ItemTouchHelper getItemTouchHelper() {
101 return mItemTouchHelper;
Jason Monk62b63a02016-02-02 15:15:31 -0500102 }
103
104 public ItemDecoration getItemDecoration() {
105 return mDecoration;
106 }
107
108 public void saveSpecs(QSTileHost host) {
109 List<String> newSpecs = new ArrayList<>();
Jason Monk89e6f012016-04-06 14:32:54 -0400110 for (int i = 0; i < mTiles.size() && mTiles.get(i) != null; i++) {
Jason Monk62b63a02016-02-02 15:15:31 -0500111 newSpecs.add(mTiles.get(i).spec);
112 }
113 host.changeTiles(mCurrentSpecs, newSpecs);
Jason Monk63554ed2016-04-29 14:58:29 -0400114 mCurrentSpecs = newSpecs;
Jason Monk62b63a02016-02-02 15:15:31 -0500115 }
116
Jason Monk903556b2017-03-29 13:13:51 -0400117 public void resetTileSpecs(QSTileHost host, List<String> specs) {
118 // Notify the host so the tiles get removed callbacks.
119 host.changeTiles(mCurrentSpecs, specs);
120 setTileSpecs(specs);
121 }
122
Jason Monk62b63a02016-02-02 15:15:31 -0500123 public void setTileSpecs(List<String> currentSpecs) {
Jason Monkb213ac42016-06-20 10:27:27 -0400124 if (currentSpecs.equals(mCurrentSpecs)) {
125 return;
126 }
Jason Monk62b63a02016-02-02 15:15:31 -0500127 mCurrentSpecs = currentSpecs;
128 recalcSpecs();
Jason Monk5db8a412015-10-21 15:16:23 -0700129 }
130
131 @Override
Jason Monk62b63a02016-02-02 15:15:31 -0500132 public void onTilesChanged(List<TileInfo> tiles) {
133 mAllTiles = tiles;
134 recalcSpecs();
Jason Monk5db8a412015-10-21 15:16:23 -0700135 }
136
Jason Monk62b63a02016-02-02 15:15:31 -0500137 private void recalcSpecs() {
138 if (mCurrentSpecs == null || mAllTiles == null) {
139 return;
Jason Monk5db8a412015-10-21 15:16:23 -0700140 }
Jason Monk62b63a02016-02-02 15:15:31 -0500141 mOtherTiles = new ArrayList<TileInfo>(mAllTiles);
142 mTiles.clear();
143 for (int i = 0; i < mCurrentSpecs.size(); i++) {
Jason Monkcb654cb2016-02-25 15:43:07 -0500144 final TileInfo tile = getAndRemoveOther(mCurrentSpecs.get(i));
145 if (tile != null) {
146 mTiles.add(tile);
147 }
Jason Monk5db8a412015-10-21 15:16:23 -0700148 }
Jason Monk62b63a02016-02-02 15:15:31 -0500149 mTiles.add(null);
Jason Monk0b349ad2016-04-04 11:18:03 -0400150 for (int i = 0; i < mOtherTiles.size(); i++) {
151 final TileInfo tile = mOtherTiles.get(i);
152 if (tile.isSystem) {
153 mOtherTiles.remove(i--);
154 mTiles.add(tile);
155 }
156 }
Jason Monk17d03992016-05-03 11:37:49 -0400157 mTileDividerIndex = mTiles.size();
Jason Monkdd036592016-05-19 13:59:41 -0400158 mTiles.add(null);
Jason Monk62b63a02016-02-02 15:15:31 -0500159 mTiles.addAll(mOtherTiles);
Jason Monkdd036592016-05-19 13:59:41 -0400160 updateDividerLocations();
Jason Monk62b63a02016-02-02 15:15:31 -0500161 notifyDataSetChanged();
162 }
Jason Monk5db8a412015-10-21 15:16:23 -0700163
Jason Monk62b63a02016-02-02 15:15:31 -0500164 private TileInfo getAndRemoveOther(String s) {
165 for (int i = 0; i < mOtherTiles.size(); i++) {
166 if (mOtherTiles.get(i).spec.equals(s)) {
167 return mOtherTiles.remove(i);
Jason Monk5db8a412015-10-21 15:16:23 -0700168 }
Jason Monk62b63a02016-02-02 15:15:31 -0500169 }
170 return null;
171 }
172
173 @Override
174 public int getItemViewType(int position) {
Jason Monk0b349ad2016-04-04 11:18:03 -0400175 if (mAccessibilityMoving && position == mEditIndex - 1) {
Jason Monk39c98e62016-03-16 09:18:35 -0400176 return TYPE_ACCESSIBLE_DROP;
177 }
Jason Monk0b349ad2016-04-04 11:18:03 -0400178 if (position == mTileDividerIndex) {
179 return TYPE_DIVIDER;
180 }
Jason Monk62b63a02016-02-02 15:15:31 -0500181 if (mTiles.get(position) == null) {
182 return TYPE_EDIT;
183 }
184 return TYPE_TILE;
185 }
186
187 @Override
188 public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
189 final Context context = parent.getContext();
190 LayoutInflater inflater = LayoutInflater.from(context);
Jason Monk0b349ad2016-04-04 11:18:03 -0400191 if (viewType == TYPE_DIVIDER) {
192 return new Holder(inflater.inflate(R.layout.qs_customize_tile_divider, parent, false));
193 }
Jason Monk39c98e62016-03-16 09:18:35 -0400194 if (viewType == TYPE_EDIT) {
Jason Monk62b63a02016-02-02 15:15:31 -0500195 return new Holder(inflater.inflate(R.layout.qs_customize_divider, parent, false));
196 }
197 FrameLayout frame = (FrameLayout) inflater.inflate(R.layout.qs_customize_tile_frame, parent,
198 false);
Jason Monk702e2eb2017-03-03 16:53:44 -0500199 frame.addView(new CustomizeTileView(context, new QSIconViewImpl(context)));
Jason Monk62b63a02016-02-02 15:15:31 -0500200 return new Holder(frame);
201 }
202
203 @Override
204 public int getItemCount() {
205 return mTiles.size();
206 }
207
208 @Override
Jason Monk17d03992016-05-03 11:37:49 -0400209 public boolean onFailedToRecycleView(Holder holder) {
210 holder.clearDrag();
211 return true;
212 }
213
214 @Override
215 public void onBindViewHolder(final Holder holder, int position) {
Jason Monk0b349ad2016-04-04 11:18:03 -0400216 if (holder.getItemViewType() == TYPE_DIVIDER) {
Jason Monkdd036592016-05-19 13:59:41 -0400217 holder.itemView.setVisibility(mTileDividerIndex < mTiles.size() - 1 ? View.VISIBLE
218 : View.INVISIBLE);
Jason Monk0b349ad2016-04-04 11:18:03 -0400219 return;
220 }
Jason Monk24dbd512016-03-16 14:56:42 -0400221 if (holder.getItemViewType() == TYPE_EDIT) {
222 ((TextView) holder.itemView.findViewById(android.R.id.title)).setText(
223 mCurrentDrag != null ? R.string.drag_to_remove_tiles
224 : R.string.drag_to_add_tiles);
225 return;
226 }
Jason Monk39c98e62016-03-16 09:18:35 -0400227 if (holder.getItemViewType() == TYPE_ACCESSIBLE_DROP) {
228 holder.mTileView.setClickable(true);
229 holder.mTileView.setFocusable(true);
230 holder.mTileView.setFocusableInTouchMode(true);
231 holder.mTileView.setVisibility(View.VISIBLE);
232 holder.mTileView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
233 holder.mTileView.setContentDescription(mContext.getString(
234 R.string.accessibility_qs_edit_position_label, position + 1));
235 holder.mTileView.setOnClickListener(new OnClickListener() {
236 @Override
237 public void onClick(View v) {
Jason Monk17d03992016-05-03 11:37:49 -0400238 selectPosition(holder.getAdapterPosition(), v);
Jason Monk39c98e62016-03-16 09:18:35 -0400239 }
240 });
241 if (mNeedsFocus) {
242 // Wait for this to get laid out then set its focus.
Jason Monk0c0df792016-06-17 13:15:34 -0400243 // Ensure that tile gets laid out so we get the callback.
244 holder.mTileView.requestLayout();
Jason Monk39c98e62016-03-16 09:18:35 -0400245 holder.mTileView.addOnLayoutChangeListener(new OnLayoutChangeListener() {
246 @Override
247 public void onLayoutChange(View v, int left, int top, int right, int bottom,
248 int oldLeft, int oldTop, int oldRight, int oldBottom) {
249 holder.mTileView.removeOnLayoutChangeListener(this);
250 holder.mTileView.requestFocus();
251 }
252 });
253 mNeedsFocus = false;
254 }
255 return;
256 }
Jason Monk62b63a02016-02-02 15:15:31 -0500257
258 TileInfo info = mTiles.get(position);
Jason Monk39c98e62016-03-16 09:18:35 -0400259
Jason Monk0b349ad2016-04-04 11:18:03 -0400260 if (position > mEditIndex) {
Jason Monk39c98e62016-03-16 09:18:35 -0400261 info.state.contentDescription = mContext.getString(
262 R.string.accessibility_qs_edit_add_tile_label, info.state.label);
263 } else if (mAccessibilityMoving) {
264 info.state.contentDescription = mContext.getString(
265 R.string.accessibility_qs_edit_position_label, position + 1);
266 } else {
267 info.state.contentDescription = mContext.getString(
268 R.string.accessibility_qs_edit_tile_label, position + 1, info.state.label);
269 }
Jason Monk62b63a02016-02-02 15:15:31 -0500270 holder.mTileView.onStateChanged(info.state);
Jason Monk0b349ad2016-04-04 11:18:03 -0400271 holder.mTileView.setAppLabel(info.appLabel);
Jason Monkb213ac42016-06-20 10:27:27 -0400272 holder.mTileView.setShowAppLabel(position > mEditIndex && !info.isSystem);
Jason Monk39c98e62016-03-16 09:18:35 -0400273
274 if (mAccessibilityManager.isTouchExplorationEnabled()) {
Jason Monk0b349ad2016-04-04 11:18:03 -0400275 final boolean selectable = !mAccessibilityMoving || position < mEditIndex;
Jason Monk39c98e62016-03-16 09:18:35 -0400276 holder.mTileView.setClickable(selectable);
277 holder.mTileView.setFocusable(selectable);
278 holder.mTileView.setImportantForAccessibility(selectable
279 ? View.IMPORTANT_FOR_ACCESSIBILITY_YES
280 : View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
281 if (selectable) {
282 holder.mTileView.setOnClickListener(new OnClickListener() {
283 @Override
284 public void onClick(View v) {
Jason Monk17d03992016-05-03 11:37:49 -0400285 int position = holder.getAdapterPosition();
Jason Monk39c98e62016-03-16 09:18:35 -0400286 if (mAccessibilityMoving) {
287 selectPosition(position, v);
288 } else {
Jason Monk0b349ad2016-04-04 11:18:03 -0400289 if (position < mEditIndex) {
Jason Monk39c98e62016-03-16 09:18:35 -0400290 showAccessibilityDialog(position, v);
291 } else {
292 startAccessibleDrag(position);
293 }
294 }
295 }
296 });
297 }
298 }
299 }
300
301 private void selectPosition(int position, View v) {
302 // Remove the placeholder.
Jason Monk39c98e62016-03-16 09:18:35 -0400303 mAccessibilityMoving = false;
Jason Monkad5db292016-05-25 10:54:06 -0400304 mTiles.remove(mEditIndex--);
Jason Monka466d162016-05-04 15:10:01 -0400305 notifyItemRemoved(mEditIndex - 1);
Jason Monkc8969632016-08-05 10:08:11 -0400306 // Don't remove items when the last position is selected.
307 if (position == mEditIndex) position--;
308
Jason Monkad5db292016-05-25 10:54:06 -0400309 move(mAccessibilityFromIndex, position, v);
Jason Monk790442e2017-02-13 17:49:39 -0500310
Jason Monkad5db292016-05-25 10:54:06 -0400311 notifyDataSetChanged();
Jason Monk39c98e62016-03-16 09:18:35 -0400312 }
313
314 private void showAccessibilityDialog(final int position, final View v) {
Jason Monkb213ac42016-06-20 10:27:27 -0400315 final TileInfo info = mTiles.get(position);
Jason Monk39c98e62016-03-16 09:18:35 -0400316 CharSequence[] options = new CharSequence[] {
317 mContext.getString(R.string.accessibility_qs_edit_move_tile, info.state.label),
318 mContext.getString(R.string.accessibility_qs_edit_remove_tile, info.state.label),
319 };
320 AlertDialog dialog = new Builder(mContext)
321 .setItems(options, new DialogInterface.OnClickListener() {
322 @Override
323 public void onClick(DialogInterface dialog, int which) {
324 if (which == 0) {
325 startAccessibleDrag(position);
326 } else {
Jason Monkb213ac42016-06-20 10:27:27 -0400327 move(position, info.isSystem ? mEditIndex : mTileDividerIndex, v);
328 notifyItemChanged(mTileDividerIndex);
329 notifyDataSetChanged();
Jason Monk39c98e62016-03-16 09:18:35 -0400330 }
331 }
332 }).setNegativeButton(android.R.string.cancel, null)
333 .create();
334 SystemUIDialog.setShowForAllUsers(dialog, true);
335 SystemUIDialog.applyFlags(dialog);
336 dialog.show();
337 }
338
339 private void startAccessibleDrag(int position) {
340 mAccessibilityMoving = true;
341 mNeedsFocus = true;
342 mAccessibilityFromIndex = position;
343 // Add placeholder for last slot.
Jason Monk0b349ad2016-04-04 11:18:03 -0400344 mTiles.add(mEditIndex++, null);
Jason Monka466d162016-05-04 15:10:01 -0400345 notifyDataSetChanged();
Jason Monk62b63a02016-02-02 15:15:31 -0500346 }
347
348 public SpanSizeLookup getSizeLookup() {
349 return mSizeLookup;
350 }
351
Jason Monk39c98e62016-03-16 09:18:35 -0400352 private boolean move(int from, int to, View v) {
Jason Monkb213ac42016-06-20 10:27:27 -0400353 if (to == from) {
354 return true;
Jason Monk39c98e62016-03-16 09:18:35 -0400355 }
356 CharSequence fromLabel = mTiles.get(from).state.label;
357 move(from, to, mTiles);
Jason Monk0b349ad2016-04-04 11:18:03 -0400358 updateDividerLocations();
Jason Monk39c98e62016-03-16 09:18:35 -0400359 CharSequence announcement;
Jason Monk0b349ad2016-04-04 11:18:03 -0400360 if (to >= mEditIndex) {
Jason Monk96defbe2016-03-29 16:51:03 -0400361 MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_QS_EDIT_REMOVE_SPEC,
362 strip(mTiles.get(to)));
363 MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_QS_EDIT_REMOVE,
364 from);
Jason Monk39c98e62016-03-16 09:18:35 -0400365 announcement = mContext.getString(R.string.accessibility_qs_edit_tile_removed,
366 fromLabel);
Jason Monk0b349ad2016-04-04 11:18:03 -0400367 } else if (from >= mEditIndex) {
Jason Monk96defbe2016-03-29 16:51:03 -0400368 MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_QS_EDIT_ADD_SPEC,
369 strip(mTiles.get(to)));
370 MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_QS_EDIT_ADD,
371 to);
Jason Monk39c98e62016-03-16 09:18:35 -0400372 announcement = mContext.getString(R.string.accessibility_qs_edit_tile_added,
373 fromLabel, (to + 1));
374 } else {
Jason Monk96defbe2016-03-29 16:51:03 -0400375 MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_QS_EDIT_MOVE_SPEC,
376 strip(mTiles.get(to)));
377 MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_QS_EDIT_MOVE,
378 to);
Jason Monk39c98e62016-03-16 09:18:35 -0400379 announcement = mContext.getString(R.string.accessibility_qs_edit_tile_moved,
380 fromLabel, (to + 1));
381 }
382 v.announceForAccessibility(announcement);
Jason Monk63554ed2016-04-29 14:58:29 -0400383 saveSpecs(mHost);
Jason Monk39c98e62016-03-16 09:18:35 -0400384 return true;
385 }
386
Jason Monk0b349ad2016-04-04 11:18:03 -0400387 private void updateDividerLocations() {
388 // The first null is the edit tiles label, the second null is the tile divider.
389 // If there is no second null, then there are no non-system tiles.
390 mEditIndex = -1;
391 mTileDividerIndex = mTiles.size();
392 for (int i = 0; i < mTiles.size(); i++) {
393 if (mTiles.get(i) == null) {
394 if (mEditIndex == -1) {
395 mEditIndex = i;
396 } else {
397 mTileDividerIndex = i;
398 }
399 }
400 }
Jason Monk42317ea2016-04-14 10:53:42 -0400401 if (mTiles.size() - 1 == mTileDividerIndex) {
Jason Monkdd036592016-05-19 13:59:41 -0400402 notifyItemChanged(mTileDividerIndex);
Jason Monk0b349ad2016-04-04 11:18:03 -0400403 }
404 }
405
Jason Monkdd036592016-05-19 13:59:41 -0400406 private static String strip(TileInfo tileInfo) {
Jason Monk96defbe2016-03-29 16:51:03 -0400407 String spec = tileInfo.spec;
408 if (spec.startsWith(CustomTile.PREFIX)) {
409 ComponentName component = CustomTile.getComponentFromSpec(spec);
410 return component.getPackageName();
411 }
412 return spec;
413 }
414
Jason Monk39c98e62016-03-16 09:18:35 -0400415 private <T> void move(int from, int to, List<T> list) {
Jason Monkdd036592016-05-19 13:59:41 -0400416 list.add(to, list.remove(from));
Jason Monk17d03992016-05-03 11:37:49 -0400417 notifyItemMoved(from, to);
Jason Monk39c98e62016-03-16 09:18:35 -0400418 }
419
Jason Monk62b63a02016-02-02 15:15:31 -0500420 public class Holder extends ViewHolder {
Jason Monk0b349ad2016-04-04 11:18:03 -0400421 private CustomizeTileView mTileView;
Jason Monk62b63a02016-02-02 15:15:31 -0500422
423 public Holder(View itemView) {
424 super(itemView);
425 if (itemView instanceof FrameLayout) {
Jason Monk0b349ad2016-04-04 11:18:03 -0400426 mTileView = (CustomizeTileView) ((FrameLayout) itemView).getChildAt(0);
Jason Monk04fd2492016-02-29 14:29:26 -0500427 mTileView.setBackground(null);
Jason Monk1aec93f2016-03-01 09:39:30 -0500428 mTileView.getIcon().disableAnimation();
Jason Monk5db8a412015-10-21 15:16:23 -0700429 }
Jason Monk5db8a412015-10-21 15:16:23 -0700430 }
431
Jason Monk17d03992016-05-03 11:37:49 -0400432 public void clearDrag() {
433 itemView.clearAnimation();
434 mTileView.findViewById(R.id.tile_label).clearAnimation();
435 mTileView.findViewById(R.id.tile_label).setAlpha(1);
436 mTileView.getAppLabel().clearAnimation();
437 mTileView.getAppLabel().setAlpha(.6f);
438 }
439
Jason Monk62b63a02016-02-02 15:15:31 -0500440 public void startDrag() {
441 itemView.animate()
442 .setDuration(DRAG_LENGTH)
443 .scaleX(DRAG_SCALE)
444 .scaleY(DRAG_SCALE);
445 mTileView.findViewById(R.id.tile_label).animate()
446 .setDuration(DRAG_LENGTH)
447 .alpha(0);
Jason Monk0b349ad2016-04-04 11:18:03 -0400448 mTileView.getAppLabel().animate()
449 .setDuration(DRAG_LENGTH)
450 .alpha(0);
Jason Monk62b63a02016-02-02 15:15:31 -0500451 }
452
453 public void stopDrag() {
454 itemView.animate()
455 .setDuration(DRAG_LENGTH)
456 .scaleX(1)
457 .scaleY(1);
458 mTileView.findViewById(R.id.tile_label).animate()
459 .setDuration(DRAG_LENGTH)
460 .alpha(1);
Jason Monk0b349ad2016-04-04 11:18:03 -0400461 mTileView.getAppLabel().animate()
462 .setDuration(DRAG_LENGTH)
463 .alpha(.6f);
Jason Monk5db8a412015-10-21 15:16:23 -0700464 }
465 }
466
Jason Monk62b63a02016-02-02 15:15:31 -0500467 private final SpanSizeLookup mSizeLookup = new SpanSizeLookup() {
Jason Monk5db8a412015-10-21 15:16:23 -0700468 @Override
Jason Monk62b63a02016-02-02 15:15:31 -0500469 public int getSpanSize(int position) {
Jason Monk0b349ad2016-04-04 11:18:03 -0400470 final int type = getItemViewType(position);
471 return type == TYPE_EDIT || type == TYPE_DIVIDER ? 3 : 1;
Jason Monk62b63a02016-02-02 15:15:31 -0500472 }
473 };
474
Andrew Sappersteinc11df252016-06-19 19:12:11 -0700475 private class TileItemDecoration extends ItemDecoration {
476 private final ColorDrawable mDrawable;
477
478 private TileItemDecoration(Context context) {
479 TypedArray ta =
480 context.obtainStyledAttributes(new int[]{android.R.attr.colorSecondary});
481 mDrawable = new ColorDrawable(ta.getColor(0, 0));
482 ta.recycle();
483 }
484
Jason Monk62b63a02016-02-02 15:15:31 -0500485
486 @Override
487 public void onDraw(Canvas c, RecyclerView parent, State state) {
488 super.onDraw(c, parent, state);
489
490 final int childCount = parent.getChildCount();
491 final int width = parent.getWidth();
492 final int bottom = parent.getBottom();
493 for (int i = 0; i < childCount; i++) {
494 final View child = parent.getChildAt(i);
495 final ViewHolder holder = parent.getChildViewHolder(child);
Jason Monkb213ac42016-06-20 10:27:27 -0400496 if (holder.getAdapterPosition() < mEditIndex && !(child instanceof TextView)) {
Jason Monk5db8a412015-10-21 15:16:23 -0700497 continue;
498 }
Jason Monk62b63a02016-02-02 15:15:31 -0500499
500 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
501 .getLayoutParams();
502 final int top = child.getTop() + params.topMargin +
503 Math.round(ViewCompat.getTranslationY(child));
504 // Draw full width, in case there aren't tiles all the way across.
505 mDrawable.setBounds(0, top, width, bottom);
506 mDrawable.draw(c);
507 break;
Jason Monk5db8a412015-10-21 15:16:23 -0700508 }
Jason Monk62b63a02016-02-02 15:15:31 -0500509 }
510 };
511
512 private final ItemTouchHelper.Callback mCallbacks = new ItemTouchHelper.Callback() {
513
514 @Override
515 public boolean isLongPressDragEnabled() {
516 return true;
Jason Monk5db8a412015-10-21 15:16:23 -0700517 }
518
519 @Override
Jason Monk62b63a02016-02-02 15:15:31 -0500520 public boolean isItemViewSwipeEnabled() {
521 return false;
Jason Monk5db8a412015-10-21 15:16:23 -0700522 }
Jason Monk5db8a412015-10-21 15:16:23 -0700523
Jason Monk62b63a02016-02-02 15:15:31 -0500524 @Override
525 public void onSelectedChanged(ViewHolder viewHolder, int actionState) {
526 super.onSelectedChanged(viewHolder, actionState);
Jason Monkb213ac42016-06-20 10:27:27 -0400527 if (actionState != ItemTouchHelper.ACTION_STATE_DRAG) {
528 viewHolder = null;
529 }
530 if (viewHolder == mCurrentDrag) return;
Jason Monk62b63a02016-02-02 15:15:31 -0500531 if (mCurrentDrag != null) {
Jason Monkb213ac42016-06-20 10:27:27 -0400532 int position = mCurrentDrag.getAdapterPosition();
533 TileInfo info = mTiles.get(position);
534 mCurrentDrag.mTileView.setShowAppLabel(
535 position > mEditIndex && !info.isSystem);
Jason Monk62b63a02016-02-02 15:15:31 -0500536 mCurrentDrag.stopDrag();
Jason Monk24dbd512016-03-16 14:56:42 -0400537 mCurrentDrag = null;
Jason Monk62b63a02016-02-02 15:15:31 -0500538 }
539 if (viewHolder != null) {
540 mCurrentDrag = (Holder) viewHolder;
541 mCurrentDrag.startDrag();
542 }
Jason Monk1c2fea82016-03-11 11:33:36 -0500543 mHandler.post(new Runnable() {
544 @Override
545 public void run() {
Jason Monk0b349ad2016-04-04 11:18:03 -0400546 notifyItemChanged(mEditIndex);
Jason Monk1c2fea82016-03-11 11:33:36 -0500547 }
548 });
Jason Monk62b63a02016-02-02 15:15:31 -0500549 }
550
551 @Override
Jason Monkb213ac42016-06-20 10:27:27 -0400552 public boolean canDropOver(RecyclerView recyclerView, ViewHolder current,
553 ViewHolder target) {
554 return target.getAdapterPosition() <= mEditIndex + 1;
555 }
556
557 @Override
Jason Monk62b63a02016-02-02 15:15:31 -0500558 public int getMovementFlags(RecyclerView recyclerView, ViewHolder viewHolder) {
wuliang9dde78f12017-08-18 22:28:46 +0800559 if (viewHolder.getItemViewType() == TYPE_EDIT || viewHolder.getItemViewType() == TYPE_DIVIDER) {
Jason Monk62b63a02016-02-02 15:15:31 -0500560 return makeMovementFlags(0, 0);
561 }
562 int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.RIGHT
563 | ItemTouchHelper.LEFT;
564 return makeMovementFlags(dragFlags, 0);
565 }
566
567 @Override
568 public boolean onMove(RecyclerView recyclerView, ViewHolder viewHolder, ViewHolder target) {
569 int from = viewHolder.getAdapterPosition();
570 int to = target.getAdapterPosition();
Jason Monk39c98e62016-03-16 09:18:35 -0400571 return move(from, to, target.itemView);
Jason Monk62b63a02016-02-02 15:15:31 -0500572 }
573
574 @Override
575 public void onSwiped(ViewHolder viewHolder, int direction) {
576 }
577 };
Jason Monk5db8a412015-10-21 15:16:23 -0700578}