/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.apps.iosched.ui;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.view.ActionMode;
import android.util.Pair;
import android.util.SparseBooleanArray;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AbsListView;
import android.widget.Adapter;
import android.widget.AdapterView;
import android.widget.ListView;
import java.util.HashSet;
/**
* Utilities for handling multiple selection in list views. Contains functionality similar to
* {@link AbsListView#CHOICE_MODE_MULTIPLE_MODAL} but that works with {@link ActionBarActivity} and
* backward-compatible action bars.
*/
public class MultiSelectionUtil {
public static Controller attachMultiSelectionController(final ListView listView,
final ActionBarActivity activity, final MultiChoiceModeListener listener) {
return Controller.attach(listView, activity, listener);
}
public static class Controller implements
ActionMode.Callback,
AdapterView.OnItemClickListener,
AdapterView.OnItemLongClickListener {
private Handler mHandler = new Handler();
private ActionMode mActionMode;
private ListView mListView = null;
private ActionBarActivity mActivity = null;
private MultiChoiceModeListener mListener = null;
private HashSet<Long> mTempIdsToCheckOnRestore;
private HashSet<Pair<Integer, Long>> mItemsToCheck;
private AdapterView.OnItemClickListener mOldItemClickListener;
private Controller() {
}
public static Controller attach(ListView listView, ActionBarActivity activity,
MultiChoiceModeListener listener) {
Controller controller = new Controller();
controller.mListView = listView;
controller.mActivity = activity;
controller.mListener = listener;
listView.setOnItemLongClickListener(controller);
return controller;
}
private void readInstanceState(Bundle savedInstanceState) {
mTempIdsToCheckOnRestore = null;
if (savedInstanceState != null) {
long[] checkedIds = savedInstanceState.getLongArray(getStateKey());
if (checkedIds != null && checkedIds.length > 0) {
mTempIdsToCheckOnRestore = new HashSet<Long>();
for (long id : checkedIds) {
mTempIdsToCheckOnRestore.add(id);
}
}
}
}
public void tryRestoreInstanceState(Bundle savedInstanceState) {
readInstanceState(savedInstanceState);
tryRestoreInstanceState();
}
public void finish() {
if (mActionMode != null) {
mActionMode.finish();
}
}
public void tryRestoreInstanceState() {
if (mTempIdsToCheckOnRestore == null || mListView.getAdapter() == null) {
return;
}
boolean idsFound = false;
Adapter adapter = mListView.getAdapter();
for (int pos = adapter.getCount() - 1; pos >= 0; pos--) {
if (mTempIdsToCheckOnRestore.contains(adapter.getItemId(pos))) {
idsFound = true;
if (mItemsToCheck == null) {
mItemsToCheck = new HashSet<Pair<Integer, Long>>();
}
mItemsToCheck.add(
new Pair<Integer, Long>(pos, adapter.getItemId(pos)));
}
}
if (idsFound) {
// We found some IDs that were checked. Let's now restore the multi-selection
// state.
mTempIdsToCheckOnRestore = null; // clear out this temp field
mActionMode = mActivity.startSupportActionMode(Controller.this);
}
}
public boolean saveInstanceState(Bundle outBundle) {
// TODO: support non-stable IDs by persisting positions instead of IDs
if (mActionMode != null && mListView.getAdapter().hasStableIds()) {
long[] checkedIds = mListView.getCheckedItemIds();
outBundle.putLongArray(getStateKey(), checkedIds);
return true;
}
return false;
}
private String getStateKey() {
return MultiSelectionUtil.class.getSimpleName() + "_" + mListView.getId();
}
@Override
public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
if (mListener.onCreateActionMode(actionMode, menu)) {
mActionMode = actionMode;
mOldItemClickListener = mListView.getOnItemClickListener();
mListView.setOnItemClickListener(Controller.this);
mListView.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE);
mHandler.removeCallbacks(mSetChoiceModeNoneRunnable);
if (mItemsToCheck != null) {
for (Pair<Integer, Long> posAndId : mItemsToCheck) {
mListView.setItemChecked(posAndId.first, true);
mListener.onItemCheckedStateChanged(mActionMode, posAndId.first,
posAndId.second, true);
}
}
return true;
}
return false;
}
@Override
public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
if (mListener.onPrepareActionMode(actionMode, menu)) {
mActionMode = actionMode;
return true;
}
return false;
}
@Override
public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
return mListener.onActionItemClicked(actionMode, menuItem);
}
@Override
public void onDestroyActionMode(ActionMode actionMode) {
mListener.onDestroyActionMode(actionMode);
SparseBooleanArray checkedPositions = mListView.getCheckedItemPositions();
if (checkedPositions != null) {
for (int i = 0; i < checkedPositions.size(); i++) {
mListView.setItemChecked(checkedPositions.keyAt(i), false);
}
}
mListView.setOnItemClickListener(mOldItemClickListener);
mActionMode = null;
mHandler.post(mSetChoiceModeNoneRunnable);
}
private Runnable mSetChoiceModeNoneRunnable = new Runnable() {
@Override
public void run() {
mListView.setChoiceMode(AbsListView.CHOICE_MODE_NONE);
}
};
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
boolean checked = mListView.isItemChecked(position);
mListener.onItemCheckedStateChanged(mActionMode, position, id, checked);
int numChecked = 0;
SparseBooleanArray checkedItemPositions = mListView.getCheckedItemPositions();
if (checkedItemPositions != null) {
for (int i = 0; i < checkedItemPositions.size(); i++) {
numChecked += checkedItemPositions.valueAt(i) ? 1 : 0;
}
}
if (numChecked <= 0) {
mActionMode.finish();
}
}
@Override
public boolean onItemLongClick(AdapterView<?> adapterView, View view, int position,
long id) {
if (mActionMode != null) {
return false;
}
mItemsToCheck = new HashSet<Pair<Integer, Long>>();
mItemsToCheck.add(new Pair<Integer, Long>(position, id));
mActionMode = mActivity.startSupportActionMode(Controller.this);
return true;
}
}
/**
* @see android.widget.AbsListView.MultiChoiceModeListener
*/
public static interface MultiChoiceModeListener extends ActionMode.Callback {
/**
* @see android.widget.AbsListView.MultiChoiceModeListener#onItemCheckedStateChanged(
* android.view.ActionMode, int, long, boolean)
*/
public void onItemCheckedStateChanged(ActionMode mode,
int position, long id, boolean checked);
}
}