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 28dcff8f..cd652893 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java @@ -50,6 +50,7 @@ import de.k3b.android.osmdroid.forge.MapsForgeSupport; import de.k3b.android.util.LogCat; import de.k3b.android.widget.ActivityWithCallContext; +import de.k3b.android.widget.LocalizedActivity; import de.k3b.database.QueryParameter; import de.k3b.io.PhotoAutoprocessingDto; import de.k3b.media.ExifInterface; @@ -93,6 +94,10 @@ public static String getGetTeaserText(Context context, String linkUrlForDetails) public static void setMediaImageDbReplacement(Context context, boolean useMediaImageDbReplacement) { final IMediaDBApi oldMediaDBApi = FotoSql.getMediaDBApi(); if ((oldMediaDBApi == null) || (Global.useMediaImageDbReplacement != useMediaImageDbReplacement)) { + + // menu must be recreated + LocalizedActivity.setMustRecreate(); + Global.useMediaImageDbReplacement = useMediaImageDbReplacement; final MediaDBContentprovider mediaDBContentprovider = new MediaDBContentprovider(context); diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/LockScreen.java b/app/src/main/java/de/k3b/android/androFotoFinder/LockScreen.java index cfffb82c..f9e2ce64 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/LockScreen.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/LockScreen.java @@ -28,6 +28,7 @@ import android.view.MenuItem; import de.k3b.android.util.MenuUtils; +import de.k3b.android.widget.LocalizedActivity; /** * #105: Management of app locking (aka Android "Screen"-pinning, "Kiosk Mode", "LockTask") @@ -52,6 +53,7 @@ public static boolean onOptionsItemSelected(Activity parent, MenuItem item) { Global.locked = true; SettingsActivity.global2Prefs(parent.getApplication()); } + LocalizedActivity.setMustRecreate(); } return true; case R.id.cmd_app_unpin2: @@ -59,6 +61,7 @@ public static boolean onOptionsItemSelected(Activity parent, MenuItem item) { Global.locked = false; SettingsActivity.global2Prefs(parent.getApplication()); parent.invalidateOptionsMenu(); + LocalizedActivity.setMustRecreate(); return true; } return false; diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailActivityViewPager.java b/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailActivityViewPager.java index a98bbc58..4a8b5f2d 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailActivityViewPager.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailActivityViewPager.java @@ -165,82 +165,7 @@ public class ImageDetailActivityViewPager extends ActivityWithAutoCloseDialogs i // if not null this one image that cannot be translated to a file uri will be shown private Uri imageUri = null; - /** executes sql to load image detail data in a background task that may survive - * conriguration change (i.e. device rotation) */ - class LocalCursorLoader implements LoaderManager.LoaderCallbacks { - /** incremented every time a new curster/query is generated */ - private int mRequeryInstanceCount = 0; - - /** called by LoaderManager.getLoader(ACTIVITY_ID) to (re)create loader - * that attaches to last query/cursor if it still exist i.e. after rotation */ - @Override - public Loader onCreateLoader(int loaderID, Bundle bundle) { - switch (loaderID) { - case ACTIVITY_ID: - mRequeryInstanceCount++; - mWaitingForMediaScannerResult = false; - if (Global.debugEnabledSql) { - Log.i(Global.LOG_CONTEXT, mDebugPrefix + " onCreateLoader" + - getDebugContext() + - " : query = " + mGalleryContentQuery); - } - return FotoSql.createCursorLoader(getApplicationContext(), mGalleryContentQuery); - default: - // An invalid id was passed in - return null; - } - } - - /** called after media db content has changed */ - @Override - public void onLoadFinished(Loader loader, Cursor data) { - // to be restored after refreshLocal if there is no mInitialFilePath - if ((mInitialScrollPosition == NO_INITIAL_SCROLL_POSITION) && (mViewPager != null)) { - mInitialScrollPosition = mViewPager.getCurrentItem(); - } - // do change the data - mAdapter.swapCursor(data); - - // restore position is invalid - final int newItemCount = mAdapter.getCount(); - - if (((newItemCount == 0)) || (mInitialScrollPosition >= newItemCount)) mInitialScrollPosition = NO_INITIAL_SCROLL_POSITION; - - if (Global.debugEnabledSql) { - Log.i(Global.LOG_CONTEXT, mDebugPrefix + " onLoadFinished" + - getDebugContext() + - " found " + ((data == null) ? 0 : newItemCount) + " rows"); - } - - // do change the data - mAdapter.notifyDataSetChanged(); - mViewPager.setAdapter(mAdapter); - - // show the changes - onLoadCompleted(); - } - - /** called by LoaderManager. after search criteria were changed or if activity is destroyed. */ - @Override - public void onLoaderReset(Loader loader) { - // rember position where we have to scroll to after refreshLocal is finished. - mInitialScrollPosition = mViewPager.getCurrentItem(); - mAdapter.swapCursor(null); - if (Global.debugEnabledSql) { - Log.i(Global.LOG_CONTEXT, mDebugPrefix + " onLoaderReset" + - getDebugContext()); - } - mAdapter.notifyDataSetChanged(); - } - - @NonNull - private String getDebugContext() { - return "(#" + mRequeryInstanceCount - + ", mScrollPosition=" + mInitialScrollPosition + - ", Path='" + mInitialFilePath + - "')"; - } - } + long mUpdateId = FotoSql.getMediaDBApi().getCurrentUpdateId(); class LocalFileCommands extends AndroidFileCommands { @Override @@ -272,73 +197,172 @@ protected void onPostProcess(String what, int opCode, SelectedFiles selectedFile } - public static class MoveOrCopyDestDirPicker extends DirectoryPickerFragment { - protected static AndroidFileCommands sFileCommands = null; + @Override + protected void onResume() { + unhideActionBar(Global.actionBarHideTimeInMilliSecs, "onResume"); + Global.debugMemory(mDebugPrefix, "onResume"); + super.onResume(); - public static MoveOrCopyDestDirPicker newInstance(boolean move, final SelectedFiles srcFotos) { - MoveOrCopyDestDirPicker f = new MoveOrCopyDestDirPicker(); + final boolean locked = LockScreen.isLocked(this); + if (this.locked != locked) { + this.locked = locked; + mMustReplaceMenue = true; + invalidateOptionsMenu(); + } - // Supply index input as an argument. - Bundle args = new Bundle(); - args.putBoolean("move", move); - AffUtils.putSelectedFiles(args, srcFotos); - f.setArguments(args); + if (Global.debugEnabledMemory) { + Log.d(Global.LOG_CONTEXT, mDebugPrefix + " - onResume cmd (" + + MoveOrCopyDestDirPicker.sFileCommands + ") => (" + mFileCommands + + ")"); - return f; } - /* do not use activity callback */ - @Override - protected void setDirectoryListener(Activity activity) {/* do not use activity callback */} + // workaround fragment lifecycle is newFragment.attach oldFragment.detach. + // this makes shure that the visible fragment has commands + MoveOrCopyDestDirPicker.sFileCommands = mFileCommands; + refreshIfNecessary(); + } - public boolean getMove() { - return getArguments().getBoolean("move", false); - } + @Override + public boolean onOptionsItemSelected(MenuItem menuItem) { + boolean reloadContext = true; + boolean result = true; + boolean slideShowStarted = mSlideShowStarted; - public SelectedFiles getSrcFotos() { - return AffUtils.getSelectedFiles(getArguments()); + onGuiTouched(); + if (LockScreen.onOptionsItemSelected(this, menuItem)) { + mMustReplaceMenue = true; + this.invalidateOptionsMenu(); + return true; } + if (mFileCommands.onOptionsItemSelected(menuItem, getCurrentFoto())) { + mModifyCount++; + } else { + // Handle presses on the action bar items + switch (menuItem.getItemId()) { + case R.id.action_details: + cmdShowDetails(getCurrentFilePath(), getCurrentImageId()); + break; - /** - * To be overwritten to check if a path can be picked. - * - * @param path to be checked if it cannot be handled - * @return null if no error else error message with the reason why it cannot be selected - */ - @Override - protected String getStatusErrorMessage(String path) { - String errorMessage = (sFileCommands == null) ? null : sFileCommands.checkWriteProtected(0, new File(path)); - if (errorMessage != null) { - int pos = errorMessage.indexOf('\n'); - return (pos > 0) ? errorMessage.substring(0,pos) : errorMessage; - } - return super.getStatusErrorMessage(path); - } + case R.id.action_view_context_mode: + pickContextDefinition(); + break; + case R.id.cmd_filemanager: + FileManagerUtil.showInFilemanager(this, getCurrentDir()); + break; - @Override - protected void onDirectoryPick(IDirectory selection) { - // super.onDirectoryPick(selection); - mModifyCount++; // copy or move initiated - getActivity().setResult((mModifyCount == 0) ? RESULT_NOCHANGE : RESULT_CHANGE); - sFileCommands.onMoveOrCopyDirectoryPick(getMove(), getSrcFotos(), selection); - dismiss(); - } - } + case R.id.action_slideshow: + reloadContext = false; + // only if not started + if (!slideShowStarted) startStopSlideShow(true); + break; - private class TagUpdateTask extends TagTask> { + case R.id.action_edit: + // #64: (not) open editor via chooser + IntentUtil.cmdStartIntent("edit", this, getCurrentFilePath(), null, null, + Intent.ACTION_EDIT, + (Global.showEditChooser) ? R.string.edit_chooser_title : 0, + R.string.edit_err_editor_not_found, ACTION_RESULT_MUST_MEDIA_SCAN); + break; - TagUpdateTask(SelectedFiles fotos) { - super(ImageDetailActivityViewPager.this,R.string.tags_activity_title); - this.getWorkflow().init(ImageDetailActivityViewPager.this, fotos, null); + case R.id.menu_item_share: + reloadContext = false; + IntentUtil.cmdStartIntent("share", this, null, null, getCurrentFilePath(), Intent.ACTION_SEND, R.string.share_menu_title, R.string.share_err_not_found, 0); + break; + + case R.id.cmd_copy: + result = cmdMoveOrCopyWithDestDirPicker(false, mFileCommands.getLastCopyToPath(), getCurrentFoto()); + break; + case R.id.cmd_move: + result = cmdMoveOrCopyWithDestDirPicker(true, mFileCommands.getLastCopyToPath(), getCurrentFoto()); + break; + case R.id.menu_item_rename: + result = onRenameQueston(getCurrentFoto(), getCurrentImageId(), getCurrentFilePath(), null); + break; + case R.id.menu_exif: + result = onEditExif(menuItem, getCurrentFoto(), getCurrentImageId(), getCurrentFilePath()); + break; + + case R.id.cmd_gallery: { + reloadContext = false; + String dirPath = getCurrentFilePath(); // PhotoPropertiesMediaFilesScanner.getDir().getAbsolutePath(); + if (dirPath != null) { + dirPath = FileUtils.getDir(dirPath).getAbsolutePath(); + GalleryFilterParameter newFilter = new GalleryFilterParameter(); + newFilter.setPath(dirPath); + // int callBackId = (PhotoPropertiesMediaFilesScanner.isNoMedia(dirPath,PhotoPropertiesMediaFilesScanner.DEFAULT_SCAN_DEPTH)) ? NOMEDIA_GALLERY : 0; + + QueryParameter query = TagSql.filter2NewQuery(this.mFilter); + FotoGalleryActivity.showActivity(" menu " + menuItem.getTitle() + "[13]" + dirPath, + this, query, 0); + } + break; + } + + case R.id.cmd_show_geo: + MapGeoPickerActivity.showActivity(" menu " + menuItem.getTitle(), + this, getCurrentFoto(), null, null, 0); + break; + + case R.id.cmd_show_geo_as: { + final long imageId = getCurrentImageId(); + IGeoPoint _geo = FotoSql.execGetPosition(null, + null, imageId, mDebugPrefix, "on cmd_show_geo_as"); + final String currentFilePath = getCurrentFilePath(); + GeoPointDto geo = new GeoPointDto(_geo.getLatitude(), _geo.getLongitude(), GeoPointDto.NO_ZOOM); + + geo.setDescription(currentFilePath); + geo.setId("" + imageId); + geo.setName("#" + imageId); + GeoUri PARSER = new GeoUri(GeoUri.OPT_PARSE_INFER_MISSING); + String uri = PARSER.toUriString(geo); + + IntentUtil.cmdStartIntent("cmd_show_geo_as", this, null, uri, null, Intent.ACTION_VIEW, R.string.geo_show_as_menu_title, R.string.geo_picker_err_not_found, 0); + break; + } + + case R.id.cmd_edit_geo: { + SelectedFiles selectedItem = getCurrentFoto(); + GeoEditActivity.showActivity(" menu " + menuItem.getTitle() + " " + selectedItem, + this, selectedItem, GeoEditActivity.RESULT_ID); + break; + } + case R.id.cmd_edit_tags: { + SelectedFiles selectedItem = getCurrentFoto(); + tagsShowEditDialog(selectedItem); + break; + } + + case R.id.cmd_about: + reloadContext = false; + AboutDialogPreference.createAboutDialog(this).show(); + break; + case R.id.cmd_settings: + reloadContext = false; + SettingsActivity.showActivity(this); + break; + case R.id.cmd_more: + reloadContext = false; + new Handler().postDelayed(new Runnable() { + public void run() { + // reopen after some delay + openOptionsMenu(); + } + }, 200); + break; + default: + result = super.onOptionsItemSelected(menuItem); + } } - @Override - protected Integer doInBackground(List... params) { - return getWorkflow().updateTags(params[0], params[1]); + if (reloadContext) { + setContextMode(menuItem.getTitle()); } + return result; + } /** @@ -658,29 +682,24 @@ protected void onPause () { super.onPause(); } - @Override - protected void onResume () { - unhideActionBar(Global.actionBarHideTimeInMilliSecs, "onResume"); - Global.debugMemory(mDebugPrefix, "onResume"); - super.onResume(); - - final boolean locked = LockScreen.isLocked(this); - if (this.locked != locked) { - this.locked = locked; - mMustReplaceMenue = true; - invalidateOptionsMenu(); - } + private boolean onRenameQueston(final SelectedFiles currentFoto, final long fotoId, final String fotoPath, final String _newName) { + if (AndroidFileCommands.canProcessFile(this, false)) { + final String newName = (_newName == null) + ? new File(getCurrentFilePath()).getName() + : _newName; - if (Global.debugEnabledMemory) { - Log.d(Global.LOG_CONTEXT, mDebugPrefix + " - onResume cmd (" + - MoveOrCopyDestDirPicker.sFileCommands + ") => (" + mFileCommands + - ")"); + Dialogs dialog = new Dialogs() { + @Override + protected void onDialogResult(String newFileName, Object... parameters) { + if (newFileName != null) { + onRenameAnswer(currentFoto, (Long) parameters[0], (String) parameters[1], newFileName); + } + } + }; + dialog.editFileName(this, getString(R.string.rename_menu_title), newName, fotoId, fotoPath); } - - // workaround fragment lifecycle is newFragment.attach oldFragment.detach. - // this makes shure that the visible fragment has commands - MoveOrCopyDestDirPicker.sFileCommands = mFileCommands; + return true; } @Override @@ -760,16 +779,6 @@ private void onLoadCompleted() { } } - private void refreshIfNecessary() { - if ((mAdapter != null) && (mViewPager != null) && (mAdapter.isInArrayMode())) { - mAdapter.refreshLocal(); - mViewPager.setAdapter(mAdapter); - - // show the changes - onLoadCompleted(); - } - } - /** * gets called if no file is found by a db-query or if jpgFullFilePath is not found in media db * return false; activity must me closed @@ -889,146 +898,43 @@ public boolean onPrepareOptionsMenu(Menu menu) { return super.onPrepareOptionsMenu(menu); } - @Override - public boolean onOptionsItemSelected(MenuItem menuItem) { - boolean reloadContext = true; - boolean result = true; - boolean slideShowStarted = mSlideShowStarted; - - onGuiTouched(); - if (LockScreen.onOptionsItemSelected(this, menuItem)) { - mMustReplaceMenue = true; - this.invalidateOptionsMenu(); - return true; - } - if (mFileCommands.onOptionsItemSelected(menuItem, getCurrentFoto())) { - mModifyCount++; - } else { - // Handle presses on the action bar items - switch (menuItem.getItemId()) { - case R.id.action_details: - cmdShowDetails(getCurrentFilePath(), getCurrentImageId()); - break; - - case R.id.action_view_context_mode: - pickContextDefinition(); - break; - case R.id.cmd_filemanager: - FileManagerUtil.showInFilemanager(this, getCurrentDir()); - break; - - - case R.id.action_slideshow: - reloadContext = false; - // only if not started - if (!slideShowStarted) startStopSlideShow(true); - break; - - case R.id.action_edit: - // #64: (not) open editor via chooser - IntentUtil.cmdStartIntent("edit", this, getCurrentFilePath(), null, null, - Intent.ACTION_EDIT, - (Global.showEditChooser) ? R.string.edit_chooser_title : 0, - R.string.edit_err_editor_not_found, ACTION_RESULT_MUST_MEDIA_SCAN); - break; - - case R.id.menu_item_share: - reloadContext = false; - IntentUtil.cmdStartIntent("share", this, null, null, getCurrentFilePath(), Intent.ACTION_SEND, R.string.share_menu_title, R.string.share_err_not_found, 0); - break; - - case R.id.cmd_copy: - result = cmdMoveOrCopyWithDestDirPicker(false, mFileCommands.getLastCopyToPath(), getCurrentFoto()); - break; - case R.id.cmd_move: - result = cmdMoveOrCopyWithDestDirPicker(true, mFileCommands.getLastCopyToPath(), getCurrentFoto()); - break; - case R.id.menu_item_rename: - result = onRenameDirQueston(getCurrentFoto(), getCurrentImageId(), getCurrentFilePath(), null); - break; - case R.id.menu_exif: - result = onEditExif(menuItem, getCurrentFoto(), getCurrentImageId(), getCurrentFilePath()); - break; - - case R.id.cmd_gallery: { - reloadContext = false; - String dirPath = getCurrentFilePath(); // PhotoPropertiesMediaFilesScanner.getDir().getAbsolutePath(); - if (dirPath != null) { - dirPath = FileUtils.getDir(dirPath).getAbsolutePath(); - GalleryFilterParameter newFilter = new GalleryFilterParameter(); - newFilter.setPath(dirPath); - // int callBackId = (PhotoPropertiesMediaFilesScanner.isNoMedia(dirPath,PhotoPropertiesMediaFilesScanner.DEFAULT_SCAN_DEPTH)) ? NOMEDIA_GALLERY : 0; - - QueryParameter query = TagSql.filter2NewQuery(this.mFilter); - FotoGalleryActivity.showActivity(" menu " + menuItem.getTitle() + "[13]" + dirPath, - this, query, 0); - } - break; - } - - case R.id.cmd_show_geo: - MapGeoPickerActivity.showActivity(" menu " + menuItem.getTitle(), - this, getCurrentFoto(), null, null, 0); - break; - - case R.id.cmd_show_geo_as: { - final long imageId = getCurrentImageId(); - IGeoPoint _geo = FotoSql.execGetPosition(null, - null, imageId, mDebugPrefix, "on cmd_show_geo_as"); - final String currentFilePath = getCurrentFilePath(); - GeoPointDto geo = new GeoPointDto(_geo.getLatitude(), _geo.getLongitude(), GeoPointDto.NO_ZOOM); - - geo.setDescription(currentFilePath); - geo.setId(""+imageId); - geo.setName("#"+imageId); - GeoUri PARSER = new GeoUri(GeoUri.OPT_PARSE_INFER_MISSING); - String uri = PARSER.toUriString(geo); - - IntentUtil.cmdStartIntent("cmd_show_geo_as", this, null, uri, null, Intent.ACTION_VIEW, R.string.geo_show_as_menu_title, R.string.geo_picker_err_not_found, 0); - break; - } + private void onRenameAnswer(SelectedFiles currentFoto, final long fotoId, final String fotoSourcePath, String newFileName) { + File src = new File(fotoSourcePath); + File dest = new File(src.getParentFile(), newFileName); - case R.id.cmd_edit_geo: { - SelectedFiles selectedItem = getCurrentFoto(); - GeoEditActivity.showActivity(" menu " + menuItem.getTitle() + " " + selectedItem, - this, selectedItem, GeoEditActivity.RESULT_ID); - break; - } - case R.id.cmd_edit_tags: { - SelectedFiles selectedItem = getCurrentFoto(); - tagsShowEditDialog(selectedItem); - break; - } + File srcXmpShort = FileProcessor.getSidecar(src, false); + boolean hasSideCarShort = ((srcXmpShort != null) && (mFileCommands.osFileExists(srcXmpShort))); + File srcXmpLong = FileProcessor.getSidecar(src, true); + boolean hasSideCarLong = ((srcXmpLong != null) && (mFileCommands.osFileExists(srcXmpLong))); - case R.id.cmd_about: - reloadContext = false; - AboutDialogPreference.createAboutDialog(this).show(); - break; - case R.id.cmd_settings: - reloadContext = false; - SettingsActivity.showActivity(this); - break; - case R.id.cmd_more: - reloadContext = false; - new Handler().postDelayed(new Runnable() { - public void run() { - // reopen after some delay - openOptionsMenu(); - } - }, 200); - break; + File destXmpShort = FileProcessor.getSidecar(dest, false); + File destXmpLong = FileProcessor.getSidecar(dest, true); - default: - result = super.onOptionsItemSelected(menuItem); - } - } + if (src.equals(dest)) return; // new name == old name ==> nothing to do - if (reloadContext) { - setContextMode(menuItem.getTitle()); + String errorMessage = null; + if (hasSideCarShort && mFileCommands.osFileExists(destXmpShort)) { + errorMessage = getString(R.string.image_err_file_exists_format, destXmpShort.getAbsoluteFile()); + } + if (hasSideCarLong && mFileCommands.osFileExists(destXmpLong)) { + errorMessage = getString(R.string.image_err_file_exists_format, destXmpLong.getAbsoluteFile()); + } + if (mFileCommands.osFileExists(dest)) { + errorMessage = getString(R.string.image_err_file_exists_format, dest.getAbsoluteFile()); } - return result; - + if (errorMessage != null) { + // dest-file already exists + Toast.makeText(this, errorMessage, Toast.LENGTH_LONG).show(); + onRenameQueston(currentFoto, fotoId, fotoSourcePath, newFileName); + } else if (mFileCommands.rename(currentFoto, dest, null)) { + mModifyCount++; + refreshIfNecessary(); + } else { + // rename failed + errorMessage = getString(R.string.image_err_file_rename_format, src.getAbsoluteFile()); + Toast.makeText(this, errorMessage, Toast.LENGTH_LONG).show(); + } } private static final int SLIDESHOW_HANDLER_ID = 2; @@ -1099,61 +1005,43 @@ private boolean onEditExif(MenuItem menuItem, SelectedFiles currentFoto, final l this, null, fotoPath, currentFoto, 0, true); return true; } - private boolean onRenameDirQueston(final SelectedFiles currentFoto, final long fotoId, final String fotoPath, final String _newName) { - if (AndroidFileCommands.canProcessFile(this, false)) { - final String newName = (_newName == null) - ? new File(getCurrentFilePath()).getName() - : _newName; + /** + * #70: Gui has changed ContextExpression + * + * @param modeName property name. if starting with "auto" then the image detail quick-botton is redefined. + * @param contextSqlColumnExpression if not empy the result of this expression is shown as context data in image detail view. + */ + private void onDefineContext(String modeName, String contextSqlColumnExpression) { + addContextColumn(mGalleryContentQuery, contextSqlColumnExpression); + if ((mGalleryContentQuery != null) + && (0 != StringUtils.compare(contextSqlColumnExpression, mContextColumnExpression)) + && (this.mAdapter != null)) { + // sql detail expression has changed and initialization has completed: requery - Dialogs dialog = new Dialogs() { - @Override - protected void onDialogResult(String newFileName, Object... parameters) { - if (newFileName != null) { - onRenameSubDirAnswer(currentFoto, (Long) parameters[0], (String) parameters[1], newFileName); - } - } - }; - dialog.editFileName(this, getString(R.string.rename_menu_title), newName, fotoId, fotoPath); + this.mContextColumnExpression = contextSqlColumnExpression; // prevent executing again + startRequery(); } - return true; - } - - private void onRenameSubDirAnswer(SelectedFiles currentFoto, final long fotoId, final String fotoSourcePath, String newFileName) { - File src = new File(fotoSourcePath); - File dest = new File(src.getParentFile(), newFileName); - - File srcXmpShort = FileProcessor.getSidecar(src, false); - boolean hasSideCarShort = ((srcXmpShort != null) && (mFileCommands.osFileExists(srcXmpShort))); - File srcXmpLong = FileProcessor.getSidecar(src, true); - boolean hasSideCarLong = ((srcXmpLong != null) && (mFileCommands.osFileExists(srcXmpLong))); + this.mContextColumnExpression = contextSqlColumnExpression; // prevent executing again - File destXmpShort = FileProcessor.getSidecar(dest, false); - File destXmpLong = FileProcessor.getSidecar(dest, true); + if (modeName != null) { + this.mContextName = modeName; + if (this.mAdapter != null) { + this.mAdapter.setIconResourceName(modeName); + } + } - if (src.equals(dest)) return; // new name == old name ==> nothing to do + } - String errorMessage = null; - if (hasSideCarShort && mFileCommands.osFileExists(destXmpShort)) { - errorMessage = getString(R.string.image_err_file_exists_format, destXmpShort.getAbsoluteFile()); - } - if (hasSideCarLong && mFileCommands.osFileExists(destXmpLong)) { - errorMessage = getString(R.string.image_err_file_exists_format, destXmpLong.getAbsoluteFile()); - } - if (mFileCommands.osFileExists(dest)) { - errorMessage = getString(R.string.image_err_file_exists_format, dest.getAbsoluteFile()); - } + private void refreshIfNecessary() { + if ((mAdapter != null) && (mViewPager != null) && (mAdapter.isInArrayMode())) { + mAdapter.refreshLocal(); + mViewPager.setAdapter(mAdapter); - if (errorMessage != null) { - // dest-file already exists - Toast.makeText(this, errorMessage, Toast.LENGTH_LONG).show(); - onRenameDirQueston(currentFoto, fotoId, fotoSourcePath, newFileName); - } else if (mFileCommands.rename(currentFoto, dest, null)) { - mModifyCount++; - } else { - // rename failed - errorMessage = getString(R.string.image_err_file_rename_format, src.getAbsoluteFile()); - Toast.makeText(this, errorMessage, Toast.LENGTH_LONG).show(); + // show the changes + onLoadCompleted(); + } else if (FotoSql.getMediaDBApi().mustRequery(mUpdateId)) { + startRequery(); } } @@ -1297,38 +1185,177 @@ private void setContextMode(Object modeName) { } } + private void startRequery() { + mUpdateId = FotoSql.getMediaDBApi().getCurrentUpdateId(); + if (mCurorLoader == null) { + // query has not been initialized + mCurorLoader = new LocalCursorLoader(); + getLoaderManager().initLoader(ACTIVITY_ID, null, mCurorLoader); + } else { + // query has changed + getLoaderManager().restartLoader(ACTIVITY_ID, null, this.mCurorLoader); + } + } + + public static class MoveOrCopyDestDirPicker extends DirectoryPickerFragment { + protected static AndroidFileCommands sFileCommands = null; + + public static MoveOrCopyDestDirPicker newInstance(boolean move, final SelectedFiles srcFotos) { + MoveOrCopyDestDirPicker f = new MoveOrCopyDestDirPicker(); + + // Supply index input as an argument. + Bundle args = new Bundle(); + args.putBoolean("move", move); + AffUtils.putSelectedFiles(args, srcFotos); + f.setArguments(args); + + return f; + } + + /* do not use activity callback */ + @Override + protected void setDirectoryListener(Activity activity) {/* do not use activity callback */} + + public boolean getMove() { + return getArguments().getBoolean("move", false); + } + + public SelectedFiles getSrcFotos() { + return AffUtils.getSelectedFiles(getArguments()); + } + + /** + * To be overwritten to check if a path can be picked. + * + * @param path to be checked if it cannot be handled + * @return null if no error else error message with the reason why it cannot be selected + */ + @Override + protected String getStatusErrorMessage(String path) { + String errorMessage = (sFileCommands == null) ? null : sFileCommands.checkWriteProtected(0, new File(path)); + if (errorMessage != null) { + int pos = errorMessage.indexOf('\n'); + return (pos > 0) ? errorMessage.substring(0, pos) : errorMessage; + } + return super.getStatusErrorMessage(path); + } + + @Override + protected void onDirectoryPick(IDirectory selection) { + // super.onDirectoryPick(selection); + mModifyCount++; // copy or move initiated + getActivity().setResult((mModifyCount == 0) ? RESULT_NOCHANGE : RESULT_CHANGE); + + sFileCommands.onMoveOrCopyDirectoryPick(getMove(), getSrcFotos(), selection); + dismiss(); + ((ImageDetailActivityViewPager) getActivity()).refreshIfNecessary(); + } + } + /** - * #70: Gui has changed ContextExpression - * @param modeName property name. if starting with "auto" then the image detail quick-botton is redefined. - * @param contextSqlColumnExpression if not empy the result of this expression is shown as context data in image detail view. + * executes sql to load image detail data in a background task that may survive + * conriguration change (i.e. device rotation) */ - private void onDefineContext(String modeName, String contextSqlColumnExpression) { - addContextColumn(mGalleryContentQuery, contextSqlColumnExpression); - if ((mGalleryContentQuery != null) - && (0 != StringUtils.compare(contextSqlColumnExpression, mContextColumnExpression)) - && (this.mAdapter != null)) { - // sql detail expression has changed and initialization has completed: requery + class LocalCursorLoader implements LoaderManager.LoaderCallbacks { + /** incremented every time a new curster/query is generated */ + private int mRequeryInstanceCount = 0; - this.mContextColumnExpression = contextSqlColumnExpression; // prevent executing again - if (mCurorLoader == null) { - // query has not been initialized - mCurorLoader = new LocalCursorLoader(); - getLoaderManager().initLoader(ACTIVITY_ID, null, mCurorLoader); - } else { - // query has changed - getLoaderManager().restartLoader(ACTIVITY_ID, null, this.mCurorLoader); + /** called by LoaderManager.getLoader(ACTIVITY_ID) to (re)create loader + * that attaches to last query/cursor if it still exist i.e. after rotation */ + @Override + public Loader onCreateLoader(int loaderID, Bundle bundle) { + switch (loaderID) { + case ACTIVITY_ID: + mRequeryInstanceCount++; + mWaitingForMediaScannerResult = false; + if (Global.debugEnabledSql) { + Log.i(Global.LOG_CONTEXT, mDebugPrefix + " onCreateLoader" + + getDebugContext() + + " : query = " + mGalleryContentQuery); + } + return FotoSql.createCursorLoader(getApplicationContext(), mGalleryContentQuery); + default: + // An invalid id was passed in + return null; } } - this.mContextColumnExpression = contextSqlColumnExpression; // prevent executing again - if (modeName != null) { - this.mContextName = modeName; - if (this.mAdapter != null) { - this.mAdapter.setIconResourceName(modeName); + /** called after media db content has changed */ + @Override + public void onLoadFinished(Loader loader, Cursor data) { + // to be restored after refreshLocal if there is no mInitialFilePath + if ((mInitialScrollPosition == NO_INITIAL_SCROLL_POSITION) && (mViewPager != null)) { + mInitialScrollPosition = mViewPager.getCurrentItem(); + } + // do change the data + mUpdateId = FotoSql.getMediaDBApi().getCurrentUpdateId(); + mAdapter.swapCursor(data); + // currentUpdateId + + // restore position is invalid + final int newItemCount = mAdapter.getCount(); + + if (((newItemCount == 0)) || (mInitialScrollPosition >= newItemCount)) + mInitialScrollPosition = NO_INITIAL_SCROLL_POSITION; + + if (Global.debugEnabledSql) { + Log.i(Global.LOG_CONTEXT, mDebugPrefix + " onLoadFinished" + + getDebugContext() + + " found " + ((data == null) ? 0 : newItemCount) + " rows"); + } + + // do change the data + mAdapter.notifyDataSetChanged(); + mViewPager.setAdapter(mAdapter); + + // show the changes + onLoadCompleted(); + } + + /** + * called by LoaderManager. after search criteria were changed or if activity is destroyed. + */ + @Override + public void onLoaderReset(Loader loader) { + // rember position where we have to scroll to after refreshLocal is finished. + mInitialScrollPosition = mViewPager.getCurrentItem(); + mAdapter.swapCursor(null); + if (Global.debugEnabledSql) { + Log.i(Global.LOG_CONTEXT, mDebugPrefix + " onLoaderReset" + + getDebugContext()); } + mAdapter.notifyDataSetChanged(); + } + + @NonNull + private String getDebugContext() { + return "(#" + mRequeryInstanceCount + + ", mScrollPosition=" + mInitialScrollPosition + + ", Path='" + mInitialFilePath + + "')"; + } + } + + private class TagUpdateTask extends TagTask> { + + TagUpdateTask(SelectedFiles fotos) { + super(ImageDetailActivityViewPager.this, R.string.tags_activity_title); + this.getWorkflow().init(ImageDetailActivityViewPager.this, fotos, null); + } + @Override + protected Integer doInBackground(List... params) { + return getWorkflow().updateTags(params[0], params[1]); + } + + @Override + protected void onPostExecute(Integer itemCount) { + super.onPostExecute(itemCount); + refreshIfNecessary(); + } } + /** #70: adds/removes/replases contextColumnExpression */ private static void addContextColumn(QueryParameter query, String contextColumnExpression) { if (query != null) { diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/IMediaDBApi.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/IMediaDBApi.java index 172a6f2c..8a9f67ed 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/queries/IMediaDBApi.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/IMediaDBApi.java @@ -63,4 +63,14 @@ Long insertOrUpdateMediaDatabase(String dbgContext, int deleteMedia(String dbgContext, String where, String[] selectionArgs, boolean preventDeleteImageFile); ContentValues getDbContent(long id); + + long getCurrentUpdateId(); + + boolean mustRequery(long updateId); + + void beginTransaction(); + + void setTransactionSuccessful(); + + void endTransaction(); } diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBApiWrapper.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBApiWrapper.java index 39469a24..29743daf 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBApiWrapper.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBApiWrapper.java @@ -35,6 +35,7 @@ public class MediaDBApiWrapper implements IMediaDBApi { protected final IMediaDBApi readChild; protected final IMediaDBApi writeChild; + protected final IMediaDBApi transactionChild; /** * count the non path write calls @@ -42,12 +43,13 @@ public class MediaDBApiWrapper implements IMediaDBApi { private int modifyCount = 0; public MediaDBApiWrapper(IMediaDBApi child) { - this(child, child); + this(child, child, child); } - public MediaDBApiWrapper(IMediaDBApi readChild, IMediaDBApi writeChild) { + public MediaDBApiWrapper(IMediaDBApi readChild, IMediaDBApi writeChild, IMediaDBApi transactionChild) { this.readChild = readChild; this.writeChild = writeChild; + this.transactionChild = transactionChild; } @Override @@ -112,4 +114,29 @@ public int deleteMedia(String dbgContext, String where, String[] selectionArgs, public ContentValues getDbContent(long id) { return readChild.getDbContent(id); } + + @Override + public long getCurrentUpdateId() { + return transactionChild.getCurrentUpdateId(); + } + + @Override + public boolean mustRequery(long updateId) { + return transactionChild.mustRequery(updateId); + } + + @Override + public void beginTransaction() { + transactionChild.beginTransaction(); + } + + @Override + public void setTransactionSuccessful() { + transactionChild.setTransactionSuccessful(); + } + + @Override + public void endTransaction() { + transactionChild.endTransaction(); + } } diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBContentprovider.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBContentprovider.java index 1e80da62..6285254d 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBContentprovider.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBContentprovider.java @@ -103,4 +103,30 @@ public int deleteMedia(String dbgContext, String where, String[] selectionArgs, public ContentValues getDbContent(final long id) { return ContentProviderMediaImpl.getDbContent(context, id); } + + @Override + public long getCurrentUpdateId() { + return 0; + } + + @Override + public boolean mustRequery(long updateId) { + return false; + } + + @Override + public void beginTransaction() { + + } + + @Override + public void setTransactionSuccessful() { + + } + + @Override + public void endTransaction() { + + } + } 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 82f2a260..2463b4d5 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 @@ -67,6 +67,9 @@ public class MediaImageDbReplacement implements IMediaDBApi { public static final String LOG_TAG = FotoSql.LOG_TAG + "DB"; + // #155 + public static final boolean debugEnabledSqlRefresh = true; + private static final String MODUL_NAME = ContentProviderMediaImpl.class.getName(); private final SQLiteDatabase db; @@ -147,25 +150,7 @@ public int execUpdate(String dbgContext, String path, ContentValues values, VISI return exexUpdateImpl(dbgContext, values, FotoSql.getFilterExprPathLikeWithVisibility(visibility), new String[]{path}); } - @Override - public int exexUpdateImpl(String dbgContext, ContentValues values, String sqlWhere, String[] selectionArgs) { - int result = -1; - Exception excpetion = null; - try { - result = db.update(Impl.table, values, sqlWhere, selectionArgs); - } catch (Exception ex) { - excpetion = ex; - } finally { - if ((excpetion != null) || ((dbgContext != null) && (Global.debugEnabledSql || LibGlobal.debugEnabledJpg))) { - Log.i(LOG_TAG, dbgContext + ":" + - MODUL_NAME + - ".exexUpdate " + excpetion + "\n" + - QueryParameter.toString(null, values.toString(), FotoSqlBase.SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME, - sqlWhere, selectionArgs, null, result), excpetion); - } - } - return result; - } + private static String currentUpdateReason = null; /** * return id of inserted item @@ -194,6 +179,52 @@ public Long insertOrUpdateMediaDatabase(String dbgContext, String dbUpdateFilter return result; } + private static long currentUpdateId = 1; + + @Override + public int exexUpdateImpl(String dbgContext, ContentValues values, String sqlWhere, String[] selectionArgs) { + int result = -1; + Exception excpetion = null; + try { + result = db.update(Impl.table, values, sqlWhere, selectionArgs); + if (result != 0) { + currentUpdateId++; + currentUpdateReason = dbgContext; + } + } catch (Exception ex) { + excpetion = ex; + } finally { + if ((excpetion != null) || ((dbgContext != null) && (Global.debugEnabledSql || LibGlobal.debugEnabledJpg))) { + Log.i(LOG_TAG, dbgContext + ":" + + MODUL_NAME + + ".exexUpdate " + excpetion + "\n" + + QueryParameter.toString(null, values.toString(), FotoSqlBase.SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME, + sqlWhere, selectionArgs, null, result), excpetion); + } + } + return result; + } + + @Override + public ContentValues getDbContent(long id) { + Cursor c = null; + try { + c = this.createCursorForQuery(null, "getDbContent", + Impl.table, FotoSql.FILTER_COL_PK, new String[]{"" + id}, null, null, "*"); + if (c.moveToNext()) { + ContentValues values = new ContentValues(); + DatabaseUtils.cursorRowToContentValues(c, values); + return values; + } + } catch (Exception ex) { + Log.e(LOG_TAG, MODUL_NAME + + ".getDbContent(id=" + id + ") failed", ex); + } finally { + if (c != null) c.close(); + } + return null; + } + /** * every database insert should go through this. adds logging if enabled * @@ -207,6 +238,11 @@ public Uri execInsert(String dbgContext, ContentValues values) { try { // on my android-4.4 insert with media_type=1001 (private) does insert with media_type=1 (image) result = db.insert(Impl.table, null, values); + if (result > 0) { + currentUpdateId++; + currentUpdateReason = dbgContext; + } + } catch (Exception ex) { excpetion = ex; } finally { @@ -265,6 +301,11 @@ public int deleteMedia(String dbgContext, String where, String[] selectionArgs, lastUsedWhereClause, lastSelectionArgs, null, delCount)); } } + if (delCount > 0) { + currentUpdateId++; + currentUpdateReason = dbgContext; + } + } catch (Exception ex) { // null pointer exception when delete matches not items?? final String msg = dbgContext + ": Exception in " + @@ -280,25 +321,33 @@ public int deleteMedia(String dbgContext, String where, String[] selectionArgs, } @Override - public ContentValues getDbContent(long id) { - Cursor c = null; - try { - c = this.createCursorForQuery(null, "getDbContent", - Impl.table, FotoSql.FILTER_COL_PK, new String[]{"" + id}, null, null, "*"); - if (c.moveToNext()) { - ContentValues values = new ContentValues(); - DatabaseUtils.cursorRowToContentValues(c, values); - return values; - } - } catch (Exception ex) { - Log.e(LOG_TAG, MODUL_NAME + - ".getDbContent(id=" + id + ") failed", ex); - } finally { - if (c != null) c.close(); + public long getCurrentUpdateId() { + return currentUpdateId; + } + + @Override + public boolean mustRequery(long updateId) { + final boolean modified = currentUpdateId != updateId; + if (modified && MediaImageDbReplacement.debugEnabledSqlRefresh) { + Log.i(MediaImageDbReplacement.LOG_TAG, "mustRequery: true because of " + currentUpdateReason); } - return null; + return modified; + } + + @Override + public void beginTransaction() { + db.beginTransaction(); } + @Override + public void setTransactionSuccessful() { + db.setTransactionSuccessful(); + } + + @Override + public void endTransaction() { + db.endTransaction(); + } public static class Impl { /** * SQL to create copy of contentprovider MediaStore.Images. 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 index 0939a6a5..88733f90 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/queries/MergedMediaDB.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MergedMediaDB.java @@ -35,7 +35,7 @@ public class MergedMediaDB extends MediaDBApiWrapper { private final IMediaDBApi contentProvider; public MergedMediaDB(IMediaDBApi database, IMediaDBApi contentProvider) { - super(database, contentProvider); + super(database, contentProvider, database); this.database = database; this.contentProvider = contentProvider; } diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/tagDB/TagWorflow.java b/app/src/main/java/de/k3b/android/androFotoFinder/tagDB/TagWorflow.java index 5e5bd9ea..475b0e3d 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/tagDB/TagWorflow.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/tagDB/TagWorflow.java @@ -29,6 +29,8 @@ import java.util.List; import de.k3b.android.androFotoFinder.Global; +import de.k3b.android.androFotoFinder.queries.FotoSql; +import de.k3b.android.androFotoFinder.queries.IMediaDBApi; import de.k3b.android.util.AndroidFileCommands; import de.k3b.io.FileCommands; import de.k3b.io.IProgessListener; @@ -78,21 +80,27 @@ public TagWorflow init(Activity context, SelectedFiles selectedItems, List /** execute the updates for all affected files in the Workflow. */ public int updateTags(List addedTags, List removedTags) { - int itemCount = 0; - if (items != null) { - int progressCountDown = 0; - int total = items.size(); - for (TagSql.TagWorflowItem item : items) { - itemCount+=updateTags(item, addedTags, removedTags); - progressCountDown--; - if (progressCountDown < 0) { - progressCountDown = 10; - if (!onProgress(itemCount, total, item.path)) break; - } - } // for each image + final IMediaDBApi mediaDBApi = FotoSql.getMediaDBApi(); + try { + mediaDBApi.beginTransaction(); // Performance boost: all db-inserts/updates in one transaction + int itemCount = 0; + if (items != null) { + int progressCountDown = 0; + int total = items.size(); + for (TagSql.TagWorflowItem item : items) { + itemCount += updateTags(item, addedTags, removedTags); + progressCountDown--; + if (progressCountDown < 0) { + progressCountDown = 10; + if (!onProgress(itemCount, total, item.path)) break; + } + } // for each image + } + mediaDBApi.setTransactionSuccessful(); + return itemCount; + } finally { + mediaDBApi.endTransaction(); } - - return itemCount; } /** update one file if tags change or xmp does not exist yet: xmp-sidecar-file, media-db and batch */ diff --git a/app/src/main/java/de/k3b/android/util/AndroidFileCommands.java b/app/src/main/java/de/k3b/android/util/AndroidFileCommands.java index 5ac78acb..dfc5f634 100644 --- a/app/src/main/java/de/k3b/android/util/AndroidFileCommands.java +++ b/app/src/main/java/de/k3b/android/util/AndroidFileCommands.java @@ -44,6 +44,7 @@ import de.k3b.android.androFotoFinder.media.AndroidPhotoPropertiesBulkUpdateService; import de.k3b.android.androFotoFinder.queries.DatabaseHelper; import de.k3b.android.androFotoFinder.queries.FotoSql; +import de.k3b.android.androFotoFinder.queries.IMediaDBApi; import de.k3b.android.androFotoFinder.tagDB.TagSql; import de.k3b.android.androFotoFinder.transactionlog.TransactionLogSql; import de.k3b.database.QueryParameter; @@ -55,6 +56,7 @@ import de.k3b.io.collections.SelectedFiles; import de.k3b.media.MediaFormatter; import de.k3b.media.PhotoPropertiesBulkUpdateService; +import de.k3b.media.PhotoPropertiesDiffCopy; import de.k3b.media.PhotoPropertiesUpdateHandler; import de.k3b.transactionlog.MediaTransactionLogEntryType; import de.k3b.transactionlog.TransactionLoggerBase; @@ -252,6 +254,21 @@ public void onMoveOrCopyDirectoryPick(boolean move, SelectedFiles selectedFiles, } } + @Override + protected int moveOrCopyFiles(final boolean move, String what, PhotoPropertiesDiffCopy exifChanges, + SelectedFiles fotos, File[] destFiles, + IProgessListener progessListener) { + IMediaDBApi api = FotoSql.getMediaDBApi(); + try { + api.beginTransaction(); + int result = super.moveOrCopyFiles(move, what, exifChanges, fotos, destFiles, progessListener); + api.setTransactionSuccessful(); + return result; + } finally { + api.endTransaction(); + } + } + @NonNull public String getLastCopyToPath() { SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(mContext); diff --git a/app/src/main/java/de/k3b/android/widget/LocalizedActivity.java b/app/src/main/java/de/k3b/android/widget/LocalizedActivity.java index a2b5e696..6ee9b8fb 100644 --- a/app/src/main/java/de/k3b/android/widget/LocalizedActivity.java +++ b/app/src/main/java/de/k3b/android/widget/LocalizedActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2018 by k3b. + * Copyright (c) 2015-2019 by k3b. * * This file is part of AndroFotoFinder and of ToGoZip. * @@ -41,26 +41,28 @@ * Created by k3b on 07.01.2016. */ public abstract class LocalizedActivity extends ActivityWithCallContext { + /** + * if this.recreationId != LocalizedActivity.currentRecreationId : activity must be recreated in on resume + */ + private static int currentRecreationId = 0; + private int recreationId = 0; + /** if myLocale != Locale.Default : activity must be recreated in on resume */ private Locale myLocale = null; + /** + * All activities will be recreated in on resume. I.E. after basic configuration change. + */ + public static void setMustRecreate() { + LocalizedActivity.currentRecreationId++; + } + @Override protected void onCreate(Bundle savedInstanceState) { fixLocale(this); super.onCreate(savedInstanceState); } - @Override - protected void onResume() { - super.onResume(); - - // Locale has changed by other Activity ? - if ((myLocale != null) && (myLocale.getLanguage() != Locale.getDefault().getLanguage())) { - myLocale = null; - recreate(LocalizedActivity.this); - } - } - /** * Set Activity-s locale to SharedPreferences-setting. * Must be called before @@ -85,11 +87,29 @@ public static void fixLocale(Context context) { // recreate(); if (context instanceof LocalizedActivity) { - ((LocalizedActivity) context).myLocale = locale; + final LocalizedActivity localizedActivity = (LocalizedActivity) context; + localizedActivity.myLocale = locale; + localizedActivity.recreationId = LocalizedActivity.currentRecreationId; } } } + @Override + protected void onResume() { + super.onResume(); + + // Locale has changed by other Activity ? + if (mustRecreate()) { + myLocale = null; + recreate(LocalizedActivity.this); + } + } + + protected boolean mustRecreate() { + return ((this.recreationId != LocalizedActivity.currentRecreationId) || + (this.myLocale != null) && (this.myLocale.getLanguage() != Locale.getDefault().getLanguage())); + } + /** force all open activity to recreate */ public static void recreate(Activity child) { Activity context = child;