/* Copyright (C) 2012 Haowen Ning 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 2 of the License, or (at your option) 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package org.liberty.android.fantastischmemo.ui; import android.Manifest; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.databinding.DataBindingUtil; import android.net.Uri; import android.os.Bundle; import android.preference.PreferenceManager; import android.support.design.widget.NavigationView; import android.support.design.widget.Snackbar; import android.support.design.widget.TabLayout; import android.support.v4.app.ActivityCompat; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentPagerAdapter; import android.support.v4.app.LoaderManager; import android.support.v4.content.AsyncTaskLoader; import android.support.v4.content.ContextCompat; import android.support.v4.content.Loader; import android.support.v4.view.GravityCompat; import android.support.v4.view.ViewPager; import android.support.v7.widget.Toolbar; import android.text.Html; import android.text.method.LinkMovementMethod; import android.util.Log; import android.view.MenuItem; import android.view.View; import android.widget.TextView; import android.widget.Toast; import com.google.common.base.Objects; import org.apache.commons.io.FileUtils; import org.liberty.android.fantastischmemo.BuildConfig; import org.liberty.android.fantastischmemo.R; import org.liberty.android.fantastischmemo.common.AMEnv; import org.liberty.android.fantastischmemo.common.AMPrefKeys; import org.liberty.android.fantastischmemo.common.BaseActivity; import org.liberty.android.fantastischmemo.databinding.MainTabsBinding; import org.liberty.android.fantastischmemo.receiver.SetAlarmReceiver; import org.liberty.android.fantastischmemo.service.AnyMemoService; import org.liberty.android.fantastischmemo.ui.loader.MultipleLoaderManager; import org.liberty.android.fantastischmemo.utils.AMFileUtil; import org.liberty.android.fantastischmemo.utils.AboutUtil; import org.liberty.android.fantastischmemo.utils.DatabaseUtil; import org.liberty.android.fantastischmemo.utils.RecentListActionModeUtil; import org.liberty.android.fantastischmemo.utils.RecentListUtil; import org.liberty.android.fantastischmemo.widget.AnyMemoWidgetProvider; import java.io.File; import java.io.IOException; import java.io.InputStream; import javax.inject.Inject; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.CompositeDisposable; import io.reactivex.functions.Consumer; public class AnyMemo extends BaseActivity { private static final String WEBSITE_VERSION="https://anymemo.org/versions-view"; private SharedPreferences settings; private CompositeDisposable disposables; private MainTabsBinding binding; @Inject AMFileUtil amFileUtil; @Inject RecentListUtil recentListUtil; @Inject DatabaseUtil databaseUtil; @Inject MultipleLoaderManager multipleLoaderManager; @Inject AboutUtil aboutUtil; @Inject RecentListActionModeUtil recentListActionModeUtil; private static final int PERMISSION_REQUEST_EXTERNAL_STORAGE = 1; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); activityComponents().inject(this); disposables = new CompositeDisposable(); binding = DataBindingUtil.setContentView(this, R.layout.main_tabs); // Request storage permission if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_EXTERNAL_STORAGE); } else { loadUiComponents(); } recentListActionModeUtil.registerForActivity(); } @Override public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { switch (requestCode) { case PERMISSION_REQUEST_EXTERNAL_STORAGE: { // If request is cancelled, the result arrays are empty. if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { loadUiComponents(); } else { Toast.makeText(this, R.string.write_storage_permission_denied_message, Toast.LENGTH_LONG) .show(); finish(); } } } } private void loadUiComponents() { settings = PreferenceManager.getDefaultSharedPreferences(this); initDrawer(); initCreateDbFab(); prepareStoreage(); prepareFirstTimeRun(); prepareNotification(); if (getIntent() != null && Objects.equal(getIntent().getAction(), Intent.ACTION_VIEW)) { handleOpenIntent(); // Set the action null when the intent is handled to prevent the logic being executed // again when the screen is rotated getIntent().setAction(null); } } /** * Initialize the Navigation drawer UI. */ private void initDrawer() { final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); final ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager); FragmentPagerAdapter adapter = new MainPagerAdapter(getSupportFragmentManager()); viewPager.setAdapter(adapter); final TabLayout tabLayout = binding.tabs; tabLayout.setupWithViewPager(viewPager); tabLayout.getTabAt(0).setIcon(R.drawable.clock); tabLayout.getTabAt(1).setIcon(R.drawable.cabinet); tabLayout.getTabAt(2).setIcon(R.drawable.download_tab); tabLayout.getTabAt(3).setIcon(R.drawable.misc); final NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view); navigationView.setNavigationItemSelectedListener( new NavigationView.OnNavigationItemSelectedListener() { @Override public boolean onNavigationItemSelected(MenuItem menuItem) { switch (menuItem.getItemId()) { case R.id.recent_tab_menu: tabLayout.getTabAt(0).select(); break; case R.id.open_tab_menu: tabLayout.getTabAt(1).select(); break; case R.id.download_tab_menu: tabLayout.getTabAt(2).select(); break; case R.id.misc_tab_menu: tabLayout.getTabAt(3).select(); break; case R.id.option_tab_menu: startActivity(new Intent(tabLayout.getContext(), OptionScreen.class)); break; case R.id.about_tab_menu: aboutUtil.createAboutDialog(); break; } menuItem.setChecked(true); binding.drawerLayout.closeDrawers(); return true; } } ); // Change the selected navigation view viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { // Nothing } @Override public void onPageSelected(int position) { navigationView.getMenu().getItem(position).setChecked(true); // Only add db FAB show in the file browser fragment if (position == 1) { binding.addDbFab.setVisibility(View.VISIBLE); } else { binding.addDbFab.setVisibility(View.GONE); } } @Override public void onPageScrollStateChanged(int state) { // Nothing } } ); getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_menu); getSupportActionBar().setDisplayHomeAsUpEnabled(true); } private void prepareStoreage() { File sdPath = new File(AMEnv.DEFAULT_ROOT_PATH); sdPath.mkdir(); if(!sdPath.canRead()){ DialogInterface.OnClickListener exitButtonListener = new DialogInterface.OnClickListener(){ public void onClick(DialogInterface arg0, int arg1){ finish(); } }; new AlertDialog.Builder(this) .setTitle(R.string.sdcard_not_available_warning_title) .setMessage(R.string.sdcard_not_available_warning_message) .setNeutralButton(R.string.exit_text, exitButtonListener) .create() .show(); } } private void prepareFirstTimeRun() { File sdPath = new File(AMEnv.DEFAULT_ROOT_PATH); //Check the version, if it is updated from an older version it will show a dialog int savedVersionCode = settings.getInt(AMPrefKeys.SAVED_VERSION_CODE_KEY, 1); int thisVersionCode; try { thisVersionCode = getPackageManager().getPackageInfo(getPackageName(), 0).versionCode; } catch (PackageManager.NameNotFoundException e) { thisVersionCode = 0; if (BuildConfig.DEBUG) { throw new RuntimeException("The version code can not be retrieved. Is it defined in build.gradle?"); } } boolean firstTime = settings.getBoolean(AMPrefKeys.FIRST_TIME_KEY, true); // Force clean preference for non-compstible versions. if (savedVersionCode < 154) { // Version 9.0.4 firstTime = true; SharedPreferences.Editor editor = settings.edit(); editor.clear(); editor.commit(); } /* First time installation! It will install the sample db * to /sdcard/AnyMemo */ if(firstTime == true){ SharedPreferences.Editor editor = settings.edit(); editor.putBoolean(AMPrefKeys.FIRST_TIME_KEY, false); editor.putString(AMPrefKeys.getRecentPathKey(0), AMEnv.DEFAULT_ROOT_PATH + AMEnv.DEFAULT_DB_NAME); editor.commit(); try { amFileUtil.copyFileFromAsset(AMEnv.DEFAULT_DB_NAME, new File(sdPath + "/" + AMEnv.DEFAULT_DB_NAME)); InputStream in2 = getResources().getAssets().open(AMEnv.EMPTY_DB_NAME); String emptyDbPath = getApplicationContext().getFilesDir().getAbsolutePath() + "/" + AMEnv.EMPTY_DB_NAME; FileUtils.copyInputStreamToFile(in2, new File(emptyDbPath)); in2.close(); } catch(IOException e){ Log.e(TAG, "Copy file error", e); } } /* Detect an update */ if (savedVersionCode != thisVersionCode) { SharedPreferences.Editor editor = settings.edit(); /* save new version number */ editor.putInt(AMPrefKeys.SAVED_VERSION_CODE_KEY, thisVersionCode); editor.commit(); View alertView = View.inflate(this, R.layout.link_alert, null); TextView textView = (TextView)alertView.findViewById(R.id.link_alert_message); textView.setText(Html.fromHtml(getString(R.string.what_is_new_message))); textView.setMovementMethod(LinkMovementMethod.getInstance()); new AlertDialog.Builder(this) .setView(alertView) .setTitle(getString(R.string.what_is_new)) .setPositiveButton(getString(R.string.ok_text), null) .setNegativeButton(getString(R.string.about_version), new DialogInterface.OnClickListener(){ @Override public void onClick(DialogInterface arg0, int arg1){ Intent myIntent = new Intent(); myIntent.setAction(Intent.ACTION_VIEW); myIntent.addCategory(Intent.CATEGORY_BROWSABLE); myIntent.setData(Uri.parse(WEBSITE_VERSION)); startActivity(myIntent); } }) .show(); } } private void prepareNotification() { SetAlarmReceiver.cancelNotificationAlarm(this); SetAlarmReceiver.setNotificationAlarm(this); } /** * Handle the "VIEW" action for other app to open a db */ private void handleOpenIntent() { multipleLoaderManager.registerLoaderCallbacks(1, new HandleOpenIntentLoaderCallbacks(getIntent().getData()), false); multipleLoaderManager.startLoading(); } /** * We create the FAB only at Activity level to make sure the FAB is not scrolling up and down * with appbar_scrolling_view_behavior. The behavior will make the FAB half visible if the FAB is * inside a ViewPager fragment. */ private void initCreateDbFab() { // Make sure the addDbFab is only shown on FileBrowser fragment if (binding.tabs.getSelectedTabPosition() != 1) { binding.addDbFab.setVisibility(View.GONE); } binding.addDbFab.setOnClickListener(new CardFragment.OnClickListener() { @Override public void onClick(View v) { disposables.add(activityComponents().databaseOperationDialogUtil().showCreateDbDialog(AMEnv.DEFAULT_ROOT_PATH) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Consumer<File>() { @Override public void accept(File file) throws Exception { appComponents().eventBus().post(new FileBrowserFragment.RefreshFileListEvent(file.getParentFile())); } })); } }); } @Override public void onDestroy() { recentListActionModeUtil.unregisterForActivity(); disposables.dispose(); super.onDestroy(); // Update the widget and cancel the notification. AnyMemoWidgetProvider.updateWidget(this); Intent myIntent = new Intent(this, AnyMemoService.class); myIntent.putExtra("request_code", AnyMemoService.CANCEL_NOTIFICATION); startService(myIntent); if (multipleLoaderManager != null) { multipleLoaderManager.destroy(); } } @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == android.R.id.home) { binding.drawerLayout.openDrawer(GravityCompat.START); return true; } return super.onOptionsItemSelected(item); } /** * The loader to copy the db from temporary location to AnyMemo folder */ private class HandleOpenIntentLoaderCallbacks implements LoaderManager.LoaderCallbacks<File> { private final Uri contentUri; public HandleOpenIntentLoaderCallbacks(Uri contentUri) { this.contentUri = contentUri; } @Override public Loader<File> onCreateLoader(int id, Bundle args) { Loader<File> loader = new AsyncTaskLoader<File>(AnyMemo.this) { @Override public File loadInBackground() { String[] splittedUri = contentUri.toString().split("/"); String newFileName = splittedUri[splittedUri.length - 1]; if (!newFileName.endsWith(".db")) { newFileName += ".db"; } File newFile = new File(AMEnv.DEFAULT_ROOT_PATH + "/" + newFileName); // First detect if the db with the same name exists. // And back kup the db if try { amFileUtil.deleteFileWithBackup(newFile.getAbsolutePath()); } catch (IOException e) { Log.e(TAG, "Failed to delete the exisitng db with backup", e); } InputStream inputStream; try { inputStream = AnyMemo.this.getContentResolver().openInputStream(contentUri); FileUtils.copyInputStreamToFile(inputStream, newFile); } catch (IOException e) { Log.e(TAG, "Error opening file from intent", e); return null; } if (!databaseUtil.checkDatabase(newFile.getAbsolutePath())) { Log.e(TAG, "Database is corrupted: " + newFile.getAbsolutePath()); newFile.delete(); return null; }; return newFile; } }; loader.forceLoad(); return loader; } @Override public void onLoadFinished(Loader<File> loader, File newFile) { if (newFile == null) { Snackbar.make(binding.drawerLayout, R.string.db_file_is_corrupted_text, Snackbar.LENGTH_LONG) .show(); // Don’t forget to show! Log.e(TAG, "Could not load db from intent"); return; } recentListUtil.addToRecentList(newFile.getAbsolutePath()); Intent intent = new Intent(); intent.setClass(AnyMemo.this, PreviewEditActivity.class); intent.putExtra(PreviewEditActivity.EXTRA_DBPATH, newFile.getAbsolutePath()); startActivity(intent); multipleLoaderManager.checkAllLoadersCompleted(); } @Override public void onLoaderReset(Loader<File> loader) { // Nothing } } private static class MainPagerAdapter extends FragmentPagerAdapter { private Fragment[] fragments = new Fragment[]{ new RecentListFragment(), new OpenTabFragment(), new DownloadTabFragment(), new MiscTabFragment() }; public MainPagerAdapter(FragmentManager fm) { super(fm); // Set arguments for the OpenTabFragment fragment // to show the create action Bundle bundle = new Bundle(); bundle.putBoolean(FileBrowserFragment.EXTRA_SHOW_CREATE_DB_BUTTON, true); fragments[1].setArguments(bundle); } @Override public Fragment getItem(int position) { return fragments[position]; } @Override public int getCount() { return fragments.length; } @Override public CharSequence getPageTitle(int position) { // Display icon only return null; } } }