com.google.android.apps.muzei.gallery.GallerySettingsActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.google.android.apps.muzei.gallery.GallerySettingsActivity.java

Source

/*
 * Copyright 2014 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.muzei.gallery;

import android.Manifest;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.TargetApi;
import android.content.ClipData;
import android.content.ComponentName;
import android.content.ContentProviderOperation;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.OperationApplicationException;
import android.content.ServiceConnection;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.RemoteException;
import android.provider.BaseColumns;
import android.provider.Settings;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.view.OnApplyWindowInsetsListener;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.WindowInsetsCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.util.DiffUtil;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseIntArray;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ViewAnimator;

import com.google.android.apps.muzei.util.MultiSelectionController;
import com.squareup.picasso.Picasso;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import static com.google.android.apps.muzei.gallery.GalleryArtSource.ACTION_PUBLISH_NEXT_GALLERY_ITEM;
import static com.google.android.apps.muzei.gallery.GalleryArtSource.EXTRA_FORCE_URI;

public class GallerySettingsActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Cursor> {
    private static final String TAG = "GallerySettingsActivity";
    private static final int REQUEST_CHOOSE_PHOTOS = 1;
    private static final int REQUEST_STORAGE_PERMISSION = 2;
    private static final String STATE_SELECTION = "selection";

    private final ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(final ComponentName name, final IBinder service) {
        }

        @Override
        public void onServiceDisconnected(final ComponentName name) {
        }
    };

    private Cursor mChosenUris;

    private Toolbar mSelectionToolbar;

    private HandlerThread mHandlerThread;
    private Handler mHandler;
    private RecyclerView mPhotoGridView;
    private int mItemSize = 10;

    private final MultiSelectionController<Uri> mMultiSelectionController = new MultiSelectionController<>(
            STATE_SELECTION);

    private ColorDrawable mPlaceholderDrawable;

    private static final SparseIntArray sRotateMenuIdsByMin = new SparseIntArray();
    private static final SparseIntArray sRotateMinsByMenuId = new SparseIntArray();

    static {
        sRotateMenuIdsByMin.put(0, R.id.action_rotate_interval_none);
        sRotateMenuIdsByMin.put(60, R.id.action_rotate_interval_1h);
        sRotateMenuIdsByMin.put(60 * 3, R.id.action_rotate_interval_3h);
        sRotateMenuIdsByMin.put(60 * 6, R.id.action_rotate_interval_6h);
        sRotateMenuIdsByMin.put(60 * 24, R.id.action_rotate_interval_24h);
        sRotateMenuIdsByMin.put(60 * 72, R.id.action_rotate_interval_72h);
        for (int i = 0; i < sRotateMenuIdsByMin.size(); i++) {
            sRotateMinsByMenuId.put(sRotateMenuIdsByMin.valueAt(i), sRotateMenuIdsByMin.keyAt(i));
        }
    }

    private List<ActivityInfo> mGetContentActivites = new ArrayList<>();

    private int mUpdatePosition = -1;
    private View mAddButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.gallery_activity);
        Toolbar appBar = (Toolbar) findViewById(R.id.app_bar);
        setSupportActionBar(appBar);

        getSupportLoaderManager().initLoader(0, null, this);

        bindService(new Intent(this, GalleryArtSource.class).setAction(GalleryArtSource.ACTION_BIND_GALLERY),
                mServiceConnection, BIND_AUTO_CREATE);

        mPlaceholderDrawable = new ColorDrawable(
                ContextCompat.getColor(this, R.color.gallery_chosen_photo_placeholder));

        mPhotoGridView = (RecyclerView) findViewById(R.id.photo_grid);
        DefaultItemAnimator itemAnimator = new DefaultItemAnimator();
        itemAnimator.setSupportsChangeAnimations(false);
        mPhotoGridView.setItemAnimator(itemAnimator);
        setupMultiSelect();

        final GridLayoutManager gridLayoutManager = new GridLayoutManager(GallerySettingsActivity.this, 1);
        mPhotoGridView.setLayoutManager(gridLayoutManager);

        final ViewTreeObserver vto = mPhotoGridView.getViewTreeObserver();
        vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                int width = mPhotoGridView.getWidth() - mPhotoGridView.getPaddingStart()
                        - mPhotoGridView.getPaddingEnd();
                if (width <= 0) {
                    return;
                }

                // Compute number of columns
                int maxItemWidth = getResources()
                        .getDimensionPixelSize(R.dimen.gallery_chosen_photo_grid_max_item_size);
                int numColumns = 1;
                while (true) {
                    if (width / numColumns > maxItemWidth) {
                        ++numColumns;
                    } else {
                        break;
                    }
                }

                int spacing = getResources().getDimensionPixelSize(R.dimen.gallery_chosen_photo_grid_spacing);
                mItemSize = (width - spacing * (numColumns - 1)) / numColumns;

                // Complete setup
                gridLayoutManager.setSpanCount(numColumns);
                mChosenPhotosAdapter.setHasStableIds(true);
                mPhotoGridView.setAdapter(mChosenPhotosAdapter);

                mPhotoGridView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                tryUpdateSelection(false);
            }
        });

        ViewCompat.setOnApplyWindowInsetsListener(mPhotoGridView, new OnApplyWindowInsetsListener() {
            @Override
            public WindowInsetsCompat onApplyWindowInsets(final View v, final WindowInsetsCompat insets) {
                int gridSpacing = getResources().getDimensionPixelSize(R.dimen.gallery_chosen_photo_grid_spacing);
                ViewCompat.onApplyWindowInsets(v,
                        insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft() + gridSpacing,
                                gridSpacing, insets.getSystemWindowInsetRight() + gridSpacing,
                                insets.getSystemWindowInsetBottom() + insets.getSystemWindowInsetTop() + gridSpacing
                                        + getResources().getDimensionPixelSize(R.dimen.gallery_fab_space)));

                return insets;
            }
        });

        Button enableRandomImages = (Button) findViewById(R.id.gallery_enable_random);
        enableRandomImages.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(final View view) {
                ActivityCompat.requestPermissions(GallerySettingsActivity.this,
                        new String[] { Manifest.permission.READ_EXTERNAL_STORAGE }, REQUEST_STORAGE_PERMISSION);
            }
        });
        Button permissionSettings = (Button) findViewById(R.id.gallery_edit_permission_settings);
        permissionSettings.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(final View view) {
                Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
                        Uri.fromParts("package", getPackageName(), null));
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                startActivity(intent);
            }
        });
        mAddButton = findViewById(R.id.add_photos_button);
        mAddButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // Use ACTION_OPEN_DOCUMENT by default for adding photos.
                // This allows us to use persistent URI permissions to access the underlying photos
                // meaning we don't need to use additional storage space and will pull in edits automatically
                // in addition to syncing deletions.
                // (There's a separate 'Import photos' option which uses ACTION_GET_CONTENT to support legacy apps)
                Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
                intent.setType("image/*");
                intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
                startActivityForResult(intent, REQUEST_CHOOSE_PHOTOS);
            }
        });
    }

    @Override
    public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions,
            @NonNull final int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode != REQUEST_STORAGE_PERMISSION) {
            return;
        }
        onDataSetChanged();
    }

    @Override
    protected void onResume() {
        super.onResume();
        // Permissions might have changed in the background
        onDataSetChanged();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mHandlerThread != null) {
            mHandlerThread.quitSafely();
            mHandlerThread = null;
        }
        unbindService(mServiceConnection);
    }

    @Override
    public boolean onCreateOptionsMenu(final Menu menu) {
        super.onCreateOptionsMenu(menu);
        getMenuInflater().inflate(R.menu.gallery_activity, menu);

        int rotateIntervalMin = GalleryArtSource.getSharedPreferences(this)
                .getInt(GalleryArtSource.PREF_ROTATE_INTERVAL_MIN, GalleryArtSource.DEFAULT_ROTATE_INTERVAL_MIN);
        int menuId = sRotateMenuIdsByMin.get(rotateIntervalMin);
        if (menuId != 0) {
            MenuItem item = menu.findItem(menuId);
            if (item != null) {
                item.setChecked(true);
            }
        }
        return true;
    }

    @Override
    public boolean onPrepareOptionsMenu(final Menu menu) {
        super.onPrepareOptionsMenu(menu);
        // Make sure the 'Import photos' MenuItem is set up properly based on the number of
        // activities that handle ACTION_GET_CONTENT
        // 0 = hide the MenuItem
        // 1 = show 'Import photos from APP_NAME' to go to the one app that exists
        // 2 = show 'Import photos...' to have the user pick which app to import photos from
        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        intent.setType("image/*");
        List<ResolveInfo> getContentActivities = getPackageManager().queryIntentActivities(intent, 0);
        mGetContentActivites.clear();
        for (ResolveInfo info : getContentActivities) {
            // Filter out the default system UI
            if (!TextUtils.equals(info.activityInfo.packageName, "com.android.documentsui")) {
                mGetContentActivites.add(info.activityInfo);
            }
        }

        // Hide the 'Import photos' action if there are no activities found
        MenuItem importPhotosMenuItem = menu.findItem(R.id.action_import_photos);
        importPhotosMenuItem.setVisible(!mGetContentActivites.isEmpty());
        // If there's only one app that supports ACTION_GET_CONTENT, tell the user what that app is
        if (mGetContentActivites.size() == 1) {
            importPhotosMenuItem.setTitle(getString(R.string.gallery_action_import_photos_from,
                    mGetContentActivites.get(0).loadLabel(getPackageManager())));
        } else {
            importPhotosMenuItem.setTitle(R.string.gallery_action_import_photos);
        }
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(final MenuItem item) {
        int itemId = item.getItemId();
        int rotateMin = sRotateMinsByMenuId.get(itemId, -1);
        if (rotateMin != -1) {
            GalleryArtSource.getSharedPreferences(GallerySettingsActivity.this).edit()
                    .putInt(GalleryArtSource.PREF_ROTATE_INTERVAL_MIN, rotateMin).apply();
            item.setChecked(true);
            return true;
        }

        if (itemId == R.id.action_import_photos) {
            if (mGetContentActivites.size() == 1) {
                // Just start the one ACTION_GET_CONTENT app
                requestGetContent(mGetContentActivites.get(0));
            } else {
                // Let the user pick which app they want to import photos from
                PackageManager packageManager = getPackageManager();
                final CharSequence[] items = new CharSequence[mGetContentActivites.size()];
                for (int h = 0; h < mGetContentActivites.size(); h++) {
                    items[h] = mGetContentActivites.get(h).loadLabel(packageManager);
                }
                new AlertDialog.Builder(this).setTitle(R.string.gallery_import_dialog_title)
                        .setItems(items, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                requestGetContent(mGetContentActivites.get(which));
                            }
                        }).show();
            }
            return true;
        } else if (itemId == R.id.action_clear_photos) {
            runOnHandlerThread(new Runnable() {
                @Override
                public void run() {
                    getContentResolver().delete(GalleryContract.ChosenPhotos.CONTENT_URI, null, null);
                }
            });
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    private void requestGetContent(ActivityInfo info) {
        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        intent.setType("image/*");
        intent.setClassName(info.packageName, info.name);
        intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
        startActivityForResult(intent, REQUEST_CHOOSE_PHOTOS);
    }

    private void runOnHandlerThread(Runnable runnable) {
        if (mHandlerThread == null) {
            mHandlerThread = new HandlerThread("GallerySettingsActivity");
            mHandlerThread.start();
            mHandler = new Handler(mHandlerThread.getLooper());
        }
        mHandler.post(runnable);
    }

    private int mLastTouchPosition;
    private int mLastTouchX, mLastTouchY;

    private void setupMultiSelect() {
        // Set up toolbar
        mSelectionToolbar = (Toolbar) findViewById(R.id.selection_toolbar);

        mSelectionToolbar.setNavigationOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mMultiSelectionController.reset(true);
            }
        });

        mSelectionToolbar.inflateMenu(R.menu.gallery_selection);
        mSelectionToolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
            @Override
            public boolean onMenuItemClick(MenuItem item) {
                int itemId = item.getItemId();
                if (itemId == R.id.action_force_now) {
                    Set<Uri> selection = mMultiSelectionController.getSelection();
                    if (selection.size() > 0) {
                        startService(new Intent(GallerySettingsActivity.this, GalleryArtSource.class)
                                .setAction(ACTION_PUBLISH_NEXT_GALLERY_ITEM)
                                .putExtra(EXTRA_FORCE_URI, selection.iterator().next()));
                        Toast.makeText(GallerySettingsActivity.this, R.string.gallery_temporary_force_image,
                                Toast.LENGTH_SHORT).show();
                    }
                    mMultiSelectionController.reset(true);
                    return true;
                } else if (itemId == R.id.action_remove) {
                    final ArrayList<Uri> removeUris = new ArrayList<>(mMultiSelectionController.getSelection());

                    runOnHandlerThread(new Runnable() {
                        @Override
                        public void run() {
                            // Update chosen URIs
                            ArrayList<ContentProviderOperation> operations = new ArrayList<>();
                            for (Uri uri : removeUris) {
                                operations.add(
                                        ContentProviderOperation.newDelete(GalleryContract.ChosenPhotos.CONTENT_URI)
                                                .withSelection(GalleryContract.ChosenPhotos.COLUMN_NAME_URI + "=?",
                                                        new String[] { uri.toString() })
                                                .build());
                            }
                            try {
                                getContentResolver().applyBatch(GalleryContract.AUTHORITY, operations);
                            } catch (RemoteException | OperationApplicationException e) {
                                Log.e(TAG, "Error deleting URIs from the ContentProvider", e);
                            }
                        }
                    });

                    mMultiSelectionController.reset(true);
                    return true;
                }
                return false;
            }
        });

        // Set up controller
        mMultiSelectionController.setCallbacks(new MultiSelectionController.Callbacks() {
            @Override
            public void onSelectionChanged(boolean restored, boolean fromUser) {
                tryUpdateSelection(!restored);
            }
        });
    }

    @Override
    public void onBackPressed() {
        if (mMultiSelectionController.getSelectedCount() > 0) {
            mMultiSelectionController.reset(true);
        } else {
            super.onBackPressed();
        }
    }

    private void tryUpdateSelection(boolean allowAnimate) {
        final View selectionToolbarContainer = findViewById(R.id.selection_toolbar_container);

        if (mUpdatePosition >= 0) {
            mChosenPhotosAdapter.notifyItemChanged(mUpdatePosition);
            mUpdatePosition = -1;
        } else {
            mChosenPhotosAdapter.notifyDataSetChanged();
        }

        int selectedCount = mMultiSelectionController.getSelectedCount();
        final boolean toolbarVisible = selectedCount > 0;
        mSelectionToolbar.getMenu().findItem(R.id.action_force_now).setVisible(selectedCount < 2);

        Boolean previouslyVisible = (Boolean) selectionToolbarContainer.getTag(0xDEADBEEF);
        if (previouslyVisible == null) {
            previouslyVisible = Boolean.FALSE;
        }

        if (previouslyVisible != toolbarVisible) {
            selectionToolbarContainer.setTag(0xDEADBEEF, toolbarVisible);

            int duration = allowAnimate ? getResources().getInteger(android.R.integer.config_shortAnimTime) : 0;
            if (toolbarVisible) {
                selectionToolbarContainer.setVisibility(View.VISIBLE);
                selectionToolbarContainer.setTranslationY(-selectionToolbarContainer.getHeight());
                selectionToolbarContainer.animate().translationY(0f).setDuration(duration).withEndAction(null);

                mAddButton.animate().scaleX(0f).scaleY(0f).setDuration(duration).withEndAction(new Runnable() {
                    @Override
                    public void run() {
                        mAddButton.setVisibility(View.INVISIBLE);
                    }
                });
            } else {
                selectionToolbarContainer.animate().translationY(-selectionToolbarContainer.getHeight())
                        .setDuration(duration).withEndAction(new Runnable() {
                            @Override
                            public void run() {
                                selectionToolbarContainer.setVisibility(View.INVISIBLE);
                            }
                        });

                mAddButton.setVisibility(View.VISIBLE);
                mAddButton.animate().scaleY(1f).scaleX(1f).setDuration(duration).withEndAction(null);
            }
        }

        if (toolbarVisible) {
            mSelectionToolbar.setTitle(Integer.toString(selectedCount));
        }
    }

    private void onDataSetChanged() {
        View emptyView = findViewById(android.R.id.empty);
        TextView emptyDescription = (TextView) findViewById(R.id.empty_description);
        if (mChosenUris != null && mChosenUris.getCount() > 0) {
            emptyView.setVisibility(View.GONE);
            // We have at least one image, so consider the Gallery source properly setup
            setResult(RESULT_OK);
        } else {
            // No chosen images, show the empty View
            emptyView.setVisibility(View.VISIBLE);
            ViewAnimator animator = (ViewAnimator) findViewById(R.id.empty_animator);
            if (ContextCompat.checkSelfPermission(this,
                    Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
                // Permission is granted, we can show the random camera photos image
                animator.setDisplayedChild(0);
                emptyDescription.setText(R.string.gallery_empty);
                setResult(RESULT_OK);
            } else {
                // We have no images until they enable the permission
                setResult(RESULT_CANCELED);
                if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                        Manifest.permission.READ_EXTERNAL_STORAGE)) {
                    // We should show rationale on why they should enable the storage permission and
                    // random camera photos
                    animator.setDisplayedChild(1);
                    emptyDescription.setText(R.string.gallery_permission_rationale);
                } else {
                    // The user has permanently denied the storage permission. Give them a link to app settings
                    animator.setDisplayedChild(2);
                    emptyDescription.setText(R.string.gallery_denied_explanation);
                }
            }
        }
    }

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        mMultiSelectionController.restoreInstanceState(savedInstanceState);
    }

    static class ViewHolder extends RecyclerView.ViewHolder {
        final View mRootView;
        final ImageView mThumbView;
        final View mCheckedOverlayView;

        ViewHolder(View root) {
            super(root);
            mRootView = root;
            mThumbView = (ImageView) root.findViewById(R.id.thumbnail);
            mCheckedOverlayView = root.findViewById(R.id.checked_overlay);
        }
    }

    private final RecyclerView.Adapter<ViewHolder> mChosenPhotosAdapter = new RecyclerView.Adapter<ViewHolder>() {
        @Override
        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View v = LayoutInflater.from(GallerySettingsActivity.this).inflate(R.layout.gallery_chosen_photo_item,
                    parent, false);
            final ViewHolder vh = new ViewHolder(v);

            v.getLayoutParams().height = mItemSize;
            v.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View view, MotionEvent motionEvent) {
                    if (motionEvent.getActionMasked() != MotionEvent.ACTION_CANCEL) {
                        mLastTouchPosition = vh.getAdapterPosition();
                        mLastTouchX = (int) motionEvent.getX();
                        mLastTouchY = (int) motionEvent.getY();
                    }
                    return false;
                }
            });
            v.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    mUpdatePosition = vh.getAdapterPosition();
                    mChosenUris.moveToPosition(mUpdatePosition);
                    Uri imageUri = Uri.parse(mChosenUris
                            .getString(mChosenUris.getColumnIndex(GalleryContract.ChosenPhotos.COLUMN_NAME_URI)));
                    mMultiSelectionController.toggle(imageUri, true);
                }
            });

            return vh;
        }

        @Override
        public void onBindViewHolder(final ViewHolder vh, int position) {
            mChosenUris.moveToPosition(position);
            Uri contentUri = ContentUris.withAppendedId(GalleryContract.ChosenPhotos.CONTENT_URI,
                    mChosenUris.getLong(mChosenUris.getColumnIndex(BaseColumns._ID)));
            Uri imageUri = Uri.parse(mChosenUris
                    .getString(mChosenUris.getColumnIndex(GalleryContract.ChosenPhotos.COLUMN_NAME_URI)));
            Picasso.with(GallerySettingsActivity.this).load(contentUri).resize(mItemSize, mItemSize).centerCrop()
                    .placeholder(mPlaceholderDrawable).into(vh.mThumbView);
            final boolean checked = mMultiSelectionController.isSelected(imageUri);
            vh.mRootView.setTag(R.id.gallery_viewtag_position, position);
            if (mLastTouchPosition == vh.getAdapterPosition()
                    && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                new Handler().post(new Runnable() {
                    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
                    @Override
                    public void run() {
                        if (checked) {
                            vh.mCheckedOverlayView.setVisibility(View.VISIBLE);
                        }

                        // find the smallest radius that'll cover the item
                        float coverRadius = maxDistanceToCorner(mLastTouchX, mLastTouchY, 0, 0,
                                vh.mRootView.getWidth(), vh.mRootView.getHeight());

                        Animator revealAnim = ViewAnimationUtils.createCircularReveal(vh.mCheckedOverlayView,
                                mLastTouchX, mLastTouchY, checked ? 0 : coverRadius, checked ? coverRadius : 0)
                                .setDuration(150);

                        if (!checked) {
                            revealAnim.addListener(new AnimatorListenerAdapter() {
                                @Override
                                public void onAnimationEnd(Animator animation) {
                                    vh.mCheckedOverlayView.setVisibility(View.GONE);
                                }
                            });
                        }
                        revealAnim.start();
                    }
                });
            } else {
                vh.mCheckedOverlayView.setVisibility(checked ? View.VISIBLE : View.GONE);
            }
        }

        private float maxDistanceToCorner(int x, int y, int left, int top, int right, int bottom) {
            float maxDistance = 0;
            maxDistance = Math.max(maxDistance, (float) Math.hypot(x - left, y - top));
            maxDistance = Math.max(maxDistance, (float) Math.hypot(x - right, y - top));
            maxDistance = Math.max(maxDistance, (float) Math.hypot(x - left, y - bottom));
            maxDistance = Math.max(maxDistance, (float) Math.hypot(x - right, y - bottom));
            return maxDistance;
        }

        @Override
        public int getItemCount() {
            return mChosenUris != null ? mChosenUris.getCount() : 0;
        }

        @Override
        public long getItemId(int position) {
            mChosenUris.moveToPosition(position);
            return mChosenUris.getLong(mChosenUris.getColumnIndex(BaseColumns._ID));
        }
    };

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent result) {
        if (requestCode != REQUEST_CHOOSE_PHOTOS || resultCode != RESULT_OK) {
            super.onActivityResult(requestCode, resultCode, result);
            return;
        }

        if (result == null) {
            return;
        }

        // Add chosen items
        final Set<Uri> uris = new HashSet<>();
        if (result.getData() != null) {
            uris.add(result.getData());
        }
        // When selecting multiple images, "Photos" returns the first URI in getData and all URIs
        // in getClipData.
        ClipData clipData = result.getClipData();
        if (clipData != null) {
            int count = clipData.getItemCount();
            for (int i = 0; i < count; i++) {
                uris.add(clipData.getItemAt(i).getUri());
            }
        }

        // Update chosen URIs
        runOnHandlerThread(new Runnable() {
            @Override
            public void run() {
                ArrayList<ContentProviderOperation> operations = new ArrayList<>();
                for (Uri uri : uris) {
                    ContentValues values = new ContentValues();
                    values.put(GalleryContract.ChosenPhotos.COLUMN_NAME_URI, uri.toString());
                    operations.add(ContentProviderOperation.newInsert(GalleryContract.ChosenPhotos.CONTENT_URI)
                            .withValues(values).build());
                }
                try {
                    getContentResolver().applyBatch(GalleryContract.AUTHORITY, operations);
                } catch (RemoteException | OperationApplicationException e) {
                    Log.e(TAG, "Error writing uris to ContentProvider", e);
                }
            }
        });
    }

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        return new CursorLoader(this, GalleryContract.ChosenPhotos.CONTENT_URI,
                new String[] { BaseColumns._ID, GalleryContract.ChosenPhotos.COLUMN_NAME_URI }, null, null, null);
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, final Cursor data) {
        if (mChosenUris == data) {
            return;
        }
        final Cursor previousData = mChosenUris;
        mChosenUris = data;
        DiffUtil.calculateDiff(new DiffUtil.Callback() {
            @Override
            public int getOldListSize() {
                return previousData != null ? previousData.getCount() : 0;
            }

            @Override
            public int getNewListSize() {
                return data.getCount();
            }

            @Override
            public boolean areItemsTheSame(final int oldItemPosition, final int newItemPosition) {
                previousData.moveToPosition(oldItemPosition);
                String oldImageUri = previousData
                        .getString(previousData.getColumnIndex(GalleryContract.ChosenPhotos.COLUMN_NAME_URI));
                data.moveToPosition(newItemPosition);
                String newImageUri = data
                        .getString(data.getColumnIndex(GalleryContract.ChosenPhotos.COLUMN_NAME_URI));
                return oldImageUri.equals(newImageUri);
            }

            @Override
            public boolean areContentsTheSame(final int oldItemPosition, final int newItemPosition) {
                // If the items are the same (same image URI), then they are equivalent and
                // no change animation is needed
                return true;
            }
        }).dispatchUpdatesTo(mChosenPhotosAdapter);
        onDataSetChanged();
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        mChosenUris = null;
        mChosenPhotosAdapter.notifyItemRangeRemoved(0, mChosenPhotosAdapter.getItemCount());
        onDataSetChanged();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        mMultiSelectionController.saveInstanceState(outState);
    }
}