Thursday, March 15, 2012

Notify User about an Upgrade version at Android Play-Store

Well, here is my impression of an upgrade feature for my Android wrapping framework (Cyborg):

It compares the versionCode of the Play-Store apk, and if the version string starts with the letter 'F', the PlayStoreModule would invoke an upgrade dialog, and would open the Play-Store, in the proper application.

I think that all the pieces are here, except for the application id, this one I got by calling on the getPlayStoreAppDetails_Async(activity, true), I've received 10 applications details printed to the log, and one of them was mine, I took the app id and used it hard coded in the top layer application.

I use the Android market api.

Underwent a bit of refactoring at: 24-03-2012
package com.nu.art.software.android.modules.market;


import java.io.IOException;
import java.util.List;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
import android.accounts.AccountManagerFuture;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;

import com.gc.android.market.api.MarketSession;
import com.gc.android.market.api.MarketSession.Callback;
import com.gc.android.market.api.model.Market.App;
import com.gc.android.market.api.model.Market.AppsRequest;
import com.gc.android.market.api.model.Market.AppsResponse;
import com.gc.android.market.api.model.Market.ResponseContext;
import com.nu.art.software.android.core.AndroidModule;
import com.nu.art.software.android.dialogs.ForceActionDialog;
import com.nu.art.software.android.modules.configuration.ApplicationConfiguration;
import com.nu.art.software.android.utils.IntentFactory;
import com.nu.art.software.android.wrapper.R;


public final class PlayStoreModule
    extends AndroidModule {

  protected static final String GoogleAccountToken_Key = "A Google Account Token";

  public static final String ApplicationPlaySotreId_Key = "Application Play Store ID";

  private final Object TokenMonitor = new Object();

  private MarketSession session;

  private String sessionToken;

  private Account googleAccount;

  private AccountManager accountManager;

  private ApplicationConfiguration configuration;

  private String deviceId;

  private String playStoreAppId;

  private Thread applicationDetailsThread;

  @Override
  protected void init() {
    deviceId = getGtalkAndroidId(getApplication());
    configuration = getModuleOrThrowException(ApplicationConfiguration.class);
    playStoreAppId = configuration.getValue(false, ApplicationPlaySotreId_Key, null);
    if (playStoreAppId == null)
      throw new IllegalStateException("Must add your Play-Store application id to the configuration with key: "
          + ApplicationPlaySotreId_Key);
    sessionToken = configuration.getValue(true, GoogleAccountToken_Key, null);
    logDebug("Loaded Google AuthToken: " + sessionToken);
    accountManager = AccountManager.get(getApplication().getApplicationContext());
    Thread googlePlayStoreAPI_LoadingThread = new Thread(new Runnable() {

      @Override
      public void run() {
        synchronized (PlayStoreModule.this) {
          session = new MarketSession();
          session.getContext().setAndroidId(deviceId);
          logDebug("Market Session - Initialized ");
        }
      }
    }"Google Play-Store API Initializer");
    googlePlayStoreAPI_LoadingThread.start();
  }

  private static final Uri URI = Uri.parse("content://com.google.android.gsf.gservices");

  private static final String ID_KEY = "android_id";

  public static String getGtalkAndroidId(Context ctx) {
    String params[] {ID_KEY};
    Cursor c = ctx.getContentResolver().query(URI, null, null, params, null);
    if (!c.moveToFirst() || c.getColumnCount() 2)
      return null;
    try {
      return Long.toHexString(Long.parseLong(c.getString(1)));
    catch (NumberFormatException e) {
      return null;
    }
  }

  private boolean setGoogleAccountToken(final Activity activity) {
    logDebug("Getting new AuthToken");
    Account[] accounts = accountManager.getAccountsByType("com.google");
    if (accounts.length == 0) {
      sessionToken = null;
      return false;
    }
    googleAccount = accounts[0];

    AccountManagerCallback<Bundle> callBack = new AccountManagerCallback<Bundle>() {

      @Override
      public void run(AccountManagerFuture<Bundle> accountManagerFuture) {
        Bundle bundle;
        try {
          bundle = accountManagerFuture.getResult();
          sessionToken = bundle.getString(AccountManager.KEY_AUTHTOKEN);
          logDebug("Google New AuthToken: " + sessionToken);
          configuration.putValue(true, GoogleAccountToken_Key, sessionToken);
          synchronized (TokenMonitor) {
            TokenMonitor.notify();
          }
        catch (OperationCanceledException e) {
          logError(e);
        catch (AuthenticatorException e) {
          accountManager.invalidateAuthToken("com.google", sessionToken);
        catch (IOException e) {
          logError(e);
        }
      }

    };
    accountManager.getAuthToken(googleAccount, "android", null, activity, callBack, null);
    return true;
  }

  public final void checkIfForceUpdateIsInOrder(final Activity activity) {
    checkIfForceUpdateIsInOrder(activity, false);
  }

  public final void checkIfForceUpdateIsInOrder(final Activity activity, final boolean list) {
    if (applicationDetailsThread != null)
      return;
    applicationDetailsThread = new Thread(new Runnable() {

      @Override
      public void run() {
        process();
        applicationDetailsThread = null;

      }

      private void process() {
        boolean waitForToken = false;
        // if (sessionToken == null)
        waitForToken = setGoogleAccountToken(activity);

        if (waitForToken)
          synchronized (TokenMonitor) {
            try {
              logDebug("Waiting for google account token");
              TokenMonitor.wait(20000);
            catch (InterruptedException e) {
              logError("Erroe while waiting for Account token", e);
              return;
            }
          }
        // Launch race condition workaround
        if (sessionToken == null)
          return;
        logDebug("Get Application Details");
        synchronized (PlayStoreModule.this) {
          getApplicationDetails(activity, list);
        }
      }

    }"Play-Store Application Details fetcher workaround Thread");
    applicationDetailsThread.start();
  }

  private final void getApplicationDetails(final Activity activity, boolean list) {
    session.setAuthSubToken(sessionToken);

    com.gc.android.market.api.model.Market.AppsRequest.Builder builder = AppsRequest.newBuilder();
    builder.setStartIndex(0);
    if (list) {
      builder.setQuery(getApplication().getName());
      builder.setEntriesCount(10);
    else {
      builder.setAppId(playStoreAppId);
      builder.setEntriesCount(1);
    }
    // builder.setWithExtendedInfo(true);
    logDebug("AppsRequest.Builder: " + builder);

    AppsRequest appsRequest = builder.build();
    session.append(appsRequest, new Callback<AppsResponse>() {

      @Override
      public void onResult(ResponseContext context, AppsResponse response) {
        List<App> apps = response.getAppList();
        logDebug("ResponseContext: " + context);
        logDebug("AppsResponse: " + response);
        App app;
        if (apps.size() != 1)
          return;

        app = apps.get(0);
        int latestVersionCodeFound = app.getVersionCode();
        String latestVersionFound = app.getVersion();
        if (latestVersionCodeFound <= getApplication().getVersionCode()) {
          logDebug("No upgrade in market.");
          return;
        }
        if (!latestVersionFound.startsWith("F")) {
          logDebug("Newer (NOT MANDATORY) version is now available in Google play store (v:" + latestVersionFound + ", vc:" + latestVersionCodeFound + ").");
          return;
        }
        logDebug("Newer (MANDATORY) version is now available in Google play store (v:" + latestVersionFound + ", vc:" + latestVersionCodeFound + ").");
        final ForceActionDialog dialog = getUpgradeDialog(activity);
        String body = dialog.getBody();
        body = body.replace("${version}", latestVersionFound);
        dialog.setBody(body);
        dialog.setOnClickListener(new OnClickListener() {

          @Override
          public void onClick(View arg0) {
            Thread thread = new Thread(new Runnable() {

              @Override
              public void run() {
                dialog.dismiss();
                Intent intent = IntentFactory.openMarketApplicationDetails(activity.getApplicationContext().getPackageName());
                activity.startActivity(intent);
              }
            }"Upgrade APK Installer");
            thread.start();
          }
        });
        dialog.showDialog();
      }
    });
    try {
      session.flush();
    catch (Exception e) {
      logError(e);
      sessionToken = null;
      toast("Error checking for Upgrade", LongToast);
    }
  }

  protected ForceActionDialog getUpgradeDialog(final Activity activity) {
    final ForceActionDialog dialog = new ForceActionDialog(activity, R.string.UpgradeRequired, R.string.NeedToUpgradeApplicationMessage,
        R.string.Upgrade);
    dialog.setCancelableFlag(false);
    return dialog;
  }
}
Java2html






No comments:

Post a Comment