org.kiwix.kiwixmobile.ZimFileSelectActivity.java Source code

Java tutorial

Introduction

Here is the source code for org.kiwix.kiwixmobile.ZimFileSelectActivity.java

Source

/*
 * Copyright 2013  Rashiq Ahmad <rashiq.z@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU  General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 */

package org.kiwix.kiwixmobile;

import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.provider.MediaStore;
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.widget.SimpleCursorAdapter;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AlphaAnimation;
import android.widget.Adapter;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import org.kiwix.kiwixmobile.utils.LanguageUtils;
import org.kiwix.kiwixmobile.utils.files.FileSearch;
import org.kiwix.kiwixmobile.utils.files.FileWriter;

public class ZimFileSelectActivity extends AppCompatActivity
        implements LoaderManager.LoaderCallbacks<Cursor>, OnItemClickListener {

    public static final String TAG_KIWIX = "kiwix";

    private static final int LOADER_ID = 0x02;

    // Adapter of the Data populated by the MediaStore
    private SimpleCursorAdapter mCursorAdapter;

    // Adapter of the Data populated by recanning the Filesystem by ourselves
    private RescanDataAdapter mRescanAdapter;

    private ArrayList<DataModel> mFiles;

    private ListView mZimFileList;

    private ProgressBar mProgressBar;

    private TextView mProgressBarMessage;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        new LanguageUtils(this).changeFont(getLayoutInflater());

        setContentView(R.layout.zim_list);
        setUpToolbar();
        mFiles = new ArrayList<DataModel>();

        checkPermissions();

    }

    private void setUpToolbar() {
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        getSupportActionBar().setHomeButtonEnabled(true);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);

        toolbar.setNavigationOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });
    }

    public void checkPermissions() {
        if (ContextCompat.checkSelfPermission(this,
                Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
                && Build.VERSION.SDK_INT > 18) {
            Toast.makeText(this, getResources().getString(R.string.request_storage), Toast.LENGTH_LONG).show();
            ActivityCompat.requestPermissions(this, new String[] { Manifest.permission.READ_EXTERNAL_STORAGE },
                    KiwixMobileActivity.REQUEST_STORAGE_PERMISSION);

        } else {
            getFiles();
        }
    }

    public void getFiles() {

        mProgressBar = (ProgressBar) findViewById(R.id.progressBar);
        mProgressBarMessage = (TextView) findViewById(R.id.progressbar_message);
        mZimFileList = (ListView) findViewById(R.id.zimfilelist);

        mZimFileList.setOnItemClickListener(this);

        mProgressBar.setVisibility(View.VISIBLE);
        setAlpha(true);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            startQuery();
        } else {
            new RescanFileSystem().execute();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
        switch (requestCode) {
        case KiwixMobileActivity.REQUEST_STORAGE_PERMISSION: {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                getFiles();
            } else {
                finish();
            }
            return;
        }

        }
    }

    @Override
    public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {

        Uri uri = MediaStore.Files.getContentUri("external");

        String[] projection = { MediaStore.Files.FileColumns._ID,
                // File Name
                MediaStore.Files.FileColumns.TITLE,
                // File Path
                MediaStore.Files.FileColumns.DATA };

        // Exclude media files, they would be here also (perhaps
        // somewhat better performance), and filter for zim files
        // (normal and first split)
        String query = MediaStore.Files.FileColumns.MEDIA_TYPE + "=" + MediaStore.Files.FileColumns.MEDIA_TYPE_NONE
                + " AND" + " ( LOWER(" + MediaStore.Images.Media.DATA + ") LIKE '%." + FileSearch.zimFiles[0] + "'"
                + " OR LOWER(" + MediaStore.Images.Media.DATA + ") LIKE '%." + FileSearch.zimFiles[1] + "'" + " ) ";

        String[] selectionArgs = null; // There is no ? in query so null here

        String sortOrder = MediaStore.Files.FileColumns.TITLE; // Sorted alphabetical
        Log.d(TAG_KIWIX, " Performing query for zim files...");

        return new CursorLoader(this, uri, projection, query, selectionArgs, sortOrder);
    }

    @Override
    public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
        Log.d(TAG_KIWIX, "DONE querying Mediastore for .zim files");
        buildArrayAdapter(cursor);
        mCursorAdapter.swapCursor(cursor);
        mRescanAdapter = buildArrayAdapter(cursor);
        mZimFileList.setAdapter(mRescanAdapter);

        // Done here to avoid that shown while loading.
        mZimFileList.setEmptyView(findViewById(R.id.zimfilelist_nozimfilesfound_view));

        if (mProgressBarMessage.getVisibility() == View.GONE) {
            mProgressBar.setVisibility(View.GONE);
            setAlpha(false);
        }

        mCursorAdapter.notifyDataSetChanged();
    }

    @Override
    public void onLoaderReset(Loader<Cursor> cursorLoader) {
        mCursorAdapter.swapCursor(null);
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {

        // Check, if the user has rescanned the file system, if he has, then we want to save this list,
        // so this can be shown again, if the activity is recreated (on a device rotation for example)
        if (!mFiles.isEmpty()) {
            Log.i(TAG_KIWIX, "Saved state of the ListView");
            outState.putParcelableArrayList("rescanData", mFiles);
        }
        super.onSaveInstanceState(outState);
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {

        // Get the rescanned data, if available. Create an Adapter for the ListView and display the list
        if (savedInstanceState.getParcelableArrayList("rescanData") != null) {
            ArrayList<DataModel> data = savedInstanceState.getParcelableArrayList("rescanData");
            mRescanAdapter = new RescanDataAdapter(ZimFileSelectActivity.this, 0, data);

            mZimFileList.setAdapter(mRescanAdapter);
        }
        super.onRestoreInstanceState(savedInstanceState);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        final MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.menu_files, menu);
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {

        switch (item.getItemId()) {
        case R.id.menu_rescan_fs:
            // Execute our AsyncTask, that scans the file system for the actual data
            new RescanFileSystem().execute();

            // Make sure, that we set mNeedsUpdate to true and to false, after the MediaStore has been
            // updated. Otherwise it will result in a endless loop.
        }

        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {

        Log.d(TAG_KIWIX, " mZimFileList.onItemClick");

        String file;

        // Check which one of the Adapters is currently filling the ListView.
        // If the data is populated by the LoaderManager cast the current selected mLibrary to Cursor,
        // if the data is populated by the ArrayAdapter, then cast it to the DataModel class.
        if (mZimFileList.getItemAtPosition(position) instanceof DataModel) {

            DataModel data = (DataModel) mZimFileList.getItemAtPosition(position);
            file = data.getPath();
        } else {
            Cursor cursor = (Cursor) mZimFileList.getItemAtPosition(position);
            file = cursor.getString(2);
        }

        finishResult(file);
    }

    // Query through the MediaStore
    protected void startQuery() {

        // Defines a list of columns to retrieve from the Cursor and load into an output row
        String[] mZimListColumns = { MediaStore.Files.FileColumns.TITLE, MediaStore.Files.FileColumns.DATA };

        // Defines a list of View IDs that will receive the Cursor columns for each row
        int[] mZimListItems = { android.R.id.text1, android.R.id.text2 };

        mCursorAdapter = new SimpleCursorAdapter(
                // The Context object
                ZimFileSelectActivity.this,
                // A layout in XML for one row in the ListView
                android.R.layout.simple_list_item_2,
                // The cursor, swapped later by cursorloader
                null,
                // A string array of column names in the cursor
                mZimListColumns,
                // An integer array of view IDs in the row layout
                mZimListItems,
                // Flags for the Adapter
                Adapter.NO_SELECTION);

        getSupportLoaderManager().initLoader(LOADER_ID, null, this);
    }

    // Get the data of our cursor and wrap it all in our ArrayAdapter.
    // We are doing this because the CursorAdapter does not allow us do remove rows from its dataset.
    private RescanDataAdapter buildArrayAdapter(Cursor cursor) {

        ArrayList<DataModel> files = new ArrayList<>();

        for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {

            if (new File(cursor.getString(2)).exists()) {
                files.add(new DataModel(cursor.getString(1), cursor.getString(2)));
            }
        }

        files = new FileWriter(ZimFileSelectActivity.this, files).getDataModelList();

        for (int i = 0; i < files.size(); i++) {

            if (!new File(files.get(i).getPath()).exists()) {
                Log.e(TAG_KIWIX, "File removed: " + files.get(i).getTitle());
                files.remove(i);
            }
        }

        files = new FileSearch().sortDataModel(files);
        mFiles = files;

        return new RescanDataAdapter(ZimFileSelectActivity.this, 0, mFiles);
    }

    // Get the selected file and return the result to the Activity, that called this Activity
    private void finishResult(String path) {

        if (path != null) {
            File file = new File(path);
            Uri uri = Uri.fromFile(file);
            Log.i(TAG_KIWIX, "Opening " + uri);
            setResult(RESULT_OK, new Intent().setData(uri));
            finish();
        } else {
            setResult(RESULT_CANCELED);
            finish();
        }
    }

    // Make the View transparent or opaque
    private void setAlpha(boolean transparent) {

        float viewTransparency = transparent ? 0.4F : 1F;

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            mZimFileList.setAlpha(viewTransparency);
        } else {
            AlphaAnimation alpha = new AlphaAnimation(viewTransparency, viewTransparency);
            alpha.setDuration(0);
            alpha.setFillAfter(true);
            mZimFileList.startAnimation(alpha);
        }
    }

    // The Adapter for the ListView for when the ListView is populated with the rescanned files
    private class RescanDataAdapter extends ArrayAdapter<DataModel> {

        public RescanDataAdapter(Context context, int textViewResourceId, List<DataModel> objects) {
            super(context, textViewResourceId, objects);
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {

            ViewHolder holder;

            // Check if we should inflate the layout for a new row, or if we can reuse a view.
            if (convertView == null) {
                convertView = View.inflate(getContext(), android.R.layout.simple_list_item_2, null);
                holder = new ViewHolder();
                holder.title = (TextView) convertView.findViewById(android.R.id.text1);
                holder.path = (TextView) convertView.findViewById(android.R.id.text2);
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder) convertView.getTag();
            }
            holder.title.setText(getItem(position).getTitle());
            holder.path.setText(getItem(position).getPath());
            return convertView;
        }

        // We are using the ViewHolder pattern in order to optimize the ListView by reusing
        // Views and saving them to this mLibrary class, and not inlating the layout every time
        // we need to create a row.
        private class ViewHolder {

            TextView title;

            TextView path;
        }
    }

    // This AsyncTask will scan the file system for files with the Extension ".zim" or ".zimaa"
    private class RescanFileSystem extends AsyncTask<Void, Void, Void> {

        @Override
        protected void onPreExecute() {

            mProgressBarMessage.setVisibility(View.VISIBLE);
            mProgressBar.setVisibility(View.VISIBLE);
            setAlpha(true);

            super.onPreExecute();
        }

        @Override
        protected Void doInBackground(Void... params) {

            mFiles = new FileSearch().findFiles();
            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            mRescanAdapter = new RescanDataAdapter(ZimFileSelectActivity.this, 0, mFiles);

            mZimFileList.setAdapter(mRescanAdapter);

            mProgressBarMessage.setVisibility(View.GONE);
            mProgressBar.setVisibility(View.GONE);
            setAlpha(false);

            new FileWriter(ZimFileSelectActivity.this).saveArray(mFiles);

            super.onPostExecute(result);
        }
    }
}