blob: 4c134516f8442df5c7f20c603a9987b761941960 [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;
Jason Monk62b63a02016-02-02 15:15:31 -050022import android.graphics.Canvas;
23import android.graphics.drawable.ColorDrawable;
Jason Monk1c2fea82016-03-11 11:33:36 -050024import android.os.Handler;
Jason Monk62b63a02016-02-02 15:15:31 -050025import android.support.v4.view.ViewCompat;
26import android.support.v7.widget.GridLayoutManager.SpanSizeLookup;
27import android.support.v7.widget.RecyclerView;
28import android.support.v7.widget.RecyclerView.ItemDecoration;
29import android.support.v7.widget.RecyclerView.State;
30import android.support.v7.widget.RecyclerView.ViewHolder;
31import android.support.v7.widget.helper.ItemTouchHelper;
Jason Monk5db8a412015-10-21 15:16:23 -070032import android.view.LayoutInflater;
33import android.view.View;
Jason Monk39c98e62016-03-16 09:18:35 -040034import android.view.View.OnClickListener;
35import android.view.View.OnLayoutChangeListener;
Jason Monk5db8a412015-10-21 15:16:23 -070036import android.view.ViewGroup;
Jason Monk39c98e62016-03-16 09:18:35 -040037import android.view.accessibility.AccessibilityManager;
Jason Monk62b63a02016-02-02 15:15:31 -050038import android.widget.FrameLayout;
Jason Monk24dbd512016-03-16 14:56:42 -040039import android.widget.TextView;
Jason Monk96defbe2016-03-29 16:51:03 -040040import com.android.internal.logging.MetricsLogger;
41import com.android.internal.logging.MetricsProto;
Jason Monk5db8a412015-10-21 15:16:23 -070042import com.android.systemui.R;
Jason Monk62b63a02016-02-02 15:15:31 -050043import com.android.systemui.qs.QSIconView;
44import com.android.systemui.qs.QSTileView;
45import com.android.systemui.qs.customize.TileAdapter.Holder;
46import com.android.systemui.qs.customize.TileQueryHelper.TileInfo;
47import com.android.systemui.qs.customize.TileQueryHelper.TileStateListener;
Jason Monk96defbe2016-03-29 16:51:03 -040048import com.android.systemui.qs.external.CustomTile;
Jason Monk5db8a412015-10-21 15:16:23 -070049import com.android.systemui.statusbar.phone.QSTileHost;
Jason Monk39c98e62016-03-16 09:18:35 -040050import com.android.systemui.statusbar.phone.SystemUIDialog;
Jason Monk5db8a412015-10-21 15:16:23 -070051
52import java.util.ArrayList;
Jason Monk5db8a412015-10-21 15:16:23 -070053import java.util.List;
54
Jason Monk62b63a02016-02-02 15:15:31 -050055public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileStateListener {
Jason Monk5db8a412015-10-21 15:16:23 -070056
Jason Monk62b63a02016-02-02 15:15:31 -050057 private static final long DRAG_LENGTH = 100;
58 private static final float DRAG_SCALE = 1.2f;
59 public static final long MOVE_DURATION = 150;
Jason Monk5db8a412015-10-21 15:16:23 -070060
Jason Monk62b63a02016-02-02 15:15:31 -050061 private static final int TYPE_TILE = 0;
62 private static final int TYPE_EDIT = 1;
Jason Monk39c98e62016-03-16 09:18:35 -040063 private static final int TYPE_ACCESSIBLE_DROP = 2;
Jason Monk62b63a02016-02-02 15:15:31 -050064
Jason Monk5db8a412015-10-21 15:16:23 -070065 private final Context mContext;
66
Jason Monk1c2fea82016-03-11 11:33:36 -050067 private final Handler mHandler = new Handler();
Jason Monk62b63a02016-02-02 15:15:31 -050068 private final List<TileInfo> mTiles = new ArrayList<>();
Jason Monkb53b6c52016-02-24 17:25:49 -050069 private final ItemTouchHelper mItemTouchHelper;
Jason Monk39c98e62016-03-16 09:18:35 -040070 private final AccessibilityManager mAccessibilityManager;
Jason Monk62b63a02016-02-02 15:15:31 -050071 private int mDividerIndex;
Jason Monk39c98e62016-03-16 09:18:35 -040072 private boolean mNeedsFocus;
Jason Monk62b63a02016-02-02 15:15:31 -050073 private List<String> mCurrentSpecs;
74 private List<TileInfo> mOtherTiles;
75 private List<TileInfo> mAllTiles;
Jason Monk5db8a412015-10-21 15:16:23 -070076
Jason Monk62b63a02016-02-02 15:15:31 -050077 private Holder mCurrentDrag;
Jason Monk39c98e62016-03-16 09:18:35 -040078 private boolean mAccessibilityMoving;
79 private int mAccessibilityFromIndex;
Jason Monk62b63a02016-02-02 15:15:31 -050080
81 public TileAdapter(Context context) {
Jason Monk5db8a412015-10-21 15:16:23 -070082 mContext = context;
Jason Monk39c98e62016-03-16 09:18:35 -040083 mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
Jason Monkb53b6c52016-02-24 17:25:49 -050084 mItemTouchHelper = new ItemTouchHelper(mCallbacks);
Jason Monk62b63a02016-02-02 15:15:31 -050085 setHasStableIds(true);
Jason Monk5db8a412015-10-21 15:16:23 -070086 }
87
88 @Override
89 public long getItemId(int position) {
Jason Monk62b63a02016-02-02 15:15:31 -050090 return mTiles.get(position) != null ? mAllTiles.indexOf(mTiles.get(position)) : -1;
91 }
92
Jason Monkb53b6c52016-02-24 17:25:49 -050093 public ItemTouchHelper getItemTouchHelper() {
94 return mItemTouchHelper;
Jason Monk62b63a02016-02-02 15:15:31 -050095 }
96
97 public ItemDecoration getItemDecoration() {
98 return mDecoration;
99 }
100
101 public void saveSpecs(QSTileHost host) {
102 List<String> newSpecs = new ArrayList<>();
103 for (int i = 0; mTiles.get(i) != null; i++) {
104 newSpecs.add(mTiles.get(i).spec);
105 }
106 host.changeTiles(mCurrentSpecs, newSpecs);
107 setTileSpecs(newSpecs);
108 }
109
110 public void setTileSpecs(List<String> currentSpecs) {
111 mCurrentSpecs = currentSpecs;
112 recalcSpecs();
Jason Monk5db8a412015-10-21 15:16:23 -0700113 }
114
115 @Override
Jason Monk62b63a02016-02-02 15:15:31 -0500116 public void onTilesChanged(List<TileInfo> tiles) {
117 mAllTiles = tiles;
118 recalcSpecs();
Jason Monk5db8a412015-10-21 15:16:23 -0700119 }
120
Jason Monk62b63a02016-02-02 15:15:31 -0500121 private void recalcSpecs() {
122 if (mCurrentSpecs == null || mAllTiles == null) {
123 return;
Jason Monk5db8a412015-10-21 15:16:23 -0700124 }
Jason Monk62b63a02016-02-02 15:15:31 -0500125 mOtherTiles = new ArrayList<TileInfo>(mAllTiles);
126 mTiles.clear();
127 for (int i = 0; i < mCurrentSpecs.size(); i++) {
Jason Monkcb654cb2016-02-25 15:43:07 -0500128 final TileInfo tile = getAndRemoveOther(mCurrentSpecs.get(i));
129 if (tile != null) {
130 mTiles.add(tile);
131 }
Jason Monk5db8a412015-10-21 15:16:23 -0700132 }
Jason Monk62b63a02016-02-02 15:15:31 -0500133 mTiles.add(null);
134 mTiles.addAll(mOtherTiles);
135 mDividerIndex = mTiles.indexOf(null);
136 notifyDataSetChanged();
137 }
Jason Monk5db8a412015-10-21 15:16:23 -0700138
Jason Monk62b63a02016-02-02 15:15:31 -0500139 private TileInfo getAndRemoveOther(String s) {
140 for (int i = 0; i < mOtherTiles.size(); i++) {
141 if (mOtherTiles.get(i).spec.equals(s)) {
142 return mOtherTiles.remove(i);
Jason Monk5db8a412015-10-21 15:16:23 -0700143 }
Jason Monk62b63a02016-02-02 15:15:31 -0500144 }
145 return null;
146 }
147
148 @Override
149 public int getItemViewType(int position) {
Jason Monk39c98e62016-03-16 09:18:35 -0400150 if (mAccessibilityMoving && position == mDividerIndex - 1) {
151 return TYPE_ACCESSIBLE_DROP;
152 }
Jason Monk62b63a02016-02-02 15:15:31 -0500153 if (mTiles.get(position) == null) {
154 return TYPE_EDIT;
155 }
156 return TYPE_TILE;
157 }
158
159 @Override
160 public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
161 final Context context = parent.getContext();
162 LayoutInflater inflater = LayoutInflater.from(context);
Jason Monk39c98e62016-03-16 09:18:35 -0400163 if (viewType == TYPE_EDIT) {
Jason Monk62b63a02016-02-02 15:15:31 -0500164 return new Holder(inflater.inflate(R.layout.qs_customize_divider, parent, false));
165 }
166 FrameLayout frame = (FrameLayout) inflater.inflate(R.layout.qs_customize_tile_frame, parent,
167 false);
168 frame.addView(new QSTileView(context, new QSIconView(context)));
169 return new Holder(frame);
170 }
171
172 @Override
173 public int getItemCount() {
174 return mTiles.size();
175 }
176
177 @Override
Jason Monk39c98e62016-03-16 09:18:35 -0400178 public void onBindViewHolder(final Holder holder, final int position) {
Jason Monk24dbd512016-03-16 14:56:42 -0400179 if (holder.getItemViewType() == TYPE_EDIT) {
180 ((TextView) holder.itemView.findViewById(android.R.id.title)).setText(
181 mCurrentDrag != null ? R.string.drag_to_remove_tiles
182 : R.string.drag_to_add_tiles);
183 return;
184 }
Jason Monk39c98e62016-03-16 09:18:35 -0400185 if (holder.getItemViewType() == TYPE_ACCESSIBLE_DROP) {
186 holder.mTileView.setClickable(true);
187 holder.mTileView.setFocusable(true);
188 holder.mTileView.setFocusableInTouchMode(true);
189 holder.mTileView.setVisibility(View.VISIBLE);
190 holder.mTileView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
191 holder.mTileView.setContentDescription(mContext.getString(
192 R.string.accessibility_qs_edit_position_label, position + 1));
193 holder.mTileView.setOnClickListener(new OnClickListener() {
194 @Override
195 public void onClick(View v) {
196 selectPosition(position, v);
197 }
198 });
199 if (mNeedsFocus) {
200 // Wait for this to get laid out then set its focus.
201 holder.mTileView.addOnLayoutChangeListener(new OnLayoutChangeListener() {
202 @Override
203 public void onLayoutChange(View v, int left, int top, int right, int bottom,
204 int oldLeft, int oldTop, int oldRight, int oldBottom) {
205 holder.mTileView.removeOnLayoutChangeListener(this);
206 holder.mTileView.requestFocus();
207 }
208 });
209 mNeedsFocus = false;
210 }
211 return;
212 }
Jason Monk62b63a02016-02-02 15:15:31 -0500213
214 TileInfo info = mTiles.get(position);
Jason Monk39c98e62016-03-16 09:18:35 -0400215
216 if (position > mDividerIndex) {
217 info.state.contentDescription = mContext.getString(
218 R.string.accessibility_qs_edit_add_tile_label, info.state.label);
219 } else if (mAccessibilityMoving) {
220 info.state.contentDescription = mContext.getString(
221 R.string.accessibility_qs_edit_position_label, position + 1);
222 } else {
223 info.state.contentDescription = mContext.getString(
224 R.string.accessibility_qs_edit_tile_label, position + 1, info.state.label);
225 }
Jason Monk62b63a02016-02-02 15:15:31 -0500226 holder.mTileView.onStateChanged(info.state);
Jason Monk39c98e62016-03-16 09:18:35 -0400227
228 if (mAccessibilityManager.isTouchExplorationEnabled()) {
229 final boolean selectable = !mAccessibilityMoving || position < mDividerIndex;
230 holder.mTileView.setClickable(selectable);
231 holder.mTileView.setFocusable(selectable);
232 holder.mTileView.setImportantForAccessibility(selectable
233 ? View.IMPORTANT_FOR_ACCESSIBILITY_YES
234 : View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
235 if (selectable) {
236 holder.mTileView.setOnClickListener(new OnClickListener() {
237 @Override
238 public void onClick(View v) {
239 if (mAccessibilityMoving) {
240 selectPosition(position, v);
241 } else {
242 if (position < mDividerIndex) {
243 showAccessibilityDialog(position, v);
244 } else {
245 startAccessibleDrag(position);
246 }
247 }
248 }
249 });
250 }
251 }
252 }
253
254 private void selectPosition(int position, View v) {
255 // Remove the placeholder.
256 mTiles.remove(mDividerIndex--);
257 mAccessibilityMoving = false;
258 move(mAccessibilityFromIndex, position, v);
259 notifyDataSetChanged();
260 }
261
262 private void showAccessibilityDialog(final int position, final View v) {
263 TileInfo info = mTiles.get(position);
264 CharSequence[] options = new CharSequence[] {
265 mContext.getString(R.string.accessibility_qs_edit_move_tile, info.state.label),
266 mContext.getString(R.string.accessibility_qs_edit_remove_tile, info.state.label),
267 };
268 AlertDialog dialog = new Builder(mContext)
269 .setItems(options, new DialogInterface.OnClickListener() {
270 @Override
271 public void onClick(DialogInterface dialog, int which) {
272 if (which == 0) {
273 startAccessibleDrag(position);
274 } else {
275 move(position, mDividerIndex, v);
276 }
277 }
278 }).setNegativeButton(android.R.string.cancel, null)
279 .create();
280 SystemUIDialog.setShowForAllUsers(dialog, true);
281 SystemUIDialog.applyFlags(dialog);
282 dialog.show();
283 }
284
285 private void startAccessibleDrag(int position) {
286 mAccessibilityMoving = true;
287 mNeedsFocus = true;
288 mAccessibilityFromIndex = position;
289 // Add placeholder for last slot.
290 mTiles.add(mDividerIndex++, null);
291 notifyDataSetChanged();
Jason Monk62b63a02016-02-02 15:15:31 -0500292 }
293
294 public SpanSizeLookup getSizeLookup() {
295 return mSizeLookup;
296 }
297
Jason Monk39c98e62016-03-16 09:18:35 -0400298 private boolean move(int from, int to, View v) {
299 if (to > mDividerIndex) {
300 if (from >= mDividerIndex) {
301 return false;
302 }
303 }
304 CharSequence fromLabel = mTiles.get(from).state.label;
305 move(from, to, mTiles);
306 mDividerIndex = mTiles.indexOf(null);
307 notifyItemChanged(from);
308 notifyItemMoved(from, to);
309 CharSequence announcement;
310 if (to >= mDividerIndex) {
Jason Monk96defbe2016-03-29 16:51:03 -0400311 MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_QS_EDIT_REMOVE_SPEC,
312 strip(mTiles.get(to)));
313 MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_QS_EDIT_REMOVE,
314 from);
Jason Monk39c98e62016-03-16 09:18:35 -0400315 announcement = mContext.getString(R.string.accessibility_qs_edit_tile_removed,
316 fromLabel);
317 } else if (from >= mDividerIndex) {
Jason Monk96defbe2016-03-29 16:51:03 -0400318 MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_QS_EDIT_ADD_SPEC,
319 strip(mTiles.get(to)));
320 MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_QS_EDIT_ADD,
321 to);
Jason Monk39c98e62016-03-16 09:18:35 -0400322 announcement = mContext.getString(R.string.accessibility_qs_edit_tile_added,
323 fromLabel, (to + 1));
324 } else {
Jason Monk96defbe2016-03-29 16:51:03 -0400325 MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_QS_EDIT_MOVE_SPEC,
326 strip(mTiles.get(to)));
327 MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_QS_EDIT_MOVE,
328 to);
Jason Monk39c98e62016-03-16 09:18:35 -0400329 announcement = mContext.getString(R.string.accessibility_qs_edit_tile_moved,
330 fromLabel, (to + 1));
331 }
332 v.announceForAccessibility(announcement);
333 return true;
334 }
335
Jason Monk96defbe2016-03-29 16:51:03 -0400336 private String strip(TileInfo tileInfo) {
337 String spec = tileInfo.spec;
338 if (spec.startsWith(CustomTile.PREFIX)) {
339 ComponentName component = CustomTile.getComponentFromSpec(spec);
340 return component.getPackageName();
341 }
342 return spec;
343 }
344
Jason Monk39c98e62016-03-16 09:18:35 -0400345 private <T> void move(int from, int to, List<T> list) {
346 list.add(from > to ? to : to + 1, list.get(from));
347 list.remove(from > to ? from + 1 : from);
348 }
349
Jason Monk62b63a02016-02-02 15:15:31 -0500350 public class Holder extends ViewHolder {
351 private QSTileView mTileView;
352
353 public Holder(View itemView) {
354 super(itemView);
355 if (itemView instanceof FrameLayout) {
356 mTileView = (QSTileView) ((FrameLayout) itemView).getChildAt(0);
Jason Monk04fd2492016-02-29 14:29:26 -0500357 mTileView.setBackground(null);
Jason Monk1aec93f2016-03-01 09:39:30 -0500358 mTileView.getIcon().disableAnimation();
Jason Monk5db8a412015-10-21 15:16:23 -0700359 }
Jason Monk5db8a412015-10-21 15:16:23 -0700360 }
361
Jason Monk62b63a02016-02-02 15:15:31 -0500362 public void startDrag() {
363 itemView.animate()
364 .setDuration(DRAG_LENGTH)
365 .scaleX(DRAG_SCALE)
366 .scaleY(DRAG_SCALE);
367 mTileView.findViewById(R.id.tile_label).animate()
368 .setDuration(DRAG_LENGTH)
369 .alpha(0);
370 }
371
372 public void stopDrag() {
373 itemView.animate()
374 .setDuration(DRAG_LENGTH)
375 .scaleX(1)
376 .scaleY(1);
377 mTileView.findViewById(R.id.tile_label).animate()
378 .setDuration(DRAG_LENGTH)
379 .alpha(1);
Jason Monk5db8a412015-10-21 15:16:23 -0700380 }
381 }
382
Jason Monk62b63a02016-02-02 15:15:31 -0500383 private final SpanSizeLookup mSizeLookup = new SpanSizeLookup() {
Jason Monk5db8a412015-10-21 15:16:23 -0700384 @Override
Jason Monk62b63a02016-02-02 15:15:31 -0500385 public int getSpanSize(int position) {
386 return getItemViewType(position) == TYPE_EDIT ? 3 : 1;
387 }
388 };
389
390 private final ItemDecoration mDecoration = new ItemDecoration() {
391 // TODO: Move this to resource.
392 private final ColorDrawable mDrawable = new ColorDrawable(0xff384248);
393
394 @Override
395 public void onDraw(Canvas c, RecyclerView parent, State state) {
396 super.onDraw(c, parent, state);
397
398 final int childCount = parent.getChildCount();
399 final int width = parent.getWidth();
400 final int bottom = parent.getBottom();
401 for (int i = 0; i < childCount; i++) {
402 final View child = parent.getChildAt(i);
403 final ViewHolder holder = parent.getChildViewHolder(child);
404 if (holder.getAdapterPosition() < mDividerIndex) {
Jason Monk5db8a412015-10-21 15:16:23 -0700405 continue;
406 }
Jason Monk62b63a02016-02-02 15:15:31 -0500407
408 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
409 .getLayoutParams();
410 final int top = child.getTop() + params.topMargin +
411 Math.round(ViewCompat.getTranslationY(child));
412 // Draw full width, in case there aren't tiles all the way across.
413 mDrawable.setBounds(0, top, width, bottom);
414 mDrawable.draw(c);
415 break;
Jason Monk5db8a412015-10-21 15:16:23 -0700416 }
Jason Monk62b63a02016-02-02 15:15:31 -0500417 }
418 };
419
420 private final ItemTouchHelper.Callback mCallbacks = new ItemTouchHelper.Callback() {
421
422 @Override
423 public boolean isLongPressDragEnabled() {
424 return true;
Jason Monk5db8a412015-10-21 15:16:23 -0700425 }
426
427 @Override
Jason Monk62b63a02016-02-02 15:15:31 -0500428 public boolean isItemViewSwipeEnabled() {
429 return false;
Jason Monk5db8a412015-10-21 15:16:23 -0700430 }
Jason Monk5db8a412015-10-21 15:16:23 -0700431
Jason Monk62b63a02016-02-02 15:15:31 -0500432 @Override
433 public void onSelectedChanged(ViewHolder viewHolder, int actionState) {
434 super.onSelectedChanged(viewHolder, actionState);
435 if (mCurrentDrag != null) {
436 mCurrentDrag.stopDrag();
Jason Monk24dbd512016-03-16 14:56:42 -0400437 mCurrentDrag = null;
Jason Monk62b63a02016-02-02 15:15:31 -0500438 }
439 if (viewHolder != null) {
440 mCurrentDrag = (Holder) viewHolder;
441 mCurrentDrag.startDrag();
442 }
Jason Monk1c2fea82016-03-11 11:33:36 -0500443 mHandler.post(new Runnable() {
444 @Override
445 public void run() {
446 notifyItemChanged(mDividerIndex);
447 }
448 });
Jason Monk62b63a02016-02-02 15:15:31 -0500449 }
450
451 @Override
452 public int getMovementFlags(RecyclerView recyclerView, ViewHolder viewHolder) {
453 if (viewHolder.getItemViewType() == TYPE_EDIT) {
454 return makeMovementFlags(0, 0);
455 }
456 int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.RIGHT
457 | ItemTouchHelper.LEFT;
458 return makeMovementFlags(dragFlags, 0);
459 }
460
461 @Override
462 public boolean onMove(RecyclerView recyclerView, ViewHolder viewHolder, ViewHolder target) {
463 int from = viewHolder.getAdapterPosition();
464 int to = target.getAdapterPosition();
Jason Monk39c98e62016-03-16 09:18:35 -0400465 return move(from, to, target.itemView);
Jason Monk62b63a02016-02-02 15:15:31 -0500466 }
467
468 @Override
469 public void onSwiped(ViewHolder viewHolder, int direction) {
470 }
471 };
Jason Monk5db8a412015-10-21 15:16:23 -0700472}