OSDN Git Service

HardwareCollector: send hardware information to GA
authorHugo <hugo@jidemail.com>
Sat, 9 Jul 2016 10:09:49 +0000 (18:09 +0800)
committerChih-Wei Huang <cwhuang@linux.org.tw>
Wed, 10 Aug 2016 09:48:27 +0000 (17:48 +0800)
This app/service will collect informations about device hardware after
boot completed and send these informations to AnalyticsService that will
in turn anonymously send these informations to Google Analytics servers.

HardwareCollector/Android.mk [new file with mode: 0644]
HardwareCollector/AndroidManifest.xml [new file with mode: 0644]
HardwareCollector/res/values/strings.xml [new file with mode: 0644]
HardwareCollector/src/org/android_x86/hardwarecollector/BootCompletedReceiver.java [new file with mode: 0644]
HardwareCollector/src/org/android_x86/hardwarecollector/HardwareCollectorService.java [new file with mode: 0644]

diff --git a/HardwareCollector/Android.mk b/HardwareCollector/Android.mk
new file mode 100644 (file)
index 0000000..1eedd46
--- /dev/null
@@ -0,0 +1,14 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+LOCAL_STATIC_JAVA_LIBRARIES := analytics-utils
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := HardwareCollector
+LOCAL_CERTIFICATE := platform
+LOCAL_PRIVILEGED_MODULE := true
+
+include $(BUILD_PACKAGE)
diff --git a/HardwareCollector/AndroidManifest.xml b/HardwareCollector/AndroidManifest.xml
new file mode 100644 (file)
index 0000000..cf01a73
--- /dev/null
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 Jide Technology Ltd.
+
+     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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="org.android_x86.hardwarecollector">
+
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+    <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
+    <uses-permission android:name="android.permission.RESTART_PACKAGES"/>
+    <application android:label="@string/app_name">
+        <service android:name=".HardwareCollectorService"
+                 android:exported="true"/>
+        <receiver android:name=".BootCompletedReceiver">
+            <intent-filter>
+                <action android:name="android.intent.action.BOOT_COMPLETED" />
+            </intent-filter>
+        </receiver>
+    </application>
+</manifest>
diff --git a/HardwareCollector/res/values/strings.xml b/HardwareCollector/res/values/strings.xml
new file mode 100644 (file)
index 0000000..3041a40
--- /dev/null
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 Jide Technology Ltd.
+
+     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.
+-->
+<resources>
+    <string name="app_name">HardwareCollector</string>
+</resources>
diff --git a/HardwareCollector/src/org/android_x86/hardwarecollector/BootCompletedReceiver.java b/HardwareCollector/src/org/android_x86/hardwarecollector/BootCompletedReceiver.java
new file mode 100644 (file)
index 0000000..b882b01
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2016 Jide Technology Ltd.
+ *
+ * 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 org.android_x86.hardwarecollector;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public class BootCompletedReceiver extends BroadcastReceiver {
+    @Override
+    public void onReceive(Context context, Intent data) {
+        String action = data.getAction();
+        Intent startIntent = new Intent(context, HardwareCollectorService.class);
+        startIntent.setAction(action);
+        context.startService(startIntent);
+    }
+}
diff --git a/HardwareCollector/src/org/android_x86/hardwarecollector/HardwareCollectorService.java b/HardwareCollector/src/org/android_x86/hardwarecollector/HardwareCollectorService.java
new file mode 100644 (file)
index 0000000..4bdc491
--- /dev/null
@@ -0,0 +1,247 @@
+/*
+ * Copyright 2016 Jide Technology Ltd.
+ *
+ * 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 org.android_x86.hardwarecollector;
+
+import org.android_x86.analytics.AnalyticsHelper;
+import org.android_x86.analytics.GeneralLogs;
+
+import android.app.IntentService;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.SurfaceTexture;
+import android.hardware.input.InputManager;
+import android.opengl.EGL14;
+import android.opengl.GLES20;
+import android.util.Log;
+import android.view.InputDevice;
+
+import org.json.JSONObject;
+
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+import javax.microedition.khronos.egl.EGLSurface;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+public class HardwareCollectorService extends IntentService {
+    private static final String TAG = "HardwareCollectorService";
+
+    private static final String GA_CATEGORY = "hardware_info";
+    private static final String GA_ACTION_GPU_RENDERER = "gpu_renderer";
+    private static final String GA_ACTION_CPU_MODEL = "cpu_model";
+    private static final String GA_ACTION_TOUCH_SCREEN_NAME = "touch_screen_name";
+    private static final String GA_ACTION_HAS_BATTERY = "has_battery";
+    private static final String GA_LABEL_HAS_BATTERY = "battery";
+    private static final String GA_LABEL_NO_BATTERY = "no_battery";
+
+    private static final String LAST_INFO_FILE_NAME = "lastInfo.json";
+    private static final String CPU_INFO_FILE = "/proc/cpuinfo";
+    private static final String CPU_INFO_MODEL_NAME_PRE = "model name\t: ";
+    private static final int TOUCHSCREEN_SOURCE_BIT = 4098;
+
+    private Context mContext;
+    private File mInfoFile;
+    private JSONObject mInfoJson;
+
+    public HardwareCollectorService() {
+        super("HardwareCollectorService");
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mContext = getBaseContext();
+        mInfoFile = new File(getApplicationContext().getFilesDir(), LAST_INFO_FILE_NAME);
+    }
+
+    @Override
+    protected void onHandleIntent(Intent intent) {
+        String action = intent.getAction();
+        Log.i(TAG, "handle intent:" + intent);
+        if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
+            uploadHardwareInfo();
+        }
+    }
+
+    private void uploadHardwareInfo() {
+        getLastInfo();
+        collectOpenGLInfo();
+        collectCPUInfo();
+        collectTouchScreenInfo();
+        collectBatteryInfo();
+    }
+
+    private void collectOpenGLInfo() {
+        try {
+            EGL10 egl = (EGL10) EGLContext.getEGL();
+            EGLSurface eglSurface = null;
+            EGLContext eglContext = null;
+
+            // initialize display
+            EGLDisplay eglDisplay = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
+            int[] iparam = new int[2];
+            egl.eglInitialize(eglDisplay, iparam);
+
+            // choose config
+            EGLConfig[] eglConfigs = new EGLConfig[1];
+            final int[] configSpec =
+                    {EGL10.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, EGL10.EGL_NONE};
+            if (egl.eglChooseConfig(eglDisplay, configSpec, eglConfigs, 1, iparam)
+                    && iparam[0] > 0) {
+                // create surface
+                SurfaceTexture surfaceTexture = new SurfaceTexture(0);
+                eglSurface = egl.eglCreateWindowSurface(
+                        eglDisplay, eglConfigs[0], surfaceTexture, null);
+                if (eglSurface != null && eglSurface != EGL10.EGL_NO_SURFACE) {
+                    // create context
+                    final int[] attribList = {EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE};
+                    eglContext = egl.eglCreateContext(
+                            eglDisplay, eglConfigs[0], EGL10.EGL_NO_CONTEXT, attribList);
+                    egl.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);
+                }
+            }
+
+            checkAndSend(GA_ACTION_GPU_RENDERER, GLES20.glGetString(GLES20.GL_RENDERER));
+        } catch (Exception e) {
+            Log.e(TAG, "fail to get GPU renderer", e);
+        }
+    }
+
+    private void collectCPUInfo() {
+        try {
+            BufferedReader reader = new BufferedReader(new FileReader(CPU_INFO_FILE));
+            String cpuInfo;
+            String model = null;
+            while ((cpuInfo = reader.readLine()) != null) {
+                if (cpuInfo.contains(CPU_INFO_MODEL_NAME_PRE)) {
+                    model = cpuInfo.substring(CPU_INFO_MODEL_NAME_PRE.length());
+                    checkAndSend(GA_ACTION_CPU_MODEL, model);
+                    break;
+                }
+            }
+            reader.close();
+        } catch (Exception e) {
+            Log.e(TAG, "fail to get CPU model name", e);
+        }
+    }
+
+    private void collectTouchScreenInfo() {
+        int[] ids = InputManager.getInstance().getInputDeviceIds();
+        for (int id : ids) {
+            InputDevice device = InputManager.getInstance().getInputDevice(id);
+            String name = device.getName();
+            if ((device.getSources() & TOUCHSCREEN_SOURCE_BIT) == TOUCHSCREEN_SOURCE_BIT) {
+                checkAndSend(GA_ACTION_TOUCH_SCREEN_NAME, name);
+                break;
+            }
+        }
+    }
+
+    private void collectBatteryInfo() {
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_BATTERY_CHANGED);
+        Intent batteryIntent = mContext.registerReceiver(null, filter);
+        String label = batteryIntent.getBooleanExtra("present", false) ?
+                GA_LABEL_HAS_BATTERY : GA_LABEL_NO_BATTERY;
+        AnalyticsHelper.CustomEvent customEvent = AnalyticsHelper.newSystemCoreEvent(
+                                    mContext, GA_CATEGORY, GA_ACTION_HAS_BATTERY);
+        customEvent.setLabel(label);
+        customEvent.sendWithSampling();
+    }
+
+    private void getLastInfo() {
+        try {
+            if (mInfoFile.exists()) {
+                String fileString = readFileAsString(mInfoFile.getPath());
+                mInfoJson = new JSONObject(fileString);
+            } else {
+                mInfoJson = new JSONObject();
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "fail to get last info file", e);
+        }
+    }
+
+    private boolean isDifferentInfo(String key, String value) {
+        try {
+            if (mInfoJson.has(key)) {
+                String lastValue = mInfoJson.getString(key);
+                return lastValue == null || !lastValue.equals(value);
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "check info failed", e);
+        }
+        return true;
+    }
+
+    private void checkAndSend(String key, String value) {
+        if (isDifferentInfo(key, value)) {
+            sendToGA(key, value);
+            saveInfo(key, value);
+        }
+    }
+
+    private void sendToGA(String action, String label) {
+        AnalyticsHelper.CustomEvent customEvent =
+                AnalyticsHelper.newSystemCoreEvent(mContext, GA_CATEGORY, action);
+        customEvent.setLabel(label);
+        customEvent.sendWithoutSampling();
+    }
+
+    private void sendToAnalytics(String key, String value) {
+        GeneralLogs logs = new GeneralLogs();
+        logs.set(key, value);
+        AnalyticsHelper.uploadLogToLogServer(mContext, logs);
+    }
+
+    private void saveInfo(String key, String value) {
+        try {
+            mInfoJson.put(key, value);
+            writeStringToFile(mInfoFile.getPath(), mInfoJson.toString());
+        } catch (Exception e) {
+            Log.e(TAG, "write info file failed", e);
+        }
+    }
+
+    public static void writeStringToFile(String filePath, String sets) throws IOException {
+        FileWriter fileWriter = new FileWriter(filePath);
+        PrintWriter out = new PrintWriter(fileWriter);
+        out.write(sets);
+        out.println();
+        fileWriter.close();
+        out.close();
+    }
+
+    private static String readFileAsString(String filePath) throws IOException {
+        StringBuffer fileData = new StringBuffer();
+        BufferedReader reader = new BufferedReader(new FileReader(filePath));
+        char[] buf = new char[1024];
+        int count = 0;
+        while ((count = reader.read(buf)) != -1) {
+            String readData = String.valueOf(buf, 0, count);
+            fileData.append(readData);
+        }
+        reader.close();
+        return fileData.toString();
+    }
+}