package net.hockeyapp.android; import android.annotation.TargetApi; import android.app.Activity; import android.app.Fragment; import android.content.Context; import android.content.Intent; import android.os.AsyncTask.Status; import android.os.Build; import android.text.TextUtils; import net.hockeyapp.android.tasks.CheckUpdateTask; import net.hockeyapp.android.tasks.CheckUpdateTaskWithUI; import net.hockeyapp.android.utils.AsyncTaskUtils; import net.hockeyapp.android.utils.Util; import java.lang.ref.WeakReference; import java.util.Date; /** * <h3>Description</h3> * <p> * The update manager sends version information to HockeyApp and * shows an alert dialog if a new version was found. **/ public class UpdateManager { public static final String INSTALLER_ADB = "adb"; public static final String INSTALLER_PACKAGE_INSTALLER_NOUGAT = "com.google.android.packageinstaller"; public static final String INSTALLER_PACKAGE_INSTALLER_NOUGAT2 = "com.android.packageinstaller"; /** * Singleton for update task. */ private static CheckUpdateTask updateTask = null; /** * Last listener instance. */ private static UpdateManagerListener lastListener = null; /** * Registers new update manager. * HockeyApp app identifier is read from AndroidManifest.xml. * * @param activity The parent activity to return to. */ public static void register(Activity activity) { String appIdentifier = Util.getAppIdentifier(activity); register(activity, appIdentifier); } /** * Registers new update manager. * HockeyApp app identifier is read from AndroidManifest.xml. * * @param activity The parent activity to return to. * @param listener Implement for callback functions. */ public static void register(Activity activity, UpdateManagerListener listener) { String appIdentifier = Util.getAppIdentifier(activity); register(activity, appIdentifier, listener); } /** * Registers new update manager. * * @param activity Parent activity. * @param appIdentifier App ID of your app on HockeyApp. */ public static void register(Activity activity, String appIdentifier) { register(activity, appIdentifier, true); } /** * Registers new update manager. * * @param activity Parent activity. * @param appIdentifier App ID of your app on HockeyApp. * @param isDialogRequired Flag to indicate if a dialog must be shown when any update is available. */ public static void register(Activity activity, String appIdentifier, boolean isDialogRequired) { register(activity, appIdentifier, null, isDialogRequired); } /** * Registers new update manager. * * @param activity Parent activity. * @param appIdentifier App ID of your app on HockeyApp. * @param listener Implement for callback functions. */ public static void register(Activity activity, String appIdentifier, UpdateManagerListener listener) { register(activity, Constants.BASE_URL, appIdentifier, listener, true); } /** * Registers new update manager. * * @param activity Parent activity. * @param appIdentifier App ID of your app on HockeyApp. * @param listener Implement for callback functions. * @param isDialogRequired Flag to indicate if a dialog must be shown when any update is available. */ public static void register(Activity activity, String appIdentifier, UpdateManagerListener listener, boolean isDialogRequired) { register(activity, Constants.BASE_URL, appIdentifier, listener, isDialogRequired); } /** * Registers new update manager. * * @param activity parent activity * @param urlString URL of the HockeyApp server * @param appIdentifier App ID of your app on HockeyApp * @param listener implement for callback functions */ public static void register(Activity activity, String urlString, String appIdentifier, UpdateManagerListener listener) { register(activity, urlString, appIdentifier, listener, true); } /** * Registers new update manager. * * @param activity parent activity * @param urlString URL of the HockeyApp server * @param appIdentifier App ID of your app on HockeyApp * @param listener implement for callback functions * @param isDialogRequired if false, no alert dialog is shown */ public static void register(Activity activity, String urlString, String appIdentifier, UpdateManagerListener listener, boolean isDialogRequired) { appIdentifier = Util.sanitizeAppIdentifier(appIdentifier); lastListener = listener; WeakReference<Activity> weakActivity = new WeakReference<Activity>(activity); if ((Util.fragmentsSupported()) && (dialogShown(weakActivity))) { return; } if ((!checkExpiryDate(weakActivity, listener)) && ((listener != null && listener.canUpdateInMarket()) || !installedFromMarket(weakActivity))) { startUpdateTask(weakActivity, urlString, appIdentifier, listener, isDialogRequired); } } /** * Registers new update manager. * * @param appContext Application context. * @param appIdentifier App ID of your app on HockeyApp. * @param listener Implement for callback functions. */ public static void registerForBackground(Context appContext, String appIdentifier, UpdateManagerListener listener) { registerForBackground(appContext, Constants.BASE_URL, appIdentifier, listener); } /** * Registers new update manager. * * @param appContext Application context. * @param urlString URL of the HockeyApp server. * @param appIdentifier App ID of your app on HockeyApp. * @param listener Implement for callback functions. */ public static void registerForBackground(Context appContext, String urlString, String appIdentifier, UpdateManagerListener listener) { appIdentifier = Util.sanitizeAppIdentifier(appIdentifier); lastListener = listener; WeakReference<Context> weakContext = new WeakReference<Context>(appContext); if ((!checkExpiryDateForBackground(listener)) && ((listener != null && listener.canUpdateInMarket()) || !installedFromMarket(weakContext))) { startUpdateTaskForBackground(weakContext, urlString, appIdentifier, listener); } } /** * Unregisters the update manager */ public static void unregister() { if (updateTask != null) { updateTask.cancel(true); updateTask.detach(); updateTask = null; } lastListener = null; } /** * Returns true if the build is expired and starts an activity if not * handled by the owner of the UpdateManager. */ private static boolean checkExpiryDate(WeakReference<Activity> weakActivity, UpdateManagerListener listener) { boolean handle = false; boolean hasExpired = checkExpiryDateForBackground(listener); if (hasExpired) { handle = listener.onBuildExpired(); } if ((hasExpired) && (handle)) { startExpiryInfoIntent(weakActivity); } return hasExpired; } /** * Returns true if the build is expired and starts an activity if not * handled by the owner of the UpdateManager. */ private static boolean checkExpiryDateForBackground(UpdateManagerListener listener) { boolean result = false; if (listener != null) { Date expiryDate = listener.getExpiryDate(); result = ((expiryDate != null) && (new Date().compareTo(expiryDate) > 0)); } return result; } /** * Returns true if the build was installed through a market. */ protected static boolean installedFromMarket(WeakReference<? extends Context> weakContext) { boolean result = false; Context context = weakContext.get(); if (context != null) { try { String installer = context.getPackageManager().getInstallerPackageName(context.getPackageName()); // if installer string is not null it might be installed by market if (!TextUtils.isEmpty(installer)) { result = true; // on Android Nougat and up when installing an app through the package installer (which HockeyApp uses itself), the installer will be // "com.google.android.packageinstaller" or "com.android.packageinstaller" which is also not to be considered as a market installation if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && (TextUtils.equals(installer, INSTALLER_PACKAGE_INSTALLER_NOUGAT) || TextUtils.equals(installer, INSTALLER_PACKAGE_INSTALLER_NOUGAT2))) { result = false; } // on some devices (Xiaomi) the installer identifier will be "adb", which is not to be considered as a market installation if (TextUtils.equals(installer, INSTALLER_ADB)) { result = false; } } } catch (Throwable e) { } } return result; } /** * Starts the ExpiryInfoActivity as a new task and finished the current * activity. */ private static void startExpiryInfoIntent(WeakReference<Activity> weakActivity) { if (weakActivity != null) { Activity activity = weakActivity.get(); if (activity != null) { activity.finish(); Intent intent = new Intent(activity, ExpiryInfoActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); activity.startActivity(intent); } } } /** * Starts the UpdateTask if not already running. Otherwise attaches the * activity to it. */ private static void startUpdateTask(WeakReference<Activity> weakActivity, String urlString, String appIdentifier, UpdateManagerListener listener, boolean isDialogRequired) { if ((updateTask == null) || (updateTask.getStatus() == Status.FINISHED)) { updateTask = new CheckUpdateTaskWithUI(weakActivity, urlString, appIdentifier, listener, isDialogRequired); AsyncTaskUtils.execute(updateTask); } else { updateTask.attach(weakActivity); } } /** * Starts the UpdateTask if not already running. Otherwise attaches the * activity to it. */ private static void startUpdateTaskForBackground(WeakReference<Context> weakContext, String urlString, String appIdentifier, UpdateManagerListener listener) { if ((updateTask == null) || (updateTask.getStatus() == Status.FINISHED)) { updateTask = new CheckUpdateTask(weakContext, urlString, appIdentifier, listener); AsyncTaskUtils.execute(updateTask); } else { updateTask.attach(weakContext); } } /** * Returns true if the dialog is already shown (only works on Android 3.0+). */ @TargetApi(11) private static boolean dialogShown(WeakReference<Activity> weakActivity) { if (weakActivity != null) { Activity activity = weakActivity.get(); if (activity != null) { Fragment existingFragment = activity.getFragmentManager().findFragmentByTag("hockey_update_dialog"); return (existingFragment != null); } } return false; } /** * Returns the last listener which has been registered with any update manager. * * @return last update manager listener */ public static UpdateManagerListener getLastListener() { return lastListener; } }