public class Config {

    private static final int DEFAULT_PORT = 4000;

    private HashMap<Class<? extends View>, ModelInfo> convertersHashMap;

    private int port;

    private Config(Builder configBuilder) {
        convertersHashMap = new HashMap<>();
        for (Map.Entry<? extends Class<? extends View>, ? extends ModelInfo> entry : configBuilder.convertersHashMap.entrySet()) {
            convertersHashMap.put(entry.getKey(), entry.getValue());
        }
        port = configBuilder.port;
    }

    public HashMap<Class<? extends View>, ModelInfo> getConvertersHashMap() {
        return convertersHashMap;
    }

    public int getPort() {
        return port;
    }

    public static class Builder {

        private HashMap<Class<? extends View>, ModelInfo> convertersHashMap;

        private int port;

        public Builder() {
            convertersHashMap = new HashMap<>();
            port = DEFAULT_PORT;
        }

        public Builder add(Class<? extends View> clazz, ModelInfo modelInfo) {
            convertersHashMap.put(clazz, modelInfo);
            return this;
        }

        public Builder port(int port) {
            port = port;
            return this;
        }

        public Config build() {
            return new Config(this);
        }
    }
} public class ScaleTypeSerializer implements JsonDeserializer<ImageView.ScaleType>, JsonSerializer<ImageView.ScaleType> {

    @Override
    public ImageView.ScaleType deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        return ImageView.ScaleType.valueOf(json.getAsString());
    }

    @Override
    public JsonElement serialize(ImageView.ScaleType src, Type typeOfSrc, JsonSerializationContext context) {
        return new JsonPrimitive(src.toString());
    }
} public class ConvertersContainer {

    private final HashMap<Class<? extends View>, ViewConverter> viewConverters;

    private final HashMap<Class<? extends ViewGroup.LayoutParams>, LayoutParamsConverter> layoutPramsConverter;

    public ConvertersContainer() {
        viewConverters = new HashMap<>();
        layoutPramsConverter = new HashMap<>();
        initViewConverters();
        initLayoutParamsConverters();
    }

    public void addConverterForView(Class<? extends View> clazz, ModelInfo modelInfo) {
        viewConverters.put(clazz, new DefaultConverter(modelInfo));
    }

    public LayoutParamsModelInfo getLayoutParamsModelInfo(Class<? extends ViewGroup.LayoutParams> clazz) {
        LayoutParamsConverter converter;
        Class clazzKey = clazz;
        do {
            converter = layoutPramsConverter.get(clazzKey);
            if (converter == null) {
                clazzKey = clazzKey.getSuperclass();
                if (clazzKey == null) {
                    throw new IllegalArgumentException("Shouldn't happens");
                }
            }
        } while (converter == null);

        return converter.getLayoutParamsModelInfo();
    }

    public ModelInfo getModelInfoForClass(Class<? extends View> clazz) {
        ViewConverter converter;
        Class clazzKey = clazz;
        do {
            converter = viewConverters.get(clazzKey);
            if (converter == null) {
                clazzKey = clazzKey.getSuperclass();
                if (clazzKey == null) {
                    throw new IllegalArgumentException("Shouldn't happens");
                }
            }
        } while (converter == null);

        return converter.getModelInfo();
    }

    private void initLayoutParamsConverters() {
        SystemLayoutParamsConverter[] systemLayoutParamsConverters = SystemLayoutParamsConverter.values();
        for (SystemLayoutParamsConverter systemLayoutParamsConverter : systemLayoutParamsConverters) {
            layoutPramsConverter.put(systemLayoutParamsConverter.clazz, systemLayoutParamsConverter);
        }
    }

    private void initViewConverters() {
        SystemViewConverter[] systemViewConverters = SystemViewConverter.values();
        for (SystemViewConverter systemViewConverter : systemViewConverters) {
            viewConverters.put(systemViewConverter.clazz, systemViewConverter);
        }
    }
} public class HierarchyViewConverter {

    private HierarchyViewProvider hierarchyViewProvider;
    private Gson gson;
    private ConvertersContainer convertersContainer;
    private FileUtilsProvider fileUtilsProvider;

    public HierarchyViewConverter() {
        hierarchyViewProvider = HierarchyViewer.injector().getHierarchyViewProvider();
        gson = HierarchyViewer.injector().getGson();
        convertersContainer = HierarchyViewer.injector().getConvertersContainer();
        fileUtilsProvider = HierarchyViewer.injector().getFileUtilsProvider();
    }

    public String getHierarchyViewJson() {
        try {
            List<View> list = hierarchyViewProvider.getViewList();
            return toJson(list);
        } catch (Exception e) {
            ThrowableModel throwableModel = new ThrowableModel();
            throwableModel.setDataFromException(e);
            return gson.toJson(throwableModel);
        }
    }

    private String toJson(final List<View> viewList) {
        final HierarchyViewModel hierarchyView = new HierarchyViewModel();
        for (final View view : viewList) {
            ModelInfo modelInfo = convertersContainer.getModelInfoForClass(view.getClass());
            modelInfo.setDataFromView(view, convertersContainer);
            String pathToFile = UUID.randomUUID().toString();
            if (fileUtilsProvider.saveViewInFile(view, pathToFile)) {
                ((ViewModelInfo) modelInfo).setPathToFile(pathToFile);
            }
            hierarchyView.add(modelInfo);
        }
        return gson.toJson(hierarchyView);
    }
} public class DeviceInfoProvider {

    private final static String LOG_TAG = "Hierarchy Viewer";
    private final Context context;
    private ServerInfoProvider serverInfoProvider;
    private Config config;

    public DeviceInfoProvider(Context context) {
        this.context = context;
        serverInfoProvider = HierarchyViewer.injector().getServerInfoProvider();
        config = HierarchyViewer.injector().getConfig();
    }

    private boolean isEmulator() {
        return Build.FINGERPRINT.startsWith("generic");
    }

    public void logServerAddressInfoMessage() {
        int port = config.getPort();

        Log.i(LOG_TAG, "##############################");
        Log.i(LOG_TAG, context.getString(R.string.open_this_webpage) + " " + serverInfoProvider.getIpAddress() + ":" + port);
        if (isEmulator()) {
            Log.i(LOG_TAG, String.format(context.getString(R.string.emulator_info), port, port));
        }
        Log.i(LOG_TAG, "##############################");
    }
} public class FileUtilsProvider {

    private static final String SERVER_CACHE_DIR = Environment.getExternalStorageDirectory().toString() + "/serverCache";

    boolean clearCacheFolder() {
        File dir = new File(SERVER_CACHE_DIR);
        File[] files = dir.listFiles();
        if (files != null) {
            for (File file : files) {
                file.delete();
            }
        }
        return true;
    }

    boolean createCacheFolderIfNotExist() {
        File dir = new File(SERVER_CACHE_DIR);
        return dir.exists() || dir.mkdir();
    }

    File getFile(String linkToFile) {
        return new File(new File(SERVER_CACHE_DIR), "IMG_" + linkToFile + ".png");
    }

    public boolean saveViewInFile(View view, String fileName) {
        view.setDrawingCacheEnabled(true);

        Bitmap cacheBitmap = view.getDrawingCache();

        if (cacheBitmap == null) {
            view.setDrawingCacheEnabled(false);
            return false;
        }
        Bitmap bitmap = Bitmap.createBitmap(cacheBitmap);
        view.setDrawingCacheEnabled(false);
        File imageFile = getFile(fileName);

        try {
            OutputStream fout = new FileOutputStream(imageFile);
            bitmap.compress(Bitmap.CompressFormat.PNG, 90, fout);
            fout.flush();
            fout.close();

        } catch (IOException e) {
            return false;
        }
        return true;
    }
} public class HierarchyViewProvider {

    private final WindowManager windowManager;

    public HierarchyViewProvider(WindowManager windowManager) {
        this.windowManager = windowManager;
    }

    public List<View> getViewList() throws IllegalAccessException, NoSuchFieldException {
        Field windowManagerGlobalField = windowManager.getClass().getDeclaredField("mGlobal");
        windowManagerGlobalField.setAccessible(true);
        Object windowManagerGlobal = windowManagerGlobalField.get(windowManager);

        Field mainViewListField = windowManagerGlobal.getClass().getDeclaredField("mViews");
        mainViewListField.setAccessible(true);

        List<View> viewList;
        Object views = mainViewListField.get(windowManagerGlobal);
        if (views instanceof List) {
            viewList = new ArrayList<>((List<View>) views);
        } else {
            View[] viewsArray = (View[]) views;
            viewList = new ArrayList<>(Arrays.asList(viewsArray));
        }

        sortByWindowType(viewList);

        return viewList;
    }

    private void sortByWindowType(List<View> viewList) {
        Collections.sort(viewList, new Comparator<View>() {
            @Override
            public int compare(View lhs, View rhs) {
                WindowManager.LayoutParams lhsLayoutParams = (WindowManager.LayoutParams) lhs.getLayoutParams();
                WindowManager.LayoutParams rhsLayoutParams = (WindowManager.LayoutParams) rhs.getLayoutParams();
                return Integer.valueOf(lhsLayoutParams.type).compareTo(rhsLayoutParams.type);
            }
        });
    }
} public class NotificationProvider {

    private static final String NOTIFICATION_ACTION_KEY = "NOTIFICATION_ACTION_KEY";
    private static final int NOTIFICATION_TURN_OFF_ACTION = 3000;
    private static final int NOTIFICATION_ID = 1000;
    private static final int NOTIFICATION_TURN_OFF_REQUEST_CODE = 2000;
    private final Context context;
    private NotificationManager notificationManager;
    private ServerInfoProvider serverInfoProvider;
    private Config config;

    public NotificationProvider(Context context) {
        this.context = context;
        notificationManager = HierarchyViewer.injector().getNotificationManager();
        serverInfoProvider = HierarchyViewer.injector().getServerInfoProvider();
        config = HierarchyViewer.injector().getConfig();
    }

    public void cancelServerAddressNotificationWithUrl() {
        notificationManager.cancel(NOTIFICATION_ID);
    }

    public boolean isTurnOffNotificationIntent(Intent intent) {
        return intent.getIntExtra(NOTIFICATION_ACTION_KEY, -1) == NOTIFICATION_TURN_OFF_ACTION;
    }

    public void showServerAddressNotificationWithUrl() {
        Intent turnOffIntent = new Intent(context, HierarchyViewerService.class);
        turnOffIntent.putExtra(NOTIFICATION_ACTION_KEY, NOTIFICATION_TURN_OFF_ACTION);
        PendingIntent turnOffPendingIntent = PendingIntent.getService(context, NOTIFICATION_TURN_OFF_REQUEST_CODE, turnOffIntent, PendingIntent.FLAG_ONE_SHOT);

        final Notification notification = new NotificationCompat.Builder(context)
                .setSmallIcon(R.drawable.ic_stat_hardware_laptop)
                .setContentText(context.getString(R.string.open_this_webpage))
                .setContentTitle("http://" + serverInfoProvider.getIpAddress() + ":" + config.getPort())
                .addAction(android.R.drawable.ic_delete, context.getString(R.string.turn_off), turnOffPendingIntent)
                .setOngoing(true)
                .build();
        notification.flags = notification.flags | Notification.DEFAULT_LIGHTS;
        notificationManager.notify(NOTIFICATION_ID, notification);
    }
} public class ServerInfoProvider {

    private Context context;

    public ServerInfoProvider(Context context) {
        this.context = context;
    }

    String getIpAddress() {
        String ipAddress = getIpAddress(true);
        if (ipAddress == null) {
            ipAddress = getIpAddress(false);
        }
        return ipAddress;
    }

    private String getIpAddress(boolean useIPv4) {
        String result = null;
        try {
            List<NetworkInterface> interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
            for (NetworkInterface intf : interfaces) {
                result = getIpAddress(intf, useIPv4);
                if (result != null) {
                    break;
                }
            }
        } catch (Exception ignored) {
        }

        return result;
    }

    private String getIpAddress(NetworkInterface intf, boolean useIPv4) {
        String result = null;

        List<InetAddress> addrs = Collections.list(intf.getInetAddresses());
        for (InetAddress addr : addrs) {
            if (!addr.isLoopbackAddress()) {
                String sAddr = addr.getHostAddress().toUpperCase();
                boolean isIPv4 = addr instanceof Inet4Address;
                if (useIPv4 && isIPv4) {
                    result = sAddr;
                    break;
                } else if (!useIPv4 && !isIPv4) {
                    result = sAddr;
                    break;
                }
            }
        }

        return result;
    }
} public class WebServer extends NanoHTTPD {

    private static final String
            MIME_PLAINTEXT = "text/plain",
            MIME_HTML = "text/html",
            MIME_JS = "application/javascript",
            MIME_CSS = "text/css",
            MIME_PNG = "image/png",
            MIME_JSON = "application/json",
            MIME_DEFAULT_BINARY = "application/octet-stream",
            MIME_XML = "text/xml",
            API_HIERARCHY = "/api/hierarchy",
            API_SCREEN = "/api/screen/";
    private static final String WEBAPP_PATH = "webapp";
    private final Context context;
    private HierarchyViewConverter hierarchyViewConverter;
    private FileUtilsProvider fileUtilsProvider;
    private ServerInfoProvider serverInfoProvider;

    public WebServer(Context ctx, int port) {
        super(port);
        context = ctx;
        hierarchyViewConverter = HierarchyViewer.injector().getHierarchyViewConverter();
        fileUtilsProvider = HierarchyViewer.injector().getFileUtilsProvider();
        serverInfoProvider = HierarchyViewer.injector().getServerInfoProvider();
    }

    @Override
    public Response serve(IHTTPSession session) {
        String uri = session.getUri();
        System.out.println("SERVER URI " + uri);
        InputStream buffer;
        try {
            if (uri != null) {
                if (uri.equals(API_HIERARCHY)) {
                    fileUtilsProvider.clearCacheFolder();
                    return new NanoHTTPD.Response(OK, MIME_JSON, hierarchyViewConverter.getHierarchyViewJson());
                } else if (uri.startsWith(API_SCREEN)) {
                    String path = uri.replace(API_SCREEN, "");
                    buffer = new FileInputStream(fileUtilsProvider.getFile(path));
                    Response streamResponse = new Response(OK, MIME_PNG, buffer);
                    streamResponse.addHeader("Connection", "Keep-alive");
                    return streamResponse;
                } else if (uri.contains(".js")) {
                    buffer = getFileFromAssets(uri.substring(1));
                    return new NanoHTTPD.Response(OK, MIME_JS, buffer);
                } else if (uri.contains(".css")) {
                    buffer = getFileFromAssets(uri.substring(1));
                    return new NanoHTTPD.Response(OK, MIME_CSS, buffer);
                } else if (uri.contains(".png")) {
                    buffer = getFileFromAssets(uri.substring(1));
                    return new NanoHTTPD.Response(OK, MIME_PNG, buffer);
                } else if (uri.contains(".html")) {
                    buffer = getFileFromAssets(uri.substring(1));
                    return new NanoHTTPD.Response(OK, MIME_HTML, buffer);
                } else {
                    return new NanoHTTPD.Response(OK, MIME_HTML, getFileFromAssets("index.html"));
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public void start() throws IOException {
        super.start();
        fileUtilsProvider.createCacheFolderIfNotExist();
        fileUtilsProvider.clearCacheFolder();
    }

    private InputStream getFileFromAssets(String path) throws IOException {
        return context.getAssets().open(WEBAPP_PATH + "/" + path);
    }
}
import android.app.NotificationManager;
import android.content.Context;
import android.view.WindowManager;
import android.widget.ImageView;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.polidea.hierarchyviewer.Config;
import com.polidea.hierarchyviewer.internal.gson.ScaleTypeSerializer;
import com.polidea.hierarchyviewer.internal.logic.ConvertersContainer;
import com.polidea.hierarchyviewer.internal.logic.HierarchyViewConverter;
import com.polidea.hierarchyviewer.internal.provider.DeviceInfoProvider;
import com.polidea.hierarchyviewer.internal.provider.FileUtilsProvider;
import com.polidea.hierarchyviewer.internal.provider.HierarchyViewProvider;
import com.polidea.hierarchyviewer.internal.provider.NotificationProvider;
import com.polidea.hierarchyviewer.internal.provider.ServerInfoProvider;
import com.polidea.hierarchyviewer.internal.provider.WebServer;
package com.polidea.hierarchyviewer.internal.dependencyinjection;



public class Injector {

    private Context context;
    private Config config;
    private WindowManager windowManager;
    private NotificationManager notificationManager;
    private ServerInfoProvider serverInfoProvider;
    private NotificationProvider notificationProvider;
    private WebServer webServer;