• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 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.gallery3d.ingest;
18 
19 import android.app.Activity;
20 import android.app.ProgressDialog;
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.ServiceConnection;
25 import android.content.res.Configuration;
26 import android.database.DataSetObserver;
27 import android.mtp.MtpObjectInfo;
28 import android.os.Bundle;
29 import android.os.Handler;
30 import android.os.IBinder;
31 import android.os.Message;
32 import android.support.v4.view.ViewPager;
33 import android.util.SparseBooleanArray;
34 import android.view.ActionMode;
35 import android.view.Menu;
36 import android.view.MenuInflater;
37 import android.view.MenuItem;
38 import android.view.View;
39 import android.widget.AbsListView.MultiChoiceModeListener;
40 import android.widget.AdapterView;
41 import android.widget.AdapterView.OnItemClickListener;
42 import android.widget.TextView;
43 
44 import com.android.gallery3d.R;
45 import com.android.gallery3d.ingest.adapter.CheckBroker;
46 import com.android.gallery3d.ingest.adapter.MtpAdapter;
47 import com.android.gallery3d.ingest.adapter.MtpPagerAdapter;
48 import com.android.gallery3d.ingest.data.MtpBitmapFetch;
49 import com.android.gallery3d.ingest.ui.DateTileView;
50 import com.android.gallery3d.ingest.ui.IngestGridView;
51 import com.android.gallery3d.ingest.ui.IngestGridView.OnClearChoicesListener;
52 
53 import java.lang.ref.WeakReference;
54 import java.util.Collection;
55 
56 public class IngestActivity extends Activity implements
57         MtpDeviceIndex.ProgressListener, ImportTask.Listener {
58 
59     private IngestService mHelperService;
60     private boolean mActive = false;
61     private IngestGridView mGridView;
62     private MtpAdapter mAdapter;
63     private Handler mHandler;
64     private ProgressDialog mProgressDialog;
65     private ActionMode mActiveActionMode;
66 
67     private View mWarningView;
68     private TextView mWarningText;
69     private int mLastCheckedPosition = 0;
70 
71     private ViewPager mFullscreenPager;
72     private MtpPagerAdapter mPagerAdapter;
73     private boolean mFullscreenPagerVisible = false;
74 
75     private MenuItem mMenuSwitcherItem;
76     private MenuItem mActionMenuSwitcherItem;
77 
78     // The MTP framework components don't give us fine-grained file copy
79     // progress updates, so for large photos and videos, we will be stuck
80     // with a dialog not updating for a long time. To give the user feedback,
81     // we switch to the animated indeterminate progress bar after the timeout
82     // specified by INDETERMINATE_SWITCH_TIMEOUT_MS. On the next update from
83     // the framework, we switch back to the normal progress bar.
84     private static final int INDETERMINATE_SWITCH_TIMEOUT_MS = 3000;
85 
86     @Override
onCreate(Bundle savedInstanceState)87     protected void onCreate(Bundle savedInstanceState) {
88         super.onCreate(savedInstanceState);
89         doBindHelperService();
90 
91         setContentView(R.layout.ingest_activity_item_list);
92         mGridView = (IngestGridView) findViewById(R.id.ingest_gridview);
93         mAdapter = new MtpAdapter(this);
94         mAdapter.registerDataSetObserver(mMasterObserver);
95         mGridView.setAdapter(mAdapter);
96         mGridView.setMultiChoiceModeListener(mMultiChoiceModeListener);
97         mGridView.setOnItemClickListener(mOnItemClickListener);
98         mGridView.setOnClearChoicesListener(mPositionMappingCheckBroker);
99 
100         mFullscreenPager = (ViewPager) findViewById(R.id.ingest_view_pager);
101 
102         mHandler = new ItemListHandler(this);
103 
104         MtpBitmapFetch.configureForContext(this);
105     }
106 
107     private OnItemClickListener mOnItemClickListener = new OnItemClickListener() {
108         @Override
109         public void onItemClick(AdapterView<?> adapterView, View itemView, int position, long arg3) {
110             mLastCheckedPosition = position;
111             mGridView.setItemChecked(position, !mGridView.getCheckedItemPositions().get(position));
112         }
113     };
114 
115     private MultiChoiceModeListener mMultiChoiceModeListener = new MultiChoiceModeListener() {
116         private boolean mIgnoreItemCheckedStateChanges = false;
117 
118         private void updateSelectedTitle(ActionMode mode) {
119             int count = mGridView.getCheckedItemCount();
120             mode.setTitle(getResources().getQuantityString(
121                     R.plurals.number_of_items_selected, count, count));
122         }
123 
124         @Override
125         public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
126                 boolean checked) {
127             if (mIgnoreItemCheckedStateChanges) return;
128             if (mAdapter.itemAtPositionIsBucket(position)) {
129                 SparseBooleanArray checkedItems = mGridView.getCheckedItemPositions();
130                 mIgnoreItemCheckedStateChanges = true;
131                 mGridView.setItemChecked(position, false);
132 
133                 // Takes advantage of the fact that SectionIndexer imposes the
134                 // need to clamp to the valid range
135                 int nextSectionStart = mAdapter.getPositionForSection(
136                         mAdapter.getSectionForPosition(position) + 1);
137                 if (nextSectionStart == position)
138                     nextSectionStart = mAdapter.getCount();
139 
140                 boolean rangeValue = false; // Value we want to set all of the bucket items to
141 
142                 // Determine if all the items in the bucket are currently checked, so that we
143                 // can uncheck them, otherwise we will check all items in the bucket.
144                 for (int i = position + 1; i < nextSectionStart; i++) {
145                     if (checkedItems.get(i) == false) {
146                         rangeValue = true;
147                         break;
148                     }
149                 }
150 
151                 // Set all items in the bucket to the desired state
152                 for (int i = position + 1; i < nextSectionStart; i++) {
153                     if (checkedItems.get(i) != rangeValue)
154                         mGridView.setItemChecked(i, rangeValue);
155                 }
156 
157                 mPositionMappingCheckBroker.onBulkCheckedChange();
158                 mIgnoreItemCheckedStateChanges = false;
159             } else {
160                 mPositionMappingCheckBroker.onCheckedChange(position, checked);
161             }
162             mLastCheckedPosition = position;
163             updateSelectedTitle(mode);
164         }
165 
166         @Override
167         public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
168             return onOptionsItemSelected(item);
169         }
170 
171         @Override
172         public boolean onCreateActionMode(ActionMode mode, Menu menu) {
173             MenuInflater inflater = mode.getMenuInflater();
174             inflater.inflate(R.menu.ingest_menu_item_list_selection, menu);
175             updateSelectedTitle(mode);
176             mActiveActionMode = mode;
177             mActionMenuSwitcherItem = menu.findItem(R.id.ingest_switch_view);
178             setSwitcherMenuState(mActionMenuSwitcherItem, mFullscreenPagerVisible);
179             return true;
180         }
181 
182         @Override
183         public void onDestroyActionMode(ActionMode mode) {
184             mActiveActionMode = null;
185             mActionMenuSwitcherItem = null;
186             mHandler.sendEmptyMessage(ItemListHandler.MSG_BULK_CHECKED_CHANGE);
187         }
188 
189         @Override
190         public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
191             updateSelectedTitle(mode);
192             return false;
193         }
194     };
195 
onOptionsItemSelected(MenuItem item)196     public boolean onOptionsItemSelected(MenuItem item) {
197         switch (item.getItemId()) {
198             case R.id.import_items:
199                 if (mActiveActionMode != null) {
200                     mHelperService.importSelectedItems(
201                             mGridView.getCheckedItemPositions(),
202                             mAdapter);
203                     mActiveActionMode.finish();
204                 }
205                 return true;
206             case R.id.ingest_switch_view:
207                 setFullscreenPagerVisibility(!mFullscreenPagerVisible);
208                 return true;
209             default:
210                 return false;
211         }
212     }
213 
214     @Override
onCreateOptionsMenu(Menu menu)215     public boolean onCreateOptionsMenu(Menu menu) {
216         MenuInflater inflater = getMenuInflater();
217         inflater.inflate(R.menu.ingest_menu_item_list_selection, menu);
218         mMenuSwitcherItem = menu.findItem(R.id.ingest_switch_view);
219         menu.findItem(R.id.import_items).setVisible(false);
220         setSwitcherMenuState(mMenuSwitcherItem, mFullscreenPagerVisible);
221         return true;
222     }
223 
224     @Override
onDestroy()225     protected void onDestroy() {
226         super.onDestroy();
227         doUnbindHelperService();
228     }
229 
230     @Override
onResume()231     protected void onResume() {
232         DateTileView.refreshLocale();
233         mActive = true;
234         if (mHelperService != null) mHelperService.setClientActivity(this);
235         updateWarningView();
236         super.onResume();
237     }
238 
239     @Override
onPause()240     protected void onPause() {
241         if (mHelperService != null) mHelperService.setClientActivity(null);
242         mActive = false;
243         cleanupProgressDialog();
244         super.onPause();
245     }
246 
247     @Override
onConfigurationChanged(Configuration newConfig)248     public void onConfigurationChanged(Configuration newConfig) {
249         super.onConfigurationChanged(newConfig);
250         MtpBitmapFetch.configureForContext(this);
251     }
252 
showWarningView(int textResId)253     private void showWarningView(int textResId) {
254         if (mWarningView == null) {
255             mWarningView = findViewById(R.id.ingest_warning_view);
256             mWarningText =
257                     (TextView)mWarningView.findViewById(R.id.ingest_warning_view_text);
258         }
259         mWarningText.setText(textResId);
260         mWarningView.setVisibility(View.VISIBLE);
261         setFullscreenPagerVisibility(false);
262         mGridView.setVisibility(View.GONE);
263     }
264 
hideWarningView()265     private void hideWarningView() {
266         if (mWarningView != null) {
267             mWarningView.setVisibility(View.GONE);
268             setFullscreenPagerVisibility(false);
269         }
270     }
271 
272     private PositionMappingCheckBroker mPositionMappingCheckBroker = new PositionMappingCheckBroker();
273 
274     private class PositionMappingCheckBroker extends CheckBroker
275         implements OnClearChoicesListener {
276         private int mLastMappingPager = -1;
277         private int mLastMappingGrid = -1;
278 
mapPagerToGridPosition(int position)279         private int mapPagerToGridPosition(int position) {
280             if (position != mLastMappingPager) {
281                mLastMappingPager = position;
282                mLastMappingGrid = mAdapter.translatePositionWithoutLabels(position);
283             }
284             return mLastMappingGrid;
285         }
286 
mapGridToPagerPosition(int position)287         private int mapGridToPagerPosition(int position) {
288             if (position != mLastMappingGrid) {
289                 mLastMappingGrid = position;
290                 mLastMappingPager = mPagerAdapter.translatePositionWithLabels(position);
291             }
292             return mLastMappingPager;
293         }
294 
295         @Override
setItemChecked(int position, boolean checked)296         public void setItemChecked(int position, boolean checked) {
297             mGridView.setItemChecked(mapPagerToGridPosition(position), checked);
298         }
299 
300         @Override
onCheckedChange(int position, boolean checked)301         public void onCheckedChange(int position, boolean checked) {
302             if (mPagerAdapter != null) {
303                 super.onCheckedChange(mapGridToPagerPosition(position), checked);
304             }
305         }
306 
307         @Override
isItemChecked(int position)308         public boolean isItemChecked(int position) {
309             return mGridView.getCheckedItemPositions().get(mapPagerToGridPosition(position));
310         }
311 
312         @Override
onClearChoices()313         public void onClearChoices() {
314             onBulkCheckedChange();
315         }
316     };
317 
318     private DataSetObserver mMasterObserver = new DataSetObserver() {
319         @Override
320         public void onChanged() {
321             if (mPagerAdapter != null) mPagerAdapter.notifyDataSetChanged();
322         }
323 
324         @Override
325         public void onInvalidated() {
326             if (mPagerAdapter != null) mPagerAdapter.notifyDataSetChanged();
327         }
328     };
329 
pickFullscreenStartingPosition()330     private int pickFullscreenStartingPosition() {
331         int firstVisiblePosition = mGridView.getFirstVisiblePosition();
332         if (mLastCheckedPosition <= firstVisiblePosition
333                 || mLastCheckedPosition > mGridView.getLastVisiblePosition()) {
334             return firstVisiblePosition;
335         } else {
336             return mLastCheckedPosition;
337         }
338     }
339 
setSwitcherMenuState(MenuItem menuItem, boolean inFullscreenMode)340     private void setSwitcherMenuState(MenuItem menuItem, boolean inFullscreenMode) {
341         if (menuItem == null) return;
342         if (!inFullscreenMode) {
343             menuItem.setIcon(android.R.drawable.ic_menu_zoom);
344             menuItem.setTitle(R.string.switch_photo_fullscreen);
345         } else {
346             menuItem.setIcon(android.R.drawable.ic_dialog_dialer);
347             menuItem.setTitle(R.string.switch_photo_grid);
348         }
349     }
350 
setFullscreenPagerVisibility(boolean visible)351     private void setFullscreenPagerVisibility(boolean visible) {
352         mFullscreenPagerVisible = visible;
353         if (visible) {
354             if (mPagerAdapter == null) {
355                 mPagerAdapter = new MtpPagerAdapter(this, mPositionMappingCheckBroker);
356                 mPagerAdapter.setMtpDeviceIndex(mAdapter.getMtpDeviceIndex());
357             }
358             mFullscreenPager.setAdapter(mPagerAdapter);
359             mFullscreenPager.setCurrentItem(mPagerAdapter.translatePositionWithLabels(
360                     pickFullscreenStartingPosition()), false);
361         } else if (mPagerAdapter != null) {
362             mGridView.setSelection(mAdapter.translatePositionWithoutLabels(
363                     mFullscreenPager.getCurrentItem()));
364             mFullscreenPager.setAdapter(null);
365         }
366         mGridView.setVisibility(visible ? View.INVISIBLE : View.VISIBLE);
367         mFullscreenPager.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
368         if (mActionMenuSwitcherItem != null) {
369             setSwitcherMenuState(mActionMenuSwitcherItem, visible);
370         }
371         setSwitcherMenuState(mMenuSwitcherItem, visible);
372     }
373 
updateWarningView()374     private void updateWarningView() {
375         if (!mAdapter.deviceConnected()) {
376             showWarningView(R.string.ingest_no_device);
377         } else if (mAdapter.indexReady() && mAdapter.getCount() == 0) {
378             showWarningView(R.string.ingest_empty_device);
379         } else {
380             hideWarningView();
381         }
382     }
383 
UiThreadNotifyIndexChanged()384     private void UiThreadNotifyIndexChanged() {
385         mAdapter.notifyDataSetChanged();
386         if (mActiveActionMode != null) {
387             mActiveActionMode.finish();
388             mActiveActionMode = null;
389         }
390         updateWarningView();
391     }
392 
notifyIndexChanged()393     protected void notifyIndexChanged() {
394         mHandler.sendEmptyMessage(ItemListHandler.MSG_NOTIFY_CHANGED);
395     }
396 
397     private static class ProgressState {
398         String message;
399         String title;
400         int current;
401         int max;
402 
reset()403         public void reset() {
404             title = null;
405             message = null;
406             current = 0;
407             max = 0;
408         }
409     }
410 
411     private ProgressState mProgressState = new ProgressState();
412 
413     @Override
onObjectIndexed(MtpObjectInfo object, int numVisited)414     public void onObjectIndexed(MtpObjectInfo object, int numVisited) {
415         // Not guaranteed to be called on the UI thread
416         mProgressState.reset();
417         mProgressState.max = 0;
418         mProgressState.message = getResources().getQuantityString(
419                 R.plurals.ingest_number_of_items_scanned, numVisited, numVisited);
420         mHandler.sendEmptyMessage(ItemListHandler.MSG_PROGRESS_UPDATE);
421     }
422 
423     @Override
onSorting()424     public void onSorting() {
425         // Not guaranteed to be called on the UI thread
426         mProgressState.reset();
427         mProgressState.max = 0;
428         mProgressState.message = getResources().getString(R.string.ingest_sorting);
429         mHandler.sendEmptyMessage(ItemListHandler.MSG_PROGRESS_UPDATE);
430     }
431 
432     @Override
onIndexFinish()433     public void onIndexFinish() {
434         // Not guaranteed to be called on the UI thread
435         mHandler.sendEmptyMessage(ItemListHandler.MSG_PROGRESS_HIDE);
436         mHandler.sendEmptyMessage(ItemListHandler.MSG_NOTIFY_CHANGED);
437     }
438 
439     @Override
onImportProgress(final int visitedCount, final int totalCount, String pathIfSuccessful)440     public void onImportProgress(final int visitedCount, final int totalCount,
441             String pathIfSuccessful) {
442         // Not guaranteed to be called on the UI thread
443         mProgressState.reset();
444         mProgressState.max = totalCount;
445         mProgressState.current = visitedCount;
446         mProgressState.title = getResources().getString(R.string.ingest_importing);
447         mHandler.sendEmptyMessage(ItemListHandler.MSG_PROGRESS_UPDATE);
448         mHandler.removeMessages(ItemListHandler.MSG_PROGRESS_INDETERMINATE);
449         mHandler.sendEmptyMessageDelayed(ItemListHandler.MSG_PROGRESS_INDETERMINATE,
450                 INDETERMINATE_SWITCH_TIMEOUT_MS);
451     }
452 
453     @Override
onImportFinish(Collection<MtpObjectInfo> objectsNotImported, int numVisited)454     public void onImportFinish(Collection<MtpObjectInfo> objectsNotImported,
455             int numVisited) {
456         // Not guaranteed to be called on the UI thread
457         mHandler.sendEmptyMessage(ItemListHandler.MSG_PROGRESS_HIDE);
458         mHandler.removeMessages(ItemListHandler.MSG_PROGRESS_INDETERMINATE);
459         // TODO: maybe show an extra dialog listing the ones that failed
460         // importing, if any?
461     }
462 
getProgressDialog()463     private ProgressDialog getProgressDialog() {
464         if (mProgressDialog == null || !mProgressDialog.isShowing()) {
465             mProgressDialog = new ProgressDialog(this);
466             mProgressDialog.setCancelable(false);
467         }
468         return mProgressDialog;
469     }
470 
updateProgressDialog()471     private void updateProgressDialog() {
472         ProgressDialog dialog = getProgressDialog();
473         boolean indeterminate = (mProgressState.max == 0);
474         dialog.setIndeterminate(indeterminate);
475         dialog.setProgressStyle(indeterminate ? ProgressDialog.STYLE_SPINNER
476                 : ProgressDialog.STYLE_HORIZONTAL);
477         if (mProgressState.title != null) {
478             dialog.setTitle(mProgressState.title);
479         }
480         if (mProgressState.message != null) {
481             dialog.setMessage(mProgressState.message);
482         }
483         if (!indeterminate) {
484             dialog.setProgress(mProgressState.current);
485             dialog.setMax(mProgressState.max);
486         }
487         if (!dialog.isShowing()) {
488             dialog.show();
489         }
490     }
491 
makeProgressDialogIndeterminate()492     private void makeProgressDialogIndeterminate() {
493         ProgressDialog dialog = getProgressDialog();
494         dialog.setIndeterminate(true);
495     }
496 
cleanupProgressDialog()497     private void cleanupProgressDialog() {
498         if (mProgressDialog != null) {
499             mProgressDialog.hide();
500             mProgressDialog = null;
501         }
502     }
503 
504     // This is static and uses a WeakReference in order to avoid leaking the Activity
505     private static class ItemListHandler extends Handler {
506         public static final int MSG_PROGRESS_UPDATE = 0;
507         public static final int MSG_PROGRESS_HIDE = 1;
508         public static final int MSG_NOTIFY_CHANGED = 2;
509         public static final int MSG_BULK_CHECKED_CHANGE = 3;
510         public static final int MSG_PROGRESS_INDETERMINATE = 4;
511 
512         WeakReference<IngestActivity> mParentReference;
513 
ItemListHandler(IngestActivity parent)514         public ItemListHandler(IngestActivity parent) {
515             super();
516             mParentReference = new WeakReference<IngestActivity>(parent);
517         }
518 
handleMessage(Message message)519         public void handleMessage(Message message) {
520             IngestActivity parent = mParentReference.get();
521             if (parent == null || !parent.mActive)
522                 return;
523             switch (message.what) {
524                 case MSG_PROGRESS_HIDE:
525                     parent.cleanupProgressDialog();
526                     break;
527                 case MSG_PROGRESS_UPDATE:
528                     parent.updateProgressDialog();
529                     break;
530                 case MSG_NOTIFY_CHANGED:
531                     parent.UiThreadNotifyIndexChanged();
532                     break;
533                 case MSG_BULK_CHECKED_CHANGE:
534                     parent.mPositionMappingCheckBroker.onBulkCheckedChange();
535                     break;
536                 case MSG_PROGRESS_INDETERMINATE:
537                     parent.makeProgressDialogIndeterminate();
538                     break;
539                 default:
540                     break;
541             }
542         }
543     }
544 
545     private ServiceConnection mHelperServiceConnection = new ServiceConnection() {
546         public void onServiceConnected(ComponentName className, IBinder service) {
547             mHelperService = ((IngestService.LocalBinder) service).getService();
548             mHelperService.setClientActivity(IngestActivity.this);
549             MtpDeviceIndex index = mHelperService.getIndex();
550             mAdapter.setMtpDeviceIndex(index);
551             if (mPagerAdapter != null) mPagerAdapter.setMtpDeviceIndex(index);
552         }
553 
554         public void onServiceDisconnected(ComponentName className) {
555             mHelperService = null;
556         }
557     };
558 
doBindHelperService()559     private void doBindHelperService() {
560         bindService(new Intent(getApplicationContext(), IngestService.class),
561                 mHelperServiceConnection, Context.BIND_AUTO_CREATE);
562     }
563 
doUnbindHelperService()564     private void doUnbindHelperService() {
565         if (mHelperService != null) {
566             mHelperService.setClientActivity(null);
567             unbindService(mHelperServiceConnection);
568         }
569     }
570 }
571