package org.commcare.engine.resource; import android.content.SharedPreferences; import org.commcare.CommCareApp; import org.commcare.CommCareApplication; import org.commcare.engine.resource.installers.SingleAppInstallation; import org.commcare.logging.AndroidLogger; import org.commcare.preferences.CommCarePreferences; import org.commcare.preferences.CommCareServerPreferences; import org.commcare.preferences.DeveloperPreferences; import org.commcare.resources.ResourceManager; import org.commcare.resources.model.Resource; import org.commcare.resources.model.ResourceTable; import org.commcare.resources.model.UnresolvedResourceException; import org.commcare.suite.model.Profile; import org.commcare.util.CommCarePlatform; import org.commcare.utils.AndroidCommCarePlatform; import org.javarosa.core.services.Logger; import java.net.MalformedURLException; import java.net.URL; import java.security.cert.CertificateException; import java.util.Date; import javax.net.ssl.SSLHandshakeException; /** * Helpers to track app state and handle errors for installation and updates. * * @author Phillip Mates (pmates@dimagi.com) */ public class ResourceInstallUtils { private static final String DEFAULT_APP_SERVER_KEY = CommCareServerPreferences.PREFS_APP_SERVER_KEY; /** * @return Is the current app's designated upgrade table staged and ready * for installation? */ public static boolean isUpdateReadyToInstall() { CommCareApp app = CommCareApplication.instance().getCurrentApp(); AndroidCommCarePlatform platform = app.getCommCarePlatform(); ResourceTable upgradeTable = platform.getUpgradeResourceTable(); return ResourceManager.isTableStagedForUpgrade(upgradeTable); } /** * @return Version from profile in the app's upgrade table; -1 if upgrade * profile not found. */ public static int upgradeTableVersion() { CommCareApp app = CommCareApplication.instance().getCurrentApp(); AndroidCommCarePlatform platform = app.getCommCarePlatform(); ResourceTable upgradeTable = platform.getUpgradeResourceTable(); Resource upgradeProfile = upgradeTable.getResourceWithId(CommCarePlatform.APP_PROFILE_RESOURCE_ID); if (upgradeProfile == null) { return -1; } return upgradeProfile.getVersion(); } /** * Initialize app's resources and database, write the app record, and * overwrite app preference's profile reference with the authoritative * reference if present. */ public static void initAndCommitApp(CommCareApp app) { // use the profile reference currently stored as a backup to the // authorative reference. SharedPreferences prefs = app.getAppPreferences(); String profileRef = prefs.getString(DEFAULT_APP_SERVER_KEY, null); initAndCommitApp(app, profileRef); } /** * Initialize app's resources and database, write the app record, and store * reference to profile in app's preferences. * * @param profileRef Store backup reference to profile if authoritative * reference isn't present in the app's profile */ public static void initAndCommitApp(CommCareApp currentApp, String profileRef) { // Initializes app resources and the app itself, including doing a // check to see if this app record was converted by the db upgrader CommCareApplication.instance().initializeGlobalResources(currentApp); // Write this App Record to storage -- needs to be performed after // localizations have been initialized (by // initializeGlobalResources), so that getDisplayName() works currentApp.writeInstalled(); Profile profile = currentApp.getCommCarePlatform().getCurrentProfile(); String authRef = profile.getAuthReference(); updateProfileRef(currentApp.getAppPreferences(), authRef, profileRef); } public static void updateProfileRef(SharedPreferences prefs, String authRef, String profileRef) { SharedPreferences.Editor edit = prefs.edit(); if (authRef != null) { edit.putString(DEFAULT_APP_SERVER_KEY, authRef); } else { edit.putString(DEFAULT_APP_SERVER_KEY, profileRef); } edit.commit(); } /** * Handle exception that occurs when a resource can't be found during an * install or update * * @return Appropriate failure status based on exception properties */ public static AppInstallStatus processUnresolvedResource(UnresolvedResourceException exception) { // couldn't find a resource, which isn't good. exception.printStackTrace(); if (ResourceInstallUtils.isBadCertificateError(exception)) { return AppInstallStatus.BadCertificate; } Logger.log(AndroidLogger.TYPE_WARNING_NETWORK, "A resource couldn't be found, almost certainly due to the network|" + exception.getMessage()); if (exception.isMessageUseful()) { return AppInstallStatus.MissingResourcesWithMessage; } else { return AppInstallStatus.MissingResources; } } private static boolean isBadCertificateError(UnresolvedResourceException e) { Throwable mExceptionCause = e.getCause(); if (mExceptionCause instanceof SSLHandshakeException) { Throwable mSecondExceptionCause = mExceptionCause.getCause(); if (mSecondExceptionCause instanceof CertificateException) { return true; } } return false; } /** * Set app's last update attempt time to now in the shared preferences */ public static void recordUpdateAttemptTime(CommCareApp app) { SharedPreferences prefs = app.getAppPreferences(); SharedPreferences.Editor editor = prefs.edit(); editor.putLong(CommCarePreferences.LAST_UPDATE_ATTEMPT, new Date().getTime()); editor.commit(); } /** * Record that an auto-update has started so that we can resume checking * for updates if logged out before the check has completed. */ public static void recordAutoUpdateStart(CommCareApp app) { updateAutoUpdateInProgressPref(app, true); } /** * Record that auto-updating has finished or been cancelled from too many * retries. Used upon login to know whether to resume an auto-update check. */ public static void recordAutoUpdateCompletion(CommCareApp app) { updateAutoUpdateInProgressPref(app, false); } private static void updateAutoUpdateInProgressPref(CommCareApp app, boolean value) { SharedPreferences prefs = app.getAppPreferences(); SharedPreferences.Editor editor = prefs.edit(); editor.putBoolean(CommCarePreferences.AUTO_UPDATE_IN_PROGRESS, value); editor.commit(); } /** * @return True if an auto-update has been registered as in-progress. */ public static boolean shouldAutoUpdateResume(CommCareApp app) { SharedPreferences prefs = app.getAppPreferences(); return prefs.getBoolean(CommCarePreferences.AUTO_UPDATE_IN_PROGRESS, false); } public static void logInstallError(Exception e, String logMessage) { e.printStackTrace(); Logger.log(AndroidLogger.TYPE_ERROR_WORKFLOW, logMessage + e.getMessage()); } /** * Add url query parameters to profile reference based on preference settings. * For instance, to point the reference to the latest app build instead of * the latest release. */ public static String addParamsToProfileReference(final String profileRef) { URL profileUrl; try { profileUrl = new URL(profileRef); } catch (MalformedURLException e) { // don't add url query params to non-url profile references return profileRef; } if (!("https".equals(profileUrl.getProtocol()) || "http".equals(profileUrl.getProtocol()))) { return profileRef; } String targetParam = CommCarePreferences.getUpdateTargetParam(); if (!"".equals(targetParam)) { if (profileUrl.getQuery() != null) { // url already has query strings, so add a new one to the end return profileRef + "&target=" + targetParam; } else { return profileRef + "?target=" + targetParam; } } return profileRef; } /** * @return default profile reference stored in the app's shared preferences */ public static String getDefaultProfileRef() { if (CommCareApplication.instance().isConsumerApp()) { return SingleAppInstallation.SINGLE_APP_REFERENCE; } else { CommCareApp app = CommCareApplication.instance().getCurrentApp(); SharedPreferences prefs = app.getAppPreferences(); return prefs.getString(DEFAULT_APP_SERVER_KEY, null); } } }