• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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.documentsui.selection.demo;
18 
19 import android.content.Context;
20 import android.os.Bundle;
21 import android.support.annotation.CallSuper;
22 import android.support.v7.app.AppCompatActivity;
23 import android.support.v7.widget.GridLayoutManager;
24 import android.support.v7.widget.RecyclerView;
25 import android.support.v7.widget.Toolbar;
26 import android.view.GestureDetector;
27 import android.view.HapticFeedbackConstants;
28 import android.view.Menu;
29 import android.view.MenuItem;
30 import android.view.MotionEvent;
31 import android.widget.Toast;
32 
33 import com.android.documentsui.R;
34 import com.android.documentsui.selection.BandSelectionHelper;
35 import com.android.documentsui.selection.ContentLock;
36 import com.android.documentsui.selection.DefaultBandHost;
37 import com.android.documentsui.selection.DefaultBandPredicate;
38 import com.android.documentsui.selection.DefaultSelectionHelper;
39 import com.android.documentsui.selection.GestureRouter;
40 import com.android.documentsui.selection.GestureSelectionHelper;
41 import com.android.documentsui.selection.ItemDetailsLookup;
42 import com.android.documentsui.selection.ItemDetailsLookup.ItemDetails;
43 import com.android.documentsui.selection.MotionInputHandler;
44 import com.android.documentsui.selection.MouseInputHandler;
45 import com.android.documentsui.selection.MutableSelection;
46 import com.android.documentsui.selection.Selection;
47 import com.android.documentsui.selection.SelectionHelper;
48 import com.android.documentsui.selection.SelectionHelper.SelectionPredicate;
49 import com.android.documentsui.selection.SelectionHelper.StableIdProvider;
50 import com.android.documentsui.selection.TouchEventRouter;
51 import com.android.documentsui.selection.TouchInputHandler;
52 import com.android.documentsui.selection.demo.SelectionDemoAdapter.OnBindCallback;
53 
54 /**
55  * ContentPager demo activity.
56  */
57 public class SelectionDemoActivity extends AppCompatActivity {
58 
59     private static final String EXTRA_SAVED_SELECTION = "demo-saved-selection";
60     private static final String EXTRA_COLUMN_COUNT = "demo-column-count";
61 
62     private Toolbar mToolbar;
63     private SelectionDemoAdapter mAdapter;
64     private SelectionHelper mSelectionHelper;
65 
66     private RecyclerView mRecView;
67     private GridLayoutManager mLayout;
68     private int mColumnCount = 1;  // This will get updated when layout changes.
69 
70     @Override
onCreate(Bundle savedInstanceState)71     protected void onCreate(Bundle savedInstanceState) {
72         super.onCreate(savedInstanceState);
73 
74         setContentView(R.layout.selection_demo_layout);
75         mToolbar = findViewById(R.id.toolbar);
76         setSupportActionBar(mToolbar);
77         mRecView = (RecyclerView) findViewById(R.id.list);
78 
79         mLayout = new GridLayoutManager(this, mColumnCount);
80         mRecView.setLayoutManager(mLayout);
81 
82         mAdapter = new SelectionDemoAdapter(this);
83         mRecView.setAdapter(mAdapter);
84 
85         StableIdProvider stableIds = new DemoStableIdProvider(mAdapter);
86 
87         // SelectionPredicate permits client control of which items can be selected.
88         SelectionPredicate canSelectAnything = new SelectionPredicate() {
89             @Override
90             public boolean canSetStateForId(String id, boolean nextState) {
91                 return true;
92             }
93 
94             @Override
95             public boolean canSetStateAtPosition(int position, boolean nextState) {
96                 return true;
97             }
98         };
99 
100         // TODO: Reload content when it changes. Could use CursorLoader.
101         // TODO: Retain selection. Restore when content changes.
102 
103         mSelectionHelper = new DefaultSelectionHelper(
104                 DefaultSelectionHelper.MODE_MULTIPLE,
105                 mAdapter,
106                 stableIds,
107                 canSelectAnything);
108 
109         // onBind event callback that allows items to be updated to reflect
110         // selection status when bound by recycler view.
111         // This allows us to defer initialization of the SelectionHelper dependency
112         // which itself depends on the Adapter.
113         mAdapter.addOnBindCallback(
114                 new OnBindCallback() {
115                     @Override
116                     void onBound(DemoHolder holder, int position) {
117                         String id = mAdapter.getStableId(position);
118                         holder.setSelected(mSelectionHelper.isSelected(id));
119                     }
120                 });
121 
122         ItemDetailsLookup detailsLookup = new DemoDetailsLookup(mRecView);
123 
124         // Setup basic input handling, with the touch handler as the default consumer
125         // of events. If mouse handling is configured as well, the mouse input
126         // related handlers will intercept mouse input events.
127 
128         // GestureRouter is responsible for routing GestureDetector events
129         // to tool-type specific handlers.
130         GestureRouter<MotionInputHandler> gestureRouter = new GestureRouter<>();
131         GestureDetector gestureDetector = new GestureDetector(this, gestureRouter);
132 
133         // TouchEventRouter takes its name from RecyclerView#OnItemTouchListener.
134         // Despite "Touch" being in the name, it receives events for all types of tools.
135         // This class is responsible for routing events to tool-type specific handlers,
136         // and if not handled by a handler, on to a GestureDetector for analysis.
137         TouchEventRouter eventRouter = new TouchEventRouter(gestureDetector);
138 
139         // Content lock provides a mechanism to block content reload while selection
140         // activities are active. If using a loader to load content, route
141         // the call through the content lock using ContentLock#runWhenUnlocked.
142         // This is especially useful when listening on content change notification.
143         ContentLock contentLock = new ContentLock();
144 
145         // GestureSelectionHelper provides logic that interprets a combination
146         // of motions and gestures in order to provide gesture driven selection support
147         // when used in conjunction with RecyclerView.
148         GestureSelectionHelper gestureHelper = GestureSelectionHelper.create(
149                 mSelectionHelper, mRecView, contentLock, detailsLookup);
150 
151         // Finally hook the framework up to listening to recycle view events.
152         mRecView.addOnItemTouchListener(eventRouter);
153 
154         // But before you move on, there's more work to do. Event plumbing has been
155         // installed, but we haven't registered any of our helpers or callbacks.
156         // Helpers contain predefined logic converting events into selection related events.
157         // Callbacks provide authors the ability to reponspond to other types of
158         // events (like "active" a tapped item). This is broken up into two main
159         // suites, one for "touch" and one for "mouse", though both can and should (usually)
160         // be configued to handle other types of input (to satisfy user expectation).
161 
162         // TOUCH (+ UNKNOWN) handeling provides gesture based selection allowing
163         // the user to long press on an item, then drag her finger over other
164         // items in order to extend the selection.
165         TouchCallbacks touchCallbacks = new TouchCallbacks(this, mRecView);
166 
167         // Provides high level glue for binding touch events and gestures to selection framework.
168         TouchInputHandler touchHandler = new TouchInputHandler(
169                 mSelectionHelper, detailsLookup, canSelectAnything, gestureHelper, touchCallbacks);
170 
171         eventRouter.register(MotionEvent.TOOL_TYPE_FINGER, gestureHelper);
172         eventRouter.register(MotionEvent.TOOL_TYPE_UNKNOWN, gestureHelper);
173 
174         gestureRouter.register(MotionEvent.TOOL_TYPE_FINGER, touchHandler);
175         gestureRouter.register(MotionEvent.TOOL_TYPE_UNKNOWN, touchHandler);
176 
177         // MOUSE (+ STYLUS) handeling provides band based selection allowing
178         // the user to click down in an empty area, then drag her mouse
179         // to create a band that covers the items she wants selected.
180         //
181         // PRO TIP: Don't skip installing mouse/stylus support. It provides
182         // improved productivity and demonstrates feature maturity that users
183         // will appreciate. See InputManager for details on more sophisticated
184         // strategies on detecting the presence of input tools.
185 
186         // Provides high level glue for binding mouse/stylus events and gestures
187         // to selection framework.
188         MouseInputHandler mouseHandler = new MouseInputHandler(
189                 mSelectionHelper, detailsLookup, new MouseCallbacks(this, mRecView));
190 
191         DefaultBandHost host = new DefaultBandHost(
192                 mRecView, R.drawable.selection_demo_band_overlay);
193 
194         // BandSelectionHelper provides support for band selection on-top of a RecyclerView
195         // instance. Given the recycling nature of RecyclerView BandSelectionController
196         // necessarily models and caches list/grid information as the user's pointer
197         // interacts with the item in the RecyclerView. Selectable items that intersect
198         // with the band, both on and off screen, are selected.
199         BandSelectionHelper bandHelper = new BandSelectionHelper(
200                 host,
201                 mAdapter,
202                 stableIds,
203                 mSelectionHelper,
204                 canSelectAnything,
205                 new DefaultBandPredicate(detailsLookup),
206                 contentLock);
207 
208 
209         eventRouter.register(MotionEvent.TOOL_TYPE_MOUSE, bandHelper);
210         eventRouter.register(MotionEvent.TOOL_TYPE_STYLUS, bandHelper);
211 
212         gestureRouter.register(MotionEvent.TOOL_TYPE_MOUSE, mouseHandler);
213         gestureRouter.register(MotionEvent.TOOL_TYPE_STYLUS, mouseHandler);
214 
215         // Aaaaan, all done with mouse/stylus selection setup!
216 
217         updateFromSavedState(savedInstanceState);
218     }
219 
220     @Override
onSaveInstanceState(Bundle state)221     protected void onSaveInstanceState(Bundle state) {
222         super.onSaveInstanceState(state);
223         MutableSelection selection = new MutableSelection();
224         mSelectionHelper.copySelection(selection);
225         state.putParcelable(EXTRA_SAVED_SELECTION, selection);
226         state.putInt(EXTRA_COLUMN_COUNT, mColumnCount);
227     }
228 
updateFromSavedState(Bundle state)229     private void updateFromSavedState(Bundle state) {
230         // In order to preserve selection across various lifecycle events be sure to save
231         // the selection in onSaveInstanceState, and to restore it when present in the Bundle
232         // pass in via onCreate(Bundle).
233         if (state != null) {
234             if (state.containsKey(EXTRA_SAVED_SELECTION)) {
235                 Selection savedSelection = state.getParcelable(EXTRA_SAVED_SELECTION);
236                 if (!savedSelection.isEmpty()) {
237                     mSelectionHelper.restoreSelection(savedSelection);
238                     CharSequence text = "Selection restored.";
239                     Toast.makeText(this, "Selection restored.", Toast.LENGTH_SHORT).show();
240                 }
241             }
242             if (state.containsKey(EXTRA_COLUMN_COUNT)) {
243                 mColumnCount = state.getInt(EXTRA_COLUMN_COUNT);
244                 mLayout.setSpanCount(mColumnCount);
245             }
246         }
247     }
248 
249     @Override
onCreateOptionsMenu(Menu menu)250     public boolean onCreateOptionsMenu(Menu menu) {
251         boolean showMenu = super.onCreateOptionsMenu(menu);
252         getMenuInflater().inflate(R.menu.selection_demo_actions, menu);
253         return showMenu;
254     }
255 
256     @Override
257     @CallSuper
onPrepareOptionsMenu(Menu menu)258     public boolean onPrepareOptionsMenu(Menu menu) {
259         super.onPrepareOptionsMenu(menu);
260         menu.findItem(R.id.option_menu_add_column).setEnabled(mColumnCount <= 3);
261         menu.findItem(R.id.option_menu_remove_column).setEnabled(mColumnCount > 1);
262         return true;
263     }
264 
265     @Override
onOptionsItemSelected(MenuItem item)266     public boolean onOptionsItemSelected(MenuItem item) {
267         switch (item.getItemId()) {
268             case R.id.option_menu_add_column:
269                 // TODO: Add columns
270                 mLayout.setSpanCount(++mColumnCount);
271                 return true;
272 
273             case R.id.option_menu_remove_column:
274                 mLayout.setSpanCount(--mColumnCount);
275                 return true;
276             default:
277                 return super.onOptionsItemSelected(item);
278         }
279     }
280 
281 
282     @Override
onBackPressed()283     public void onBackPressed () {
284         if (mSelectionHelper.hasSelection()) {
285             mSelectionHelper.clearSelection();
286             mSelectionHelper.clearProvisionalSelection();
287         } else {
288             super.onBackPressed();
289         }
290     }
291 
toast(Context context, String msg)292     private static void toast(Context context, String msg) {
293         Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
294     }
295 
296     @Override
onDestroy()297     protected void onDestroy() {
298         mSelectionHelper.clearSelection();
299         super.onDestroy();
300     }
301 
302     @Override
onStart()303     protected void onStart() {
304         super.onStart();
305         mAdapter.loadData();
306     }
307 
308     // Implementation of MouseInputHandler.Callbacks allows handling
309     // of higher level events, like onActivated.
310     private static final class MouseCallbacks extends MouseInputHandler.Callbacks {
311 
312         private final Context mContext;
313         private final RecyclerView mRecView;
314 
MouseCallbacks(Context context, RecyclerView recView)315         MouseCallbacks(Context context, RecyclerView recView) {
316             mContext = context;
317             mRecView = recView;
318         }
319 
320         @Override
onItemActivated(ItemDetails item, MotionEvent e)321         public boolean onItemActivated(ItemDetails item, MotionEvent e) {
322             toast(mContext, "Activate item: " + item.getStableId());
323             return true;
324         }
325 
326         @Override
onContextClick(MotionEvent e)327         public boolean onContextClick(MotionEvent e) {
328             toast(mContext, "Context click received.");
329             return true;
330         }
331 
332         @Override
onPerformHapticFeedback()333         public void onPerformHapticFeedback() {
334             mRecView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
335         }
336     };
337 
338     private static final class TouchCallbacks extends TouchInputHandler.Callbacks {
339 
340         private final Context mContext;
341         private final RecyclerView mRecView;
342 
TouchCallbacks(Context context, RecyclerView recView)343         private TouchCallbacks(Context context, RecyclerView recView) {
344 
345             mContext = context;
346             mRecView = recView;
347         }
348 
349         @Override
onItemActivated(ItemDetails item, MotionEvent e)350         public boolean onItemActivated(ItemDetails item, MotionEvent e) {
351             toast(mContext, "Activate item: " + item.getStableId());
352             return true;
353         }
354 
355         @Override
onPerformHapticFeedback()356         public void onPerformHapticFeedback() {
357             mRecView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
358         }
359     }
360 }