com.appsimobile.appsii.module.apps.ReorderAppsFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.appsimobile.appsii.module.apps.ReorderAppsFragment.java

Source

/*
 * Copyright 2015. Appsi Mobile
 *
 * 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.appsimobile.appsii.module.apps;

import android.app.Fragment;
import android.app.LoaderManager;
import android.content.AsyncQueryHandler;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Loader;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.util.SimpleArrayMap;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.TextView;

import com.appsimobile.appsii.R;
import com.appsimobile.appsii.compat.LauncherAppsCompat;
import com.appsimobile.appsii.dagger.AppInjector;
import com.appsimobile.appsii.module.BaseListAdapter;
import com.appsimobile.appsii.module.ViewHolder;
import com.mobeta.android.dslv.ConditionalRemovableAdapter;
import com.mobeta.android.dslv.DragSortListView;

import java.util.ArrayList;
import java.util.List;

import javax.inject.Inject;

/**
 * Created by nick on 31/08/14.
 */
public class ReorderAppsFragment extends Fragment
        implements DragSortListView.RemoveListener, DragSortListView.DropListener, AdapterView.OnItemClickListener {

    /**
     * The list-view used to re-order the tags. We register a few listeners on the
     * list to get updates when items are re-ordered or removed
     */
    DragSortListView mDragSortListView;

    /**
     * The adapter applied to the list-view.
     */
    AppsAdapter mAppsAdapter;

    /**
     * True when we are updating the database. While the database is being updated,
     * we ignore changes to the tags and don't update the underlying list to prevent
     * flickering.
     */
    boolean mIsChangeInProgress;

    /**
     * The handler used to apply the user's changes
     */
    @Nullable
    QueryHandler mQueryHandler;

    RetainQueryHelperFragment mRetainFragment;
    @Inject
    LauncherAppsCompat mLauncherAppsCompat;
    private AppTag mAppTag;

    public static Fragment createInstance(AppTag appTag) {
        ReorderAppsFragment result = new ReorderAppsFragment();
        Bundle args = new Bundle();
        args.putParcelable("tag", appTag);

        result.setArguments(args);
        return result;
    }

    @Override
    public void onStart() {
        super.onStart();
        mRetainFragment.setAdapter(mAppsAdapter);
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        AppInjector.inject(this);
        // create the adapter here to make sure we can set it's data and register it's listener
        // This ensures the life cycles are the same
        mAppsAdapter = new AppsAdapter();

        // now get the tag we need to display the data from
        if (savedInstanceState != null) {
            mAppTag = savedInstanceState.getParcelable("tag");
        }

        // see if the retain fragment is already present. Otherwise create it.
        // the key has to be unique because multiple pages may be added by the pager.
        String key = "retain";
        mRetainFragment = (RetainQueryHelperFragment) getFragmentManager().findFragmentByTag(key);

        // when the fragment was null, create it and add it.
        if (mRetainFragment == null) {
            mRetainFragment = new RetainQueryHelperFragment();
            getFragmentManager().beginTransaction().add(mRetainFragment, key).commit();
        }

        // now that the fragment is added, initialize the loader.
        getLoaderManager().initLoader(100, null, new AllAppsLoaderCallbacks());

        if (mAppTag != null) {
            mRetainFragment.loadTaggedApps(mAppTag.id);
        }
    }

    @Override
    public void onStop() {
        super.onStop();
        mRetainFragment.setAdapter(null);
    }

    @Override
    public void remove(int i) {
        mIsChangeInProgress = true;
        TaggedApp item = mAppsAdapter.getItem(i);
        doRemoveTaggedApp(item);
        // set this after removal. Otherwise the query-handler does
        // not know if it is a newly deleted item or not.
        // The queryHandler uses the mDeleted field to know if it is
        // a second delete
        if (item.mDeleted) {
            mAppsAdapter.removeItem(i);
        } else {
            item.mDeleted = true;
            mAppsAdapter.notifyDataSetChanged();
        }
    }

    void doRemoveTaggedApp(TaggedApp item) {
        // We only create the query-handler if it is really needed. So create
        // it when null
        if (mQueryHandler == null) {
            mQueryHandler = new QueryHandler(getActivity().getContentResolver());
        }
        mQueryHandler.removeTaggedApp(item);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putParcelable("tag", mAppTag);
    }

    @Override
    public void drop(int from, int to) {
        if (from != to) {
            mIsChangeInProgress = true;
            doUpdateOrder(mAppsAdapter.getItems(), from, to);
        }

    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_reorder_app_tags, container, false);
    }

    /**
     * Starts an update in the order. The order in the database is changed to
     * reflect the order in the given list with the item at position from
     * changed to 'to'.
     */
    private void doUpdateOrder(List<TaggedApp> items, int from, int to) {
        // We only create the query-handler if it is really needed. So create
        // it when null
        if (mQueryHandler == null) {
            mQueryHandler = new QueryHandler(getActivity().getContentResolver());
        }
        // create a new list, make this list the same size as the original one,
        // and apply the change to it.
        int count = items.size();
        List<TaggedApp> newOrdering = new ArrayList<>(count);

        newOrdering.addAll(items);
        TaggedApp moved = newOrdering.remove(from);
        newOrdering.add(to, moved);

        // now update the adapter to make sure it already reflects the change
        mAppsAdapter.setItems(newOrdering);

        // start the update on the database
        mQueryHandler.updateOrdering(newOrdering);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        mDragSortListView = (DragSortListView) view.findViewById(R.id.sort_list_view);
        mDragSortListView.setDropListener(this);
        mDragSortListView.setRemoveListener(this);
        mDragSortListView.setAdapter(mAppsAdapter);
        mDragSortListView.setOnItemClickListener(this);
    }

    public void setAppTag(AppTag appTag) {
        mAppTag = appTag;
        mRetainFragment.loadTaggedApps(appTag.id);
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        TaggedApp app = mAppsAdapter.getItem(position);
        if (app.mDeleted) {
            if (mQueryHandler == null) {
                mQueryHandler = new QueryHandler(getActivity().getContentResolver());
            }
            mQueryHandler.undeleteTaggedApp(app);
            app.mDeleted = false;
            mAppsAdapter.notifyDataSetChanged();
        }
    }

    void onDeleteFinished() {
        mIsChangeInProgress = false;
    }

    void onUpdateFinished() {
        mIsChangeInProgress = false;
    }

    void onAllAppsLoaded(List<AppEntry> entries) {
        mRetainFragment.setAllApps(entries);
    }

    public static class TaggedAppViewHolder extends ViewHolder {

        final TextView mTextView;

        final View mDragHandle;

        final View mUndoDelete;
        final Context mContext;
        AppIconLoaderTaskImpl mAppIconLoaderTask;

        public TaggedAppViewHolder(View view) {
            super(view);
            mContext = view.getContext();
            mTextView = (TextView) view.findViewById(R.id.tag_title);
            mDragHandle = view.findViewById(R.id.drag_handle);
            mUndoDelete = view.findViewById(R.id.undo_delete_text);
        }

        public void loadIcon(Context context, TaggedApp item) {
            if (mAppIconLoaderTask != null) {
                mAppIconLoaderTask.cancel(true);
            }
            mAppIconLoaderTask = new AppIconLoaderTaskImpl(mContext, item.mAppEntry, context.getPackageManager());
            mAppIconLoaderTask.execute();
        }

        /**
         * When true, this item was just removed. We display an undo text
         * to restore the view
         */
        void setLeaveBehindVisible(boolean visible) {
            if (visible) {
                mUndoDelete.setVisibility(View.VISIBLE);
                mTextView.setVisibility(View.GONE);
                mDragHandle.setVisibility(View.GONE);
            } else {
                mUndoDelete.setVisibility(View.GONE);
                mTextView.setVisibility(View.VISIBLE);
                mDragHandle.setVisibility(View.VISIBLE);
            }
        }

        void onIconLoaded(Drawable drawable) {
            int dimen = (int) (mContext.getResources().getDisplayMetrics().density * 42);
            drawable.setBounds(0, 0, dimen, dimen);
            mTextView.setCompoundDrawables(drawable, null, null, null);
        }

        class AppIconLoaderTaskImpl extends AppIconLoaderTask {

            AppIconLoaderTaskImpl(Context context, AppEntry appEntry, PackageManager packageManager) {
                super(context, appEntry, packageManager);
            }

            @Override
            protected void onPostExecute(Drawable drawable) {
                onIconLoaded(drawable);
            }
        }

    }

    /**
     * A simple fragment that will retain some of the loading state to make it quicker to
     * restore the state of the adapter
     */
    public static class RetainQueryHelperFragment extends Fragment {

        final SimpleArrayMap<ComponentName, AppEntry> mAppsByComponent = new SimpleArrayMap<>();

        List<AppEntry> mAllApps;

        List<TaggedApp> mTaggedApps;

        long mAppTagId = Long.MIN_VALUE;

        QueryHandler mQueryHandler;

        AppsAdapter mAppsAdapter;

        boolean mLoadRequested;

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setRetainInstance(true);
            mQueryHandler = new QueryHandler(getActivity().getContentResolver());
        }

        public void setAdapter(AppsAdapter adapter) {
            mAppsAdapter = adapter;
            if (adapter != null) {
                if (mTaggedApps != null) {
                    mAppsAdapter.setItems(mTaggedApps);
                } else {
                    mAppsAdapter.clear();
                }
            }
        }

        public void setAllApps(List<AppEntry> allApps) {
            mAllApps = allApps;
            mAppsByComponent.clear();

            int size = allApps.size();
            for (int i = 0; i < size; i++) {
                AppEntry e = allApps.get(i);
                mAppsByComponent.put(e.getComponentName(), e);
            }
            loadIfNeeded();
        }

        private void loadIfNeeded() {
            if (mLoadRequested) {
                if (mAllApps != null) {
                    mQueryHandler.loadTaggedApps();
                    mLoadRequested = false;
                }
            }
        }

        void loadTaggedApps(long tagId) {
            if (mAppTagId != tagId) {
                mAppTagId = tagId;
                if (mAllApps == null) {
                    mLoadRequested = true;
                } else {
                    mQueryHandler.loadTaggedApps();
                }
            }
        }

        void setAppsInTag(List<TaggedApp> result) {
            mTaggedApps = result;
            if (mAppsAdapter != null) {
                mAppsAdapter.setItems(result);
            }
        }

        class QueryHandler extends AsyncQueryHandler {

            int mActiveToken = 0;

            public QueryHandler(ContentResolver cr) {
                super(cr);
            }

            void loadTaggedApps() {
                cancelOperation(mActiveToken);
                mActiveToken++;
                startQuery(mActiveToken, null,
                        ContentUris.withAppendedId(AppsContract.TaggedAppColumns.CONTENT_URI, mAppTagId),
                        AppQuery.PROJECTION, null, null, AppQuery.ORDER);
            }

            @Override
            protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
                // only handle the result when the token was not changed
                if (token == mActiveToken) {
                    List<TaggedApp> result = new ArrayList<>();
                    while (cursor.moveToNext()) {
                        String comp = cursor.getString(AppQuery.COMPONENT_NAME);
                        ComponentName componentName = ComponentName.unflattenFromString(comp);
                        AppEntry appEntry = mAppsByComponent.get(componentName);

                        if (appEntry == null)
                            continue;

                        TaggedApp app = new TaggedApp();

                        app.mId = cursor.getLong(AppQuery._ID);
                        app.mTagId = cursor.getLong(AppQuery.TAG_ID);
                        app.mTagName = cursor.getString(AppQuery.TAG_NAME);
                        app.mAppEntry = appEntry;
                        app.mComponentName = componentName;

                        result.add(app);
                    }
                    setAppsInTag(result);
                }
            }
        }

    }

    public class AppsAdapter extends BaseListAdapter<TaggedApp, TaggedAppViewHolder>
            implements ConditionalRemovableAdapter {

        @Override
        protected long getItemId(TaggedApp appTag) {
            return appTag.mId;
        }

        @Override
        protected TaggedAppViewHolder newViewHolder(LayoutInflater inflater, ViewGroup parent) {
            View view = inflater.inflate(R.layout.list_item_tagged_app, parent, false);
            return new TaggedAppViewHolder(view);
        }

        @Override
        protected void bindViewHolder(TaggedApp item, TaggedAppViewHolder holder) {
            holder.mTextView.setText(item.mAppEntry.getLabel());
            holder.loadIcon(holder.mTextView.getContext(), item);
            holder.setLeaveBehindVisible(item.mDeleted);
        }

        @Override
        public boolean hasStableIds() {
            return true;
        }

        @Override
        public boolean isEnabled(int position) {
            // when the item is deleted, clicking it is enabled
            return getItem(position).mDeleted;
        }

        @Override
        public boolean canRemove(int pos) {
            return true;
        }
    }

    public class QueryHandler extends AsyncQueryHandler {

        private int mUpdatingCount;

        public QueryHandler(ContentResolver cr) {
            super(cr);
        }

        public void updateOrdering(List<TaggedApp> newOrdering) {
            int size = newOrdering.size();
            mUpdatingCount = size;
            for (int i = 0; i < size; i++) {
                ContentValues values = new ContentValues();
                values.put(AppsContract.TagColumns.POSITION, i);
                TaggedApp tag = newOrdering.get(i);
                Uri uri = ContentUris.withAppendedId(AppsContract.TaggedAppColumns.CONTENT_URI, tag.mId);
                startUpdate(i, tag, uri, values, null, null);
            }
        }

        public void undeleteTaggedApp(TaggedApp app) {
            ContentValues contentValues = new ContentValues();
            contentValues.put(AppsContract.TaggedAppColumns.DELETED, 0);

            startUpdate(2, app, ContentUris.withAppendedId(AppsContract.TaggedAppColumns.CONTENT_URI, app.mId),
                    contentValues, null, null);

        }

        @Override
        protected void onUpdateComplete(int token, Object cookie, int result) {
            if (token == 0) {
                mUpdatingCount--;
                if (mUpdatingCount == 0) {
                    onUpdateFinished();
                }
            } else if (token == 1) {
                onDeleteFinished();
            } else if (token == 2) {
                // undelete finished
            }
        }

        public void removeTaggedApp(TaggedApp app) {
            startDelete(0, null, AppsContract.TaggedAppColumns.CONTENT_URI, AppQuery.WHERE_DELETED, null);
            if (app.mDeleted)
                return;

            ContentValues contentValues = new ContentValues();
            contentValues.put(AppsContract.TaggedAppColumns.DELETED, 1);

            startUpdate(1, app, ContentUris.withAppendedId(AppsContract.TaggedAppColumns.CONTENT_URI, app.mId),
                    contentValues, null, null);
        }

    }

    class AllAppsLoaderCallbacks implements LoaderManager.LoaderCallbacks<List<AppEntry>> {

        @Override
        public Loader<List<AppEntry>> onCreateLoader(int id, Bundle args) {
            return new AppListLoader(getActivity(), mLauncherAppsCompat);
        }

        @Override
        public void onLoadFinished(Loader<List<AppEntry>> loader, List<AppEntry> data) {
            onAllAppsLoaded(data);
        }

        @Override
        public void onLoaderReset(Loader<List<AppEntry>> loader) {

        }
    }

}