com.androidquery.service.MarketService.java Source code

Java tutorial

Introduction

Here is the source code for com.androidquery.service.MarketService.java

Source

/*
 * Copyright 2011 - AndroidQuery.com (tinyeeliu@gmail.com)
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.androidquery.service;

import java.util.Locale;

import org.json.JSONObject;
import org.xml.sax.XMLReader;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.preference.PreferenceManager;
import android.text.Editable;
import android.text.Html;
import android.text.Html.TagHandler;

import com.androidquery.AQuery;
import com.androidquery.callback.AjaxCallback;
import com.androidquery.callback.AjaxStatus;
import com.androidquery.util.AQUtility;

public class MarketService {

    private Activity act;
    private AQuery aq;
    private Handler handler;
    private String locale;
    private String rateUrl;
    private String updateUrl;
    private boolean force;
    private int progress;
    private long expire = 12 * 60 * 1000;

    private String version;
    private boolean fetch;
    private boolean completed;
    private int level = REVISION;

    /** Update check level REVISION. */
    public static final int REVISION = 0;

    /** Update check level MINOR. */
    public static final int MINOR = 1;

    /** Update check level MAJOR. */
    public static final int MAJOR = 2;

    /**
     * Instantiates a new MarketService.
     *
     * @param act Current activity.
     */

    public MarketService(Activity act) {
        this.act = act;
        this.aq = new AQuery(act);
        this.handler = new Handler();
        this.locale = Locale.getDefault().toString();
        this.rateUrl = getMarketUrl();
        this.updateUrl = rateUrl;
    }

    /**
     * Set the destination url of the default rate/review button.
     *
     * @param url url
     * @return self
     */
    public MarketService rateUrl(String url) {
        this.rateUrl = url;
        return this;
    }

    /**
     * Set the update check granularity level. Default is REVISION.
     * 
     * <br>
     * 
     * Can be REVISION, MINOR, or MAJOR.
     *
     * <br>
     *
     * App version format: MAJOR.MINOR.REVISION
     * 
     * <br>
     * 
     * Example:
     * 
     * <br>
     * Current app version: 3.1.2
     * <br>
     * Newest app version: 3.1.4
     * <br>
     * Update notice will show if level is REVISION, because the revision code is higher.
     * <br>
     * Update notice will NOT show if level is MINOR, because the minor code is equal (or higher).
     * 
     *
     * @param level granularity level
     * @return self
     */
    public MarketService level(int level) {
        this.level = level;
        return this;
    }

    /**
     * Set the destination url of the default update button.
     *
     * @param url url
     * @return self
     */
    public MarketService updateUrl(String url) {
        this.updateUrl = url;
        return this;
    }

    /**
     * Force the update dialog to a specific locale. Example: en_US, ja_JP.
     *
     * @param locale interface locale
     * @return self
     */

    public MarketService locale(String locale) {
        this.locale = locale;
        return this;
    }

    /**
     * Display a progress view during version check.
     *
     * @param id view id
     * @return self
     */
    public MarketService progress(int id) {
        this.progress = id;
        return this;
    }

    /**
     * Force a version check against the AQuery server and show a dialog regardless of versions.
     *
     * @param force force an update check
     * @return self
     */
    public MarketService force(boolean force) {
        this.force = force;
        return this;
    }

    /**
     * The time duration which last version check expires. Default is 10 hours.
     *
     * @param expire expire time in milliseconds
     * @return self
     */

    public MarketService expire(long expire) {
        this.expire = expire;
        return this;
    }

    private static ApplicationInfo ai;

    private ApplicationInfo getApplicationInfo() {

        if (ai == null) {
            ai = act.getApplicationInfo();
        }

        return ai;
    }

    private static PackageInfo pi;

    private PackageInfo getPackageInfo() {

        if (pi == null) {
            try {
                pi = act.getPackageManager().getPackageInfo(getAppId(), 0);

            } catch (NameNotFoundException e) {
                e.printStackTrace();
            }
        }
        return pi;
    }

    private String getHost() {

        return "https://androidquery.appspot.com";
    }

    private String getQueryUrl() {
        String appId = getAppId();
        String url = getHost() + "/api/market?app=" + appId + "&locale=" + locale + "&version=" + getVersion()
                + "&code=" + getVersionCode() + "&aq=" + AQuery.VERSION;
        if (force) {
            url += "&force=true";
        }
        return url;
    }

    private String getAppId() {

        return getApplicationInfo().packageName;
    }

    private Drawable getAppIcon() {
        Drawable d = getApplicationInfo().loadIcon(act.getPackageManager());
        return d;
    }

    private String getVersion() {
        return getPackageInfo().versionName;
    }

    private int getVersionCode() {
        return getPackageInfo().versionCode;
    }

    /**
     * Perform a version check.
     *
     * 
     */

    public void checkVersion() {

        String url = getQueryUrl();

        AjaxCallback<JSONObject> cb = new AjaxCallback<JSONObject>();
        cb.url(url).type(JSONObject.class).handler(handler, "marketCb").fileCache(!force).expire(expire);

        aq.progress(progress).ajax(cb);

    }

    private static boolean openUrl(Activity act, String url) {

        try {

            if (url == null)
                return false;

            Uri uri = Uri.parse(url);
            Intent intent = new Intent(Intent.ACTION_VIEW, uri);
            act.startActivity(intent);

            return true;
        } catch (Exception e) {
            return false;
        }
    }

    private String getMarketUrl() {
        String id = getAppId();
        return "market://details?id=" + id;
    }

    protected void callback(String url, JSONObject jo, AjaxStatus status) {

        if (jo == null)
            return;

        String latestVer = jo.optString("version", "0");
        int latestCode = jo.optInt("code", 0);

        AQUtility.debug("version", getVersion() + "->" + latestVer + ":" + getVersionCode() + "->" + latestCode);
        AQUtility.debug("outdated", outdated(latestVer, latestCode));

        if (force || outdated(latestVer, latestCode)) {
            showUpdateDialog(jo);
        }

    }

    private boolean outdated(String latestVer, int latestCode) {

        String skip = getSkipVersion(act);

        if (latestVer.equals(skip)) {
            return false;
        }

        String version = getVersion();
        int code = getVersionCode();

        if (!version.equals(latestVer)) {
            if (code <= latestCode) {
                //return true;
                return requireUpdate(version, latestVer, level);
            }
        }

        return false;
    }

    private boolean requireUpdate(String existVer, String latestVer, int level) {

        if (existVer.equals(latestVer))
            return false;

        try {

            String[] evs = existVer.split("\\.");
            String[] lvs = latestVer.split("\\.");

            if (evs.length < 3 || lvs.length < 3)
                return true;

            switch (level) {
            case REVISION:
                if (!evs[evs.length - 1].equals(lvs[lvs.length - 1])) {
                    return true;
                }
            case MINOR:
                if (!evs[evs.length - 2].equals(lvs[lvs.length - 2])) {
                    return true;
                }
            case MAJOR:
                if (!evs[evs.length - 3].equals(lvs[lvs.length - 3])) {
                    return true;
                }
                return false;
            default:
                return true;
            }

        } catch (Exception e) {
            AQUtility.report(e);
            return true;
        }

    }

    protected void showUpdateDialog(JSONObject jo) {

        if (jo == null || version != null)
            return;

        if (!isActive())
            return;

        JSONObject dia = jo.optJSONObject("dialog");

        String update = dia.optString("update", "Update");
        String skip = dia.optString("skip", "Skip");
        String rate = dia.optString("rate", "Rate");
        //String message = dia.optString("body", "");
        String body = dia.optString("wbody", "");
        String title = dia.optString("title", "Update Available");

        AQUtility.debug("wbody", body);

        version = jo.optString("version", null);

        Drawable icon = getAppIcon();

        Context context = act;

        final AlertDialog dialog = new AlertDialog.Builder(context).setIcon(icon).setTitle(title)
                .setPositiveButton(rate, handler).setNeutralButton(skip, handler).setNegativeButton(update, handler)
                .create();

        dialog.setMessage(Html.fromHtml(patchBody(body), null, handler));

        aq.show(dialog);

        return;

    }

    private static String patchBody(String body) {
        return "<small>" + body + "</small>";
    }

    private static final String SKIP_VERSION = "aqs.skip";

    private static void setSkipVersion(Context context, String version) {

        PreferenceManager.getDefaultSharedPreferences(context).edit().putString(SKIP_VERSION, version).commit();
    }

    private static String getSkipVersion(Context context) {
        return PreferenceManager.getDefaultSharedPreferences(context).getString(SKIP_VERSION, null);
    }

    private boolean isActive() {
        if (act.isFinishing())
            return false;
        return true;
    }

    private static final String BULLET = "";

    private class Handler implements DialogInterface.OnClickListener, TagHandler {

        @SuppressWarnings("unused")
        public void marketCb(String url, JSONObject jo, AjaxStatus status) {

            if (act.isFinishing())
                return;

            if (jo != null) {

                String s = jo.optString("status");

                if ("1".equals(s)) {

                    if (jo.has("dialog")) {
                        cb(url, jo, status);
                    }

                    if (!fetch && jo.optBoolean("fetch", false) && status.getSource() == AjaxStatus.NETWORK) {

                        fetch = true;

                        String marketUrl = jo.optString("marketUrl", null);

                        AjaxCallback<String> cb = new AjaxCallback<String>();
                        cb.url(marketUrl).type(String.class).handler(this, "detailCb");
                        aq.progress(progress).ajax(cb);

                    }

                } else if ("0".equals(s)) {
                    status.invalidate();
                } else {
                    cb(url, jo, status);
                }

            } else {
                cb(url, jo, status);
            }
        }

        private void cb(String url, JSONObject jo, AjaxStatus status) {

            if (!completed) {
                completed = true;
                progress = 0;
                callback(url, jo, status);
            }
        }

        @SuppressWarnings("unused")
        public void detailCb(String url, String html, AjaxStatus status) {

            if (html != null && html.length() > 1000) {

                String qurl = getQueryUrl();

                AjaxCallback<JSONObject> cb = new AjaxCallback<JSONObject>();
                cb.url(qurl).type(JSONObject.class).handler(this, "marketCb");
                cb.param("html", html);

                aq.progress(progress).ajax(cb);

            }

        }

        @Override
        public void onClick(DialogInterface dialog, int which) {

            switch (which) {
            case AlertDialog.BUTTON_POSITIVE:
                openUrl(act, rateUrl);
                break;
            case AlertDialog.BUTTON_NEGATIVE:
                openUrl(act, updateUrl);
                break;
            case AlertDialog.BUTTON_NEUTRAL:
                setSkipVersion(act, version);
                break;
            }

        }

        @Override
        public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {

            if ("li".equals(tag)) {

                if (opening) {
                    output.append("  ");
                    output.append(BULLET);
                    output.append("  ");
                } else {
                    output.append("\n");
                }

            }
        }

    }

}