package com.door43.translationstudio; import android.app.Activity; import android.content.Context; import android.content.SharedPreferences; import android.content.res.Configuration; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.provider.Settings; import android.support.annotation.Nullable; import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; import com.door43.tools.reporting.Logger; import com.door43.translationstudio.core.ArchiveDetails; import com.door43.translationstudio.core.Library; import com.door43.translationstudio.core.Profile; import com.door43.translationstudio.core.TargetTranslation; import com.door43.translationstudio.core.TranslationViewMode; import com.door43.translationstudio.core.Translator; import com.door43.translationstudio.core.Util; import com.door43.translationstudio.util.SdUtils; import com.door43.util.FileUtilities; import com.door43.util.StorageUtils; import com.door43.util.StringUtilities; import com.door43.util.Zip; import org.apache.commons.io.FileUtils; import org.json.JSONException; import org.json.JSONObject; import java.io.File; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; /** * This class provides global access to the application context as well as other important tools */ public class AppContext { private static final String PREFERENCES_NAME = "com.door43.translationstudio.general"; private static final String DEFAULT_LIBRARY_ZIP = "library.zip"; private static final String TARGET_TRANSLATIONS_DIR = "translations"; public static final String PROFILES_DIR = "profiles"; public static final String TRANSLATION_STUDIO = "translationStudio"; public static final String LAST_VIEW_MODE = "last_view_mode_"; public static final String LAST_FOCUS_CHAPTER = "last_focus_chapter_"; public static final String LAST_FOCUS_FRAME = "last_focus_frame_"; public static final String OPEN_SOURCE_TRANSLATIONS = "open_source_translations_"; public static final String SELECTED_SOURCE_TRANSLATION = "selected_source_translation_"; public static final String LAST_CHECKED_SERVER_FOR_UPDATES = "last_checked_server_for_updates"; public static final String LAST_TRANSLATION = "last_translation"; public static final String EXTRA_SOURCE_DRAFT_TRANSLATION_ID = "extra_source_translation_id"; public static final String EXTRA_TARGET_TRANSLATION_ID = "extra_target_translation_id"; public static final String EXTRA_CHAPTER_ID = "extra_chapter_id"; public static final String EXTRA_FRAME_ID = "extra_frame_id"; public static final String EXTRA_VIEW_MODE = "extra_view_mode_id"; private static final String PROFILE = "profile"; public static final String TAG = AppContext.class.toString(); private static final String ASSETS_DIR = "assets"; private static MainApplication mContext; public static final Bundle args = new Bundle(); /** * Initializes the basic functions context. * It is very important to initialize the class before using it because it assumes * the context has already been set. * @param context The application context. This can only be set once. */ public AppContext(MainApplication context) { mContext = context; } /** * Returns an instance of the library * @return */ @Nullable public static Library getLibrary() { // NOTE: rather than keeping the library around we rebuild it so that changes to the user settings will work String server = mContext.getUserPreferences().getString(SettingsActivity.KEY_PREF_MEDIA_SERVER, mContext.getResources().getString(R.string.pref_default_media_server)); String rootApiUrl = server + mContext.getResources().getString(R.string.root_catalog_api); try { return new Library(mContext, rootApiUrl, new File(getPublicDirectory(), ASSETS_DIR)); } catch (IOException e) { Logger.e(TAG, "Failed to create the library", e); } return null; } /** * Returns the version of the terms of use * @return */ public static int getTermsOfUseVersion() { return mContext.getResources().getInteger(R.integer.terms_of_use_version); } /** * Checks if the device is a tablet * @return */ public static boolean isTablet() { return (mContext.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE; } /** * Deploys the default index and the target languages * The * @throws Exception */ public static void deployDefaultLibrary() throws Exception { Library library = getLibrary(); File archive = mContext.getCacheDir().createTempFile("index", ".zip"); Util.writeStream(mContext.getAssets().open(DEFAULT_LIBRARY_ZIP), archive); File tempLibraryDir = new File(mContext.getCacheDir(), System.currentTimeMillis() + ""); tempLibraryDir.mkdirs(); Zip.unzip(archive, tempLibraryDir); File[] dbs = tempLibraryDir.listFiles(); if(dbs.length == 1) { library.deploy(dbs[0]); } else { FileUtils.deleteQuietly(archive); FileUtils.deleteQuietly(tempLibraryDir); throw new Exception("Invalid index count in '" + DEFAULT_LIBRARY_ZIP + "'. Expecting 1 but found " + dbs.length); } // clean up FileUtils.deleteQuietly(archive); FileUtils.deleteQuietly(tempLibraryDir); } /** * Returns an instance of the translator. * Target translations are stored in the public directory so that they persist if the app is uninstalled. * @return */ public static Translator getTranslator() { return new Translator(mContext, getProfile(), new File(getPublicDirectory(), TARGET_TRANSLATIONS_DIR)); } /** * Checks if the package asset exists * @param path * @return */ public static boolean assetExists(String path) { try { mContext.getAssets().open(path); return true; } catch (IOException e) { return false; } } /** * Returns the main application context * @return */ public static MainApplication context() { return mContext; } /** * Returns the unique device id for this device * @return */ public static String udid() { return Settings.Secure.getString(mContext.getContentResolver(), Settings.Secure.ANDROID_ID); } /** * Returns the directory in which the ssh keys are stored * @return */ public static File getKeysFolder() { File folder = new File(mContext.getFilesDir() + "/" + mContext.getResources().getString(R.string.keys_dir) + "/"); if(!folder.exists()) { folder.mkdir(); } return folder; } /** * Returns the file to the external public downloads directory * @return */ public static File getPublicDownloadsDirectory() { File dir; // TRICKY: KITKAT introduced changes to the external media that made sd cards read only if(Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { // || Root.isDeviceRooted() StorageUtils.StorageInfo removeableMediaInfo = StorageUtils.getRemoveableMediaDevice(); if(removeableMediaInfo != null) { dir = new File("/storage/" + removeableMediaInfo.getMountName() + SdUtils.DOWNLOAD_TRANSLATION_STUDIO_FOLDER); } else { dir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), TRANSLATION_STUDIO); } } else { dir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), TRANSLATION_STUDIO); } dir.mkdirs(); return dir; } /** * Creates a backup of a target translation in all the right places * @param targetTranslation the target translation that will be backed up * @param orphaned if true this backup will be orphaned (time stamped) * @return true if the backup was actually performed */ public static boolean backupTargetTranslation(TargetTranslation targetTranslation, Boolean orphaned) throws Exception { if(targetTranslation != null && getProfile() != null) { String name = targetTranslation.getId(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HH.mm.ss", Locale.US); if (orphaned) { name += "." + sdf.format(new Date()); } File temp = null; try { temp = File.createTempFile(name, "." + Translator.ARCHIVE_EXTENSION); targetTranslation.setDefaultContributor(getProfile().getNativeSpeaker()); getTranslator().exportArchive(targetTranslation, temp); if (temp.exists() && temp.isFile()) { // copy into backup locations File downloadsBackup = new File(getPublicDownloadsDirectory(), name + "." + Translator.ARCHIVE_EXTENSION); File publicBackup = new File(getPublicDirectory(), "backups/" + name + "." + Translator.ARCHIVE_EXTENSION); downloadsBackup.getParentFile().mkdirs(); publicBackup.getParentFile().mkdirs(); // check if we need to backup if(!orphaned) { ArchiveDetails downloadsDetails = ArchiveDetails.newInstance(downloadsBackup, "en", getLibrary()); ArchiveDetails publicDetails = ArchiveDetails.newInstance(publicBackup, "en", getLibrary()); // TRICKY: we only generate backups with a single target translation inside. if(downloadsDetails != null && downloadsDetails.targetTranslationDetails[0].commitHash.equals(targetTranslation.getCommitHash()) && publicDetails != null && publicDetails.targetTranslationDetails[0].commitHash.equals(targetTranslation.getCommitHash())) { return false; } } FileUtils.copyFile(temp, downloadsBackup); FileUtils.copyFile(temp, publicBackup); return true; } } catch (Exception e) { if (temp != null) { FileUtils.deleteQuietly(temp); } throw e; } } return false; } /** * Returns the path to the public files directory. * Files saved in this directory will not be removed when the application is uninstalled * @return */ public static File getPublicDirectory() { File dir = new File(Environment.getExternalStorageDirectory(), TRANSLATION_STUDIO); dir.mkdirs(); return dir; } /** * Sets the last opened view mode for a target translation * @param targetTranslationId * @param viewMode */ public static void setLastViewMode(String targetTranslationId, TranslationViewMode viewMode) { SharedPreferences prefs = mContext.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); editor.putString(LAST_VIEW_MODE + targetTranslationId, viewMode.toString()); editor.apply(); } /** * Returns the last view mode of the target translation. * The default view mode will be returned if there is no recorded last view mode * * @param targetTranslationId * @return */ public static TranslationViewMode getLastViewMode(String targetTranslationId) { SharedPreferences prefs = mContext.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); TranslationViewMode viewMode = TranslationViewMode.get(prefs.getString(LAST_VIEW_MODE + targetTranslationId, null)); if(viewMode == null) { return TranslationViewMode.READ; } return viewMode; } /** * Returns the last focused target translation * @return */ public static String getLastFocusTargetTranslation() { SharedPreferences prefs = mContext.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); return prefs.getString(LAST_TRANSLATION, null); } /** * Sets the last focused target translation * @param targetTranslationId */ public static void setLastFocusTargetTranslation(String targetTranslationId) { SharedPreferences prefs = mContext.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); editor.putString(LAST_TRANSLATION, targetTranslationId); editor.apply(); } /** * Sets the last focused chapter and frame for a target translation * @param targetTranslationId * @param chapterId * @param frameId */ public static void setLastFocus(String targetTranslationId, String chapterId, String frameId) { SharedPreferences prefs = mContext.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); editor.putString(LAST_FOCUS_CHAPTER + targetTranslationId, chapterId); editor.putString(LAST_FOCUS_FRAME + targetTranslationId, frameId); editor.apply(); setLastFocusTargetTranslation(targetTranslationId); } /** * Returns the id of the chapter that was last in focus for this target translation * @param targetTranslationId * @return */ public static String getLastFocusChapterId(String targetTranslationId) { SharedPreferences prefs = mContext.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); return prefs.getString(LAST_FOCUS_CHAPTER + targetTranslationId, null); } /** * Returns the id of the frame that was last in focus for this target translation * @param targetTranslationId * @return */ public static String getLastFocusFrameId(String targetTranslationId) { SharedPreferences prefs = mContext.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); return prefs.getString(LAST_FOCUS_FRAME + targetTranslationId, null); } /** * Adds a source translation to the list of open tabs on a target translation * @param targetTranslationId * @param sourceTranslationId */ public static void addOpenSourceTranslation(String targetTranslationId, String sourceTranslationId) { if(sourceTranslationId != null && !sourceTranslationId.isEmpty()) { SharedPreferences prefs = mContext.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); String[] sourceTranslationIds = getOpenSourceTranslationIds(targetTranslationId); String newIdSet = ""; for (String id : sourceTranslationIds) { if (!id.equals(sourceTranslationId)) { newIdSet += id + "|"; } } newIdSet += sourceTranslationId; editor.putString(OPEN_SOURCE_TRANSLATIONS + targetTranslationId, newIdSet); editor.apply(); } } /** * Removes a source translation from the list of open tabs on a target translation * @param targetTranslationId * @param sourceTranslationId */ public static void removeOpenSourceTranslation(String targetTranslationId, String sourceTranslationId) { if(sourceTranslationId != null && !sourceTranslationId.isEmpty()) { SharedPreferences prefs = mContext.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); String[] sourceTranslationIds = getOpenSourceTranslationIds(targetTranslationId); String newIdSet = ""; for (String id : sourceTranslationIds) { if (!id.equals(sourceTranslationId)) { if (newIdSet.isEmpty()) { newIdSet = id; } else { newIdSet += "|" + id; } } else if(id.equals(getSelectedSourceTranslationId(targetTranslationId))) { // unset the selected tab if it is removed setSelectedSourceTranslation(targetTranslationId, null); } } editor.putString(OPEN_SOURCE_TRANSLATIONS + targetTranslationId, newIdSet); editor.apply(); } } /** * Returns an array of open source translation tabs on a target translation * @param targetTranslationId * @return */ public static String[] getOpenSourceTranslationIds(String targetTranslationId) { SharedPreferences prefs = mContext.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); String idSet = prefs.getString(OPEN_SOURCE_TRANSLATIONS + targetTranslationId, "").trim(); if(idSet.isEmpty()) { return new String[0]; } else { return idSet.split("\\|"); } } /** * Sets or removes the selected open source translation tab on a target translation * @param targetTranslationId * @param sourceTranslationId if null the selection will be unset */ public static void setSelectedSourceTranslation(String targetTranslationId, String sourceTranslationId) { SharedPreferences prefs = mContext.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); if(sourceTranslationId != null && !sourceTranslationId.isEmpty()) { editor.putString(SELECTED_SOURCE_TRANSLATION + targetTranslationId, sourceTranslationId); } else { editor.remove(SELECTED_SOURCE_TRANSLATION + targetTranslationId); } editor.apply(); } /** * Returns the selected open source translation tab on the target translation * If there is no selection the first open tab will be set as the selected tab * @param targetTranslationId * @return */ public static String getSelectedSourceTranslationId(String targetTranslationId) { SharedPreferences prefs = mContext.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); String selectedSourceTranslationId = prefs.getString(SELECTED_SOURCE_TRANSLATION + targetTranslationId, null); if(selectedSourceTranslationId == null || selectedSourceTranslationId.isEmpty()) { // default to first tab String[] openSourceTranslationIds = getOpenSourceTranslationIds(targetTranslationId); if(openSourceTranslationIds.length > 0) { selectedSourceTranslationId = openSourceTranslationIds[0]; setSelectedSourceTranslation(targetTranslationId, selectedSourceTranslationId); } } return selectedSourceTranslationId; } /** * Removes all settings for a target translation * @param targetTranslationId */ public static void clearTargetTranslationSettings(String targetTranslationId) { SharedPreferences prefs = mContext.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); editor.remove(SELECTED_SOURCE_TRANSLATION + targetTranslationId); editor.remove(OPEN_SOURCE_TRANSLATIONS + targetTranslationId); editor.remove(LAST_FOCUS_FRAME + targetTranslationId); editor.remove(LAST_FOCUS_CHAPTER + targetTranslationId); editor.remove(LAST_VIEW_MODE + targetTranslationId); editor.apply(); } /** * Returns the address for the media server * @return */ public static String getMediaServer() { String url = AppContext.context().getUserPreferences().getString(SettingsActivity.KEY_PREF_MEDIA_SERVER, AppContext.context().getResources().getString(R.string.pref_default_media_server)); return StringUtilities.ltrim(url, '/'); } /** * Returns the sharing directory * @return */ public static File getSharingDir() { File file = new File(mContext.getCacheDir(), "sharing"); file.mkdirs(); return file; } /** * Returns the last time we checked the server for updates * @return */ public static long getLastCheckedForUpdates() { SharedPreferences prefs = mContext.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); return prefs.getLong(LAST_CHECKED_SERVER_FOR_UPDATES, 0L); } /** * Sets the last time we checked the server for updates to the library * @param timeMillis */ public static void setLastCheckedForUpdates(long timeMillis) { SharedPreferences prefs = mContext.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); editor.putLong(LAST_CHECKED_SERVER_FOR_UPDATES, timeMillis); editor.apply(); } /** * Sets the alias to be displayed when others see this device on the network * @param alias */ public static void setDeviceNetworkAlias(String alias) { if(alias.trim().isEmpty()) { alias = null; } setUserString(SettingsActivity.KEY_PREF_DEVICE_ALIAS, alias); } /** * Returns the alias to be displayed when others see this device on the network * @return */ @Nullable public static String getDeviceNetworkAlias() { String name = getUserString(SettingsActivity.KEY_PREF_DEVICE_ALIAS, ""); if(name.isEmpty()) { return null; } else { return name; } } /** * Returns the current user profile * @return */ public static Profile getProfile() { String profileString = getUserString(PROFILE, null); try { if (profileString != null) { return Profile.fromJSON(new JSONObject(profileString)); } } catch (Exception e) { Logger.e(TAG, "Failed to parse the profile", e); } return null; } /** * Stores the user profile * * @param profile */ public static void setProfile(Profile profile) { try { if(profile != null) { String profileString = profile.toJSON().toString(); setUserString(PROFILE, profileString); } else { setUserString(PROFILE, null); FileUtilities.deleteRecursive(getKeysFolder()); } } catch (JSONException e) { Logger.e(TAG, "setProfile: Failed to encode profile data", e); } } /** * Returns the string value of a user preference or the default value * @param preferenceKey * @param defaultResource * @return */ public static String getUserString(String preferenceKey, int defaultResource) { return getUserString(preferenceKey, mContext.getResources().getString(defaultResource)); } /** * Closes the keyboard in the given activity * @param activity */ public static void closeKeyboard(Activity activity) { if(activity != null) { if (activity.getCurrentFocus() != null) { try { InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(activity.getCurrentFocus().getWindowToken(), 0); } catch (Exception e) { } } else { try { activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); } catch (Exception e) { } } } } /** * Returns the string value of a user preference or the default value * @param preferenceKey * @param defaultValue * @return */ public static String getUserString(String preferenceKey, String defaultValue) { return mContext.getUserPreferences().getString(preferenceKey, defaultValue); } /** * Sets the value of a user string. * @param preferenceKey * @param value if null the string will be removed */ public static void setUserString(String preferenceKey, String value) { SharedPreferences.Editor editor = mContext.getUserPreferences().edit(); if(value == null) { editor.remove(preferenceKey); } else { editor.putString(preferenceKey, value); } editor.apply(); } }