com.gc.android.market.api;

时间:2022-10-29 01:04:11


package com.gc.android.market.api;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.zip.GZIPInputStream;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

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.CategoriesRequest;
import com.gc.android.market.api.model.Market.CategoriesResponse;
import com.gc.android.market.api.model.Market.CommentsRequest;
import com.gc.android.market.api.model.Market.CommentsResponse;
import com.gc.android.market.api.model.Market.GetImageRequest;
import com.gc.android.market.api.model.Market.GetImageResponse;
import com.gc.android.market.api.model.Market.Request;
import com.gc.android.market.api.model.Market.Request.RequestGroup;
import com.gc.android.market.api.model.Market.RequestContext;
import com.gc.android.market.api.model.Market.Response;
import com.gc.android.market.api.model.Market.Response.ResponseGroup;
import com.gc.android.market.api.model.Market.ResponseContext;
import com.gc.android.market.api.model.Market.GetAssetRequest;
import com.gc.android.market.api.model.Market.GetAssetResponse;;

/**
 * MarketSession session = new MarketSession();
 * session.login(login,password, androidId);
 * For asyncronous calls use append, callback and flush
 * session.append(xxx,yyy);
 * session.append(xxx,yyy);
 * ...
 * session.flush();
 *  For syncronous call, use the specific method
 */
public class MarketSession {
        
        public static interface Callback<T> {
                
                public void onResult(ResponseContext context, T response);
        }
        /*
         * SERVICE : Service required to the market. 
         * Default value: android. This service must be used to query info to the Market
         * androidsecure: This service must be used to download apps
         * sierra (checkout): This service must be used for checkout (at moment unused)
         */

        public String SERVICE = "android";

        private static final String URL_LOGIN = "https://www.google.com/accounts/ClientLogin";
        public static final String ACCOUNT_TYPE_GOOGLE = "GOOGLE";
        public static final String ACCOUNT_TYPE_HOSTED = "HOSTED";
        public static final String ACCOUNT_TYPE_HOSTED_OR_GOOGLE = "HOSTED_OR_GOOGLE";
        public static final int PROTOCOL_VERSION = 2;
        Request.Builder request = Request.newBuilder();
        RequestContext.Builder context = RequestContext.newBuilder();
        public RequestContext.Builder getContext() {
                return context;
        }
        
        List<Callback<?>> callbacks = new Vector<Callback<?>>(); 
        String authSubToken = null;
        
        public String getAuthSubToken() {
                return authSubToken;
        }

        /*
         * Login must set isSecure to false for list and download
         */
        public MarketSession(Boolean isSecure) {
        if (isSecure)
            SERVICE = "androidsecure";
        else 
            SERVICE = "android";
        context.setIsSecure(false);
        context.setVersion(2009011);
        setLocale(Locale.getDefault());
        context.setDeviceAndSdkVersion("passion:9");
        setOperatorTMobile();
        }
        
        public void setLocale(Locale locale) {
                context.setUserLanguage(locale.getLanguage().toLowerCase());
                context.setUserCountry(locale.getCountry().toLowerCase());
        }
        
        public void setOperator(String alpha, String numeric) {
                setOperator(alpha, alpha, numeric, numeric);
        }
        
        public void setOperatorTMobile() {
                setOperator("T-Mobile", "310260");
        }
        
        public void setOperatorSFR() {
                setOperator("F SFR", "20810");
        }
        
        public void setOperatorO2() {
                setOperator("o2 - de", "26207");
        }
        
        public void setOperatorSimyo() {
                setOperator("E-Plus", "simyo", "26203", "26203");
        }
        
        public void setOperatorSunrise() {
                setOperator("sunrise", "22802");
        }
        
        /**
         * http://www.2030.tk/wiki/Android_market_switch
         */
        public void setOperator(String alpha, String simAlpha, String numeric, String simNumeric) {
                context.setOperatorAlpha(alpha);
                context.setSimOperatorAlpha(simAlpha);
                context.setOperatorNumeric(numeric);
                context.setSimOperatorNumeric(simNumeric);
        }
        
        public void setAuthSubToken(String authSubToken) {
                context.setAuthSubToken(authSubToken);
                this.authSubToken = authSubToken; 
        }
        
        public void setIsSecure(Boolean isSecure) {
                context.setIsSecure(isSecure);
        }
        
        public void setAndroidId(String androidId) {
                context.setAndroidId(androidId);
        }
        
        
        public void login(String email, String password, String androidId) {
                this.login(email, password, androidId, ACCOUNT_TYPE_HOSTED_OR_GOOGLE);
        }
        
        public void login(String email, String password, String androidId,
                        String accountType) {
                //Android ID must an unique identifier associated to the account 
                //used in in the login
                setAndroidId(androidId);
                Map<String,String> params = new LinkedHashMap<String,String>();
                params.put("Email", email);
                params.put("Passwd", password);
                
                params.put("service", SERVICE);
        //      params.put("source", source);
                params.put("accountType", accountType);

                // Login at Google.com
                try {
                        String data = Tools.postUrl(URL_LOGIN, params);
                        StringTokenizer st = new StringTokenizer(data, "\n\r=");
                        String authKey = null;
                        while (st.hasMoreTokens()) {
                                if (st.nextToken().equalsIgnoreCase("Auth")) {
                                        authKey = st.nextToken();
                                        break;
                                }
                        }
                        if(authKey == null)
                                throw new RuntimeException("authKey not found in "+ data);

                        setAuthSubToken(authKey);
                } catch(Tools.HttpException httpEx) {
                        if(httpEx.getErrorCode() != 403)
                                throw httpEx;
                        
                        String data = httpEx.getErrorData();
                        StringTokenizer st = new StringTokenizer(data, "\n\r=");
                        String googleErrorCode = null;
                        while (st.hasMoreTokens()) {
                                if (st.nextToken().equalsIgnoreCase("Error")) {
                                        googleErrorCode = st.nextToken();
                                        break;
                                }
                        }
                        if(googleErrorCode == null)
                                throw httpEx;
                        
                        throw new LoginException(googleErrorCode);
                } catch (IOException ex) {
                        throw new RuntimeException(ex);
                }
        }
        
        public List<Object> queryApp(AppsRequest requestGroup)
        {
                List<Object> retList = new ArrayList<Object>();
                
                request.addRequestGroup(RequestGroup.newBuilder().setAppsRequest(requestGroup));
                
                RequestContext ctxt = context.build();
                context = RequestContext.newBuilder(ctxt);
                request.setContext(ctxt);
                try {
                        Response resp = executeProtobuf(request.build());
                        for(ResponseGroup grp : resp.getResponseGroupList()) {
                                if(grp.hasAppsResponse())
                                        retList.add(grp.getAppsResponse());
                        }
                } finally {
                        request = Request.newBuilder();
                }
                return retList;
        }
        
        public CategoriesResponse queryCategories() {
                RequestContext ctxt = context.build();
                context = RequestContext.newBuilder(ctxt);
                request.setContext(ctxt);
                CategoriesResponse categoriesResponse = null;
                try {
                        Response response = executeProtobuf(request.addRequestGroup(
                                        RequestGroup.newBuilder().setCategoriesRequest(
                                                        CategoriesRequest.newBuilder().build())).setContext(ctxt).build());
                        categoriesResponse = response.getResponseGroup(0).getCategoriesResponse();
                } finally {
                        request = Request.newBuilder();;
                }
                return categoriesResponse;
        }       

        public GetAssetResponse queryGetAssetRequest(String assetId){
            setIsSecure(true);
            RequestContext ctxt = context.build();
            context = RequestContext.newBuilder(ctxt);
            request.setContext(ctxt);
            GetAssetResponse assetResponse = null;
            try {
                Response response = executeProtobuf(request.addRequestGroup(
                            RequestGroup.newBuilder().setGetAssetRequest(
                            GetAssetRequest.newBuilder().setAssetId(
                            assetId).build())).setContext(ctxt).build());
                assetResponse = response.getResponseGroup(0).getGetAssetResponse();
            } finally {
                setIsSecure(false);
                request = Request.newBuilder(); 
            }
            return assetResponse; 
        }
        
        
        public void append(AppsRequest requestGroup, Callback<AppsResponse> responseCallback) {
                request.addRequestGroup(RequestGroup.newBuilder().setAppsRequest(requestGroup));
                callbacks.add(responseCallback);
        }
        
        public void append(GetImageRequest requestGroup, Callback<GetImageResponse> responseCallback) {
                request.addRequestGroup(RequestGroup.newBuilder().setImageRequest(requestGroup));
                callbacks.add(responseCallback);
        }
        
        public void append(CommentsRequest requestGroup, Callback<CommentsResponse> responseCallback) {
                request.addRequestGroup(RequestGroup.newBuilder().setCommentsRequest(requestGroup));
                callbacks.add(responseCallback);
        }
        
        public void append(CategoriesRequest requestGroup, Callback<CategoriesResponse> responseCallback) {
                request.addRequestGroup(RequestGroup.newBuilder().setCategoriesRequest(requestGroup));
                callbacks.add(responseCallback);
        }
        
        @SuppressWarnings("unchecked")
        public void flush() {
                RequestContext ctxt = context.build();
                context = RequestContext.newBuilder(ctxt);
                request.setContext(ctxt);
                try {
                        Response resp = executeProtobuf(request.build());
                        int i = 0;
                        for(ResponseGroup grp : resp.getResponseGroupList()) {
                                Object val = null;
                                if(grp.hasAppsResponse())
                                        val = grp.getAppsResponse();
                                if(grp.hasCategoriesResponse())
                                        val = grp.getCategoriesResponse();
                                if(grp.hasCommentsResponse())
                                        val = grp.getCommentsResponse();
                                if(grp.hasImageResponse())
                                        val = grp.getImageResponse();
                        ((Callback)callbacks.get(i)).onResult(grp.getContext(), val);
                                i++;
                        }
                } finally {
                        request = Request.newBuilder();
                        callbacks.clear();
                }
        }
        
        public ResponseGroup execute(RequestGroup requestGroup) {
                RequestContext ctxt = context.build();
                context = RequestContext.newBuilder(ctxt);
                request.setContext(ctxt);
                Response resp = executeProtobuf(request.addRequestGroup(requestGroup).setContext(ctxt).build());
                return resp.getResponseGroup(0);
        }
        
        private Response executeProtobuf(Request request) {
                byte[] requestBytes = request.toByteArray();
                byte[] responseBytes = null;            
                try {
                    if (!context.getIsSecure())
                        responseBytes = executeRawHttpQuery(requestBytes);
                    else 
                        responseBytes = executeRawHttpsQuery(requestBytes);
                        Response r = Response.parseFrom(responseBytes);
                        return r;
                } catch(Exception ex) {
                        throw new RuntimeException(ex);
                }
        }
        
        private byte[] executeRawHttpQuery(byte[] request) {
                try {
                        
                        URL url = new URL("http://android.clients.google.com/market/api/ApiRequest");
                        HttpURLConnection cnx = (HttpURLConnection)url.openConnection();
                        cnx.setDoOutput(true);
                        cnx.setRequestMethod("POST");
                        cnx.setRequestProperty("Cookie","ANDROID="+authSubToken);
                        cnx.setRequestProperty("User-Agent", "Android-Market/2 (sapphire PLAT-RC33); gzip");
                        cnx.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
                        cnx.setRequestProperty("Accept-Charset","ISO-8859-1,utf-8;q=0.7,*;q=0.7");
                        
                        String request64 = Base64.encodeBytes(request,Base64.URL_SAFE);
                        
                        String requestData = "version="+PROTOCOL_VERSION+"&request="+request64;
                        
                        
                        cnx.setFixedLengthStreamingMode(requestData.getBytes("UTF-8").length);
                        OutputStream os = cnx.getOutputStream();
                        os.write(requestData.getBytes());
                        os.close();
                        
                        if(cnx.getResponseCode() >= 400) {
                                throw new RuntimeException("Response code = " + cnx.getResponseCode() + 
                                                ", msg = " + cnx.getResponseMessage());
                        }
                        
                        InputStream is = cnx.getInputStream();
                        GZIPInputStream gzIs = new GZIPInputStream(is);
                        ByteArrayOutputStream bos = new ByteArrayOutputStream();
                        byte[] buff = new byte[1024];
                        while(true) {
                                int nb = gzIs.read(buff);
                                if(nb < 0)
                                        break;
                                bos.write(buff,0,nb);
                        }
                        is.close();
                        cnx.disconnect();

                        return bos.toByteArray();
                } catch(Exception ex) {
                        throw new RuntimeException(ex);
                }
        }
    private Boolean trustAll() {
        TrustManager[] trustAllCerts = new TrustManager[]{
                new X509TrustManager() {
                    public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                        return null;
                    }
                    public void checkClientTrusted(
                        java.security.cert.X509Certificate[] certs, String authType) {
                    }
                    public void checkServerTrusted(
                        java.security.cert.X509Certificate[] certs, String authType) {
                    }
                }
            };
        try {
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, trustAllCerts, new java.security.SecureRandom());
            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
            HttpsURLConnection.setDefaultHostnameVerifier( new HostnameVerifier()
            {
                public boolean verify(String arg0, SSLSession arg1) {
                    return true;
                }
            }
            ); 
            return true;
        } catch (Exception e) {
            return false;
        }
    }
    private byte[] executeRawHttpsQuery(byte[] request){
        if (request == null)
            return null;        
        if (!trustAll())
            return null;
        try {
            URL url = new URL("https://android.clients.google.com/market/api/ApiRequest");
            HttpsURLConnection cnx = (HttpsURLConnection)url.openConnection();
            cnx.setDoOutput(true);
            cnx.setRequestMethod("POST");
            cnx.setRequestProperty("Cookie","ANDROIDSECURE=" + this.getAuthSubToken());
            cnx.setRequestProperty("User-Agent", "Android-Market/2 (sapphire PLAT-RC33); gzip");
            cnx.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
            cnx.setRequestProperty("Accept-Charset","ISO-8859-1,utf-8;q=0.7,*;q=0.7");
            String request64 = Base64.encodeBytes(request,Base64.URL_SAFE);
            String requestData = "version="+PROTOCOL_VERSION+"&request="+request64;
            cnx.setFixedLengthStreamingMode(requestData.getBytes("UTF-8").length);
            OutputStream os = cnx.getOutputStream();
            os.write(requestData.getBytes());
            os.close();
            if(cnx.getResponseCode() >= 400) {
                cnx.disconnect();
                throw new IOException("Response code = " + cnx.getResponseCode() + 
                        ", msg = " + cnx.getResponseMessage());
            }
            InputStream is = cnx.getInputStream();
            GZIPInputStream gzIs = new GZIPInputStream(is);
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            byte[] buff = new byte[1024];
            while(true) {
                int nb = gzIs.read(buff);
                if(nb < 0)
                    break;
                bos.write(buff,0,nb);
            }
            is.close();
            cnx.disconnect();
            return bos.toByteArray();
      } catch(Exception ex) {
            throw new RuntimeException(ex);
        }
    }           
}