com.ultramegasoft.flavordex2.dialog.AppChooserDialog.java Source code

Java tutorial

Introduction

Here is the source code for com.ultramegasoft.flavordex2.dialog.AppChooserDialog.java

Source

/*
 * The MIT License (MIT)
 * Copyright  2016 Steve Guidetti
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the Software?), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED AS IS?, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package com.ultramegasoft.flavordex2.dialog;

import android.annotation.SuppressLint;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.AlertDialog;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import com.ultramegasoft.flavordex2.R;
import com.ultramegasoft.flavordex2.util.AppImportUtils;
import com.ultramegasoft.flavordex2.util.EntryUtils;
import com.ultramegasoft.flavordex2.widget.EntryHolder;

import java.lang.ref.WeakReference;
import java.util.ArrayList;

/**
 * Dialog for choosing which app to import journal entries from.
 *
 * @author Steve Guidetti
 */
public class AppChooserDialog extends DialogFragment {
    private static final String TAG = "AppChooserDialog";

    /**
     * Keys for the Fragment arguments
     */
    private static final String ARG_MULTI_CHOICE = "multi_choice";

    /**
     * The ListView from the layout
     */
    private ListView mListView;

    /**
     * Show the dialog.
     *
     * @param fm          The FragmentManager to use
     * @param multiChoice Whether to allow multiple selections
     */
    public static void showDialog(@NonNull FragmentManager fm, boolean multiChoice) {
        final DialogFragment fragment = new AppChooserDialog();

        final Bundle args = new Bundle();
        args.putBoolean(ARG_MULTI_CHOICE, multiChoice);
        fragment.setArguments(args);

        fragment.show(fm, TAG);
    }

    @NonNull
    @Override
    @SuppressLint("InflateParams")
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        final Context context = getContext();
        if (context == null) {
            return super.onCreateDialog(savedInstanceState);
        }

        final Bundle args = getArguments();
        final boolean multiChoice = args != null && args.getBoolean(ARG_MULTI_CHOICE);
        final View root = LayoutInflater.from(context).inflate(R.layout.dialog_app_chooser, null);

        mListView = root.findViewById(R.id.list);
        mListView.setAdapter(new AppListAdapter(context, multiChoice));

        final int count;
        if (multiChoice) {
            count = mListView.getCount();
            mListView.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE);
            mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                    invalidateButtons();
                }
            });

            final ListAdapter adapter = mListView.getAdapter();
            for (int i = 0; i < count; i++) {
                mListView.setItemChecked(i, adapter.isEnabled(i));
            }
        } else {
            count = 1;
            mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                    selectItem(id);
                }
            });
        }

        final TextView header = root.findViewById(R.id.header);
        final String appsString = getResources().getQuantityString(R.plurals.app, count);
        header.setText(getString(R.string.header_select_app, appsString));

        final AlertDialog.Builder builder = new AlertDialog.Builder(context).setTitle(R.string.title_import_app)
                .setIcon(R.drawable.ic_import).setView(root).setNegativeButton(R.string.button_cancel, null);

        if (multiChoice) {
            builder.setPositiveButton(R.string.button_import, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    importSelected();
                }
            });
        }

        return builder.create();
    }

    /**
     * Update the status of the dialog buttons.
     */
    private void invalidateButtons() {
        final AlertDialog dialog = (AlertDialog) getDialog();
        final boolean itemSelected = mListView.getCheckedItemCount() > 0;
        dialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(itemSelected);
    }

    /**
     * Select a single app to import entries from.
     *
     * @param id The selected item position
     */
    private void selectItem(long id) {
        final FragmentManager fm = getFragmentManager();
        if (fm != null) {
            AppImportDialog.showDialog(fm, (int) id);
        }
        dismiss();
    }

    /**
     * Import all of the entries from the selected apps.
     */
    private void importSelected() {
        final FragmentManager fm = getFragmentManager();
        if (fm == null) {
            return;
        }

        final AppListAdapter adapter = (AppListAdapter) mListView.getAdapter();

        final int[] appIds = new int[mListView.getCheckedItemCount()];
        final CharSequence[] appNames = new String[mListView.getCheckedItemCount()];

        AppImportUtils.AppHolder appHolder;
        for (int i = 0, j = 0; i < mListView.getCount(); i++) {
            if (mListView.isItemChecked(i)) {
                appHolder = adapter.getItem(i);
                appIds[j] = appHolder.app;
                appNames[j] = appHolder.title;
                j++;
            }
        }

        ImporterFragment.init(fm, appIds, appNames);
    }

    /**
     * Custom Adapter for listing installed apps.
     */
    @SuppressWarnings("MethodDoesntCallSuperMethod")
    private static class AppListAdapter extends BaseAdapter {
        /**
         * The Context
         */
        @NonNull
        private final Context mContext;

        /**
         * Whether to allow multiple selections
         */
        private final boolean mMultiChoice;

        /**
         * The list of installed apps
         */
        @NonNull
        private final ArrayList<AppImportUtils.AppHolder> mData;

        /**
         * @param context     The Context
         * @param multiChoice Whether to allow multiple selections
         */
        AppListAdapter(@NonNull Context context, boolean multiChoice) {
            mContext = context;
            mMultiChoice = multiChoice;
            mData = AppImportUtils.getInstalledApps(context.getPackageManager());
        }

        @Override
        public int getCount() {
            return mData.size();
        }

        @Override
        public AppImportUtils.AppHolder getItem(int position) {
            return mData.get(position);
        }

        @Override
        public long getItemId(int position) {
            return getItem(position).app;
        }

        @Override
        public int getViewTypeCount() {
            return 2;
        }

        @Override
        public int getItemViewType(int position) {
            return isEnabled(position) ? 0 : 1;
        }

        @Override
        public boolean isEnabled(int position) {
            return getItem(position).supported;
        }

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

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            final boolean enabled = isEnabled(position);
            if (convertView == null) {
                final int id = enabled ? R.layout.app_list_item : R.layout.app_list_item_disabled;
                convertView = LayoutInflater.from(mContext).inflate(id, parent, false);

                final Holder holder = new Holder();
                holder.icon = convertView.findViewById(R.id.icon);
                holder.title = convertView.findViewById(R.id.title);
                convertView.setTag(holder);

                convertView.findViewById(R.id.checkbox).setVisibility(mMultiChoice ? View.VISIBLE : View.GONE);
            }

            final AppImportUtils.AppHolder appHolder = getItem(position);

            final Holder holder = (Holder) convertView.getTag();
            holder.icon.setImageDrawable(appHolder.icon);
            if (enabled) {
                holder.title.setText(appHolder.title);
            } else {
                holder.title.setText(mContext.getString(R.string.message_app_requires_update, appHolder.title));
            }

            return convertView;
        }

        /**
         * Holder for View references
         */
        private static class Holder {
            ImageView icon;
            TextView title;
        }
    }

    /**
     * Fragment for importing all entries from the selected apps in the background.
     */
    public static class ImporterFragment extends BackgroundProgressDialog {
        private static final String TAG = "ImporterFragment";

        /**
         * Keys for the Fragment arguments
         */
        private static final String ARG_APP_IDS = "app_ids";
        private static final String ARG_APP_NAMES = "app_names";

        /**
         * The list of source apps to import from
         */
        private int[] mApps;

        /**
         * The names of the apps
         */
        private CharSequence[] mAppNames;

        /**
         * Start a new instance of this Fragment.
         *
         * @param fm       The FragmentManager to use
         * @param appIds   The source app IDs
         * @param appNames The names of the apps
         */
        static void init(@NonNull FragmentManager fm, @NonNull int[] appIds, @NonNull CharSequence[] appNames) {
            final DialogFragment fragment = new ImporterFragment();

            final Bundle args = new Bundle();
            args.putIntArray(ARG_APP_IDS, appIds);
            args.putCharSequenceArray(ARG_APP_NAMES, appNames);
            fragment.setArguments(args);

            fragment.show(fm, TAG);
        }

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            final Bundle args = getArguments();
            if (args != null) {
                mApps = args.getIntArray(ARG_APP_IDS);
                mAppNames = args.getCharSequenceArray(ARG_APP_NAMES);
            }
        }

        @NonNull
        @Override
        @SuppressWarnings("MethodDoesntCallSuperMethod")
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            final ProgressDialog dialog = new ProgressDialog(getContext());

            dialog.setIcon(R.drawable.ic_import);
            dialog.setTitle(R.string.title_importing);
            dialog.setIndeterminate(false);
            dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
            dialog.setMessage("");

            return dialog;
        }

        @Override
        protected void startTask() {
            final Context context = getContext();
            if (context != null) {
                new ImportTask(context, this, mApps, mAppNames).execute();
            }
        }

        /**
         * Task for importing entries in the background.
         */
        private static class ImportTask extends AsyncTask<Void, Integer, Void> {
            /**
             * The Context reference
             */
            @NonNull
            private final WeakReference<Context> mContext;

            /**
             * The Fragment
             */
            @NonNull
            private final ImporterFragment mFragment;

            /**
             * The list of source apps to import from
             */
            @NonNull
            private final int[] mApps;

            /**
             * The names of the apps
             */
            @NonNull
            private final CharSequence[] mAppNames;

            /**
             * @param context The Context
             */
            ImportTask(@NonNull Context context, @NonNull ImporterFragment fragment, @NonNull int[] apps,
                    @NonNull CharSequence[] appNames) {
                mContext = new WeakReference<>(context.getApplicationContext());
                mFragment = fragment;
                mApps = apps;
                mAppNames = appNames;
            }

            @Override
            protected Void doInBackground(Void... params) {
                final Context context = mContext.get();
                if (context == null) {
                    return null;
                }

                final ContentResolver cr = context.getContentResolver();

                int appId;
                for (int i = 0; i < mApps.length; i++) {
                    appId = mApps[i];

                    final Uri uri = AppImportUtils.getEntriesUri(appId);
                    final String[] projection = { AppImportUtils.EntriesColumns._ID };
                    final Cursor cursor = cr.query(uri, projection, null, null, null);
                    int count;

                    if (cursor == null) {
                        continue;
                    }

                    try {
                        EntryHolder entry;
                        count = cursor.getCount();
                        int j = 0;
                        while (cursor.moveToNext()) {
                            entry = AppImportUtils.importEntry(context, appId, cursor.getLong(0));
                            try {
                                EntryUtils.insertEntry(context, entry);
                            } catch (SQLiteException e) {
                                Log.e(TAG, "Failed to insert entry: " + entry.title, e);
                            }
                            publishProgress(i, ++j, count);
                        }
                    } finally {
                        cursor.close();
                    }
                    publishProgress(i, count, count);
                }
                return null;
            }

            @Override
            protected void onProgressUpdate(Integer... values) {
                super.onProgressUpdate(values);

                final ProgressDialog dialog = (ProgressDialog) mFragment.getDialog();
                if (dialog != null) {
                    dialog.setMessage(mAppNames[values[0]]);
                    dialog.setMax(values[2]);
                    dialog.setProgress(values[1]);
                }
            }

            @Override
            protected void onPostExecute(Void result) {
                super.onPostExecute(result);

                final Context context = mContext.get();
                if (context != null) {
                    Toast.makeText(context, R.string.message_import_complete, Toast.LENGTH_LONG).show();
                }

                mFragment.dismiss();
            }
        }
    }
}