From ea14a125888a0d399652787c96c33eb842a24b4e Mon Sep 17 00:00:00 2001 From: k3b <1374583+k3b@users.noreply.github.com> Date: Wed, 18 Dec 2019 22:12:56 +0100 Subject: [PATCH] #155: load MediaImageDbReplacement from Contentprovider --- .../androFotoFinder/AndroFotoFinderApp.java | 43 ++++- .../androFotoFinder/FotoGalleryActivity.java | 10 +- .../k3b/android/androFotoFinder/Global.java | 3 +- .../androFotoFinder/SettingsActivity.java | 156 +++++++++--------- .../backup/BackupAsyncTask.java | 116 +------------ .../backup/BackupProgressActivity.java | 87 +++------- .../androFotoFinder/backup/package-info.java | 2 +- .../queries/DatabaseHelper.java | 20 +-- .../queries/MediaDBUpdater.java | 51 ++++++ .../queries/MediaImageDbReplacement.java | 144 +++++++++++++++- .../queries/MergedMediaDB.java | 127 ++++++++++++++ .../k3b/android/widget/ProgressActivity.java | 94 +++++++++++ .../android/widget/ProgressableAsyncTask.java | 152 +++++++++++++++++ app/src/main/res/menu/menu_ao10.xml | 10 ++ app/src/main/res/values/strings.xml | 3 + app/src/main/res/xml/preferences.xml | 6 + .../java/de/k3b/database/QueryParameter.java | 14 +- 17 files changed, 755 insertions(+), 283 deletions(-) create mode 100644 app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBUpdater.java create mode 100644 app/src/main/java/de/k3b/android/androFotoFinder/queries/MergedMediaDB.java create mode 100644 app/src/main/java/de/k3b/android/widget/ProgressActivity.java create mode 100644 app/src/main/java/de/k3b/android/widget/ProgressableAsyncTask.java create mode 100644 app/src/main/res/menu/menu_ao10.xml diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java b/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java index 65d3e901..8052fe7b 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java @@ -21,6 +21,7 @@ import android.app.Application; import android.content.Context; +import android.database.sqlite.SQLiteDatabase; import android.support.annotation.NonNull; import android.util.Log; import android.widget.Toast; @@ -36,9 +37,14 @@ import de.k3b.LibGlobal; import de.k3b.android.GuiUtil; import de.k3b.android.androFotoFinder.imagedetail.HugeImageLoader; +import de.k3b.android.androFotoFinder.queries.DatabaseHelper; import de.k3b.android.androFotoFinder.queries.FotoSql; import de.k3b.android.androFotoFinder.queries.FotoSqlBase; +import de.k3b.android.androFotoFinder.queries.IMediaDBApi; import de.k3b.android.androFotoFinder.queries.MediaDBContentprovider; +import de.k3b.android.androFotoFinder.queries.MediaDBUpdater; +import de.k3b.android.androFotoFinder.queries.MediaImageDbReplacement; +import de.k3b.android.androFotoFinder.queries.MergedMediaDB; import de.k3b.android.osmdroid.forge.MapsForgeSupport; import de.k3b.android.util.LogCat; import de.k3b.android.widget.ActivityWithCallContext; @@ -58,6 +64,11 @@ */ public class AndroFotoFinderApp extends Application { private static String fileNamePrefix = "androFotofinder.logcat-"; + private static MediaDBUpdater mediaDbUpdater = null; + + public static MediaDBUpdater getMediaDbUpdater() { + return mediaDbUpdater; + } private LogCat mCrashSaveToFile = null; @@ -77,6 +88,35 @@ public static String getGetTeaserText(Context context, String linkUrlForDetails) return result; } + public static void setMediaImageDbReplacement(Context context, boolean useMediaImageDbReplacement) { + final IMediaDBApi oldMediaDBApi = FotoSql.getMediaDBApi(); + if ((oldMediaDBApi == null) || (Global.useMediaImageDbReplacement != useMediaImageDbReplacement)) { + Global.useMediaImageDbReplacement = useMediaImageDbReplacement; + + final MediaDBContentprovider mediaDBContentprovider = new MediaDBContentprovider(context); + + if (Global.useMediaImageDbReplacement) { + final SQLiteDatabase writableDatabase = DatabaseHelper.getWritableDatabase(context); + final MediaImageDbReplacement mediaImageDbReplacement = new MediaImageDbReplacement(writableDatabase); + FotoSql.setMediaDBApi(new MergedMediaDB(mediaImageDbReplacement, mediaDBContentprovider)); + + AndroFotoFinderApp.mediaDbUpdater = new MediaDBUpdater(context, writableDatabase); + + if (FotoSql.getCount(new QueryParameter().addWhere("1 = 1")) == 0) { + // database is empty; reload from Contentprovider + AndroFotoFinderApp.mediaDbUpdater.rebuild(context, null); + } + } else { + if ((oldMediaDBApi != null) && (AndroFotoFinderApp.mediaDbUpdater != null)) { + // switching from mediaImageDbReplacement to Contentprovider + AndroFotoFinderApp.mediaDbUpdater.clearMediaCopy(); + } + FotoSql.setMediaDBApi(mediaDBContentprovider); + AndroFotoFinderApp.mediaDbUpdater = null; + } + } + } + /* private RefWatcher refWatcher; @@ -95,9 +135,6 @@ public static RefWatcher getRefWatcher(Context context) { // StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyDeath().build()); FotoSqlBase.init(); - /// #155: todo: depending on android-api version set IMediaDBApi - FotoSql.setMediaDBApi(new MediaDBContentprovider(this)); - super.onCreate(); LibGlobal.appName = getString(R.string.app_name); diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/FotoGalleryActivity.java b/app/src/main/java/de/k3b/android/androFotoFinder/FotoGalleryActivity.java index 41ca82d3..883c4402 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/FotoGalleryActivity.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/FotoGalleryActivity.java @@ -44,8 +44,8 @@ import de.k3b.android.widget.AboutDialogPreference; import de.k3b.android.widget.BaseQueryActivity; import de.k3b.database.QueryParameter; -import de.k3b.io.collections.SelectedItems; import de.k3b.io.IDirectory; +import de.k3b.io.collections.SelectedItems; /** * Gallery: Show zeoro or more images in a grid optionally filtered by a @@ -167,6 +167,11 @@ public boolean onCreateOptionsMenu(Menu menu) { inflater.inflate(R.menu.menu_gallery_non_selected_only, menu); inflater.inflate(R.menu.menu_gallery_non_multiselect, menu); + + if (Global.useMediaImageDbReplacement) { + inflater.inflate(R.menu.menu_ao10, menu); + } + /* getActionBar().setListNavigationCallbacks(); MenuItem sorter = menu.getItem(R.id.cmd_sort); @@ -202,6 +207,9 @@ public boolean onOptionsItemSelected(MenuItem item) { case R.id.cmd_about: AboutDialogPreference.createAboutDialog(this).show(); return true; + case R.id.cmd_db_reload: + AndroFotoFinderApp.getMediaDbUpdater().rebuild(this, null); + return true; case R.id.cmd_more: new Handler().postDelayed(new Runnable() { public void run() { diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/Global.java b/app/src/main/java/de/k3b/android/androFotoFinder/Global.java index b654e478..21a12bb7 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/Global.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/Global.java @@ -127,7 +127,8 @@ public static class Media { // #155: fix android10 incompatibility // Build.VERSION_CODES.??ANDROID10?? = 29 //!!! - public static final boolean useMediaImageDbReplacement = true; + public static boolean useMediaImageDbReplacement = true; + // public static final boolean useMediaImageDbReplacement = (Build.VERSION.SDK_INT >= 29); /** map with blue selection markers: how much to area to increase */ public static final double mapMultiselectionBoxIncreaseByProcent = 100.0; diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/SettingsActivity.java b/app/src/main/java/de/k3b/android/androFotoFinder/SettingsActivity.java index fb04cffa..bdd79fa9 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/SettingsActivity.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/SettingsActivity.java @@ -60,82 +60,6 @@ public class SettingsActivity extends PreferenceActivity { private int INSTALL_REQUEST_CODE = 1927; - @Override - protected void onCreate(final Bundle savedInstanceState) { - LocalizedActivity.fixLocale(this); // #21: Support to change locale at runtime - super.onCreate(savedInstanceState); - - if (Global.debugEnabled) { - // todo create junit integration tests with arabic locale from this. - StringFormatResourceTests.test(this); - } - - final Intent intent = getIntent(); - if (Global.debugEnabled && (intent != null)){ - Log.d(Global.LOG_CONTEXT, "SettingsActivity onCreate " + intent.toUri(Intent.URI_INTENT_SCHEME)); - } - - this.addPreferencesFromResource(R.xml.preferences); - prefsInstance = PreferenceManager - .getDefaultSharedPreferences(this); - global2Prefs(this.getApplication()); - - // #21: Support to change locale at runtime - defaultLocalePreference = - (ListPreference) findPreference(Global.PREF_KEY_USER_LOCALE); - defaultLocalePreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - setLanguage((String) newValue); - LocalizedActivity.recreate(SettingsActivity.this); - return true; // change is allowed - } - }); - - mediaUpdateStrategyPreference = - (ListPreference) findPreference("mediaUpdateStrategy"); - mediaUpdateStrategyPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - LibGlobal.mediaUpdateStrategy = (String) newValue; - setPref(LibGlobal.mediaUpdateStrategy, mediaUpdateStrategyPreference, R.array.pref_media_update_strategy_names); - return true; - } - }); - setPref(LibGlobal.mediaUpdateStrategy, mediaUpdateStrategyPreference, R.array.pref_media_update_strategy_names); - - findPreference("debugClearLog").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - onDebugClearLogCat(); - return false; // donot close - } - }); - findPreference("debugSaveLog").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - onDebugSaveLogCat(); - return false; // donot close - } - }); - findPreference("translate").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - onTranslate(); - return false; // donot close - } - }); - - // #21: Support to change locale at runtime - updateSummary(); - } - - @Override - public void onPause() { - prefs2Global(this); - super.onPause(); - } - public static void global2Prefs(Context context) { fixDefaults(context, null, null); @@ -168,6 +92,7 @@ public static void global2Prefs(Context context) { prefs.putBoolean("xmp_file_schema_long", LibGlobal.preferLongXmpFormat); prefs.putBoolean("mapsForgeEnabled", Global.mapsForgeEnabled); + prefs.putBoolean("useMediaImageDbReplacement", Global.useMediaImageDbReplacement); prefs.putBoolean("locked", Global.locked); prefs.putString("passwordHash", Global.passwordHash); @@ -191,6 +116,12 @@ public static void global2Prefs(Context context) { } + @Override + public void onPause() { + prefs2Global(this); + super.onPause(); + } + public static void prefs2Global(Context context) { File previousCacheRoot = Global.thumbCacheRoot; File previousMapsForgeDir = Global.mapsForgeDir; @@ -241,8 +172,8 @@ public static void prefs2Global(Context context) { LibGlobal.preferLongXmpFormat = getPref(prefs, "xmp_file_schema_long", LibGlobal.preferLongXmpFormat); Global.mapsForgeEnabled = getPref(prefs, "mapsForgeEnabled", Global.mapsForgeEnabled); + AndroFotoFinderApp.setMediaImageDbReplacement(context, getPref(prefs, "useMediaImageDbReplacement", Global.useMediaImageDbReplacement)); - Global.imageDetailThumbnailIfBiggerThan = getPref(prefs, "imageDetailThumbnailIfBiggerThan" , Global.imageDetailThumbnailIfBiggerThan); Global.maxSelectionMarkersInMap = getPref(prefs, "maxSelectionMarkersInMap" , Global.maxSelectionMarkersInMap); @@ -285,6 +216,77 @@ public static void prefs2Global(Context context) { fixDefaults(context, previousCacheRoot, previousMapsForgeDir); } + @Override + protected void onCreate(final Bundle savedInstanceState) { + LocalizedActivity.fixLocale(this); // #21: Support to change locale at runtime + super.onCreate(savedInstanceState); + + if (Global.debugEnabled) { + // todo create junit integration tests with arabic locale from this. + StringFormatResourceTests.test(this); + } + + final Intent intent = getIntent(); + if (Global.debugEnabled && (intent != null)) { + Log.d(Global.LOG_CONTEXT, "SettingsActivity onCreate " + intent.toUri(Intent.URI_INTENT_SCHEME)); + } + + this.addPreferencesFromResource(R.xml.preferences); + this.addPreferencesFromResource(R.xml.preferences); + prefsInstance = PreferenceManager + .getDefaultSharedPreferences(this); + global2Prefs(this.getApplication()); + + // #21: Support to change locale at runtime + defaultLocalePreference = + (ListPreference) findPreference(Global.PREF_KEY_USER_LOCALE); + defaultLocalePreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + setLanguage((String) newValue); + LocalizedActivity.recreate(SettingsActivity.this); + return true; // change is allowed + } + }); + + mediaUpdateStrategyPreference = + (ListPreference) findPreference("mediaUpdateStrategy"); + mediaUpdateStrategyPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + LibGlobal.mediaUpdateStrategy = (String) newValue; + setPref(LibGlobal.mediaUpdateStrategy, mediaUpdateStrategyPreference, R.array.pref_media_update_strategy_names); + return true; + } + }); + setPref(LibGlobal.mediaUpdateStrategy, mediaUpdateStrategyPreference, R.array.pref_media_update_strategy_names); + + findPreference("debugClearLog").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + onDebugClearLogCat(); + return false; // donot close + } + }); + findPreference("debugSaveLog").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + onDebugSaveLogCat(); + return false; // donot close + } + }); + findPreference("translate").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + onTranslate(); + return false; // donot close + } + }); + + // #21: Support to change locale at runtime + updateSummary(); + } + private static void fixDefaults(Context context, File previousCacheRoot, File previousMapsForgeDir) { boolean mustSave = false; diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/backup/BackupAsyncTask.java b/app/src/main/java/de/k3b/android/androFotoFinder/backup/BackupAsyncTask.java index b95c9dee..9f683891 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/backup/BackupAsyncTask.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/backup/BackupAsyncTask.java @@ -21,82 +21,39 @@ import android.app.Activity; import android.content.Context; -import android.content.Intent; -import android.os.AsyncTask; import android.util.Log; import android.widget.ProgressBar; import android.widget.TextView; import java.util.Date; -import java.util.concurrent.atomic.AtomicBoolean; -import de.k3b.android.androFotoFinder.Common; import de.k3b.android.androFotoFinder.Global; import de.k3b.android.androFotoFinder.R; +import de.k3b.android.widget.ProgressableAsyncTask; import de.k3b.io.IProgessListener; import de.k3b.zip.IZipConfig; import de.k3b.zip.LibZipGlobal; -import de.k3b.zip.ProgressFormatter; import de.k3b.zip.ZipConfigDto; import de.k3b.zip.ZipStorage; -/** - * ProgressData: Text that can be displayed as progress message - * in owning Activity. Translated from android independant {@link de.k3b.io.IProgessListener} - */ -class ProgressData { - final int itemcount; - final int size; - final String message; - - ProgressData(int itemcount, int size, String message) { - this.itemcount = itemcount; - this.size = size; - this.message = message; - } -} - /** * Async ancapsulation of * {@link de.k3b.android.androFotoFinder.backup.Backup2ZipService} */ -public class BackupAsyncTask extends AsyncTask implements IProgessListener { +public class BackupAsyncTask extends ProgressableAsyncTask implements IProgessListener { private final String mDebugPrefix = "BackupAsyncTask "; private final Backup2ZipService service; - private Activity activity; - private ProgressBar mProgressBar = null; - private TextView status; - private AtomicBoolean isActive = new AtomicBoolean(true); - - private final ProgressFormatter formatter; - // last known number of items to be processed - private int lastSize = 0; public BackupAsyncTask(Context context, ZipConfigDto mZipConfigData, ZipStorage zipStorage, Date backupDate) { this.service = new Backup2ZipService(context.getApplicationContext(), mZipConfigData, zipStorage, backupDate, BackupOptions.ALL); - - formatter = new ProgressFormatter(); } - /** - * (Re-)Attach owning Activity to BackupAsyncTask - * (i.e. after Device rotation - * - * @param why - * @param activity new owner - * @param progressBar To be updated while compression task is running - * @param status To be updated while compression task is running - */ + @Override public void setContext(String why, Activity activity, ProgressBar progressBar, TextView status) { - if (LibZipGlobal.debugEnabled) { - Log.d(LibZipGlobal.LOG_TAG, mDebugPrefix + why + " setContext " + activity); - } - this.activity = activity; - mProgressBar = progressBar; - this.status = status; + super.setContext(why, activity, progressBar, status); service.setProgessListener((progressBar != null) ? this : null); } @@ -136,71 +93,12 @@ protected void onPostExecute(IZipConfig zipConfig) { } } - private void finish(String why, int resultCode, CharSequence message) { - if (message != null) { - Intent intent = new Intent(); - intent.putExtra(Common.EXTRA_TITLE, message); - activity.setResult(resultCode, intent); - } else { - activity.setResult(resultCode); - } - activity.finish(); - - setContext(why + mDebugPrefix + " finish ", null, null, null); - - } - - /** called on error */ - @Override - protected void onCancelled() { - if (activity != null) { - if (LibZipGlobal.debugEnabled || Global.debugEnabled) { - Log.d(LibZipGlobal.LOG_TAG, activity.getClass().getSimpleName() + ": " + activity.getText(android.R.string.cancel)); - } - - finish(mDebugPrefix + " onCancelled ", Activity.RESULT_CANCELED, activity.getText(android.R.string.cancel)); - } - } - - public static boolean isActive(BackupAsyncTask backupAsyncTask) { - return (backupAsyncTask != null) && (backupAsyncTask.isActive.get()); - } - - /** - * de.k3b.io.IProgessListener: - * - * called every time when command makes some little progress in non gui thread. - * return true to continue - */ @Override public boolean onProgress(int itemcount, int size, String message) { - publishProgress(new ProgressData(itemcount, size, message)); - - final boolean cancelled = this.isCancelled(); - if (cancelled) { + boolean result = onProgress(itemcount, size, message); + if (isCancelled()) { if (this.service != null) this.service.cancel(); - if (LibZipGlobal.debugEnabled) { - Log.d(LibZipGlobal.LOG_TAG, mDebugPrefix + " cancel backup pressed "); - } - } - return !cancelled; - } - - /** called from {@link AsyncTask} in gui task */ - @Override - protected void onProgressUpdate(ProgressData... values) { - final ProgressData progressData = values[0]; - if (mProgressBar != null) { - int size = progressData.size; - if ((size != 0) && (size > lastSize)) { - mProgressBar.setMax(size); - lastSize = size; - } - mProgressBar.setProgress(progressData.itemcount); - } - if (this.status != null) { - this.status.setText(formatter.format(progressData.itemcount, progressData.size)); - // values[0].message); } + return result; } } diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/backup/BackupProgressActivity.java b/app/src/main/java/de/k3b/android/androFotoFinder/backup/BackupProgressActivity.java index 1c17f7b5..b21d7b73 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/backup/BackupProgressActivity.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/backup/BackupProgressActivity.java @@ -29,9 +29,6 @@ import android.support.annotation.NonNull; import android.support.v4.provider.DocumentFile; import android.util.Log; -import android.view.View; -import android.widget.Button; -import android.widget.ProgressBar; import android.widget.TextView; import java.io.File; @@ -42,7 +39,8 @@ import de.k3b.android.androFotoFinder.Global; import de.k3b.android.androFotoFinder.R; import de.k3b.android.util.IntentUtil; -import de.k3b.android.widget.LocalizedActivity; +import de.k3b.android.widget.ProgressActivity; +import de.k3b.android.widget.ProgressableAsyncTask; import de.k3b.zip.IZipConfig; import de.k3b.zip.LibZipGlobal; import de.k3b.zip.ZipConfigDto; @@ -52,7 +50,7 @@ /** * #108: Showing progress while backup/compression-to-zip is executed */ -public class BackupProgressActivity extends LocalizedActivity { +public class BackupProgressActivity extends ProgressActivity { /** * document tree supported since andrid-5.0. For older devices use folder picker */ @@ -62,7 +60,7 @@ public class BackupProgressActivity extends LocalizedActivity { private static String mDebugPrefix = "BuProgressActivity: "; // != null while async backup is running - private static BackupAsyncTask backupAsyncTask = null; + private static ProgressableAsyncTask asyncTask = null; private static Date backupDate = null; private IZipConfig mZipConfigData = null; @@ -123,7 +121,17 @@ private static DocumentFile getDocFile(Context context, @NonNull String dir) { } -/* + @Override + protected ProgressableAsyncTask getAsyncTask() { + return asyncTask; + } + + @Override + protected void setAsyncTask(ProgressableAsyncTask asyncTask) { + BackupProgressActivity.asyncTask = asyncTask; + } + + /* @Override protected void onPause() { setBackupAsyncTaskProgessReceiver(null); @@ -141,16 +149,16 @@ protected void onCreate(Bundle savedInstanceState) { mZipConfigData = (IZipConfig) intent.getSerializableExtra(EXTRA_STATE_ZIP_CONFIG); - if (backupAsyncTask == null) { + if (getAsyncTask() == null) { backupDate = new Date(); final String zipDir = mZipConfigData.getZipDir(); final String zipName = ZipConfigDto.getZipFileName(mZipConfigData, backupDate); ZipStorage zipStorage = getCurrentStorage(this, zipDir, zipName); - backupAsyncTask = new BackupAsyncTask(this, new ZipConfigDto(mZipConfigData), zipStorage, - backupDate); - setBackupAsyncTaskProgessReceiver(mDebugPrefix + "onCreate create backupAsyncTask ", this); - backupAsyncTask.execute(); + setAsyncTask(new BackupAsyncTask(this, new ZipConfigDto(mZipConfigData), zipStorage, + backupDate)); + setAsyncTaskProgessReceiver(mDebugPrefix + "onCreate create asyncTask ", this); + getAsyncTask().execute(); } final TextView lblContext = (TextView) findViewById(R.id.lbl_context); @@ -165,60 +173,5 @@ protected void onCreate(Bundle savedInstanceState) { lblContext.setText(contextMessage); } - @Override - protected void onDestroy() { - setBackupAsyncTaskProgessReceiver(mDebugPrefix + "onDestroy ", null); - super.onDestroy(); - } - - @Override - protected void onResume() { - setBackupAsyncTaskProgessReceiver(mDebugPrefix + "onResume ", this); - Global.debugMemory(mDebugPrefix, "onResume"); - super.onResume(); - - } - - /** - * (Re-)Connects this activity back with static backupAsyncTask - */ - private void setBackupAsyncTaskProgessReceiver(String why, Activity progressReceiver) { - boolean isActive = BackupAsyncTask.isActive(backupAsyncTask); - boolean running = (progressReceiver != null) && isActive; - - String debugContext = why + mDebugPrefix + " setBackupAsyncTaskProgessReceiver isActive=" + isActive + - ", running=" + running + - " "; - - if (backupAsyncTask != null) { - final ProgressBar progressBar = (ProgressBar) this.findViewById(R.id.progressBar); - final TextView status = (TextView) this.findViewById(R.id.lbl_status); - final Button buttonCancel = (Button) this.findViewById(R.id.cmd_cancel); - - // setVisibility(running, progressBar, buttonCancel); - - if (running) { - backupAsyncTask.setContext(debugContext, this, progressBar, status); - final String _debugContext = debugContext; - buttonCancel.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (LibZipGlobal.debugEnabled) { - Log.d(LibZipGlobal.LOG_TAG, mDebugPrefix + " button Cancel backup pressed initialized by " + _debugContext); - } - backupAsyncTask.cancel(false); - buttonCancel.setVisibility(View.INVISIBLE); - } - }); - - } else { - backupAsyncTask.setContext(debugContext, null, null, null); - buttonCancel.setOnClickListener(null); - if (!isActive) { - backupAsyncTask = null; - } - } - } - } } diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/backup/package-info.java b/app/src/main/java/de/k3b/android/androFotoFinder/backup/package-info.java index 166a8b4f..c4cbd97c 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/backup/package-info.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/backup/package-info.java @@ -26,7 +26,7 @@ * * {@link de.k3b.android.androFotoFinder.backup.Backup2ZipService} : Collects Files to backed up from database via Filter * * {@link de.k3b.android.androFotoFinder.backup.ApmZipCompressJob} : Executes the compression (with android specific Filesystem) * * {@link de.k3b.zip.CompressJob} : Executes the compression (with android independant implementation) - * * {@link de.k3b.android.androFotoFinder.backup.ProgressData} : Data containing compression progress + * * {@link de.k3b.android.androFotoFinder.backup.BackupProgressActivity} : Data containing compression progress * * {@link de.k3b.io.IProgessListener} : android independant compression progress **/ package de.k3b.android.androFotoFinder.backup; diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/DatabaseHelper.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/DatabaseHelper.java index 76150770..5a983f79 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/queries/DatabaseHelper.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/DatabaseHelper.java @@ -51,6 +51,12 @@ public static SQLiteDatabase getWritableDatabase(Context context) { return instance.getWritableDatabase(); } + public static void version2Upgrade_RecreateMediDbCopy(final SQLiteDatabase db) { + for (String sql : MediaImageDbReplacement.Impl.DDL) { + db.execSQL(sql); + } + } + /** * called if database doesn-t exist yet */ @@ -58,24 +64,18 @@ public static SQLiteDatabase getWritableDatabase(Context context) { public void onCreate(final SQLiteDatabase db) { db.execSQL(TransactionLogSql.CREATE_TABLE); - this.version2Upgrade_MediDbCopy(db); + this.version2Upgrade_RecreateMediDbCopy(db); } + private static DatabaseHelper instance = null; + @Override public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) { Log.w(this.getClass().toString(), "Upgrading database from version " + oldVersion + " to " + newVersion + ". (Old data is kept.)"); if (oldVersion < DatabaseHelper.DATABASE_VERSION_2_MEDIA_DB_COPY) { - this.version2Upgrade_MediDbCopy(db); - } - } - - private static DatabaseHelper instance = null; - - private void version2Upgrade_MediDbCopy(final SQLiteDatabase db) { - for (String sql : MediaImageDbReplacement.Impl.DDL) { - db.execSQL(sql); + this.version2Upgrade_RecreateMediDbCopy(db); } } } diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBUpdater.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBUpdater.java new file mode 100644 index 00000000..146eded6 --- /dev/null +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBUpdater.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2019 by k3b. + * + * This file is part of AndroFotoFinder / #APhotoManager. + * + * 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 + * (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, see + */ +package de.k3b.android.androFotoFinder.queries; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.widget.Toast; + +import java.util.Date; + +import de.k3b.io.IProgessListener; + +public class MediaDBUpdater { + private final Context context; + private final SQLiteDatabase writableDatabase; + + public MediaDBUpdater(Context context, SQLiteDatabase writableDatabase) { + this.context = context; + this.writableDatabase = writableDatabase; + } + + public void rebuild(Context context, IProgessListener progessListener) { + long start = new Date().getTime(); + clearMediaCopy(); + MediaImageDbReplacement.Impl.updateMedaiCopy(context, writableDatabase, null, progessListener); + start = (new Date().getTime() - start) / 1000; + final String text = "load db " + start + " secs"; + Toast.makeText(context, text, Toast.LENGTH_LONG).show(); + if (progessListener != null) progessListener.onProgress(0, 0, text); + } + + public void clearMediaCopy() { + DatabaseHelper.version2Upgrade_RecreateMediDbCopy(writableDatabase); + } +} diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaImageDbReplacement.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaImageDbReplacement.java index 855f2029..92f546e9 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaImageDbReplacement.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaImageDbReplacement.java @@ -23,6 +23,7 @@ import android.database.Cursor; import android.database.DatabaseUtils; import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteStatement; import android.net.Uri; import android.os.Build; import android.os.CancellationSignal; @@ -33,8 +34,10 @@ import de.k3b.LibGlobal; import de.k3b.android.androFotoFinder.Global; +import de.k3b.android.androFotoFinder.R; import de.k3b.database.QueryParameter; import de.k3b.io.AlbumFile; +import de.k3b.io.IProgessListener; import de.k3b.io.StringUtils; import de.k3b.io.VISIBILITY; @@ -117,11 +120,12 @@ private Cursor createCursorForQuery(StringBuilder out_debugMessage, String dbgCo excpetion = ex; } finally { if ((excpetion != null) || Global.debugEnabledSql || (out_debugMessage != null)) { + final int count = (query == null) ? 0 : query.getCount(); StringBuilder message = StringUtils.appendMessage(out_debugMessage, excpetion, dbgContext, MODUL_NAME + ".createCursorForQuery:\n", QueryParameter.toString(sqlSelectColums, null, Impl.table, sqlWhereStatement, - selectionArgs, sqlSortOrder, query.getCount())); + selectionArgs, sqlSortOrder, count)); if (out_debugMessage == null) { Log.i(Global.LOG_CONTEXT, message.toString(), excpetion); } // else logging is done by caller @@ -299,6 +303,7 @@ public static class Impl { * copied from android-4.4 android database. Removed columns not used */ public static final String[] DDL = new String[]{ + "DROP TABLE IF EXISTS \"files\"", "CREATE TABLE \"files\" (\n" + "\t_id INTEGER PRIMARY KEY AUTOINCREMENT,\n" + "\t_size INTEGER,\n" + @@ -392,6 +397,78 @@ private static boolean isDouble(int index) { return index >= dblMin && index <= dblMax; } + private static String getSqlInsertWithParams() { + StringBuilder sql = new StringBuilder(); + + sql.append("INSERT INTO ").append(table).append("(").append(USED_MEDIA_COLUMNS[0]); + for (int i = 1; i < USED_MEDIA_COLUMNS.length; i++) { + sql.append(", ").append(USED_MEDIA_COLUMNS[i]); + } + sql.append(") VALUES (?"); + for (int i = 1; i < USED_MEDIA_COLUMNS.length; i++) { + sql.append(", ?"); + } + sql.append(")"); + return sql.toString(); + } + + private static String getSqlUpdateWithParams() { + StringBuilder sql = new StringBuilder(); + + sql.append("UPDATE ").append(table).append(" SET "); + for (int i = 1; i < USED_MEDIA_COLUMNS.length; i++) { + if (i > 1) sql.append(", "); + sql.append(USED_MEDIA_COLUMNS[i]).append("=?"); + } + sql.append(" WHERE ").append(USED_MEDIA_COLUMNS[0]).append("=?"); + return sql.toString(); + } + + private static int bindAndExecUpdate(Cursor c, SQLiteStatement sql) { + sql.clearBindings(); + + // sql where + sql.bindLong(dblMax + 1, c.getLong(intMin)); + + for (int i = intMin + 1; i <= intMax; i++) { + if (!c.isNull(i)) { + sql.bindLong(i, c.getLong(i)); + } + } + for (int i = txtMin; i <= txtMax; i++) { + if (!c.isNull(i)) { + sql.bindString(i, c.getString(i)); + } + } + for (int i = dblMin; i <= dblMax; i++) { + if (!c.isNull(i)) { + sql.bindDouble(i, c.getDouble(i)); + } + } + return sql.executeUpdateDelete(); + } + + private static void bindAndExecInsert(Cursor c, SQLiteStatement sql) { + sql.clearBindings(); + + for (int i = intMin; i <= intMax; i++) { + if (!c.isNull(i)) { + sql.bindLong(i + 1, c.getLong(i)); + } + } + for (int i = txtMin; i <= txtMax; i++) { + if (!c.isNull(i)) { + sql.bindString(i + 1, c.getString(i)); + } + } + for (int i = dblMin; i <= dblMax; i++) { + if (!c.isNull(i)) { + sql.bindDouble(i + 1, c.getDouble(i)); + } + } + sql.executeInsert(); + } + private static ContentValues getContentValues(Cursor cursor, ContentValues destination) { destination.clear(); int colCount = cursor.getColumnCount(); @@ -411,8 +488,18 @@ private static ContentValues getContentValues(Cursor cursor, ContentValues desti return destination; } - public static int updateMedaiCopy(Context context, SQLiteDatabase db, Date lastUpdate) { - int changeCount = 0; + public static void clearMedaiCopy(SQLiteDatabase db) { + try { + db.execSQL("DROP TABLE " + table); + } catch (Exception ex) { + // Log.e(Global.LOG_CONTEXT, "FotoSql.execGetFotoPaths() Cannot get path from: " + FotoSql.SQL_COL_PATH + " like '" + pathFilter +"'", ex); + } finally { + } + } + + + public static int updateMedaiCopy(Context context, SQLiteDatabase db, Date lastUpdate, IProgessListener progessListener) { + int progress = 0; QueryParameter query = queryGetAllColumns; long _lastUpdate = (lastUpdate != null) ? (lastUpdate.getTime() / 1000L) : 0L; @@ -423,24 +510,63 @@ public static int updateMedaiCopy(Context context, SQLiteDatabase db, Date lastU // FotoSql.createCursorForQuery() } Cursor c = null; - ContentValues contentValues = new ContentValues(); + SQLiteStatement sqlInsert = null; + SQLiteStatement sqlUpdate = null; + SQLiteStatement lastSql = null; + boolean isUpdate = false; + // ContentValues contentValues = new ContentValues(); try { - c = ContentProviderMediaImpl.createCursorForQuery(null, "execGetFotoPaths(pathFilter)", context, + db.beginTransaction(); // Performance boost: all db-inserts/updates in one transaction + + if (progessListener != null) progessListener.onProgress(progress, 0, + context.getString(R.string.load_db_menu_title)); + + c = ContentProviderMediaImpl.createCursorForQuery(null, "updateMedaiCopy", context, query, null, null); + int itemCount = c.getCount(); + + sqlInsert = db.compileStatement(getSqlInsertWithParams()); + sqlUpdate = db.compileStatement(getSqlUpdateWithParams()); while (c.moveToNext()) { - getContentValues(c, contentValues); - save(db, c, contentValues, _lastUpdate); + // getContentValues(c, contentValues); + + isUpdate = (c.getLong(colDATE_ADDED) <= _lastUpdate); + + if (isUpdate) { + lastSql = sqlUpdate; + isUpdate = bindAndExecUpdate(c, sqlUpdate) > 0; + // 0 affected update rows: must insert + } + + if (!isUpdate) { + lastSql = sqlInsert; + bindAndExecInsert(c, sqlInsert); + } + + lastSql = null; + // save(db, c, contentValues, _lastUpdate); + if ((progessListener != null) && (progress % 100) == 0) { + if (!progessListener.onProgress(progress, itemCount, context.getString(R.string.scanner_update_result_format, progress))) { + // canceled in gui thread + return -progress; + } + } + progress++; } + db.setTransactionSuccessful(); // This commits the transaction if there were no exceptions } catch (Exception ex) { - // Log.e(Global.LOG_CONTEXT, "FotoSql.execGetFotoPaths() Cannot get path from: " + FotoSql.SQL_COL_PATH + " like '" + pathFilter +"'", ex); + Log.e(Global.LOG_CONTEXT, "MediaImageDbReplacement.updateMedaiCopy cannot insert/update: " + lastSql + " from " + c, ex); } finally { + sqlInsert.close(); + sqlUpdate.close(); + db.endTransaction(); if (c != null) c.close(); } if (Global.debugEnabled) { // Log.d(Global.LOG_CONTEXT, "FotoSql.execGetFotoPaths() result count=" + result.size()); } - return 0; + return progress; } private static void save(SQLiteDatabase db, Cursor c, ContentValues contentValues, long lastUpdate) { diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/MergedMediaDB.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MergedMediaDB.java new file mode 100644 index 00000000..0939a6a5 --- /dev/null +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MergedMediaDB.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2019 by k3b. + * + * This file is part of AndroFotoFinder / #APhotoManager. + * + * 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 + * (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, see + */ +package de.k3b.android.androFotoFinder.queries; + +import android.content.ContentValues; +import android.net.Uri; + +import de.k3b.io.VISIBILITY; + +/** + * #155: All reads are done through database while writes are + * applied to database and contentProvider. + *

+ * Since Android-10 (api 29) using sqLite functions as content-provider-columns is not possible anymore. + * Therefore apm uses a copy of contentprovider MediaStore.Images with same column names and same pk. + */ +public class MergedMediaDB extends MediaDBApiWrapper { + private final IMediaDBApi database; + private final IMediaDBApi contentProvider; + + public MergedMediaDB(IMediaDBApi database, IMediaDBApi contentProvider) { + super(database, contentProvider); + this.database = database; + this.contentProvider = contentProvider; + } + + @Override + public int execUpdate(String dbgContext, long id, ContentValues values) { + int result = super.execUpdate(dbgContext, id, values); + database.execUpdate(dbgContext, id, values); + return result; + } + + @Override + public int execUpdate(String dbgContext, String path, ContentValues values, VISIBILITY visibility) { + int result = super.execUpdate(dbgContext, path, values, visibility); + database.execUpdate(dbgContext, path, values, visibility); + return result; + } + + @Override + public int exexUpdateImpl(String dbgContext, ContentValues values, String sqlWhere, String[] selectionArgs) { + int result = super.exexUpdateImpl(dbgContext, values, sqlWhere, selectionArgs); + database.exexUpdateImpl(dbgContext, values, sqlWhere, selectionArgs); + return result; + } + + /** + * return id of inserted item + * + * @param dbgContext + * @param dbUpdateFilterJpgFullPathName + * @param values + * @param visibility + * @param updateSuccessValue + */ + @Override + public Long insertOrUpdateMediaDatabase(String dbgContext, String dbUpdateFilterJpgFullPathName, + ContentValues values, VISIBILITY visibility, Long updateSuccessValue) { + Long result = updateSuccessValue; + + int modifyCount = contentProvider.execUpdate(dbgContext, dbUpdateFilterJpgFullPathName, + values, visibility); + + if (modifyCount == 0) { + // update failed (probably becauce oldFullPathName not found. try insert it. + FotoSql.addDateAdded(values); + + // insert into contentProvider and database + Uri uriWithId = execInsert(dbgContext, values); + result = FotoSql.getId(uriWithId); + } else { + // update into contentprovider successfull. also add to database + database.execUpdate(dbgContext, dbUpdateFilterJpgFullPathName, + values, visibility); + } + return result; + } + + /** + * every database insert should go through this. adds logging if enabled + * + * @param dbgContext + * @param values + */ + @Override + public Uri execInsert(String dbgContext, ContentValues values) { + Uri result = super.execInsert(dbgContext, values); + + // insert with same pk as contentprovider does + values.put(FotoSql.SQL_COL_PK, FotoSql.getId(result)); + database.execInsert(dbgContext, values); + values.remove(FotoSql.SQL_COL_PK); + return result; + } + + /** + * Deletes media items specified by where with the option to prevent cascade delete of the image. + * + * @param dbgContext + * @param where + * @param selectionArgs + * @param preventDeleteImageFile + */ + @Override + public int deleteMedia(String dbgContext, String where, String[] selectionArgs, boolean preventDeleteImageFile) { + int result = super.deleteMedia(dbgContext, where, selectionArgs, preventDeleteImageFile); + database.deleteMedia(dbgContext, where, selectionArgs, preventDeleteImageFile); + return result; + } +} diff --git a/app/src/main/java/de/k3b/android/widget/ProgressActivity.java b/app/src/main/java/de/k3b/android/widget/ProgressActivity.java new file mode 100644 index 00000000..1a1fa1c8 --- /dev/null +++ b/app/src/main/java/de/k3b/android/widget/ProgressActivity.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2019 by k3b. + * + * This file is part of AndroFotoFinder / #APhotoManager. + * + * 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 + * (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, see + */ +package de.k3b.android.widget; + +import android.app.Activity; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.ProgressBar; +import android.widget.TextView; + +import de.k3b.android.androFotoFinder.Global; +import de.k3b.android.androFotoFinder.R; +import de.k3b.zip.LibZipGlobal; + +public abstract class ProgressActivity extends LocalizedActivity { + private static String mDebugPrefix = "ProgressActivity: "; + + abstract protected ProgressableAsyncTask getAsyncTask(); + + abstract protected void setAsyncTask(ProgressableAsyncTask asyncTask); + + @Override + protected void onDestroy() { + setAsyncTaskProgessReceiver(mDebugPrefix + "onDestroy ", null); + super.onDestroy(); + } + + @Override + protected void onResume() { + setAsyncTaskProgessReceiver(mDebugPrefix + "onResume ", this); + Global.debugMemory(mDebugPrefix, "onResume"); + super.onResume(); + + } + + /** + * (Re-)Connects this activity back with static asyncTask + */ + protected void setAsyncTaskProgessReceiver(String why, Activity progressReceiver) { + boolean isActive = ProgressableAsyncTask.isActive(getAsyncTask()); + boolean running = (progressReceiver != null) && isActive; + + String debugContext = why + mDebugPrefix + " setBackupAsyncTaskProgessReceiver isActive=" + isActive + + ", running=" + running + + " "; + + if (getAsyncTask() != null) { + final ProgressBar progressBar = (ProgressBar) this.findViewById(R.id.progressBar); + final TextView status = (TextView) this.findViewById(R.id.lbl_status); + final Button buttonCancel = (Button) this.findViewById(R.id.cmd_cancel); + + // setVisibility(running, progressBar, buttonCancel); + + if (running) { + getAsyncTask().setContext(debugContext, this, progressBar, status); + final String _debugContext = debugContext; + buttonCancel.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (LibZipGlobal.debugEnabled) { + Log.d(LibZipGlobal.LOG_TAG, mDebugPrefix + " button Cancel backup pressed initialized by " + _debugContext); + } + getAsyncTask().cancel(false); + buttonCancel.setVisibility(View.INVISIBLE); + } + }); + + } else { + getAsyncTask().setContext(debugContext, null, null, null); + buttonCancel.setOnClickListener(null); + if (!isActive) { + setAsyncTask(null); + } + } + } + } +} diff --git a/app/src/main/java/de/k3b/android/widget/ProgressableAsyncTask.java b/app/src/main/java/de/k3b/android/widget/ProgressableAsyncTask.java new file mode 100644 index 00000000..ca74abd7 --- /dev/null +++ b/app/src/main/java/de/k3b/android/widget/ProgressableAsyncTask.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2019 by k3b. + * + * This file is part of AndroFotoFinder / #APhotoManager. + * + * 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 + * (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, see + */ +package de.k3b.android.widget; + +import android.app.Activity; +import android.content.Intent; +import android.os.AsyncTask; +import android.util.Log; +import android.widget.ProgressBar; +import android.widget.TextView; + +import java.util.concurrent.atomic.AtomicBoolean; + +import de.k3b.android.androFotoFinder.Common; +import de.k3b.android.androFotoFinder.Global; +import de.k3b.io.IProgessListener; +import de.k3b.zip.LibZipGlobal; +import de.k3b.zip.ProgressFormatter; + +public abstract class ProgressableAsyncTask extends AsyncTask implements IProgessListener { + protected final ProgressFormatter formatter = new ProgressFormatter(); + private final String mDebugPrefix = "ProgressableAsyncTask "; + protected Activity activity; + protected AtomicBoolean isActive = new AtomicBoolean(true); + // last known number of items to be processed + protected int lastSize = 0; + private ProgressBar mProgressBar = null; + private TextView status; + + public static boolean isActive(ProgressableAsyncTask backupAsyncTask) { + return (backupAsyncTask != null) && (backupAsyncTask.isActive.get()); + } + + /** + * (Re-)Attach owning Activity to BackupAsyncTask + * (i.e. after Device rotation + * + * @param why + * @param activity new owner + * @param progressBar To be updated while compression task is running + * @param status To be updated while compression task is running + */ + public void setContext(String why, Activity activity, ProgressBar progressBar, TextView status) { + if (LibZipGlobal.debugEnabled) { + Log.d(LibZipGlobal.LOG_TAG, mDebugPrefix + why + " setContext " + activity); + } + this.activity = activity; + mProgressBar = progressBar; + this.status = status; + } + + /** + * called on error + */ + @Override + protected void onCancelled() { + if (activity != null) { + if (LibZipGlobal.debugEnabled || Global.debugEnabled) { + Log.d(LibZipGlobal.LOG_TAG, activity.getClass().getSimpleName() + ": " + activity.getText(android.R.string.cancel)); + } + + finish(mDebugPrefix + " onCancelled ", Activity.RESULT_CANCELED, activity.getText(android.R.string.cancel)); + } + } + + protected void finish(String why, int resultCode, CharSequence message) { + if (message != null) { + Intent intent = new Intent(); + intent.putExtra(Common.EXTRA_TITLE, message); + activity.setResult(resultCode, intent); + } else { + activity.setResult(resultCode); + } + activity.finish(); + + setContext(why + mDebugPrefix + " finish ", null, null, null); + + } + + /** + * de.k3b.io.IProgessListener: + *

+ * called every time when command makes some little progress in non gui thread. + * return true to continue + */ + @Override + public boolean onProgress(int itemcount, int size, String message) { + publishProgress(new ProgressData(itemcount, size, message)); + + final boolean cancelled = this.isCancelled(); + if (cancelled) { + if (LibZipGlobal.debugEnabled) { + Log.d(LibZipGlobal.LOG_TAG, mDebugPrefix + " cancel backup pressed "); + } + } + return !cancelled; + } + + + /** + * called from {@link AsyncTask} in gui task + */ + @Override + protected void onProgressUpdate(ProgressData... values) { + final ProgressData progressData = values[0]; + if (mProgressBar != null) { + int size = progressData.size; + if ((size != 0) && (size > lastSize)) { + mProgressBar.setMax(size); + lastSize = size; + } + mProgressBar.setProgress(progressData.itemcount); + } + if (this.status != null) { + this.status.setText(formatter.format(progressData.itemcount, progressData.size)); + // values[0].message); + } + } +} + +/** + * ProgressData: Text that can be displayed as progress message + * in owning Activity. Translated from android independant {@link de.k3b.io.IProgessListener} + */ +class ProgressData { + final int itemcount; + final int size; + final String message; + + ProgressData(int itemcount, int size, String message) { + this.itemcount = itemcount; + this.size = size; + this.message = message; + } +} + diff --git a/app/src/main/res/menu/menu_ao10.xml b/app/src/main/res/menu/menu_ao10.xml new file mode 100644 index 00000000..f2749423 --- /dev/null +++ b/app/src/main/res/menu/menu_ao10.xml @@ -0,0 +1,10 @@ + +

+ + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 38ec4e98..8030dfd9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -279,5 +279,8 @@ You can undo hiding by calling the mediascanner from gallery-menu." < ! - - what type of data should be saven ? - - > What: --> + + + (Re)Load Media Database diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 671db294..343f0a22 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -126,6 +126,12 @@ this program. If not, see + + ... lists) { return result; } - private static boolean append(StringBuilder result, String blockPrefix, List list, String delimiter, String before, String after) { + private static boolean append(StringBuilder result, String blockPrefix, List list, String seperator, String before, String after) { if (isNotEmpty(list)) { if (blockPrefix != null) { result.append(blockPrefix); @@ -519,8 +519,8 @@ private static boolean append(StringBuilder result, String blockPrefix, List list) { + return toSeperatedFieldListOrNull(list, ", "); + } + + private static String toSeperatedFieldListOrNull(List list, String seperator) { if (isNotEmpty(list)) { StringBuilder result = new StringBuilder(); - append(result, null, list, ", ", null, null); + append(result, null, list, seperator, null, null); return result.toString(); } return null;