Android-x86
Fork
Faire un don

  • R/O
  • HTTP
  • SSH
  • HTTPS

packages-services-Analytics: Commit

packages/services/Analytics


Commit MetaInfo

Révisionbafb078d9cdec28c16553de479e413229e7bcad2 (tree)
l'heure2016-08-10 18:48:29
AuteurHugo <hugo@jide...>
CommiterChih-Wei Huang

Message de Log

AnalyticsService: send anonymous usage information to GA

This library receive usage informations from system processes and system
applications and send these informations anonymously to Google Analytics
servers.

Change Summary

Modification

--- /dev/null
+++ b/Service/Android.mk
@@ -0,0 +1,24 @@
1+LOCAL_PATH := $(call my-dir)
2+
3+include $(CLEAR_VARS)
4+
5+LOCAL_MODULE_TAGS := optional
6+LOCAL_STATIC_JAVA_LIBRARIES := \
7+ analytics-utils \
8+ googleanalytics \
9+ org.apache.http.legacy \
10+
11+LOCAL_SRC_FILES := \
12+ $(call all-java-files-under, src) \
13+ $(call all-proto-files-under, protos)
14+
15+LOCAL_PROTOC_OPTIMIZE_TYPE := lite
16+LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/protos/
17+
18+LOCAL_PACKAGE_NAME := AnalyticsService
19+LOCAL_CERTIFICATE := platform
20+LOCAL_PRIVILEGED_MODULE := true
21+
22+LOCAL_PROGUARD_FLAG_FILES := proguard.flags
23+
24+include $(BUILD_PACKAGE)
--- /dev/null
+++ b/Service/AndroidManifest.xml
@@ -0,0 +1,48 @@
1+<?xml version="1.0" encoding="utf-8"?>
2+<!-- Copyright (C) 2016 Jide Technology Ltd.
3+
4+ Licensed under the Apache License, Version 2.0 (the "License");
5+ you may not use this file except in compliance with the License.
6+ You may obtain a copy of the License at
7+
8+ http://www.apache.org/licenses/LICENSE-2.0
9+
10+ Unless required by applicable law or agreed to in writing, software
11+ distributed under the License is distributed on an "AS IS" BASIS,
12+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+ See the License for the specific language governing permissions and
14+ limitations under the License.
15+-->
16+
17+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
18+ package="org.android_x86.analytics">
19+
20+ <uses-permission android:name="android.permission.INTERNET" />
21+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
22+ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
23+ <uses-permission android:name="android.permission.BATTERY_STATS" />
24+ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
25+ <uses-permission android:name="android.permission.READ_SETTINGS" />
26+ <uses-permission android:name="android.permission.WRITE_SETTINGS" />
27+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
28+ <uses-permission android:name="android.permission.GET_ACCOUNTS" />
29+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
30+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
31+ <application android:label="@string/app_name"
32+ android:persistent="true">
33+ <service android:name=".AnalyticsService"
34+ android:exported="true" />
35+ <receiver android:name=".BootCompletedReceiver">
36+ <intent-filter>
37+ <action android:name="android.intent.action.BOOT_COMPLETED" />
38+ </intent-filter>
39+ </receiver>
40+ <activity android:name=".UsageStatisticsSettings">
41+ <intent-filter>
42+ <action android:name="android.intent.action.MAIN" />
43+ <action android:name="org.android_x86.analytics.SETUP_USAGE_STATISTICS" />
44+ <category android:name="android.intent.category.DEFAULT" />
45+ </intent-filter>
46+ </activity>
47+ </application>
48+</manifest>
--- /dev/null
+++ b/Service/proguard.flags
@@ -0,0 +1,3 @@
1+
2+-dontwarn org.apache.http.**
3+-dontwarn android.net.http.**
--- /dev/null
+++ b/Service/res/values/analytics.xml
@@ -0,0 +1,24 @@
1+<?xml version="1.0" encoding="utf-8"?>
2+<!-- Copyright (C) 2016 Jide Technology Ltd.
3+
4+ Licensed under the Apache License, Version 2.0 (the "License");
5+ you may not use this file except in compliance with the License.
6+ You may obtain a copy of the License at
7+
8+ http://www.apache.org/licenses/LICENSE-2.0
9+
10+ Unless required by applicable law or agreed to in writing, software
11+ distributed under the License is distributed on an "AS IS" BASIS,
12+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+ See the License for the specific language governing permissions and
14+ limitations under the License.
15+-->
16+<resources>
17+ <!--Replace placeholder ID with your tracking ID-->
18+ <string name="ga_trackingId">UA-10249025-9</string>
19+ <!--Enable automatic activity tracking-->
20+ <bool name="ga_autoActivityTracking">false</bool>
21+
22+ <!--Enable automatic exception tracking-->
23+ <bool name="ga_reportUncaughtExceptions">false</bool>
24+</resources>
--- /dev/null
+++ b/Service/res/values/strings.xml
@@ -0,0 +1,19 @@
1+<?xml version="1.0" encoding="utf-8"?>
2+<!-- Copyright (C) 2016 Jide Technology Ltd.
3+
4+ Licensed under the Apache License, Version 2.0 (the "License");
5+ you may not use this file except in compliance with the License.
6+ You may obtain a copy of the License at
7+
8+ http://www.apache.org/licenses/LICENSE-2.0
9+
10+ Unless required by applicable law or agreed to in writing, software
11+ distributed under the License is distributed on an "AS IS" BASIS,
12+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+ See the License for the specific language governing permissions and
14+ limitations under the License.
15+-->
16+<resources>
17+ <string name="app_name">AnalyticsService</string>
18+ <string name="app_setting_name">AnalyticsService Settings</string>
19+</resources>
--- /dev/null
+++ b/Service/src/org/android_x86/analytics/AnalyticsService.java
@@ -0,0 +1,357 @@
1+/*
2+ * Copyright 2016 Jide Technology Ltd.
3+ *
4+ * Licensed under the Apache License, Version 2.0 (the "License");
5+ * you may not use this file except in compliance with the License.
6+ * You may obtain a copy of the License at
7+ *
8+ * http://www.apache.org/licenses/LICENSE-2.0
9+ *
10+ * Unless required by applicable law or agreed to in writing, software
11+ * distributed under the License is distributed on an "AS IS" BASIS,
12+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+ * See the License for the specific language governing permissions and
14+ * limitations under the License.
15+ */
16+package org.android_x86.analytics;
17+
18+import android.app.AlarmManager;
19+import android.content.BroadcastReceiver;
20+import android.content.ComponentName;
21+import android.content.Context;
22+import android.content.Intent;
23+import android.content.IntentFilter;
24+import android.content.SharedPreferences;
25+import android.net.ConnectivityManager;
26+import android.os.BadParcelableException;
27+import android.os.Bundle;
28+import android.os.SystemClock;
29+import android.os.SystemProperties;
30+import android.util.Log;
31+import org.android_x86.analytics.AnalyticsHelper;
32+import org.android_x86.analytics.GeneralLogs;
33+import org.android_x86.analytics.ImmortalIntentService;
34+
35+import java.util.HashMap;
36+
37+public class AnalyticsService extends ImmortalIntentService {
38+ private static final String TAG = "AnalyticsService";
39+ private static final boolean LOG = false;
40+
41+ public static final String sSharedPreferencesKey = "org.android_x86.analytics.prefs";
42+
43+ private static final int MS_IN_SECOND = 1000;
44+ // ga event
45+ private static final String EVENT_CATEGORY_POWER = "power";
46+
47+ private static final String EVENT_BOOT_COMPLETED = "boot_completed";
48+ private static final String EVENT_SHUTDOWN = "shutdown";
49+ private static final String EVENT_SCREEN_ON = "screen_on";
50+ private static final String EVENT_SCREEN_OFF = "screen_off";
51+ // SharedPreferences_KEY
52+ private static final String SHARED_PREFS_KEY_SCREEN_CHANGE_TIME = "screen_change_time";
53+ private static final String SHARED_PREFS_KEY_LATEST_SEND_TIME = "latest_send_time";
54+ // System property for usage_statistics
55+ private static final String PROPERTY_USAGE_STATISTICS = "persist.sys.usage_statistics";
56+
57+ private boolean mEnable;
58+ private SharedPreferences mSharedPrefs;
59+ private BroadcastReceiver mReceiver;
60+
61+ private final HashMap<String, EventHandler> mStaticEventHandlers =
62+ new HashMap<String, EventHandler>();
63+
64+ private LogHelper mLogHelper;
65+
66+ public AnalyticsService() {
67+ super("AnalyticsService");
68+ initEventHandlers();
69+ }
70+
71+ @Override
72+ public void onCreate() {
73+ super.onCreate();
74+ if (LOG) {
75+ Log.d(TAG, "AnalyticsService onCreate");
76+ }
77+ mEnable = SystemProperties.getBoolean(PROPERTY_USAGE_STATISTICS, true);
78+
79+ mSharedPrefs = getSharedPreferences(sSharedPreferencesKey,
80+ Context.MODE_PRIVATE);
81+
82+ mReceiver = new BroadcastReceiver() {
83+ @Override
84+ public void onReceive(Context context, Intent intent) {
85+ String action = intent.getAction();
86+ if (LOG) {
87+ Log.d(TAG, "Receive Intent: " + Util.toString(intent));
88+ }
89+ if (Intent.ACTION_SCREEN_OFF.equals(action)) {
90+ AnalyticsHelper.screenOff(getBaseContext());
91+ PowerStats.onScreenOff(context);
92+ } else if (Intent.ACTION_SCREEN_ON.equals(action)) {
93+ AnalyticsHelper.screenOn(getBaseContext());
94+ PowerStats.onScreenOn(context);
95+ } else if (Intent.ACTION_SHUTDOWN.equals(action)) {
96+ AnalyticsHelper.onShutdown(getBaseContext());
97+ PowerStats.onScreenOff(context);
98+ } else if (BootCompletedReceiver.ACTION_BOOT_COMPLETED.equals(action)) {
99+ AnalyticsHelper.onBootCompleted(getBaseContext());
100+ PowerStats.onScreenOn(context);
101+ } else if (Intent.ACTION_POWER_CONNECTED.equals(action)) {
102+ PowerStats.onPowerConnected(context);
103+ } else if (Intent.ACTION_POWER_DISCONNECTED.equals(action)) {
104+ PowerStats.onPowerDisconnected(context);
105+ }
106+ }
107+ };
108+
109+ // Register for Intent broadcasts for...
110+ IntentFilter filter = new IntentFilter();
111+ filter.addAction(Intent.ACTION_SCREEN_OFF);
112+ filter.addAction(Intent.ACTION_SCREEN_ON);
113+ filter.addAction(Intent.ACTION_SHUTDOWN);
114+ filter.addAction(BootCompletedReceiver.ACTION_BOOT_COMPLETED);
115+ filter.addAction(BootCompletedReceiver.ACTION_SEND_LOGS);
116+ filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
117+ filter.addAction(Intent.ACTION_POWER_CONNECTED);
118+ filter.addAction(Intent.ACTION_POWER_DISCONNECTED);
119+ getBaseContext().registerReceiver(mReceiver, filter);
120+
121+ mLogHelper = new LogHelper(this);
122+ }
123+
124+ @Override
125+ protected void onHandleIntent(Intent intent) {
126+ String action = intent.getAction();
127+ if (!mEnable){
128+ if (LOG) {
129+ Log.d(TAG, "USAGE STATISTICS not enable");
130+ }
131+ return;
132+ }
133+ EventHandler eventHandler = mStaticEventHandlers.get(action);
134+ if (eventHandler != null){
135+ eventHandler.onEvent(intent);
136+ } else if (!Intent.ACTION_BOOT_COMPLETED.equals(action)){
137+ Log.w(TAG, "unknow action :" + action);
138+ }
139+ if (LOG) {
140+ Log.d(TAG, "Handle Intent: " + Util.toString(intent));
141+ }
142+ }
143+
144+ @Override
145+ public void onDestroy() {
146+ if (LOG){
147+ Log.d(TAG, "onDestroy");
148+ }
149+ super.onDestroy();
150+ }
151+
152+ private void onHitScreen(Intent data) {
153+ String componentName;
154+ try {
155+ componentName = data.getStringExtra(AnalyticsHelper.EXTRA_COMPONENT_NAME);
156+ } catch (BadParcelableException e) {
157+ Log.w(TAG, "ignore BadParcelableException", e);
158+ return;
159+ }
160+ if (componentName == null) {
161+ Log.w(TAG, "onHitScreen, cannot get data");
162+ return;
163+ }
164+ if (LOG) {
165+ Log.v(TAG, "hitScreen:" + componentName);
166+ }
167+ ComponentName component = ComponentName.unflattenFromString(componentName);
168+ if (component == null) {
169+ Log.e(TAG, "onHitScreen, invalid ComponentName: " + componentName);
170+ return;
171+ }
172+
173+ mLogHelper.newAppViewBuilder()
174+ .setActivityDimensions(component)
175+ .send();
176+ }
177+
178+ private static long getCurrentTimeInSeconds() {
179+ return System.currentTimeMillis() / MS_IN_SECOND;
180+ }
181+
182+ private void saveScreenChangeTime(long nowSeconds) {
183+ mSharedPrefs.edit()
184+ .putLong(SHARED_PREFS_KEY_SCREEN_CHANGE_TIME, nowSeconds)
185+ .commit();
186+ }
187+
188+ private void removeScreenChangeTime() {
189+ mSharedPrefs.edit()
190+ .remove(SHARED_PREFS_KEY_SCREEN_CHANGE_TIME)
191+ .commit();
192+ }
193+
194+ private Long getDurationAndSaveScreenChangeTime() {
195+ long nowSeconds = getCurrentTimeInSeconds();
196+ long latestChangeTime = mSharedPrefs.getLong(SHARED_PREFS_KEY_SCREEN_CHANGE_TIME, -1);
197+
198+ saveScreenChangeTime(nowSeconds);
199+ if (latestChangeTime == -1) {
200+ return null;
201+ }
202+ return nowSeconds - latestChangeTime;
203+ }
204+
205+ private void onBootCompleted(Intent data) {
206+ long bootTime = SystemClock.elapsedRealtime() / MS_IN_SECOND;
207+ mLogHelper.newEventBuilder(EVENT_CATEGORY_POWER, EVENT_BOOT_COMPLETED, null, bootTime)
208+ .send();
209+ }
210+
211+ private void onShutdown(Intent data) {
212+ long powerOnIncludeSleep;
213+ long powerOnNotSleep;
214+ try {
215+ powerOnIncludeSleep = data.
216+ getLongExtra(AnalyticsHelper.EXTRA_TIME_INCLUDE_SLEEP, -1);
217+ powerOnNotSleep = data.
218+ getLongExtra(AnalyticsHelper.EXTRA_TIME_NOT_COUNTING_SLEEP, -1);
219+ } catch (BadParcelableException e) {
220+ Log.w(TAG, "ignore BadParcelableException", e);
221+ return;
222+ }
223+ if (powerOnIncludeSleep == -1 || powerOnNotSleep == -1) {
224+ Log.w(TAG, "onShutdown, cannot get data");
225+ return;
226+ }
227+ mLogHelper.newEventBuilder(
228+ EVENT_CATEGORY_POWER, EVENT_SHUTDOWN, null, powerOnIncludeSleep)
229+ .setPower(powerOnNotSleep)
230+ .send();
231+ }
232+
233+ private void onScreenOn(Intent data) {
234+ Long screenOffDuration = getDurationAndSaveScreenChangeTime();
235+
236+ mLogHelper.newEventBuilder(
237+ EVENT_CATEGORY_POWER, EVENT_SCREEN_ON, null, screenOffDuration)
238+ .send();
239+ }
240+
241+ private void onScreenOff(Intent data) {
242+ Long screenOnDuration = getDurationAndSaveScreenChangeTime();
243+ mLogHelper.newEventBuilder(
244+ EVENT_CATEGORY_POWER, EVENT_SCREEN_OFF, null, screenOnDuration)
245+ .send();
246+ }
247+
248+ private static final String MAIN_THREAD = "main";
249+
250+ private void onException(Intent data) {
251+ String exceptionDescription;
252+ String threadName;
253+ String packageName;
254+ try {
255+ exceptionDescription = data.getStringExtra(AnalyticsHelper.EXTRA_EXCEPTION);
256+ threadName = data.getStringExtra(AnalyticsHelper.EXTRA_THREAD_NAME);
257+ packageName = data.getStringExtra(AnalyticsHelper.EXTRA_PACKAGE_NAME);
258+ } catch (BadParcelableException e) {
259+ Log.w(TAG, "ignore BadParcelableException", e);
260+ return;
261+ }
262+ if (exceptionDescription == null) {
263+ Log.e(TAG, "onException, cannot get data");
264+ return;
265+ }
266+
267+ if (threadName != null &&
268+ !threadName.isEmpty() &&
269+ !threadName.equals(MAIN_THREAD)) {
270+ exceptionDescription = "Thread: " + threadName + " " + exceptionDescription;
271+ }
272+
273+ mLogHelper.newExceptionBuilder(exceptionDescription)
274+ .setPackageDimensions(packageName)
275+ .send();
276+ }
277+
278+ private void onCustomEvent(Intent data) {
279+ String event_category;
280+ String event_action;
281+ String event_label;
282+ Long event_value;
283+ String packageName;
284+ boolean hasSampling;
285+ try {
286+ event_category = data.getStringExtra(AnalyticsHelper.EXTRA_EVENT_CATEGORY);
287+ event_action = data.getStringExtra(AnalyticsHelper.EXTRA_EVENT_ACTION);
288+ event_label = data.getStringExtra(AnalyticsHelper.EXTRA_EVENT_LABEL);
289+ event_value = (Long) data.getSerializableExtra(AnalyticsHelper.EXTRA_EVENT_VALUE);
290+ packageName = data.getStringExtra(AnalyticsHelper.EXTRA_PACKAGE_NAME);
291+ hasSampling = data.getBooleanExtra(AnalyticsHelper.EXTRA_HAS_SAMPLING, true);
292+ } catch (BadParcelableException e) {
293+ Log.w(TAG, "ignore BadParcelableException", e);
294+ return;
295+ }
296+ mLogHelper.newEventBuilder(
297+ event_category, event_action, event_label, event_value)
298+ .setPackageDimensions(packageName)
299+ .send();
300+ }
301+
302+ private void initEventHandlers() {
303+ mStaticEventHandlers.put(AnalyticsHelper.ACTION_HIT_SCREEN, new EventHandler() {
304+ @Override
305+ void onEvent(Intent intent) {
306+ onHitScreen(intent);
307+ }
308+ });
309+ mStaticEventHandlers.put(AnalyticsHelper.ACTION_BOOT_COMPLETED, new EventHandler() {
310+ @Override
311+ void onEvent(Intent intent) {
312+ // save boot completed time
313+ saveScreenChangeTime(getCurrentTimeInSeconds());
314+
315+ onBootCompleted(intent);
316+ }
317+ });
318+ mStaticEventHandlers.put(AnalyticsHelper.ACTION_SHUTDOWN, new EventHandler() {
319+ @Override
320+ void onEvent(Intent intent) {
321+ onShutdown(intent);
322+
323+ // Note: add one extra screen off event to calculate correct screen on duration
324+ // time
325+ onScreenOff(null);
326+ removeScreenChangeTime();
327+ }
328+ });
329+ mStaticEventHandlers.put(AnalyticsHelper.ACTION_SCREEN_ON, new EventHandler() {
330+ @Override
331+ void onEvent(Intent intent) {
332+ onScreenOn(intent);
333+ }
334+ });
335+ mStaticEventHandlers.put(AnalyticsHelper.ACTION_SCREEN_OFF, new EventHandler() {
336+ @Override
337+ void onEvent(Intent intent) {
338+ onScreenOff(intent);
339+ }
340+ });
341+ mStaticEventHandlers.put(AnalyticsHelper.ACTION_EXCEPTION, new EventHandler() {
342+ @Override
343+ void onEvent(Intent intent) {
344+ onException(intent);
345+ }
346+ });
347+ mStaticEventHandlers.put(AnalyticsHelper.ACTION_CUSTOM_EVENT, new EventHandler() {
348+ @Override
349+ void onEvent(Intent intent) {
350+ onCustomEvent(intent);
351+ }
352+ });
353+ }
354+ static abstract class EventHandler {
355+ abstract void onEvent(Intent intent);
356+ }
357+}
--- /dev/null
+++ b/Service/src/org/android_x86/analytics/BatteryState.java
@@ -0,0 +1,132 @@
1+/*
2+ * Copyright 2016 Jide Technology Ltd.
3+ *
4+ * Licensed under the Apache License, Version 2.0 (the "License");
5+ * you may not use this file except in compliance with the License.
6+ * You may obtain a copy of the License at
7+ *
8+ * http://www.apache.org/licenses/LICENSE-2.0
9+ *
10+ * Unless required by applicable law or agreed to in writing, software
11+ * distributed under the License is distributed on an "AS IS" BASIS,
12+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+ * See the License for the specific language governing permissions and
14+ * limitations under the License.
15+ */
16+package org.android_x86.analytics;
17+
18+import android.content.Context;
19+import android.content.Intent;
20+import android.content.IntentFilter;
21+import android.os.BatteryManager;
22+
23+public class BatteryState {
24+
25+ private final Intent mIntent;
26+
27+ public BatteryState(Intent batteryChangedIntent) {
28+ mIntent = batteryChangedIntent;
29+ }
30+
31+ /**
32+ * Gets BatteryState or null.
33+ */
34+ public static BatteryState of(Context context) {
35+ Intent intent = context.registerReceiver(null,
36+ new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
37+ if (intent == null) {
38+ return null;
39+ }
40+ return new BatteryState(intent);
41+ }
42+
43+ /**
44+ * Gets BatteryManager.EXTRA_STATUS, return BatteryManager.BATTERY_STATUS_UNKNOWN
45+ * if failed to get.
46+ */
47+ public int getStatus() {
48+ return mIntent.getIntExtra(BatteryManager.EXTRA_STATUS,
49+ BatteryManager.BATTERY_STATUS_UNKNOWN);
50+ }
51+
52+ /**
53+ * Gets BatteryManager.EXTRA_PLUGGED, return 0 if failed to get.
54+ */
55+ public int getPlugged() {
56+ return mIntent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
57+ }
58+
59+ /**
60+ * Gets BatteryManager.EXTRA_LEVEL, return -1 if failed to get.
61+ */
62+ public int getLevel() {
63+ return mIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
64+ }
65+
66+ /**
67+ * Gets BatteryManager.EXTRA_SCALE, return -1 if failed to get.
68+ */
69+ public int getScale() {
70+ return mIntent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
71+ }
72+
73+ /**
74+ * Gets battery percentage or -1 if failed.
75+ */
76+ public float getLevelPercentage() {
77+ int level = getLevel();
78+ int scale = getScale();
79+ if (level < 0 || scale <= 0) {
80+ return -1;
81+ }
82+ return (100.0f * level) / scale;
83+ }
84+
85+ /**
86+ * Whether battery is charging
87+ */
88+ public boolean isCharging() {
89+ return isCharging(getStatus(), getPlugged());
90+ }
91+
92+ /**
93+ * Whether battery is charging
94+ * @param status corresponds to BatteryManager.EXTRA_STATUS
95+ * @param plugged corresponds to BatteryManager.EXTRA_PLUGGED
96+ */
97+ public static boolean isCharging(int status, int plugged) {
98+ // fix bug: it's also charging when status is
99+ // "BATTERY_STATUS_DISCHARGING, BATTERY_PLUGGED_*" after boot machine with plugged
100+ return status == BatteryManager.BATTERY_STATUS_CHARGING ||
101+ status == BatteryManager.BATTERY_STATUS_FULL ||
102+ plugged != 0;
103+ }
104+
105+ private static final int MIN_BATTERY_PERCENTAGE = 5;
106+
107+ /**
108+ * Whether power is sufficient to do some heavy tasks
109+ */
110+ public boolean isPowerSufficient() {
111+ return isPowerSufficient(MIN_BATTERY_PERCENTAGE);
112+ }
113+
114+ /**
115+ * Whether power is sufficient to do some heavy tasks
116+ */
117+ private boolean isPowerSufficient(int minBatteryPercentage) {
118+ return isCharging() || getLevelPercentage() > minBatteryPercentage;
119+ }
120+
121+ /**
122+ * Whether power is sufficient to do some heavy tasks
123+ */
124+ public static boolean isPowerSufficient(Context context) {
125+ BatteryState state = of(context);
126+ if (state == null) {
127+ return false;
128+ }
129+ return state.isPowerSufficient();
130+ }
131+
132+}
--- /dev/null
+++ b/Service/src/org/android_x86/analytics/BootCompletedReceiver.java
@@ -0,0 +1,80 @@
1+/*
2+ * Copyright 2016 Jide Technology Ltd.
3+ *
4+ * Licensed under the Apache License, Version 2.0 (the "License");
5+ * you may not use this file except in compliance with the License.
6+ * You may obtain a copy of the License at
7+ *
8+ * http://www.apache.org/licenses/LICENSE-2.0
9+ *
10+ * Unless required by applicable law or agreed to in writing, software
11+ * distributed under the License is distributed on an "AS IS" BASIS,
12+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+ * See the License for the specific language governing permissions and
14+ * limitations under the License.
15+ */
16+package org.android_x86.analytics;
17+
18+import java.util.Calendar;
19+import java.util.GregorianCalendar;
20+import java.util.Random;
21+
22+import android.app.AlarmManager;
23+import android.app.PendingIntent;
24+import android.content.BroadcastReceiver;
25+import android.content.ComponentName;
26+import android.content.Context;
27+import android.content.Intent;
28+import android.util.Log;
29+import org.android_x86.analytics.AnalyticsHelper;
30+
31+public class BootCompletedReceiver extends BroadcastReceiver {
32+ private static final String TAG = "BootCompletedReceiver";
33+
34+ public static final String ACTION_BOOT_COMPLETED = "org.android_x86.boot_completed";
35+ public static final String ACTION_SEND_LOGS = "org.android_x86.send_logs";
36+
37+ @Override
38+ public void onReceive(Context context, Intent data) {
39+ String action = data.getAction();
40+ Intent startIntent = new Intent(action);
41+ startIntent.setComponent(
42+ new ComponentName(
43+ AnalyticsHelper.TARGET_PACKAGE_NAME,
44+ AnalyticsHelper.TARGET_CLASS_NAME));
45+ if (!Intent.ACTION_BOOT_COMPLETED.equals(action)) {
46+ Log.w(TAG, "unknow action:" + action);
47+ return;
48+ }
49+ context.startService(startIntent);
50+ context.sendBroadcast(new Intent(ACTION_BOOT_COMPLETED));
51+
52+ // Set alarm to send logs periodically
53+ AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
54+ Intent intent = new Intent(ACTION_SEND_LOGS);
55+ am.setInexactRepeating(AlarmManager.RTC, getStartTime(), AlarmManager.INTERVAL_HALF_HOUR,
56+ PendingIntent.getBroadcast(context, 0, intent, 0));
57+ }
58+
59+ // begin and end hour that we enable sending logs
60+ private static final int BEGIN_HOUR = 0;
61+ private static final int END_HOUR = 24;
62+ private static final int DELAY_SECONDS = 10;
63+
64+ /**
65+ * Gets start time in [BEGIN_HOUR, END_HOUR)
66+ */
67+ private static long getStartTime() {
68+ GregorianCalendar calendar = new GregorianCalendar();
69+ int currentHour = calendar.get(Calendar.HOUR_OF_DAY);
70+ if (currentHour >= BEGIN_HOUR || currentHour < END_HOUR) {
71+ // start after DELAY_SECONDS if boot in [BEGIN_HOUR, END_HOUR)
72+ calendar.add(Calendar.SECOND, DELAY_SECONDS);
73+ } else {
74+ // choose time in [BEGIN_HOUR, END_HOUR) randomly
75+ int startHour = BEGIN_HOUR + new Random().nextInt(END_HOUR + 24 - BEGIN_HOUR);
76+ calendar.add(Calendar.HOUR_OF_DAY, startHour - currentHour);
77+ }
78+ return calendar.getTimeInMillis();
79+ }
80+}
--- /dev/null
+++ b/Service/src/org/android_x86/analytics/Fields.java
@@ -0,0 +1,76 @@
1+/*
2+ * Copyright 2016 Jide Technology Ltd.
3+ *
4+ * Licensed under the Apache License, Version 2.0 (the "License");
5+ * you may not use this file except in compliance with the License.
6+ * You may obtain a copy of the License at
7+ *
8+ * http://www.apache.org/licenses/LICENSE-2.0
9+ *
10+ * Unless required by applicable law or agreed to in writing, software
11+ * distributed under the License is distributed on an "AS IS" BASIS,
12+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+ * See the License for the specific language governing permissions and
14+ * limitations under the License.
15+ */
16+package org.android_x86.analytics;
17+
18+class Fields {
19+
20+ enum Metric {
21+ METRIC_POWER_ON_NOT_INCLUDE_SLEEP(1);
22+
23+ private final int value;
24+ Metric(int value) { this.value = value; }
25+ public int getValue() { return value; }
26+ }
27+
28+ enum FieldEnum {
29+ // Google analytics fields, see http://goo.gl/M6dK2U
30+ // Common fields
31+ APP_ID(1),
32+ APP_VERSION(2),
33+ APP_NAME(3),
34+ SCREEN_NAME(4);
35+
36+ private final int value;
37+ FieldEnum(int value) { this.value = value; }
38+ public int getValue() { return value; }
39+ }
40+
41+ // Custom dimension
42+ enum Dimension {
43+ DIMENSION_BUILD_TYPE(1),
44+ DIMENSION_BUILD_FLAVOR(2),
45+ DIMENSION_DEVICE(3),
46+ DIMENSION_MODEL(4),
47+ DIMENSION_BUILD_VERSION(5),
48+ DIMENSION_POWER_TYPE(6),
49+ DIMENSION_INPUT_TYPE(7),
50+ DIMENSION_DISPLAY_TYPE(8),
51+ DIMENSION_TAG(9),
52+ DIMENSION_NETWORK_TYPE(10),
53+ DIMENSION_RESERVED(11),
54+ DIMENSION_RESOLUTION(12),
55+ DIMENSION_DENSITY(13);
56+
57+ private final int value;
58+ Dimension(int value) { this.value = value; }
59+ public int getValue() { return value; }
60+ }
61+
62+ enum Type {
63+ // Use int_key, see FieldEnum
64+ DEFAULT(0),
65+ // Use int_key, correspond to google analytics' custom dimension
66+ CUSTOM_DIMENSION(1),
67+ // Use int_key, correspond to google analytics' custom metric
68+ CUSTOM_METRIC(2),
69+ // Use keys
70+ CUSTOM_GENERAL_LOG(3);
71+
72+ private final int value;
73+ Type(int value) { this.value = value; }
74+ public int getValue() { return value; }
75+ }
76+}
--- /dev/null
+++ b/Service/src/org/android_x86/analytics/ImmortalIntentService.java
@@ -0,0 +1,139 @@
1+/*
2+ * Copyright 2016 Jide Technology Ltd.
3+ *
4+ * Licensed under the Apache License, Version 2.0 (the "License");
5+ * you may not use this file except in compliance with the License.
6+ * You may obtain a copy of the License at
7+ *
8+ * http://www.apache.org/licenses/LICENSE-2.0
9+ *
10+ * Unless required by applicable law or agreed to in writing, software
11+ * distributed under the License is distributed on an "AS IS" BASIS,
12+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+ * See the License for the specific language governing permissions and
14+ * limitations under the License.
15+ */
16+package org.android_x86.analytics;
17+
18+import android.app.Service;
19+import android.content.Intent;
20+import android.os.Handler;
21+import android.os.HandlerThread;
22+import android.os.IBinder;
23+import android.os.Looper;
24+import android.os.Message;
25+
26+/**
27+ * @see android.app.IntentService
28+ * not stopSelf(msg.arg1)
29+ */
30+public abstract class ImmortalIntentService extends Service {
31+ private volatile Looper mServiceLooper;
32+ private volatile ServiceHandler mServiceHandler;
33+ private String mName;
34+ private boolean mRedelivery;
35+
36+ private final class ServiceHandler extends Handler {
37+ public ServiceHandler(Looper looper) {
38+ super(looper);
39+ }
40+
41+ @Override
42+ public void handleMessage(Message msg) {
43+ onHandleIntent((Intent)msg.obj);
44+ }
45+ }
46+
47+ /**
48+ * Creates an IntentService. Invoked by your subclass's constructor.
49+ *
50+ * @param name Used to name the worker thread, important only for debugging.
51+ */
52+ public ImmortalIntentService(String name) {
53+ super();
54+ mName = name;
55+ }
56+
57+ /**
58+ * Sets intent redelivery preferences. Usually called from the constructor
59+ * with your preferred semantics.
60+ *
61+ * <p>If enabled is true,
62+ * {@link #onStartCommand(Intent, int, int)} will return
63+ * {@link Service#START_REDELIVER_INTENT}, so if this process dies before
64+ * {@link #onHandleIntent(Intent)} returns, the process will be restarted
65+ * and the intent redelivered. If multiple Intents have been sent, only
66+ * the most recent one is guaranteed to be redelivered.
67+ *
68+ * <p>If enabled is false (the default),
69+ * {@link #onStartCommand(Intent, int, int)} will return
70+ * {@link Service#START_NOT_STICKY}, and if the process dies, the Intent
71+ * dies along with it.
72+ */
73+ public void setIntentRedelivery(boolean enabled) {
74+ mRedelivery = enabled;
75+ }
76+
77+ @Override
78+ public void onCreate() {
79+ // TODO: It would be nice to have an option to hold a partial wakelock
80+ // during processing, and to have a static startService(Context, Intent)
81+ // method that would launch the service & hand off a wakelock.
82+
83+ super.onCreate();
84+ HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
85+ thread.start();
86+
87+ mServiceLooper = thread.getLooper();
88+ mServiceHandler = new ServiceHandler(mServiceLooper);
89+ }
90+
91+ @Override
92+ public void onStart(Intent intent, int startId) {
93+ Message msg = mServiceHandler.obtainMessage();
94+ msg.arg1 = startId;
95+ msg.obj = intent;
96+ mServiceHandler.sendMessage(msg);
97+ }
98+
99+ /**
100+ * You should not override this method for your IntentService. Instead,
101+ * override {@link #onHandleIntent}, which the system calls when the IntentService
102+ * receives a start request.
103+ * @see android.app.Service#onStartCommand
104+ */
105+ @Override
106+ public int onStartCommand(Intent intent, int flags, int startId) {
107+ onStart(intent, startId);
108+ return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
109+ }
110+
111+ @Override
112+ public void onDestroy() {
113+ mServiceLooper.quit();
114+ }
115+
116+ /**
117+ * Unless you provide binding for your service, you don't need to implement this
118+ * method, because the default implementation returns null.
119+ * @see android.app.Service#onBind
120+ */
121+ @Override
122+ public IBinder onBind(Intent intent) {
123+ return null;
124+ }
125+
126+ /**
127+ * This method is invoked on the worker thread with a request to process.
128+ * Only one Intent is processed at a time, but the processing happens on a
129+ * worker thread that runs independently from other application logic.
130+ * So, if this code takes a long time, it will hold up other requests to
131+ * the same IntentService, but it will not hold up anything else.
132+ * When all requests have been handled, the IntentService stops itself,
133+ * so you should not call {@link #stopSelf}.
134+ *
135+ * @param intent The value passed to {@link
136+ * android.content.Context#startService(Intent)}.
137+ */
138+ protected abstract void onHandleIntent(Intent intent);
139+}
--- /dev/null
+++ b/Service/src/org/android_x86/analytics/LogHelper.java
@@ -0,0 +1,169 @@
1+/*
2+ * Copyright 2016 Jide Technology Ltd.
3+ *
4+ * Licensed under the Apache License, Version 2.0 (the "License");
5+ * you may not use this file except in compliance with the License.
6+ * You may obtain a copy of the License at
7+ *
8+ * http://www.apache.org/licenses/LICENSE-2.0
9+ *
10+ * Unless required by applicable law or agreed to in writing, software
11+ * distributed under the License is distributed on an "AS IS" BASIS,
12+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+ * See the License for the specific language governing permissions and
14+ * limitations under the License.
15+ */
16+package org.android_x86.analytics;
17+
18+import java.util.HashSet;
19+import java.util.Map;
20+import java.util.Set;
21+
22+import android.content.ComponentName;
23+import android.content.Context;
24+import android.os.Build;
25+import android.util.DisplayMetrics;
26+import android.util.Log;
27+
28+import com.google.analytics.tracking.android.EasyTracker;
29+import com.google.analytics.tracking.android.Fields;
30+import com.google.analytics.tracking.android.MapBuilder;
31+import com.google.analytics.tracking.android.Tracker;
32+
33+import org.android_x86.analytics.AnalyticsHelper;
34+import org.android_x86.analytics.GeneralLogs;
35+import org.android_x86.analytics.Fields.Type;
36+import org.android_x86.analytics.Fields.FieldEnum;
37+import org.android_x86.analytics.Fields.Metric;
38+import org.android_x86.analytics.Fields.Dimension;
39+
40+public class LogHelper {
41+ private static final String TAG = "LogHelper";
42+ private static final boolean DEBUG = AnalyticsHelper.DEBUG;
43+
44+ private static final double SAMPLING_RATE = 0; //TODO
45+
46+ private final Context mContext;
47+ private final Tracker mTracker;
48+
49+ /**
50+ * Log to Google analytics
51+ */
52+ public LogHelper(Context context) {
53+ mContext = context;
54+ mTracker = EasyTracker.getInstance(context);
55+ }
56+
57+ public LogBuilder newAppViewBuilder() {
58+ return new LogBuilder(MapBuilder.createAppView());
59+ }
60+
61+ public LogBuilder newEventBuilder(
62+ String category, String action, String label, Long value) {
63+ MapBuilder mapBuilder = MapBuilder.createEvent(category, action, label, value);
64+ LogBuilder builder = new LogBuilder(mapBuilder);
65+ return builder;
66+ }
67+
68+ public LogBuilder newExceptionBuilder(String exceptionDescription) {
69+ return new LogBuilder(MapBuilder.createException(exceptionDescription, true));
70+ }
71+
72+ public class LogBuilder {
73+ private final MapBuilder mBuilder;
74+
75+ // do not check if field redundant!
76+
77+ private LogBuilder(MapBuilder builder) {
78+ mBuilder = builder;
79+ }
80+
81+ /**
82+ * Sets common field
83+ */
84+ private void setCommonField(FieldEnum key, String value) {
85+ if (mBuilder == null) return;
86+ switch (key) {
87+ case APP_ID:
88+ mBuilder.set(Fields.APP_ID, value);
89+ break;
90+ case APP_VERSION:
91+ mBuilder.set(Fields.APP_VERSION, value);
92+ break;
93+ case APP_NAME:
94+ mBuilder.set(Fields.APP_NAME, value);
95+ break;
96+ case SCREEN_NAME:
97+ mBuilder.set(Fields.SCREEN_NAME, value);
98+ break;
99+ default:
100+ throw new UnsupportedOperationException("Invalid field: " + key);
101+ }
102+ }
103+
104+ /**
105+ * Sets Google Analytics field without check
106+ */
107+ private <T> LogBuilder set(Type type, T key, Object value) {
108+ if (mBuilder == null) return this;
109+ switch (type) {
110+ case DEFAULT:
111+ setCommonField((FieldEnum)key, value.toString());
112+ break;
113+ case CUSTOM_DIMENSION:
114+ mBuilder.set(Fields.customDimension(((Dimension)key).getValue()), value.toString());
115+ break;
116+ case CUSTOM_METRIC:
117+ mBuilder.set(Fields.customMetric(((Metric)key).getValue()), value.toString());
118+ break;
119+ default:
120+ throw new UnsupportedOperationException("Invalid Type: " + type + ", key: "
121+ + key + ", value=" + value);
122+ }
123+ return this;
124+ }
125+
126+ public LogBuilder setPower(long powerOnNotSleep) {
127+ set(Type.CUSTOM_METRIC, Metric.METRIC_POWER_ON_NOT_INCLUDE_SLEEP, powerOnNotSleep);
128+ return this;
129+ }
130+
131+ public LogBuilder setPackageDimensions(String packageName) {
132+ // removed a lots of informations!
133+ set(Type.DEFAULT, FieldEnum.APP_NAME, packageName);
134+ return this;
135+ }
136+
137+ public LogBuilder setActivityDimensions(ComponentName component) {
138+ // removed a lots of informations!
139+ set(Type.DEFAULT, FieldEnum.APP_NAME, component.getPackageName());
140+ set(Type.DEFAULT, FieldEnum.SCREEN_NAME, component.getClassName());
141+ return this;
142+ }
143+
144+ public void send() {
145+ // removed a lots of informations!
146+
147+ if (mBuilder != null) {
148+ // Add common fields to MapBuilder
149+ set(Type.CUSTOM_DIMENSION, Dimension.DIMENSION_BUILD_TYPE, Build.TYPE);
150+ set(Type.CUSTOM_DIMENSION, Dimension.DIMENSION_DEVICE, Build.DEVICE);
151+ set(Type.CUSTOM_DIMENSION, Dimension.DIMENSION_MODEL, Build.MODEL);
152+ set(Type.CUSTOM_DIMENSION, Dimension.DIMENSION_BUILD_VERSION, Util.BuildUtil.getBuildVersion());
153+
154+ DisplayMetrics metrics = Util.getDefaultDisplayMetrics(mContext);
155+ float rate = Util.getDefaultDisplayRefreshRate(mContext);
156+ set(Type.CUSTOM_DIMENSION, Dimension.DIMENSION_RESOLUTION,
157+ metrics.widthPixels + " * " + metrics.heightPixels + " "
158+ + Integer.toString((int) rate) + "Hz");
159+ set(Type.CUSTOM_DIMENSION, Dimension.DIMENSION_DENSITY, metrics.densityDpi);
160+
161+ Map<String, String> map = mBuilder.build();
162+ if (DEBUG) {
163+ Log.d(TAG, "Google Analytics log entry: " + map);
164+ }
165+ mTracker.send(map);
166+ }
167+ }
168+ }
169+}
--- /dev/null
+++ b/Service/src/org/android_x86/analytics/PowerStats.java
@@ -0,0 +1,114 @@
1+/*
2+ * Copyright 2016 Jide Technology Ltd.
3+ *
4+ * Licensed under the Apache License, Version 2.0 (the "License");
5+ * you may not use this file except in compliance with the License.
6+ * You may obtain a copy of the License at
7+ *
8+ * http://www.apache.org/licenses/LICENSE-2.0
9+ *
10+ * Unless required by applicable law or agreed to in writing, software
11+ * distributed under the License is distributed on an "AS IS" BASIS,
12+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+ * See the License for the specific language governing permissions and
14+ * limitations under the License.
15+ */
16+package org.android_x86.analytics;
17+
18+import android.content.Context;
19+import android.os.SystemClock;
20+
21+public class PowerStats {
22+ private static final long MIN_STATS_INTERVAL_MILLIS = 15 * 60 * 1000;
23+ private static final long TEN_HOUR_MILLIS = 10 * 60 * 60 * 1000;
24+
25+ private static final String EVENT_CATEGORY_POWER_USAGE = "power_usage";
26+ private static final String ACTION_DISCHARGE_SCREEN_ON = "discharge_screen_on";
27+ private static final String ACTION_DISCHARGE_SCREEN_OFF = "discharge_screen_off";
28+
29+ private final boolean mIsScreenOn;
30+ private final boolean mIsCharging;
31+ private final long mTime;
32+ private final float mPercentage;
33+
34+ private static PowerStats mPowerStats;
35+
36+ private PowerStats(boolean isScreenOn, boolean isCharging, long time, float percentage) {
37+ mIsScreenOn = isScreenOn;
38+ mIsCharging = isCharging;
39+ mTime = time;
40+ mPercentage = percentage;
41+ }
42+
43+ @Override
44+ public String toString() {
45+ return "isScreenOn: " + mIsScreenOn
46+ + " isCharging: " + mIsCharging
47+ + " time: " + mTime
48+ + " percentage: " + mPercentage;
49+ }
50+
51+ public static void onScreenOn(Context context) {
52+ onIntent(context, true, null);
53+ }
54+
55+ public static void onScreenOff(Context context) {
56+ onIntent(context, false, null);
57+ }
58+
59+ public static void onPowerConnected(Context context) {
60+ onIntent(context, null, true);
61+ }
62+
63+ public static void onPowerDisconnected(Context context) {
64+ onIntent(context, null, false);
65+ }
66+
67+ private static void onIntent(Context context, Boolean isScreenOn, Boolean isCharging) {
68+ BatteryState state = BatteryState.of(context);
69+ if (state == null) {
70+ return;
71+ }
72+ float percentage = state.getLevelPercentage();
73+ if (percentage < 0) {
74+ return;
75+ }
76+ new PowerStats(
77+ isScreenOn != null ? isScreenOn : Util.isScreenOn(context),
78+ isCharging != null ? isCharging : state.isCharging(),
79+ SystemClock.elapsedRealtime(),
80+ percentage)
81+ .handle(context);
82+ }
83+
84+ private void handle(Context context) {
85+ PowerStats previous;
86+ synchronized(PowerStats.class) {
87+ previous = mPowerStats;
88+ // only save battery percentage when screen on/off or charging status change.
89+ if (previous != null
90+ && (mIsScreenOn == previous.mIsScreenOn)
91+ && (mIsCharging == previous.mIsCharging)) {
92+ return;
93+ }
94+ mPowerStats = this;
95+ }
96+ if (previous == null) {
97+ return;
98+ }
99+ long interval = mTime - previous.mTime;
100+ float percentageReduce = previous.mPercentage - mPercentage;
101+ if (interval > MIN_STATS_INTERVAL_MILLIS
102+ && percentageReduce >= 0
103+ && !previous.mIsCharging) {
104+ long value = (long) (percentageReduce * TEN_HOUR_MILLIS / interval);
105+ AnalyticsHelper.newSystemCoreEvent(context,
106+ EVENT_CATEGORY_POWER_USAGE,
107+ previous.mIsScreenOn ? ACTION_DISCHARGE_SCREEN_ON
108+ : ACTION_DISCHARGE_SCREEN_OFF)
109+ .setLabel(Long.toString(value))
110+ .setValue(value)
111+ .sendWithSampling();
112+ }
113+ }
114+}
--- /dev/null
+++ b/Service/src/org/android_x86/analytics/Util.java
@@ -0,0 +1,439 @@
1+/*
2+ * Copyright 2016 Jide Technology Ltd.
3+ *
4+ * Licensed under the Apache License, Version 2.0 (the "License");
5+ * you may not use this file except in compliance with the License.
6+ * You may obtain a copy of the License at
7+ *
8+ * http://www.apache.org/licenses/LICENSE-2.0
9+ *
10+ * Unless required by applicable law or agreed to in writing, software
11+ * distributed under the License is distributed on an "AS IS" BASIS,
12+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+ * See the License for the specific language governing permissions and
14+ * limitations under the License.
15+ */
16+package org.android_x86.analytics;
17+
18+import java.io.ByteArrayOutputStream;
19+import java.io.IOException;
20+import java.io.UnsupportedEncodingException;
21+import java.io.InputStream;
22+import java.io.OutputStream;
23+import java.security.MessageDigest;
24+import java.security.NoSuchAlgorithmException;
25+import java.util.Formatter;
26+import java.util.Random;
27+import java.util.Collections;
28+import java.util.ArrayList;
29+import java.util.List;
30+import java.net.MalformedURLException;
31+import java.net.URI;
32+import java.net.URISyntaxException;
33+import java.net.URL;
34+import java.net.HttpURLConnection;
35+
36+import org.apache.http.HttpEntity;
37+import org.apache.http.HttpResponse;
38+import org.apache.http.HttpStatus;
39+import org.apache.http.StatusLine;
40+import org.apache.http.client.HttpClient;
41+import org.apache.http.client.methods.HttpPost;
42+import org.apache.http.impl.client.DefaultHttpClient;
43+import org.apache.http.params.CoreProtocolPNames;
44+
45+import org.json.JSONException;
46+import org.json.JSONObject;
47+
48+import android.os.PowerManager;
49+import android.os.Bundle;
50+import android.os.Build;
51+import android.content.Context;
52+import android.content.Intent;
53+import android.util.DisplayMetrics;
54+import android.view.WindowManager;
55+import android.net.ConnectivityManager;
56+import android.net.NetworkInfo;
57+import android.net.wifi.WifiInfo;
58+import android.net.wifi.WifiManager;
59+import android.provider.Settings.Secure;
60+
61+public class Util {
62+ private Util() {
63+ }
64+
65+ public static DisplayMetrics getDefaultDisplayMetrics(Context context) {
66+ WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
67+ DisplayMetrics metrics = new DisplayMetrics();
68+ wm.getDefaultDisplay().getRealMetrics(metrics);
69+ return metrics;
70+ }
71+
72+ public static float getDefaultDisplayRefreshRate(Context context) {
73+ WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
74+ return wm.getDefaultDisplay().getRefreshRate();
75+ }
76+
77+ /* --- DebugUtil --- */
78+
79+ /**
80+ * Gets Intent's debug string with extras.
81+ */
82+ public static String toString(Intent intent) {
83+ StringBuilder sb = new StringBuilder();
84+ sb.append(intent.getAction());
85+ Bundle extras = intent.getExtras();
86+ if (extras != null && !extras.isEmpty()) {
87+ sb.append(" " + extras);
88+ }
89+ return sb.toString();
90+ }
91+
92+ /* --- PowerUtil --- */
93+
94+ public static boolean isScreenOn(Context context) {
95+ return ((PowerManager) context.getSystemService(Context.POWER_SERVICE)).isScreenOn();
96+ }
97+
98+ /* --- BuildUtil --- */
99+
100+ public static class BuildUtil {
101+
102+ public static final String UNKNOWN = "unknown";
103+
104+ public static final String NIGHTLY = "nightly";
105+ public static final String BETA = "beta";
106+ public static final String STABLE = "stable";
107+
108+ /**
109+ * Gets locale string.
110+ */
111+ public static String getLocale(Context context) {
112+ return context.getResources().getConfiguration().locale.toString();
113+ }
114+
115+ /**
116+ * Gets build flavor
117+ */
118+ public static String getFlavor() {
119+ String version = Build.VERSION.INCREMENTAL;
120+ if (!version.isEmpty()) {
121+ switch (version.charAt(0)) {
122+ case 'N':
123+ return NIGHTLY;
124+ case 'B':
125+ return BETA;
126+ case 'S':
127+ return STABLE;
128+ }
129+ }
130+ return UNKNOWN;
131+ }
132+
133+ /**
134+ * Gets ANDROID_ID or empty string if not present.
135+ */
136+ public static String getAndroidID(Context context) {
137+ String androidId = Secure.getString(context.getContentResolver(), Secure.ANDROID_ID);
138+ return androidId == null ? "" : androidId;
139+ }
140+
141+ /**
142+ * Gets full build version.
143+ */
144+ public static String getBuildVersion() {
145+ if (Build.VERSION.INCREMENTAL.startsWith(Build.VERSION.RELEASE)) {
146+ return Build.VERSION.INCREMENTAL;
147+ }
148+ return Build.VERSION.RELEASE + '-' + Build.VERSION.INCREMENTAL;
149+ }
150+ }
151+
152+ /* --- IOUtil --- */
153+
154+ public static class IOUtil {
155+
156+ public static final String UTF8 = "UTF-8";
157+
158+ private static final int BUFFER_SIZE = 16 * 1024;
159+
160+ /**
161+ * Reads input stream to an output stream and close input stream (not close output stream).
162+ * @param in
163+ * @param out
164+ * @throws IOException
165+ */
166+ public static void toOutputStream(InputStream in, OutputStream out) throws IOException {
167+ try {
168+ byte[] buffer = new byte[BUFFER_SIZE];
169+ int size;
170+ while ((size = in.read(buffer)) != -1) {
171+ out.write(buffer, 0, size);
172+ }
173+ } finally {
174+ in.close();
175+ }
176+ }
177+
178+ /**
179+ * Writes byte array to output stream and close output stream.
180+ * @throws IOException
181+ */
182+ public static void toAndCloseOutputStream(byte[] data, OutputStream out) throws IOException {
183+ try {
184+ out.write(data);
185+ } finally {
186+ out.close();
187+ }
188+ }
189+
190+ /**
191+ * Writes a string to output stream and close output stream.
192+ * @throws IOException
193+ */
194+ public static void toAndCloseOutputStream(String s, OutputStream out) throws IOException {
195+ toAndCloseOutputStream(s.getBytes(UTF8), out);
196+ }
197+
198+ /**
199+ * Reads input stream to a string and close input stream.
200+ * @throws IOException
201+ */
202+ public static String toString(InputStream in) throws IOException {
203+ ByteArrayOutputStream out = new ByteArrayOutputStream();
204+ try {
205+ toOutputStream(in, out);
206+ return out.toString(UTF8);
207+ } finally {
208+ out.close();
209+ }
210+ }
211+ }
212+
213+ /* --- HttpUtil --- */
214+
215+ public static class HttpStatusException extends Exception {
216+ private final int mStatusCode;
217+
218+ public HttpStatusException(int statusCode) {
219+ super();
220+ mStatusCode = statusCode;
221+ }
222+
223+ public HttpStatusException(int statusCode, String message) {
224+ super(message);
225+ mStatusCode = statusCode;
226+ }
227+
228+ @Override
229+ public String getMessage() {
230+ return "StatusCode: " + mStatusCode + " " + super.getMessage();
231+ }
232+
233+ public int getStatusCode() {
234+ return mStatusCode;
235+ }
236+ }
237+
238+ public static class HttpStatusLineException extends HttpStatusException {
239+ private final StatusLine mStatusLine;
240+
241+ public HttpStatusLineException(StatusLine statusLine) {
242+ super(statusLine.getStatusCode());
243+ mStatusLine = statusLine;
244+ }
245+
246+ public HttpStatusLineException(StatusLine statusLine, String message) {
247+ super(statusLine.getStatusCode(), message);
248+ mStatusLine = statusLine;
249+ }
250+
251+ @Override
252+ public String getMessage() {
253+ return "StatusLine: " + mStatusLine + " " + super.getMessage();
254+ }
255+
256+ public StatusLine getStatusLine() {
257+ return mStatusLine;
258+ }
259+ }
260+
261+ /**
262+ * HTTP post to given URL.
263+ */
264+ public static HttpEntity doPost(String url, HttpEntity entity)
265+ throws URISyntaxException, IOException, HttpStatusLineException {
266+ HttpClient client = new DefaultHttpClient();
267+ client.getParams().setParameter(CoreProtocolPNames.USER_AGENT, "android");
268+ HttpPost request = new HttpPost();
269+ request.setURI(new URI(url));
270+ request.setEntity(entity);
271+
272+ HttpResponse response = client.execute(request);
273+ StatusLine statusLine = response.getStatusLine();
274+ if (statusLine.getStatusCode() != HttpStatus.SC_OK) {
275+ throw new HttpStatusLineException(statusLine);
276+ }
277+ return response.getEntity();
278+ }
279+
280+ /**
281+ * Gets JSON from URL
282+ */
283+ public static JSONObject getJsonFromUrl(String url)
284+ throws MalformedURLException, JSONException, IOException {
285+ return new JSONObject(IOUtil.toString(new URL(url).openStream()));
286+ }
287+
288+ /**
289+ * Posts JSON request and get JSON reply.
290+ */
291+ public static JSONObject postAndGetJson(String url, String jsonRequest)
292+ throws MalformedURLException, IOException, HttpStatusException, JSONException {
293+ HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
294+ conn.setDoOutput(true);
295+ conn.setDoInput(true);
296+ conn.setRequestMethod("POST");
297+ conn.setRequestProperty("Content-Type", "application/json; charset=utf8");
298+ conn.setRequestProperty("Accept", "application/json");
299+ IOUtil.toAndCloseOutputStream(jsonRequest, conn.getOutputStream());
300+
301+ int responseCode = conn.getResponseCode();
302+ if (responseCode != HttpURLConnection.HTTP_OK) {
303+ throw new HttpStatusException(responseCode);
304+ }
305+ return new JSONObject(IOUtil.toString(conn.getInputStream()));
306+ }
307+
308+ /**
309+ * Posts JSON request and get JSON reply.
310+ */
311+ public static JSONObject postAndGetJson(String url, JSONObject jsonRequest)
312+ throws MalformedURLException, IOException, HttpStatusException, JSONException {
313+ return postAndGetJson(url, jsonRequest.toString());
314+ }
315+
316+ /* --- NetUtil --- */
317+
318+ public static ConnectivityManager getConnectivityManager(Context context) {
319+ return (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
320+ }
321+
322+ /**
323+ * Whether network is good to send cloud server requests
324+ */
325+ public static boolean isNetworkGood(ConnectivityManager cm) {
326+ NetworkInfo info = cm.getActiveNetworkInfo();
327+ return info != null && info.isConnected();
328+ }
329+
330+ /**
331+ * Whether network is good to send cloud server requests
332+ */
333+ public static boolean isNetworkGood(Context context) {
334+ return isNetworkGood(getConnectivityManager(context));
335+ }
336+
337+ /**
338+ * Gets MAC address or empty string.
339+ */
340+ public static String getMacAddress(Context context) {
341+ WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
342+ WifiInfo wifiInfo = wifiManager.getConnectionInfo();
343+ if (wifiInfo != null) {
344+ String mac = wifiInfo.getMacAddress();
345+ if (mac != null) {
346+ return mac;
347+ }
348+ }
349+ return "";
350+ }
351+
352+ /* --- SecurityUtil --- */
353+
354+ public static class SecurityUtil {
355+
356+ /**
357+ * Converts bytes to hex string.
358+ */
359+ public static String byteToHex(byte[] bytes) {
360+ Formatter formatter = new Formatter();
361+ for (byte b : bytes) {
362+ formatter.format("%02x", b);
363+ }
364+ String result = formatter.toString();
365+ formatter.close();
366+ return result;
367+ }
368+
369+ /**
370+ * Use SHA-1 algorithm to hash given string and return a hex string.
371+ */
372+ public static String sha1(String s)
373+ throws NoSuchAlgorithmException, UnsupportedEncodingException {
374+ return byteToHex(MessageDigest.getInstance("SHA-1").digest(s.getBytes("UTF-8")));
375+ }
376+
377+ }
378+
379+ /* --- SignatureBuilder --- */
380+
381+ public static class SignatureBuilder {
382+ private final List<String> mInfos;
383+
384+ private SignatureBuilder(String... infos) {
385+ mInfos = new ArrayList<String>();
386+ for (String s : infos) {
387+ mInfos.add(s);
388+ }
389+ }
390+
391+ /**
392+ * Gets a random SignatureBuilder
393+ */
394+ public static SignatureBuilder of() {
395+ return new SignatureBuilder(
396+ String.valueOf(System.currentTimeMillis()),
397+ String.valueOf(new Random().nextLong()),
398+ Build.SERIAL);
399+ }
400+
401+ /**
402+ * Gets informations used to generate signature.
403+ */
404+ public List<String> getInfos() {
405+ return mInfos;
406+ }
407+
408+ /**
409+ * Builds signature with secret key and separator.
410+ */
411+ public String build(String key, String separator)
412+ throws NoSuchAlgorithmException, UnsupportedEncodingException {
413+ List<String> list = new ArrayList<String>(mInfos);
414+ list.add(key);
415+ Collections.sort(list);
416+
417+ StringBuilder sb = new StringBuilder();
418+ for (String s : list) {
419+ sb.append(s).append(separator);
420+ }
421+ return SecurityUtil.sha1(sb.toString());
422+ }
423+
424+ /**
425+ * Builds signature with secret key and separator, return comma separated string of source
426+ * informations and signature.
427+ */
428+ public String buildToJoinString(String key, String separator)
429+ throws NoSuchAlgorithmException, UnsupportedEncodingException {
430+ String signature = build(key, separator);
431+ StringBuilder sb = new StringBuilder();
432+ for (String s : mInfos) {
433+ sb.append(s).append(',');
434+ }
435+ sb.append(signature);
436+ return sb.toString();
437+ }
438+ }
439+}
Afficher sur ancien navigateur de dépôt.