com.facebook.stetho.inspector.ChromeDiscoveryHandler.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.stetho.inspector.ChromeDiscoveryHandler.java

Source

/*
 * Copyright (c) 2014-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */

package com.facebook.stetho.inspector;

import javax.annotation.Nullable;

import java.io.IOException;
import java.io.UnsupportedEncodingException;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;

import com.facebook.stetho.common.ProcessUtil;
import com.facebook.stetho.common.Utf8Charset;
import com.facebook.stetho.server.LocalSocketHttpServer;
import com.facebook.stetho.server.SecureHttpRequestHandler;

import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.entity.StringEntity;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpRequestHandlerRegistry;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

/**
 * Provides sufficient responses to convince Chrome's {@code chrome://inspect/devices} that we're
 * "one of them".  Note that we are being discovered automatically by the name of our socket
 * as defined in {@link LocalSocketHttpServer}.  After discovery, we're required to provide
 * some context on how exactly to display and inspect what we have.
 */
public class ChromeDiscoveryHandler extends SecureHttpRequestHandler {
    private static final String PAGE_ID = "1";

    private static final String PATH_PAGE_LIST = "/json";
    private static final String PATH_VERSION = "/json/version";
    private static final String PATH_ACTIVATE = "/json/activate/" + PAGE_ID;

    /**
     * Latest version of the WebKit Inspector UI that we've tested again (ideally).
     */
    private static final String WEBKIT_REV = "@188492";
    private static final String WEBKIT_VERSION = "537.36 (" + WEBKIT_REV + ")";

    private static final String USER_AGENT = "Stetho";

    /**
     * Structured version of the WebKit Inspector protocol that we understand.
     */
    private static final String PROTOCOL_VERSION = "1.1";

    private final Context mContext;
    private final String mInspectorPath;

    @Nullable
    private StringEntity mVersionResponse;
    @Nullable
    private StringEntity mPageListResponse;

    public ChromeDiscoveryHandler(Context context, String inspectorPath) {
        super(context);
        mContext = context;
        mInspectorPath = inspectorPath;
    }

    public void register(HttpRequestHandlerRegistry registry) {
        registry.register(PATH_PAGE_LIST, this);
        registry.register(PATH_VERSION, this);
        registry.register(PATH_ACTIVATE + "*", this);
    }

    @Override
    protected void handleSecured(HttpRequest request, HttpResponse response, HttpContext context)
            throws HttpException, IOException {
        Uri uri = Uri.parse(request.getRequestLine().getUri());
        String path = uri.getPath();
        try {
            if (PATH_VERSION.equals(path)) {
                handleVersion(response);
            } else if (PATH_PAGE_LIST.equals(path)) {
                handlePageList(response);
            } else if (PATH_ACTIVATE.equals(path)) {
                handleActivate(response);
            } else {
                response.setStatusCode(HttpStatus.SC_NOT_IMPLEMENTED);
                response.setReasonPhrase("Not Implemented");
                response.setEntity(new StringEntity("No support for " + uri.getPath()));
            }
        } catch (JSONException e) {
            response.setStatusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR);
            response.setReasonPhrase("Internal Server Error");
            response.setEntity(new StringEntity(e.toString(), Utf8Charset.NAME));
        }
    }

    private void handleVersion(HttpResponse response) throws JSONException, UnsupportedEncodingException {
        if (mVersionResponse == null) {
            JSONObject reply = new JSONObject();
            reply.put("WebKit-Version", WEBKIT_VERSION);
            reply.put("User-Agent", USER_AGENT);
            reply.put("Protocol-Version", PROTOCOL_VERSION);
            reply.put("Browser", getAppLabelAndVersion());
            reply.put("Android-Package", mContext.getPackageName());
            mVersionResponse = createStringEntity("application/json", reply.toString());
        }
        setSuccessfulResponse(response, mVersionResponse);
    }

    private void handlePageList(HttpResponse response) throws JSONException, UnsupportedEncodingException {
        if (mPageListResponse == null) {
            JSONArray reply = new JSONArray();
            JSONObject page = new JSONObject();
            page.put("type", "app");
            page.put("title", makeTitle());
            page.put("id", PAGE_ID);
            page.put("description", "");

            page.put("webSocketDebuggerUrl", "ws://" + mInspectorPath);
            Uri chromeFrontendUrl = new Uri.Builder().scheme("http")
                    .authority("chrome-devtools-frontend.appspot.com").appendEncodedPath("serve_rev")
                    .appendEncodedPath(WEBKIT_REV).appendEncodedPath("devtools.html")
                    .appendQueryParameter("ws", mInspectorPath).build();
            page.put("devtoolsFrontendUrl", chromeFrontendUrl.toString());

            reply.put(page);
            mPageListResponse = createStringEntity("application/json", reply.toString());
        }
        setSuccessfulResponse(response, mPageListResponse);
    }

    private String makeTitle() {
        StringBuilder b = new StringBuilder();
        b.append(getAppLabel());

        b.append(" (powered by Stetho)");

        String processName = ProcessUtil.getProcessName();
        int colonIndex = processName.indexOf(':');
        if (colonIndex >= 0) {
            String nonDefaultProcessName = processName.substring(colonIndex);
            b.append(nonDefaultProcessName);
        }

        return b.toString();
    }

    private void handleActivate(HttpResponse response) throws UnsupportedEncodingException {
        // Arbitrary response seem acceptable :)
        setSuccessfulResponse(response, createStringEntity("text/plain", "Target activation ignored"));
    }

    private static StringEntity createStringEntity(String contentType, String responseJson)
            throws UnsupportedEncodingException {
        StringEntity entity = new StringEntity(responseJson, Utf8Charset.NAME);
        entity.setContentType(contentType);
        return entity;
    }

    private static void setSuccessfulResponse(HttpResponse response, HttpEntity entity) {
        response.setStatusCode(HttpStatus.SC_OK);
        response.setReasonPhrase("OK");
        response.setEntity(entity);
    }

    private String getAppLabelAndVersion() {
        StringBuilder b = new StringBuilder();
        PackageManager pm = mContext.getPackageManager();
        b.append(getAppLabel());
        b.append('/');
        try {
            PackageInfo info = pm.getPackageInfo(mContext.getPackageName(), 0 /* flags */);
            b.append(info.versionName);
        } catch (PackageManager.NameNotFoundException e) {
            throw new RuntimeException(e);
        }
        return b.toString();
    }

    private CharSequence getAppLabel() {
        PackageManager pm = mContext.getPackageManager();
        return pm.getApplicationLabel(mContext.getApplicationInfo());
    }
}