Showing posts with label New Version. Show all posts
Showing posts with label New Version. Show all posts

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