2 * Copyright (C) 2013 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.gallery3d.ingest;
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;
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;
53 import java.lang.ref.WeakReference;
54 import java.util.Collection;
56 public class IngestActivity extends Activity implements
57 MtpDeviceIndex.ProgressListener, ImportTask.Listener {
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;
67 private View mWarningView;
68 private TextView mWarningText;
69 private int mLastCheckedPosition = 0;
71 private ViewPager mFullscreenPager;
72 private MtpPagerAdapter mPagerAdapter;
73 private boolean mFullscreenPagerVisible = false;
75 private MenuItem mMenuSwitcherItem;
76 private MenuItem mActionMenuSwitcherItem;
79 protected void onCreate(Bundle savedInstanceState) {
80 super.onCreate(savedInstanceState);
81 doBindHelperService();
83 setContentView(R.layout.ingest_activity_item_list);
84 mGridView = (IngestGridView) findViewById(R.id.ingest_gridview);
85 mAdapter = new MtpAdapter(this);
86 mAdapter.registerDataSetObserver(mMasterObserver);
87 mGridView.setAdapter(mAdapter);
88 mGridView.setMultiChoiceModeListener(mMultiChoiceModeListener);
89 mGridView.setOnItemClickListener(mOnItemClickListener);
90 mGridView.setOnClearChoicesListener(mPositionMappingCheckBroker);
92 mFullscreenPager = (ViewPager) findViewById(R.id.ingest_view_pager);
94 mHandler = new ItemListHandler(this);
96 MtpBitmapFetch.configureForContext(this);
99 private OnItemClickListener mOnItemClickListener = new OnItemClickListener() {
101 public void onItemClick(AdapterView<?> adapterView, View itemView, int position, long arg3) {
102 mLastCheckedPosition = position;
103 mGridView.setItemChecked(position, !mGridView.getCheckedItemPositions().get(position));
107 private MultiChoiceModeListener mMultiChoiceModeListener = new MultiChoiceModeListener() {
108 private boolean mIgnoreItemCheckedStateChanges = false;
110 private void updateSelectedTitle(ActionMode mode) {
111 int count = mGridView.getCheckedItemCount();
112 mode.setTitle(getResources().getQuantityString(
113 R.plurals.number_of_items_selected, count, count));
117 public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
119 if (mIgnoreItemCheckedStateChanges) return;
120 if (mAdapter.itemAtPositionIsBucket(position)) {
121 SparseBooleanArray checkedItems = mGridView.getCheckedItemPositions();
122 mIgnoreItemCheckedStateChanges = true;
123 mGridView.setItemChecked(position, false);
125 // Takes advantage of the fact that SectionIndexer imposes the
126 // need to clamp to the valid range
127 int nextSectionStart = mAdapter.getPositionForSection(
128 mAdapter.getSectionForPosition(position) + 1);
129 if (nextSectionStart == position)
130 nextSectionStart = mAdapter.getCount();
132 boolean rangeValue = false; // Value we want to set all of the bucket items to
134 // Determine if all the items in the bucket are currently checked, so that we
135 // can uncheck them, otherwise we will check all items in the bucket.
136 for (int i = position + 1; i < nextSectionStart; i++) {
137 if (checkedItems.get(i) == false) {
143 // Set all items in the bucket to the desired state
144 for (int i = position + 1; i < nextSectionStart; i++) {
145 if (checkedItems.get(i) != rangeValue)
146 mGridView.setItemChecked(i, rangeValue);
149 mPositionMappingCheckBroker.onBulkCheckedChange();
150 mIgnoreItemCheckedStateChanges = false;
152 mPositionMappingCheckBroker.onCheckedChange(position, checked);
154 mLastCheckedPosition = position;
155 updateSelectedTitle(mode);
159 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
160 return onOptionsItemSelected(item);
164 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
165 MenuInflater inflater = mode.getMenuInflater();
166 inflater.inflate(R.menu.ingest_menu_item_list_selection, menu);
167 updateSelectedTitle(mode);
168 mActiveActionMode = mode;
169 mActionMenuSwitcherItem = menu.findItem(R.id.ingest_switch_view);
170 setSwitcherMenuState(mActionMenuSwitcherItem, mFullscreenPagerVisible);
175 public void onDestroyActionMode(ActionMode mode) {
176 mActiveActionMode = null;
177 mActionMenuSwitcherItem = null;
178 mHandler.sendEmptyMessage(ItemListHandler.MSG_BULK_CHECKED_CHANGE);
182 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
183 updateSelectedTitle(mode);
188 public boolean onOptionsItemSelected(MenuItem item) {
189 switch (item.getItemId()) {
190 case R.id.import_items:
191 if (mActiveActionMode != null) {
192 mHelperService.importSelectedItems(
193 mGridView.getCheckedItemPositions(),
195 mActiveActionMode.finish();
198 case R.id.ingest_switch_view:
199 setFullscreenPagerVisibility(!mFullscreenPagerVisible);
207 public boolean onCreateOptionsMenu(Menu menu) {
208 MenuInflater inflater = getMenuInflater();
209 inflater.inflate(R.menu.ingest_menu_item_list_selection, menu);
210 mMenuSwitcherItem = menu.findItem(R.id.ingest_switch_view);
211 menu.findItem(R.id.import_items).setVisible(false);
212 setSwitcherMenuState(mMenuSwitcherItem, mFullscreenPagerVisible);
217 protected void onDestroy() {
219 doUnbindHelperService();
223 protected void onResume() {
224 DateTileView.refreshLocale();
226 if (mHelperService != null) mHelperService.setClientActivity(this);
232 protected void onPause() {
233 if (mHelperService != null) mHelperService.setClientActivity(null);
235 cleanupProgressDialog();
240 public void onConfigurationChanged(Configuration newConfig) {
241 super.onConfigurationChanged(newConfig);
242 MtpBitmapFetch.configureForContext(this);
245 private void showWarningView(int textResId) {
246 if (mWarningView == null) {
247 mWarningView = findViewById(R.id.ingest_warning_view);
249 (TextView)mWarningView.findViewById(R.id.ingest_warning_view_text);
251 mWarningText.setText(textResId);
252 mWarningView.setVisibility(View.VISIBLE);
253 setFullscreenPagerVisibility(false);
254 mGridView.setVisibility(View.GONE);
257 private void hideWarningView() {
258 if (mWarningView != null) {
259 mWarningView.setVisibility(View.GONE);
260 setFullscreenPagerVisibility(false);
264 private PositionMappingCheckBroker mPositionMappingCheckBroker = new PositionMappingCheckBroker();
266 private class PositionMappingCheckBroker extends CheckBroker
267 implements OnClearChoicesListener {
268 private int mLastMappingPager = -1;
269 private int mLastMappingGrid = -1;
271 private int mapPagerToGridPosition(int position) {
272 if (position != mLastMappingPager) {
273 mLastMappingPager = position;
274 mLastMappingGrid = mAdapter.translatePositionWithoutLabels(position);
276 return mLastMappingGrid;
279 private int mapGridToPagerPosition(int position) {
280 if (position != mLastMappingGrid) {
281 mLastMappingGrid = position;
282 mLastMappingPager = mPagerAdapter.translatePositionWithLabels(position);
284 return mLastMappingPager;
288 public void setItemChecked(int position, boolean checked) {
289 mGridView.setItemChecked(mapPagerToGridPosition(position), checked);
293 public void onCheckedChange(int position, boolean checked) {
294 if (mPagerAdapter != null) {
295 super.onCheckedChange(mapGridToPagerPosition(position), checked);
300 public boolean isItemChecked(int position) {
301 return mGridView.getCheckedItemPositions().get(mapPagerToGridPosition(position));
305 public void onClearChoices() {
306 onBulkCheckedChange();
310 private DataSetObserver mMasterObserver = new DataSetObserver() {
312 public void onChanged() {
313 if (mPagerAdapter != null) mPagerAdapter.notifyDataSetChanged();
317 public void onInvalidated() {
318 if (mPagerAdapter != null) mPagerAdapter.notifyDataSetChanged();
322 private int pickFullscreenStartingPosition() {
323 int firstVisiblePosition = mGridView.getFirstVisiblePosition();
324 if (mLastCheckedPosition <= firstVisiblePosition
325 || mLastCheckedPosition > mGridView.getLastVisiblePosition()) {
326 return firstVisiblePosition;
328 return mLastCheckedPosition;
332 private void setSwitcherMenuState(MenuItem menuItem, boolean inFullscreenMode) {
333 if (menuItem == null) return;
334 if (!inFullscreenMode) {
335 menuItem.setIcon(android.R.drawable.ic_menu_zoom);
336 menuItem.setTitle(R.string.switch_photo_fullscreen);
338 menuItem.setIcon(android.R.drawable.ic_dialog_dialer);
339 menuItem.setTitle(R.string.switch_photo_grid);
343 private void setFullscreenPagerVisibility(boolean visible) {
344 mFullscreenPagerVisible = visible;
346 if (mPagerAdapter == null) {
347 mPagerAdapter = new MtpPagerAdapter(this, mPositionMappingCheckBroker);
348 mPagerAdapter.setMtpDeviceIndex(mAdapter.getMtpDeviceIndex());
350 mFullscreenPager.setAdapter(mPagerAdapter);
351 mFullscreenPager.setCurrentItem(mPagerAdapter.translatePositionWithLabels(
352 pickFullscreenStartingPosition()), false);
353 } else if (mPagerAdapter != null) {
354 mGridView.setSelection(mAdapter.translatePositionWithoutLabels(
355 mFullscreenPager.getCurrentItem()));
356 mFullscreenPager.setAdapter(null);
358 mGridView.setVisibility(visible ? View.INVISIBLE : View.VISIBLE);
359 mFullscreenPager.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
360 if (mActionMenuSwitcherItem != null) {
361 setSwitcherMenuState(mActionMenuSwitcherItem, visible);
363 setSwitcherMenuState(mMenuSwitcherItem, visible);
366 private void updateWarningView() {
367 if (!mAdapter.deviceConnected()) {
368 showWarningView(R.string.ingest_no_device);
369 } else if (mAdapter.indexReady() && mAdapter.getCount() == 0) {
370 showWarningView(R.string.ingest_empty_device);
376 private void UiThreadNotifyIndexChanged() {
377 mAdapter.notifyDataSetChanged();
378 if (mActiveActionMode != null) {
379 mActiveActionMode.finish();
380 mActiveActionMode = null;
385 protected void notifyIndexChanged() {
386 mHandler.sendEmptyMessage(ItemListHandler.MSG_NOTIFY_CHANGED);
389 private static class ProgressState {
395 public void reset() {
403 private ProgressState mProgressState = new ProgressState();
406 public void onObjectIndexed(MtpObjectInfo object, int numVisited) {
407 // Not guaranteed to be called on the UI thread
408 mProgressState.reset();
409 mProgressState.max = 0;
410 mProgressState.message = getResources().getQuantityString(
411 R.plurals.ingest_number_of_items_scanned, numVisited, numVisited);
412 mHandler.sendEmptyMessage(ItemListHandler.MSG_PROGRESS_UPDATE);
416 public void onSorting() {
417 // Not guaranteed to be called on the UI thread
418 mProgressState.reset();
419 mProgressState.max = 0;
420 mProgressState.message = getResources().getString(R.string.ingest_sorting);
421 mHandler.sendEmptyMessage(ItemListHandler.MSG_PROGRESS_UPDATE);
425 public void onIndexFinish() {
426 // Not guaranteed to be called on the UI thread
427 mHandler.sendEmptyMessage(ItemListHandler.MSG_PROGRESS_HIDE);
428 mHandler.sendEmptyMessage(ItemListHandler.MSG_NOTIFY_CHANGED);
432 public void onImportProgress(final int visitedCount, final int totalCount,
433 String pathIfSuccessful) {
434 // Not guaranteed to be called on the UI thread
435 mProgressState.reset();
436 mProgressState.max = totalCount;
437 mProgressState.current = visitedCount;
438 mProgressState.title = getResources().getString(R.string.ingest_importing);
439 mHandler.sendEmptyMessage(ItemListHandler.MSG_PROGRESS_UPDATE);
443 public void onImportFinish(Collection<MtpObjectInfo> objectsNotImported,
445 // Not guaranteed to be called on the UI thread
446 mHandler.sendEmptyMessage(ItemListHandler.MSG_PROGRESS_HIDE);
447 // TODO: maybe show an extra dialog listing the ones that failed
448 // importing, if any?
451 private ProgressDialog getProgressDialog() {
452 if (mProgressDialog == null || !mProgressDialog.isShowing()) {
453 mProgressDialog = new ProgressDialog(this);
454 mProgressDialog.setCancelable(false);
456 return mProgressDialog;
459 private void updateProgressDialog() {
460 ProgressDialog dialog = getProgressDialog();
461 boolean indeterminate = (mProgressState.max == 0);
462 dialog.setIndeterminate(indeterminate);
463 dialog.setProgressStyle(indeterminate ? ProgressDialog.STYLE_SPINNER
464 : ProgressDialog.STYLE_HORIZONTAL);
465 if (mProgressState.title != null) {
466 dialog.setTitle(mProgressState.title);
468 if (mProgressState.message != null) {
469 dialog.setMessage(mProgressState.message);
471 if (!indeterminate) {
472 dialog.setProgress(mProgressState.current);
473 dialog.setMax(mProgressState.max);
475 if (!dialog.isShowing()) {
480 private void cleanupProgressDialog() {
481 if (mProgressDialog != null) {
482 mProgressDialog.hide();
483 mProgressDialog = null;
487 // This is static and uses a WeakReference in order to avoid leaking the Activity
488 private static class ItemListHandler extends Handler {
489 public static final int MSG_PROGRESS_UPDATE = 0;
490 public static final int MSG_PROGRESS_HIDE = 1;
491 public static final int MSG_NOTIFY_CHANGED = 2;
492 public static final int MSG_BULK_CHECKED_CHANGE = 3;
494 WeakReference<IngestActivity> mParentReference;
496 public ItemListHandler(IngestActivity parent) {
498 mParentReference = new WeakReference<IngestActivity>(parent);
501 public void handleMessage(Message message) {
502 IngestActivity parent = mParentReference.get();
503 if (parent == null || !parent.mActive)
505 switch (message.what) {
506 case MSG_PROGRESS_HIDE:
507 parent.cleanupProgressDialog();
509 case MSG_PROGRESS_UPDATE:
510 parent.updateProgressDialog();
512 case MSG_NOTIFY_CHANGED:
513 parent.UiThreadNotifyIndexChanged();
515 case MSG_BULK_CHECKED_CHANGE:
516 parent.mPositionMappingCheckBroker.onBulkCheckedChange();
524 private ServiceConnection mHelperServiceConnection = new ServiceConnection() {
525 public void onServiceConnected(ComponentName className, IBinder service) {
526 mHelperService = ((IngestService.LocalBinder) service).getService();
527 mHelperService.setClientActivity(IngestActivity.this);
528 MtpDeviceIndex index = mHelperService.getIndex();
529 mAdapter.setMtpDeviceIndex(index);
530 if (mPagerAdapter != null) mPagerAdapter.setMtpDeviceIndex(index);
533 public void onServiceDisconnected(ComponentName className) {
534 mHelperService = null;
538 private void doBindHelperService() {
539 bindService(new Intent(getApplicationContext(), IngestService.class),
540 mHelperServiceConnection, Context.BIND_AUTO_CREATE);
543 private void doUnbindHelperService() {
544 if (mHelperService != null) {
545 mHelperService.setClientActivity(null);
546 unbindService(mHelperServiceConnection);