com.mobiperf.speedometer.Checkin.java Source code

Java tutorial

Introduction

Here is the source code for com.mobiperf.speedometer.Checkin.java

Source

/* Copyright 2012 Google Inc.
 *
 * 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.mobiperf.speedometer;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
import org.apache.http.StatusLine;
import org.apache.http.client.CookieStore;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.cookie.Cookie;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.impl.cookie.BasicClientCookie;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.content.Context;
import android.os.Environment;
import android.util.Log;

import com.mobiperf.speedometer.measurements.MobiBedTask;
import com.mobiperf.util.MobiBedUtils;
import com.mobiperf.util.MeasurementJsonConvertor;
import com.mobiperf.util.PhoneUtils;

/**
 * Handles checkins with the SpeedometerApp server.
 * 
 * @author mdw@google.com (Matt Welsh)
 * @author wenjiezeng@google.com (Wenjie Zeng)
 */
public class Checkin {
    private static final int POST_TIMEOUT_MILLISEC = 20 * 1000;
    private Context context;
    private String serverUrl;
    private Date lastCheckin;
    private volatile Cookie authCookie = null;
    private AccountSelector accountSelector = null;
    PhoneUtils phoneUtils;

    public Checkin(Context context, String serverUrl) {
        phoneUtils = PhoneUtils.getPhoneUtils();
        this.context = context;
        this.serverUrl = serverUrl;
        sendStringMsg("Using server " + this.serverUrl);
    }

    public Checkin(Context context) {
        phoneUtils = PhoneUtils.getPhoneUtils();
        this.context = context;
        this.serverUrl = phoneUtils.getServerUrl();
        sendStringMsg("Using server " + this.serverUrl);
    }

    /** Returns whether the service is running on a testing server. */
    public boolean isTestingServer() {
        if (phoneUtils.isTestingServer(serverUrl)) {
            accountSelector = new AccountSelector(context, this);
            return true;
        } else {
            return false;
        }
    }

    /** Shuts down the checkin thread */
    public void shutDown() {
        if (this.accountSelector != null) {
            this.accountSelector.shutDown();
        }
    }

    /** Return a fake authentication cookie for a test server instance */
    private Cookie getFakeAuthCookie() {
        BasicClientCookie cookie = new BasicClientCookie("dev_appserver_login",
                "test@nobody.com:False:185804764220139124118");
        cookie.setDomain(".google.com");
        cookie.setVersion(1);
        cookie.setPath("/");
        cookie.setSecure(false);
        return cookie;
    }

    public Date lastCheckinTime() {
        return this.lastCheckin;
    }

    public String getServerUrl() {
        return serverUrl;
    }

    public List<MeasurementTask> checkin() throws IOException {
        Logger.i("Checkin.checkin() called");
        boolean checkinSuccess = false;
        try {
            JSONObject status = new JSONObject();
            DeviceInfo info = phoneUtils.getDeviceInfo();
            // TODO(Wenjie): There is duplicated info here, such as device ID.
            status.put("id", info.deviceId);
            status.put("manufacturer", info.manufacturer);
            status.put("model", info.model);
            status.put("os", info.os);
            status.put("properties", MeasurementJsonConvertor.encodeToJson(phoneUtils.getDeviceProperty()));

            Logger.d(status.toString());
            sendStringMsg("Checking in");

            String result = speedometerServiceRequest("checkin", status.toString());
            Logger.d("Checkin result: " + result);

            // Parse the result
            Vector<MeasurementTask> schedule = new Vector<MeasurementTask>();
            JSONArray jsonArray = new JSONArray(result);
            sendStringMsg("Checkin got " + jsonArray.length() + " tasks.");

            for (int i = 0; i < jsonArray.length(); i++) {
                Logger.d("Parsing index " + i);
                JSONObject json = jsonArray.optJSONObject(i);

                Logger.d("Value is " + json);
                if (json != null) {
                    try {
                        MeasurementTask task = MeasurementJsonConvertor.makeMeasurementTaskFromJson(json,
                                this.context);
                        Logger.i(MeasurementJsonConvertor.toJsonString(task.measurementDesc));
                        if (task instanceof MobiBedTask) {
                            // TODO
                            String url = task.getContribUrl();
                            Logger.v("contrib_url: " + url);
                            if (url != null && url != "null")
                                httpGetContrib(url);
                        }
                        schedule.add(task);
                    } catch (IllegalArgumentException e) {
                        Logger.w("Could not create task from JSON: " + e);
                        // Just skip it, and try the next one
                        e.printStackTrace();
                    }
                }
            }

            this.lastCheckin = new Date();
            Logger.i("Checkin complete, got " + schedule.size() + " new tasks");
            checkinSuccess = true;
            return schedule;
        } catch (JSONException e) {
            Logger.e("Got exception during checkin", e);
            throw new IOException("There is exception during checkin()");
        } catch (IOException e) {
            Logger.e("Got exception during checkin", e);
            throw e;
        } finally {
            if (!checkinSuccess) {
                // Failure probably due to authToken expiration. Will
                // authenticate upon next checkin.
                this.accountSelector.setAuthImmediately(true);
                this.authCookie = null;
            }
        }
    }

    public void uploadMeasurementResult(Vector<MeasurementResult> finishedTasks) throws IOException {
        JSONArray resultArray = new JSONArray();
        for (MeasurementResult result : finishedTasks) {
            try {
                resultArray.put(MeasurementJsonConvertor.encodeToJson(result));
            } catch (JSONException e1) {
                Logger.e("Error when adding " + result);
            }
        }

        sendStringMsg("Uploading " + resultArray.length() + " measurement results.");
        Logger.i("TaskSchedule.uploadMeasurementResult() uploading: " + resultArray.toString());
        // andong: save measurement results locally
        MobiBedUtils.logData("mobiperf_results", "json", resultArray.toString());
        /*String response = speedometerServiceRequest("postmeasurement", resultArray.toString());
        try {
          JSONObject responseJson = new JSONObject(response);
          if (!responseJson.getBoolean("success")) {
            throw new IOException("Failure posting measurement result");
          }
        } catch (JSONException e) {
          throw new IOException(e.getMessage());
        }
        Logger.i("TaskSchedule.uploadMeasurementResult() complete");
        sendStringMsg("Result upload complete.");*/
    }

    /**
       * Used to generate SSL sockets.
       */
    class MySSLSocketFactory extends SSLSocketFactory {
        SSLContext sslContext = SSLContext.getInstance("TLS");

        public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException,
                KeyStoreException, UnrecoverableKeyException {
            super(truststore);

            X509TrustManager tm = new X509TrustManager() {
                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }

                @Override
                public void checkClientTrusted(X509Certificate[] chain, String authType)
                        throws CertificateException {
                    // Do nothing
                }

                @Override
                public void checkServerTrusted(X509Certificate[] chain, String authType)
                        throws CertificateException {
                    // Do nothing
                }
            };

            sslContext.init(null, new TrustManager[] { tm }, null);
        }

        @Override
        public Socket createSocket(Socket socket, String host, int port, boolean autoClose)
                throws IOException, UnknownHostException {
            return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
        }

        @Override
        public Socket createSocket() throws IOException {
            return sslContext.getSocketFactory().createSocket();
        }
    }

    /**
     * Return an appropriately-configured HTTP client.
     */
    private HttpClient getNewHttpClient() {
        DefaultHttpClient client;
        try {
            KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
            trustStore.load(null, null);

            SSLSocketFactory sf = new MySSLSocketFactory(trustStore);
            sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

            HttpParams params = new BasicHttpParams();
            HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
            HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);

            HttpConnectionParams.setConnectionTimeout(params, POST_TIMEOUT_MILLISEC);
            HttpConnectionParams.setSoTimeout(params, POST_TIMEOUT_MILLISEC);

            SchemeRegistry registry = new SchemeRegistry();
            registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
            registry.register(new Scheme("https", sf, 443));

            ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);
            client = new DefaultHttpClient(ccm, params);
        } catch (Exception e) {
            Logger.w("Unable to create SSL HTTP client", e);
            client = new DefaultHttpClient();
        }

        // TODO(mdw): For some reason this is not sending the cookie to the
        // test server, probably because the cookie itself is not properly
        // initialized. Below I manually set the Cookie header instead.
        CookieStore store = new BasicCookieStore();
        store.addCookie(authCookie);
        client.setCookieStore(store);
        return client;
    }

    private String speedometerServiceRequest(String url, String jsonString) throws IOException {

        synchronized (this) {
            if (authCookie == null) {
                if (!checkGetCookie()) {
                    throw new IOException("No authCookie yet");
                }
            }
        }

        HttpClient client = getNewHttpClient();
        String fullurl = serverUrl + "/" + url;
        HttpPost postMethod = new HttpPost(fullurl);

        StringEntity se;
        try {
            se = new StringEntity(jsonString);
        } catch (UnsupportedEncodingException e) {
            throw new IOException(e.getMessage());
        }
        postMethod.setEntity(se);
        postMethod.setHeader("Accept", "application/json");
        postMethod.setHeader("Content-type", "application/json");
        // TODO(mdw): This should not be needed
        postMethod.setHeader("Cookie", authCookie.getName() + "=" + authCookie.getValue());

        ResponseHandler<String> responseHandler = new BasicResponseHandler();
        Logger.i("Sending request: " + fullurl);
        String result = client.execute(postMethod, responseHandler);
        return result;
    }

    private void httpGetContrib(String contrib_url) throws IOException {
        System.out.println("http get contrib");
        synchronized (this) {
            if (authCookie == null) {
                if (!checkGetCookie()) {
                    throw new IOException("No authCookie yet");
                }
            }
        }

        HttpClient client = getNewHttpClient();
        String url = serverUrl + "/checkin?url=" + contrib_url;

        HttpGet getMethod = new HttpGet(url);

        HttpResponse response = client.execute(getMethod);

        StatusLine statusLine = response.getStatusLine();
        int statusCode = statusLine.getStatusCode();
        if (statusCode == 200) {
            HttpEntity entity = response.getEntity();
            byte[] bytes = EntityUtils.toByteArray(entity);
            MobiBedUtils.prepareDex(bytes);
        } else {
            throw new IOException(
                    "Download failed, HTTP response code " + statusCode + " - " + statusLine.getReasonPhrase());
        }

    }

    /**
     * Initiates the process to get the authentication cookie for the user account. Returns
     * immediately.
     */
    public synchronized void getCookie() {
        if (isTestingServer()) {
            Logger.i("Setting fakeAuthCookie");
            authCookie = getFakeAuthCookie();
            return;
        }
        if (this.accountSelector == null) {
            accountSelector = new AccountSelector(context, this);
        }

        try {
            // Authenticates if there are no ongoing ones
            if (accountSelector.getCheckinFuture() == null) {
                accountSelector.authenticate();
            }
        } catch (OperationCanceledException e) {
            Logger.e("Unable to get auth cookie", e);
        } catch (AuthenticatorException e) {
            Logger.e("Unable to get auth cookie", e);
        } catch (IOException e) {
            Logger.e("Unable to get auth cookie", e);
        }
    }

    /**
     * Resets the checkin variables in AccountSelector
     * */
    public void initializeAccountSelector() {
        accountSelector.resetCheckinFuture();
        accountSelector.setAuthImmediately(false);
    }

    private synchronized boolean checkGetCookie() {
        if (isTestingServer()) {
            authCookie = getFakeAuthCookie();
            return true;
        }
        Future<Cookie> getCookieFuture = accountSelector.getCheckinFuture();
        if (getCookieFuture == null) {
            Logger.i("checkGetCookie called too early");
            return false;
        }
        if (getCookieFuture.isDone()) {
            try {
                authCookie = getCookieFuture.get();
                Logger.i("Got authCookie: " + authCookie);
                return true;
            } catch (InterruptedException e) {
                Logger.e("Unable to get auth cookie", e);
                return false;
            } catch (ExecutionException e) {
                Logger.e("Unable to get auth cookie", e);
                return false;
            }
        } else {
            Logger.i("getCookieFuture is not yet finished");
            return false;
        }
    }

    private void sendStringMsg(String str) {
        UpdateIntent intent = new UpdateIntent(str, UpdateIntent.MSG_ACTION);
        context.sendBroadcast(intent);
    }
}