/*
* 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.external.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.external.androidquery.AQuery;
import com.external.androidquery.callback.AjaxCallback;
import com.external.androidquery.callback.AjaxStatus;
import com.external.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");
}
}
}
}
}