OSDN Git Service

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