summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorgkipnis <gkipnis@Garys-MBP.sjc.cyngn.internal>2016-02-11 14:41:27 -0800
committergkipnis <gkipnis@Garys-MBP.sjc.cyngn.internal>2016-02-11 14:41:27 -0800
commita19a59d45c8d92171aaf31361ddbdef82d6da0b7 (patch)
treee92fac0f57ca033ef05e89d72c3ce22da3b5498c
downloadandroid_packages_providers_DataUsageProvider-a19a59d45c8d92171aaf31361ddbdef82d6da0b7.tar.gz
android_packages_providers_DataUsageProvider-a19a59d45c8d92171aaf31361ddbdef82d6da0b7.tar.bz2
android_packages_providers_DataUsageProvider-a19a59d45c8d92171aaf31361ddbdef82d6da0b7.zip
Initial checkin of the DataUsageProvider
Implements the provider, service and other misc logic for the DataUsage Warning/Disable support that was added to the Settings App.
-rw-r--r--Android.mk28
-rw-r--r--AndroidManifest.xml67
-rwxr-xr-xres/drawable-hdpi/data_usage_48dp.pngbin0 -> 714 bytes
-rwxr-xr-xres/drawable-hdpi/data_usage_disable_24dp.pngbin0 -> 261 bytes
-rwxr-xr-xres/drawable-hdpi/data_usage_hide_24dp.pngbin0 -> 207 bytes
-rw-r--r--res/drawable-hdpi/ic_launcher.pngbin0 -> 9397 bytes
-rw-r--r--res/drawable-ldpi/ic_launcher.pngbin0 -> 2729 bytes
-rwxr-xr-xres/drawable-mdpi/data_usage_48dp.pngbin0 -> 364 bytes
-rwxr-xr-xres/drawable-mdpi/data_usage_disable_24dp.pngbin0 -> 195 bytes
-rwxr-xr-xres/drawable-mdpi/data_usage_hide_24dp.pngbin0 -> 164 bytes
-rw-r--r--res/drawable-mdpi/ic_launcher.pngbin0 -> 5237 bytes
-rwxr-xr-xres/drawable-xhdpi/data_usage_48dp.pngbin0 -> 590 bytes
-rwxr-xr-xres/drawable-xhdpi/data_usage_disable_24dp.pngbin0 -> 289 bytes
-rwxr-xr-xres/drawable-xhdpi/data_usage_hide_24dp.pngbin0 -> 235 bytes
-rw-r--r--res/drawable-xhdpi/ic_launcher.pngbin0 -> 14383 bytes
-rwxr-xr-xres/drawable-xxhdpi/data_usage_48dp.pngbin0 -> 843 bytes
-rwxr-xr-xres/drawable-xxhdpi/data_usage_disable_24dp.pngbin0 -> 388 bytes
-rwxr-xr-xres/drawable-xxhdpi/data_usage_hide_24dp.pngbin0 -> 309 bytes
-rwxr-xr-xres/drawable-xxxhdpi/data_usage_48dp.pngbin0 -> 1044 bytes
-rwxr-xr-xres/drawable-xxxhdpi/data_usage_hide_24dp.pngbin0 -> 377 bytes
-rw-r--r--res/values/colors.xml5
-rw-r--r--res/values/strings.xml26
-rw-r--r--src/org/cyanogenmod/providers/datausage/AppItem.java85
-rw-r--r--src/org/cyanogenmod/providers/datausage/BootReceiver.java32
-rw-r--r--src/org/cyanogenmod/providers/datausage/DataUsageAppInstallReceiver.java49
-rw-r--r--src/org/cyanogenmod/providers/datausage/DataUsageAppInstallService.java96
-rw-r--r--src/org/cyanogenmod/providers/datausage/DataUsageNotificationReceiver.java90
-rw-r--r--src/org/cyanogenmod/providers/datausage/DataUsageProvider.java268
-rw-r--r--src/org/cyanogenmod/providers/datausage/DataUsageService.java569
-rw-r--r--src/org/cyanogenmod/providers/datausage/DataUsageServiceEnableReceiver.java35
-rw-r--r--src/org/cyanogenmod/providers/datausage/DataUsageUtils.java179
-rw-r--r--src/org/cyanogenmod/providers/datausage/UidDetail.java23
-rw-r--r--src/org/cyanogenmod/providers/datausage/UidDetailProvider.java175
33 files changed, 1727 insertions, 0 deletions
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..59edc71
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,28 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+# LOCAL_JAVA_LIBRARIES := bouncycastle conscrypt telephony-common ims-common
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ org.cyanogenmod.platform.sdk \
+ android-support-v4 \
+ android-support-v13 \
+ gson
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+LOCAL_JAVA_LIBRARIES :=
+
+LOCAL_PACKAGE_NAME := DataUsageProvider
+LOCAL_CERTIFICATE := platform
+LOCAL_PRIVILEGED_MODULE := true
+
+# include frameworks/opt/setupwizard/navigationbar/common.mk
+# include frameworks/opt/setupwizard/library/common.mk
+# include frameworks/base/packages/SettingsLib/common.mk
+
+include $(BUILD_PACKAGE)
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
new file mode 100644
index 0000000..40cd622
--- /dev/null
+++ b/AndroidManifest.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="org.cyanogenmod.providers.datausage"
+ android:sharedUserId="android.uid.system"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <uses-sdk android:minSdkVersion="22"/>
+
+ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+ <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" />
+
+ <application android:label="@string/app_name"
+ android:icon="@drawable/ic_launcher">
+
+ <!-- Runs in the phone process since it needs access to the Phone object -->
+ <service android:name=".DataUsageService"
+ android:label="DataUsageService">
+ </service>
+
+ <service android:name=".DataUsageAppInstallService"
+ android:label="DataUsageAppInstallService" >
+ </service>
+
+ <provider android:name=".DataUsageProvider"
+ android:authorities="org.cyanogenmod.providers.datausage"
+ android:readPermission="cyanogenmod.permission.READ_DATAUSAGE"
+ android:writePermission="cyanogenmod.permission.WRITE_DATAUSAGE"
+ android:exported="true">
+ </provider>
+
+ <receiver android:name=".DataUsageServiceEnableReceiver">
+ <intent-filter>
+ <action android:name="org.cyanogenmod.providers.datausage.enable" />
+ </intent-filter>
+ </receiver>
+
+ <receiver android:name=".DataUsageNotificationReceiver">
+ <intent-filter>
+ <action android:name="org.cyanogenmod.providers.datausage.hide_action"/>
+ <action android:name="org.cyanogenmod.providers.datausage.disable_action"/>
+ </intent-filter>
+ </receiver>
+
+ <receiver android:name=".DataUsageAppInstallReceiver"
+ android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.PACKAGE_ADDED" />
+ <action android:name="android.intent.action.PACKAGE_REMOVED" />
+ <action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
+ <action android:name="android.intent.action.PACKAGE_CHANGED" />
+ <action android:name="android.intent.action.PACKAGE_REPLACED" />
+ <data android:scheme="package" />
+ </intent-filter>
+ </receiver>
+
+ <receiver android:name=".BootReceiver" android:enabled="true">
+ <intent-filter>
+ <action android:name="android.intent.action.BOOT_COMPLETED" />
+ </intent-filter>
+ </receiver>
+
+
+ </application>
+</manifest>
diff --git a/res/drawable-hdpi/data_usage_48dp.png b/res/drawable-hdpi/data_usage_48dp.png
new file mode 100755
index 0000000..a888965
--- /dev/null
+++ b/res/drawable-hdpi/data_usage_48dp.png
Binary files differ
diff --git a/res/drawable-hdpi/data_usage_disable_24dp.png b/res/drawable-hdpi/data_usage_disable_24dp.png
new file mode 100755
index 0000000..1fce6de
--- /dev/null
+++ b/res/drawable-hdpi/data_usage_disable_24dp.png
Binary files differ
diff --git a/res/drawable-hdpi/data_usage_hide_24dp.png b/res/drawable-hdpi/data_usage_hide_24dp.png
new file mode 100755
index 0000000..1a9cd75
--- /dev/null
+++ b/res/drawable-hdpi/data_usage_hide_24dp.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_launcher.png b/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
--- /dev/null
+++ b/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/res/drawable-ldpi/ic_launcher.png b/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000..9923872
--- /dev/null
+++ b/res/drawable-ldpi/ic_launcher.png
Binary files differ
diff --git a/res/drawable-mdpi/data_usage_48dp.png b/res/drawable-mdpi/data_usage_48dp.png
new file mode 100755
index 0000000..a43fa3c
--- /dev/null
+++ b/res/drawable-mdpi/data_usage_48dp.png
Binary files differ
diff --git a/res/drawable-mdpi/data_usage_disable_24dp.png b/res/drawable-mdpi/data_usage_disable_24dp.png
new file mode 100755
index 0000000..d9721f5
--- /dev/null
+++ b/res/drawable-mdpi/data_usage_disable_24dp.png
Binary files differ
diff --git a/res/drawable-mdpi/data_usage_hide_24dp.png b/res/drawable-mdpi/data_usage_hide_24dp.png
new file mode 100755
index 0000000..40a1a84
--- /dev/null
+++ b/res/drawable-mdpi/data_usage_hide_24dp.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher.png b/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
--- /dev/null
+++ b/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/res/drawable-xhdpi/data_usage_48dp.png b/res/drawable-xhdpi/data_usage_48dp.png
new file mode 100755
index 0000000..8683a2e
--- /dev/null
+++ b/res/drawable-xhdpi/data_usage_48dp.png
Binary files differ
diff --git a/res/drawable-xhdpi/data_usage_disable_24dp.png b/res/drawable-xhdpi/data_usage_disable_24dp.png
new file mode 100755
index 0000000..c11e596
--- /dev/null
+++ b/res/drawable-xhdpi/data_usage_disable_24dp.png
Binary files differ
diff --git a/res/drawable-xhdpi/data_usage_hide_24dp.png b/res/drawable-xhdpi/data_usage_hide_24dp.png
new file mode 100755
index 0000000..6bc4372
--- /dev/null
+++ b/res/drawable-xhdpi/data_usage_hide_24dp.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_launcher.png b/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/res/drawable-xxhdpi/data_usage_48dp.png b/res/drawable-xxhdpi/data_usage_48dp.png
new file mode 100755
index 0000000..88c2232
--- /dev/null
+++ b/res/drawable-xxhdpi/data_usage_48dp.png
Binary files differ
diff --git a/res/drawable-xxhdpi/data_usage_disable_24dp.png b/res/drawable-xxhdpi/data_usage_disable_24dp.png
new file mode 100755
index 0000000..ce41f10
--- /dev/null
+++ b/res/drawable-xxhdpi/data_usage_disable_24dp.png
Binary files differ
diff --git a/res/drawable-xxhdpi/data_usage_hide_24dp.png b/res/drawable-xxhdpi/data_usage_hide_24dp.png
new file mode 100755
index 0000000..51b4401
--- /dev/null
+++ b/res/drawable-xxhdpi/data_usage_hide_24dp.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/data_usage_48dp.png b/res/drawable-xxxhdpi/data_usage_48dp.png
new file mode 100755
index 0000000..23e6d93
--- /dev/null
+++ b/res/drawable-xxxhdpi/data_usage_48dp.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/data_usage_hide_24dp.png b/res/drawable-xxxhdpi/data_usage_hide_24dp.png
new file mode 100755
index 0000000..df42fee
--- /dev/null
+++ b/res/drawable-xxxhdpi/data_usage_hide_24dp.png
Binary files differ
diff --git a/res/values/colors.xml b/res/values/colors.xml
new file mode 100644
index 0000000..a9011f2
--- /dev/null
+++ b/res/values/colors.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources >
+ <!-- Data usage Notification icon color -->
+ <color name="data_usage_notification_icon_color">#607D8B</color>
+</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
new file mode 100644
index 0000000..2c77dcc
--- /dev/null
+++ b/res/values/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name">Data Usage Provider</string>
+ <string name="data_usage_disable_short">DISABLE</string>
+ <string name="data_usage_disable_long">DISABLE APP\'s MOBILE DATA</string>
+ <string name="data_usage_hide">HIDE</string>
+ <string name="data_usage_disable_message">Disabling Cellular Data Network Access for %s</string>
+ <string name="data_usage_hide_message">Hiding Data Usage Warnings for %s</string>
+ <string name="data_usage_notify_title">Mobile Data Alert</string>
+ <string name="data_usage_notify_big_text">%s is using a lot of mobile data. Tap to view details.</string>
+
+ <!-- Title for a work profile. [CHAR LIMIT=25] -->
+ <string name="managed_user_title">Work profile</string>
+ <!-- Manage apps, individual app screen, substituted for the application's label when the app's label CAN NOT be determined.-->
+ <string name="unknown">Unknown</string>
+ <!-- Title for Guest user [CHAR LIMIT=35] -->
+ <string name="user_guest">Guest</string>
+ <!-- [CHAR LIMIT=NONE] Label of a running process that represents another user -->
+ <string name="running_process_item_user_label">User: <xliff:g id="user_name">%1$s</xliff:g></string>
+ <!-- Label for kernel threads in battery usage -->
+ <string name="process_kernel_label">Android OS</string>
+ <!-- Title of data usage item that represents all uninstalled applications. [CHAR LIMIT=48] -->
+ <string name="data_usage_uninstalled_apps">Removed apps</string>
+ <!-- Title of data usage item that represents all uninstalled applications or removed users. [CHAR LIMIT=48] -->
+ <string name="data_usage_uninstalled_apps_users">Removed apps and users</string>
+</resources>
diff --git a/src/org/cyanogenmod/providers/datausage/AppItem.java b/src/org/cyanogenmod/providers/datausage/AppItem.java
new file mode 100644
index 0000000..a8ed8ca
--- /dev/null
+++ b/src/org/cyanogenmod/providers/datausage/AppItem.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.cyanogenmod.providers.datausage;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.SparseBooleanArray;
+
+public class AppItem implements Comparable<AppItem>, Parcelable {
+ public static final int CATEGORY_USER = 0;
+ public static final int CATEGORY_APP_TITLE = 1;
+ public static final int CATEGORY_APP = 2;
+
+ public final int key;
+ public boolean restricted;
+ public int category;
+
+ public SparseBooleanArray uids = new SparseBooleanArray();
+ public long total;
+
+ public AppItem() {
+ this.key = 0;
+ }
+
+ public AppItem(int key) {
+ this.key = key;
+ }
+
+ public AppItem(Parcel parcel) {
+ key = parcel.readInt();
+ uids = parcel.readSparseBooleanArray();
+ total = parcel.readLong();
+ }
+
+ public void addUid(int uid) {
+ uids.put(uid, true);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(key);
+ dest.writeSparseBooleanArray(uids);
+ dest.writeLong(total);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public int compareTo(AppItem another) {
+ int comparison = Integer.compare(category, another.category);
+ if (comparison == 0) {
+ comparison = Long.compare(another.total, total);
+ }
+ return comparison;
+ }
+
+ public static final Creator<AppItem> CREATOR = new Creator<AppItem>() {
+ @Override
+ public AppItem createFromParcel(Parcel in) {
+ return new AppItem(in);
+ }
+
+ @Override
+ public AppItem[] newArray(int size) {
+ return new AppItem[size];
+ }
+ };
+}
diff --git a/src/org/cyanogenmod/providers/datausage/BootReceiver.java b/src/org/cyanogenmod/providers/datausage/BootReceiver.java
new file mode 100644
index 0000000..75e8e52
--- /dev/null
+++ b/src/org/cyanogenmod/providers/datausage/BootReceiver.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.cyanogenmod.providers.datausage;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+public class BootReceiver extends BroadcastReceiver {
+
+ private static final String TAG = BootReceiver.class.getSimpleName();
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ DataUsageUtils.startDataUsageServiceIfEnabled(context);
+ }
+}
diff --git a/src/org/cyanogenmod/providers/datausage/DataUsageAppInstallReceiver.java b/src/org/cyanogenmod/providers/datausage/DataUsageAppInstallReceiver.java
new file mode 100644
index 0000000..f6de8f7
--- /dev/null
+++ b/src/org/cyanogenmod/providers/datausage/DataUsageAppInstallReceiver.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.cyanogenmod.providers.datausage;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+/**
+ * This class implements the receiver that will handle app installs & uninstalls
+ * when an app is installed, add an entry in the datausage table
+ * when an app is removed, remove the entry from the datausage table
+ */
+
+public class DataUsageAppInstallReceiver extends BroadcastReceiver {
+ private static final String TAG = DataUsageAppInstallReceiver.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ @Override
+ public void onReceive(final Context context, Intent intent) {
+ if (DEBUG) {
+ Log.v(TAG, "AppInstallReceiver: onReceive");
+ }
+
+ Intent appInstallServiceIntent = new Intent(context, DataUsageAppInstallService.class);
+ appInstallServiceIntent.setAction(intent.getAction());
+ if (intent.hasExtra(Intent.EXTRA_UID)) {
+ appInstallServiceIntent.putExtra(
+ Intent.EXTRA_UID,
+ intent.getIntExtra(Intent.EXTRA_UID, 0));
+ }
+ context.startService(appInstallServiceIntent);
+ }
+}
diff --git a/src/org/cyanogenmod/providers/datausage/DataUsageAppInstallService.java b/src/org/cyanogenmod/providers/datausage/DataUsageAppInstallService.java
new file mode 100644
index 0000000..c906b10
--- /dev/null
+++ b/src/org/cyanogenmod/providers/datausage/DataUsageAppInstallService.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.cyanogenmod.providers.datausage;
+
+import android.app.IntentService;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+public class DataUsageAppInstallService extends IntentService {
+ private static final String TAG = DataUsageAppInstallService.class.getSimpleName();
+ private static final boolean DEBUG = true;
+
+ /**
+ * Creates an IntentService. Invoked by your subclass's constructor.
+ *
+ * @param name Used to name the worker thread, important only for debugging.
+ */
+ public DataUsageAppInstallService() {
+ super(TAG);
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ String action = intent.getAction();
+ Context context = this;
+
+ final boolean added;
+ final boolean changed;
+ final boolean removed;
+ if ("ANDROID.INTENT.ACTION.PACKAGE_ADDED".equalsIgnoreCase(action)) {
+ added = true;
+ removed = false;
+ } else if ("ANDROID.INTENT.ACTION.PACKAGE_CHANGED".equalsIgnoreCase(action)) {
+ added = false;
+ removed = false;
+ changed = true;
+ } else if ("ANDROID.INTENT.ACTION.PACKAGE_REPLACED".equalsIgnoreCase(action)) {
+ added = false;
+ removed = false;
+ changed = true;
+ } else if ("ANDROID.INTENT.ACTION.PACKAGE_REMOVED".equalsIgnoreCase(action)) {
+ added = false;
+ removed = true;
+ } else if ("ANDROID.INTENT.ACTION.PACKAGE_FULLY_REMOVED".equalsIgnoreCase(action)) {
+ added = false;
+ removed = true;
+ } else {
+ Log.e(TAG, "Unknown Action:" + action);
+ return;
+ }
+
+ int uid = -1;
+ if (intent.hasExtra(Intent.EXTRA_UID)) {
+ uid = intent.getIntExtra(Intent.EXTRA_UID, 0);
+ }
+
+ if (uid <= 0) {
+ Log.e(TAG, "Invalid UID:" + uid + " for Action:" + action);
+ return;
+ }
+
+ UidDetailProvider uidDetailProvider = new UidDetailProvider(context);
+ UidDetail uidDetail = uidDetailProvider.getUidDetail(uid, true);
+ String label = "";
+ if (uidDetail != null) {
+ label = uidDetail.label.toString();
+ }
+
+ if (added) {
+ if (DEBUG) {
+ Log.v(TAG, "Adding " + label + " to DataUsage DB");
+ }
+ DataUsageUtils.addApp(context, uid, label);
+ } else if (removed) {
+ if (DEBUG) {
+ Log.v(TAG, "Removing " + label + " to DataUsage DB");
+ }
+ DataUsageUtils.removeApp(context, uid);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/org/cyanogenmod/providers/datausage/DataUsageNotificationReceiver.java b/src/org/cyanogenmod/providers/datausage/DataUsageNotificationReceiver.java
new file mode 100644
index 0000000..3a66fc5
--- /dev/null
+++ b/src/org/cyanogenmod/providers/datausage/DataUsageNotificationReceiver.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.cyanogenmod.providers.datausage;
+
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+import android.widget.Toast;
+
+import android.net.NetworkPolicyManager;
+import static android.net.NetworkPolicyManager.POLICY_REJECT_APP_METERED_USAGE;
+
+import cyanogenmod.providers.DataUsageContract;
+
+
+/**
+ * This class implements the receiver that will handle clicks on the buttons
+ * in the Data Usage Notification
+ * Disable - disables the wireless network traffic for the specified uid
+ * Hide - disables data usage checks for the specified uid
+ */
+
+public class DataUsageNotificationReceiver extends BroadcastReceiver {
+ private static final String TAG = DataUsageNotificationReceiver.class.getSimpleName();
+ private static final boolean DEBUG = true;
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ int uid = 0;
+ String title;
+ if (intent.hasExtra(DataUsageService.DATA_USAGE_NOTIFICATION_UID)) {
+ // Settings app uses long, but NetworkPolicyManager uses int
+ // I guess UIDs are limited to 32 bits, so casting should not cause a problem
+ uid = (int)intent.getLongExtra(DataUsageService.DATA_USAGE_NOTIFICATION_UID, 0);
+ }
+ if (intent.hasExtra(DataUsageService.DATA_USAGE_NOTIFICATION_TITLE)) {
+ title = intent.getStringExtra(DataUsageService.DATA_USAGE_NOTIFICATION_TITLE);
+ } else {
+ title = "";
+ }
+
+ if (uid == 0) {
+ Log.e(TAG, "Invalid UID:" + uid + " for Action:" + action);
+ return;
+ }
+
+ if (DataUsageService.HIDE_ACTION.equals(action)) {
+ Toast.makeText(context, context.getString(R.string.data_usage_hide_message, title),
+ Toast
+ .LENGTH_LONG)
+ .show();
+
+ ContentValues values = new ContentValues();
+ values.put(DataUsageContract.ENB, 0);
+ values.put(DataUsageContract.ACTIVE, 0);
+ values.put(DataUsageContract.BYTES, 0);
+
+ DataUsageUtils.enbApp(context, uid, false);
+
+ } else if (DataUsageService.DISABLE_ACTION.equals(action)) {
+ Toast.makeText(context, context.getString(R.string.data_usage_disable_message, title),
+ Toast.LENGTH_LONG).show();
+ NetworkPolicyManager policyManager = NetworkPolicyManager.from(context);
+ policyManager.addUidPolicy(uid, POLICY_REJECT_APP_METERED_USAGE);
+ }
+
+ // cancel the notification
+ NotificationManager notificationManager = (NotificationManager)context.getSystemService
+ (Context.NOTIFICATION_SERVICE);
+ notificationManager.cancel(DataUsageService.DATA_USAGE_SERVICE_NOTIFICATION_ID);
+ }
+}
diff --git a/src/org/cyanogenmod/providers/datausage/DataUsageProvider.java b/src/org/cyanogenmod/providers/datausage/DataUsageProvider.java
new file mode 100644
index 0000000..b038aae
--- /dev/null
+++ b/src/org/cyanogenmod/providers/datausage/DataUsageProvider.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.cyanogenmod.providers.datausage;
+
+import android.content.ContentProvider;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.util.Log;
+
+import cyanogenmod.providers.DataUsageContract;
+
+/**
+ * ContentProvider for the DataUsage statistics gathering of the Settings App
+ * Keeps track of various per App configuration/state variables that are used to determine
+ * if and when to generate an App specific DataUsage warning
+ */
+
+public class DataUsageProvider extends ContentProvider {
+ private static final boolean DEBUG = false;
+ private static final String TAG = DataUsageProvider.class.getSimpleName();
+ private static final String DATABASE_NAME = "datausage.db";
+ private static final int DATABASE_VERSION = 1;
+
+ private DatabaseHelper mOpenHelper;
+
+ // define database matching constants
+ private static final int DATAUSAGE_ALL = 0;
+ private static final int DATAUSAGE_ID = 1;
+ private static final int DATAUSAGE_UID = 2;
+
+ // build a URI matcher - add routes to it (if any)
+ private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+ static {
+ sURIMatcher.addURI(DataUsageContract.DATAUSAGE_AUTHORITY,
+ DataUsageContract.DATAUSAGE_TABLE, DATAUSAGE_ALL);
+ sURIMatcher.addURI(DataUsageContract.DATAUSAGE_AUTHORITY,
+ DataUsageContract.DATAUSAGE_TABLE + "/#", DATAUSAGE_ID);
+ sURIMatcher.addURI(DataUsageContract.DATAUSAGE_AUTHORITY,
+ DataUsageContract.DATAUSAGE_TABLE + "/uid/*", DATAUSAGE_UID);
+ }
+
+ // Database Helper Class
+ private static class DatabaseHelper extends SQLiteOpenHelper {
+ private Context mContext;
+
+ public DatabaseHelper (Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ mContext = context;
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ // setup database schema
+ db.execSQL(
+ "CREATE TABLE " + DataUsageContract.DATAUSAGE_TABLE +
+ "(" + DataUsageContract._ID + " INTEGER PRIMARY KEY, " +
+ DataUsageContract.UID + " INTEGER, " +
+ DataUsageContract.ENB + " INTEGER DEFAULT 0, " +
+ DataUsageContract.ACTIVE + " INTEGER DEFAULT 0, " +
+ DataUsageContract.LABEL + " STRING, " +
+ DataUsageContract.BYTES + " INTEGER DEFAULT 0, " +
+ DataUsageContract.SLOW_AVG + " INTEGER DEFAULT 0, " +
+ DataUsageContract.SLOW_SAMPLES + " INTEGER DEFAULT 0, " +
+ DataUsageContract.FAST_AVG + " INTEGER DEFAULT 0, " +
+ DataUsageContract.FAST_SAMPLES + " INTEGER DEFAULT 0, " +
+ DataUsageContract.EXTRA + " STRING );"
+ );
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ return;
+ }
+ }
+
+
+ @Override
+ public boolean onCreate() {
+ mOpenHelper = new DatabaseHelper(getContext());
+ return true;
+ }
+
+ @Override
+ public Cursor query(
+ Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder
+ ) {
+ SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+ qb.setTables(DataUsageContract.DATAUSAGE_TABLE);
+
+ int match = sURIMatcher.match(uri);
+
+ if (DEBUG) {
+ Log.v(TAG, "Query uri=" + uri + ", match=" + match);
+ }
+
+ switch (match) {
+ case DATAUSAGE_ALL:
+ break;
+
+ case DATAUSAGE_ID:
+ break;
+
+ case DATAUSAGE_UID:
+ break;
+
+ default:
+ Log.e(TAG, "query: invalid request: " + uri);
+ return null;
+ }
+
+ Cursor cursor;
+ SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+ cursor = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder);
+
+ return cursor;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ int match = sURIMatcher.match(uri);
+
+ switch(match) {
+ case DATAUSAGE_ALL:
+ return "vnd.android.cursor.dir/datausage_entry";
+ case DATAUSAGE_ID:
+ case DATAUSAGE_UID:
+ return "vnd.android.cursor.item/datausage_entry";
+ default:
+ throw new IllegalArgumentException("UNKNOWN URI: " + uri);
+ }
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ int match = sURIMatcher.match(uri);
+ if (DEBUG) {
+ Log.v(TAG, "Insert uri=" + uri + ", match=" + match);
+ }
+
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ long rowID = db.insert(DataUsageContract.DATAUSAGE_TABLE, null, values);
+
+ if (DEBUG) {
+ Log.v(TAG, "inserted " + values + " rowID=" + rowID);
+ }
+
+ return ContentUris.withAppendedId(DataUsageContract.CONTENT_URI, rowID);
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ int match = sURIMatcher.match(uri);
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+
+ if (DEBUG) {
+ Log.v(TAG, "Delete uri=" + uri + ", match=" + match);
+ }
+
+ switch(match) {
+ case DATAUSAGE_ALL:
+ break;
+ case DATAUSAGE_UID:
+ if (selection != null || selectionArgs != null) {
+ throw new UnsupportedOperationException(
+ "Cannot delete URI:" + uri + " with a select clause"
+ );
+ }
+ String uidNumber = uri.getLastPathSegment();
+ selection = DataUsageContract.UID + " = ? ";
+ selectionArgs = new String [] {uidNumber};
+ break;
+ default:
+ throw new UnsupportedOperationException(
+ "Cannot delete URI:" + uri
+ );
+ }
+ int count = db.delete(DataUsageContract.DATAUSAGE_TABLE, selection, selectionArgs);
+ return count;
+ }
+
+ // update is always done by UID
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ long count = 0;
+ int match = sURIMatcher.match(uri);
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ String uid;
+
+ if (DEBUG) {
+ Log.v(TAG, "Update uri=" + uri + ", match=" + match);
+ }
+
+ switch(match) {
+ case DATAUSAGE_ALL:
+ uid = selectionArgs[0];
+ break;
+ case DATAUSAGE_UID:
+ if (selection != null || selectionArgs != null) {
+ throw new UnsupportedOperationException(
+ "Cannot update URI " + uri + " with a select clause"
+ );
+ }
+ selection = DataUsageContract.UID + " = ? ";
+ uid = uri.getLastPathSegment();
+ selectionArgs = new String [] { uid };
+ break;
+ default:
+ throw new UnsupportedOperationException("Cannot update that URI: " + uri);
+
+ }
+
+ // if no record is found, then perform an insert, so make the db transaction atomic
+ if (DEBUG) {
+ Log.v(TAG, "Update: Values:" + values.toString() + " selection:" + selection + " " +
+ " selectionArgs:" + selectionArgs[0]);
+ }
+ // count = db.update(DATAUSAGE_TABLE, values, selection, selectionArgs);
+
+ db.beginTransaction();
+ try {
+ count = db.update(DataUsageContract.DATAUSAGE_TABLE, values, selection, selectionArgs);
+
+ if (DEBUG) {
+ Log.v(TAG, "Update count:" + count);
+ }
+ if (count == 0) {
+ if (DEBUG) {
+ Log.v(TAG, "Count==0, Performing Insert");
+ }
+ values.put(DataUsageContract.UID, uid);
+ count = db.insert(DataUsageContract.DATAUSAGE_TABLE, null, values);
+ }
+ db.setTransactionSuccessful();
+ } finally {
+ if (DEBUG) {
+ Log.v(TAG, "dbEndTransaction");
+ }
+ db.endTransaction();
+ }
+ if (DEBUG) {
+ Log.v(TAG, "Update result for uri=" + uri + " count=" + count);
+ }
+ return (int)count;
+ }
+}
diff --git a/src/org/cyanogenmod/providers/datausage/DataUsageService.java b/src/org/cyanogenmod/providers/datausage/DataUsageService.java
new file mode 100644
index 0000000..ce328cc
--- /dev/null
+++ b/src/org/cyanogenmod/providers/datausage/DataUsageService.java
@@ -0,0 +1,569 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.cyanogenmod.providers.datausage;
+
+import android.app.ActivityManager;
+import android.app.IntentService;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.TaskStackBuilder;
+import android.content.ComponentName;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import android.net.NetworkTemplate;
+import static android.net.NetworkTemplate.buildTemplateMobileAll;
+import android.net.INetworkStatsService;
+import android.net.INetworkStatsSession;
+import static android.net.TrafficStats.UID_REMOVED;
+
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.net.NetworkStats;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.content.pm.UserInfo;
+
+import android.util.SparseArray;
+import com.google.gson.Gson;
+
+import cyanogenmod.providers.DataUsageContract;
+
+
+/**
+ * IntentService, launched by the AlarmManager at Boot Time from (BootReceiver) used
+ * to collect per app cellular usage networking statistics and generate warning messages
+ * to the user when an App consumes too much BW, giving the user an option to disable
+ * Warning Message generation or to disable Network Access for the offending App
+ */
+
+public class DataUsageService extends IntentService {
+ private final static String TAG = DataUsageService.class.getSimpleName();
+ private final static String TAB_MOBILE = "mobile";
+ private Context mContext;
+ private final static boolean DEBUG = true;
+
+ // Service worker tasks will run on the background thread, create a Handler to
+ // communicate with UI Thread, if needed
+ private Handler mUiHandler = new Handler();
+
+ private INetworkStatsService mStatsService;
+ private INetworkStatsSession mStatsSession;
+ private NetworkTemplate mTemplate;
+ private SubscriptionManager mSubscriptionManager;
+ private List<SubscriptionInfo> mSubInfoList;
+ private Map<Integer,String> mMobileTagMap;
+ private UserManager mUserManager;
+ private List<UserHandle> mProfiles;
+ private long mLargest;
+ private int mCurrentUserId;
+ private UidDetailProvider mUidDetailProvider;
+ SparseArray<AppItem> mKnownItems;
+ private NotificationManager mNotificationManager;
+
+ // quick way to generate warnings
+ // TODO - set to false before releasing
+ private static final boolean FAST_MODE = false;
+
+ // specifies minimum number of samples to collect before running algorithm
+ // 1 hours worth of active traffic to establish a baseline
+ private static final int MIN_SLOW_SAMPLE_COUNT = FAST_MODE ? 5 : 60;
+ // 5 min worth of active traffic
+ private static final int MIN_FAST_SAMPLE_COUNT = FAST_MODE ? 1 : 5;
+
+ // specifies percentage by which fast average must exceed slow avg to trigger a warning
+ // one standard deviation - or should it be 34%, since we are only looking at above and not
+ // below. And how many standard deviations should it be?
+ private static final int WARNING_PERCENTAGE = FAST_MODE ? 10 : 68;
+
+ // specifies the number of samples to keep in the database for postprocessing and
+ // algorithm evaluation
+ private final static int MAX_EXTRA_SAMPLE_COUNT = 1000;
+
+ // specifies maximum bw that is still considered as idle - to discard pings, etc...
+ private static final long MAX_IDLE_BW = 5 * 1024;
+ // specifies the sample period in msec
+ public static final long SAMPLE_PERIOD = 60000;
+ public static final long START_DELAY = 60000;
+
+ // notification ID to use by the DataUsageService for updates to notifications
+ public static final int DATA_USAGE_SERVICE_NOTIFICATION_ID = 102030;
+
+ public static final String HIDE_ACTION =
+ "org.cyanogenmod.providers.datausage.hide_action";
+ public static final String DISABLE_ACTION =
+ "org.cyanogenmod.providers.datausage.disable_action";
+ public static final int DATA_USAGE_BROADCAST_REQUEST_CODE = 0x102040; // TODO - ???
+ public static final String DATA_USAGE_NOTIFICATION_UID =
+ "org.cyanogenmod.providers.datausage.notification_uid";
+ public static final String DATA_USAGE_NOTIFICATION_TITLE =
+ "org.cyanogenmod.providers.datausage.notification_title";
+
+ public DataUsageService() {
+ super(TAG);
+ }
+
+ @android.support.annotation.Nullable
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ }
+
+ /**
+ * When periodic alarm is generated, via AlarmManager, the Intent is delivered here
+ * @param intent
+ */
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ mContext = this;
+
+ // initialize various networking managers/interfaces/sessions/etc...
+ mStatsService = INetworkStatsService.Stub.asInterface(
+ ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
+ mStatsSession = null;
+ try {
+ mStatsSession = mStatsService.openSession();
+ mSubscriptionManager = SubscriptionManager.from(mContext);
+ mSubInfoList = mSubscriptionManager.getActiveSubscriptionInfoList();
+ mMobileTagMap = initMobileTabTag(mSubInfoList);
+ mTemplate = buildTemplateMobileAll(
+ getActiveSubscriberId(mContext, getSubId(TAB_MOBILE + "1")));
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException: " + e.getMessage());
+ }
+
+ mUserManager = (UserManager)mContext.getSystemService(Context.USER_SERVICE);
+ mProfiles = mUserManager.getUserProfiles();
+ mCurrentUserId = ActivityManager.getCurrentUser();
+ mUidDetailProvider = new UidDetailProvider(mContext);
+ mKnownItems = new SparseArray<AppItem>();
+ mNotificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
+
+ // run the actual dataUsage collection and processing
+ dataUsageUpdate();
+ }
+
+ private static String getActiveSubscriberId(Context context, int subId) {
+ final TelephonyManager tele = TelephonyManager.from(context);
+ String retVal = tele.getSubscriberId(subId);
+ return retVal;
+ }
+
+
+ private int getSubId(String currentTab) {
+ if (mMobileTagMap != null) {
+ Set<Integer> set = mMobileTagMap.keySet();
+ for (Integer subId : set) {
+ if (mMobileTagMap.get(subId).equals(currentTab)) {
+ return subId;
+ }
+ }
+ }
+ return -1;
+ }
+
+ private Map<Integer, String> initMobileTabTag(List<SubscriptionInfo> subInfoList) {
+ Map<Integer, String> map = null;
+ if (subInfoList != null) {
+ String mobileTag;
+ map = new HashMap<Integer, String>();
+ for (SubscriptionInfo subInfo : subInfoList) {
+ mobileTag = TAB_MOBILE + String.valueOf(subInfo.getSubscriptionId());
+ map.put(subInfo.getSubscriptionId(), mobileTag);
+ }
+ }
+ return map;
+ }
+
+ /**
+ * Accumulate data usage of a network stats entry for the item mapped by the collapse key.
+ * Creates the item, if needed
+ *
+ */
+ private void accumulate(int collapseKey, NetworkStats.Entry entry, int itemCategory) {
+ int uid = entry.uid;
+ AppItem item = mKnownItems.get(collapseKey);
+ if (item == null) {
+ item = new AppItem(collapseKey);
+ item.category = itemCategory;
+ mKnownItems.put(item.key, item);
+ }
+ item.addUid(uid);
+ item.total += entry.rxBytes + entry.txBytes;
+ if (mLargest < item.total) {
+ mLargest = item.total;
+ }
+
+ }
+
+
+
+ private void clearStats() {
+ for(int i = 0; i < mKnownItems.size(); i++) {
+ int key = mKnownItems.keyAt(i);
+ AppItem appItem = mKnownItems.get(key);
+ appItem.total = 0;
+ }
+ }
+
+ private class DataUsageExtraInfo {
+ ArrayList<Long> samples;
+ }
+ private String mAppWarnExtra;
+
+ private void dataUsageUpdate() {
+ long startTime = 0;
+ long endTime = System.currentTimeMillis() * 2;
+ mLargest = 0;
+
+ clearStats();
+
+ NetworkStats networkStats = null;
+ try {
+ if (mStatsSession != null) {
+ networkStats = mStatsSession.getSummaryForAllUid(mTemplate, startTime, endTime,
+ false);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException: " + e.getMessage());
+ }
+
+
+ // collect network stats for all app consuming bw
+ if (networkStats != null) {
+ int size = networkStats.size();
+ NetworkStats.Entry entry = null;
+
+ for(int i = 0; i < size; i++) {
+ entry = networkStats.getValues(i, entry);
+ int collapseKey;
+ int category;
+ int uid = entry.uid;
+ int userId = UserHandle.getUserId(uid);
+ if (UserHandle.isApp(uid)) {
+ if (mProfiles.contains(new UserHandle(userId))) {
+ if (userId != mCurrentUserId) {
+ // add to a managed user item
+ int managedKey = UidDetailProvider.buildKeyForUser(userId);
+ accumulate(managedKey, entry, AppItem.CATEGORY_USER);
+ }
+ collapseKey = uid;
+ category = AppItem.CATEGORY_APP;
+ } else {
+ // if it is a removed user, add it to the removed users' key
+ UserInfo userInfo = mUserManager.getUserInfo(userId);
+ if (userInfo == null) {
+ collapseKey = UID_REMOVED;
+ category = AppItem.CATEGORY_APP;
+ } else {
+ collapseKey = UidDetailProvider.buildKeyForUser(userId);
+ category = AppItem.CATEGORY_USER;
+ }
+ }
+ accumulate(collapseKey, entry, category);
+ }
+ }
+ }
+ boolean appWarnActive = false;
+ long appWarnBytes = 0;
+ long appWarnUid;
+ int appWarnSlowSamples;
+ int appWarnFastSamples;
+ long appWarnSlowAvg;
+ long appWarnFastAvg;
+ String appWarnExtra = "";
+
+ // lookup Apps in the DB that have warning enabled
+ Cursor cursor = getContentResolver().query(
+ DataUsageContract.CONTENT_URI,
+ null, // projection - return all
+ DataUsageContract.ENB + " = ? ",
+ new String [] { "1" },
+ null
+ );
+ if (cursor == null) {
+ return;
+ }
+
+ while(cursor.moveToNext()) {
+ appWarnUid = cursor.getInt(DataUsageContract.COLUMN_OF_UID);
+ appWarnActive = cursor.getInt(DataUsageContract.COLUMN_OF_ACTIVE) > 0;
+ appWarnBytes = cursor.getLong(DataUsageContract.COLUMN_OF_BYTES);
+ appWarnSlowSamples = cursor.getInt(DataUsageContract.COLUMN_OF_SLOW_SAMPLES);
+ appWarnSlowAvg = cursor.getLong(DataUsageContract.COLUMN_OF_SLOW_AVG);
+ appWarnFastSamples = cursor.getInt(DataUsageContract.COLUMN_OF_FAST_SAMPLES);
+ appWarnFastAvg = cursor.getLong(DataUsageContract.COLUMN_OF_FAST_AVG);
+ mAppWarnExtra = cursor.getString(DataUsageContract.COLUMN_OF_EXTRA);
+
+ AppItem appItem = mKnownItems.get((int)appWarnUid);
+
+ if (appItem != null) {
+ final UidDetail detail = mUidDetailProvider.getUidDetail(appItem.key, true);
+ long bytesDelta = appWarnBytes == 0 ? 0 : appItem.total - appWarnBytes;
+ if (DEBUG) {
+ Log.v(TAG, detail.label.toString() +
+ " cur:" + appItem.total +
+ " prev:" + appWarnBytes +
+ " SlowSamples:" + appWarnSlowSamples +
+ " SlowAvg:" + appWarnSlowAvg +
+ " FastSamples:" + appWarnFastSamples +
+ " FastAvg:" + appWarnFastAvg
+ );
+ }
+ if (bytesDelta > MAX_IDLE_BW) {
+ // enough BW consumed during this sample - evaluate algorithm
+ if (appWarnSlowSamples < MIN_SLOW_SAMPLE_COUNT) {
+ // not enough samples acquired for the slow average, keep accumulating
+ // samples
+ appWarnSlowAvg = computeAvg(appWarnSlowAvg, appWarnSlowSamples,
+ MIN_SLOW_SAMPLE_COUNT, bytesDelta);
+ appWarnSlowSamples++;
+
+ // fast average requires fewer samples than slow average, so at this point
+ // we may have accumulated enough or not, need to check
+ if (appWarnFastSamples < MIN_FAST_SAMPLE_COUNT) {
+ // not enough fast samples
+ appWarnFastAvg = computeAvg(appWarnFastAvg, appWarnFastSamples,
+ MIN_FAST_SAMPLE_COUNT, bytesDelta);
+ appWarnFastSamples++;
+ } else {
+ // enough fast samples
+ appWarnFastAvg = computeAvg(appWarnFastAvg, appWarnFastSamples,
+ MIN_FAST_SAMPLE_COUNT, bytesDelta);
+ }
+
+ updateDb(appItem.key,
+ appWarnSlowAvg, appWarnSlowSamples,
+ appWarnFastAvg, appWarnFastSamples,
+ 0, appItem.total);
+ } else {
+ // enough samples acquired for the average, evaluate warning algorithm
+ float avgExceedPercent = appWarnFastAvg-appWarnSlowAvg;
+ avgExceedPercent /= appWarnSlowAvg;
+ avgExceedPercent *= 100;
+
+ if ((appWarnFastAvg > appWarnSlowAvg) && (avgExceedPercent >
+ WARNING_PERCENTAGE)) {
+ genNotification(appItem.key, detail.label.toString(), !appWarnActive);
+ if (!appWarnActive) {
+ appWarnActive = true;
+ }
+ } else {
+ appWarnActive = false;
+ }
+ appWarnSlowAvg = computeAvg(appWarnSlowAvg, appWarnSlowSamples,
+ MIN_SLOW_SAMPLE_COUNT, bytesDelta);
+ appWarnFastAvg = computeAvg(appWarnFastAvg, appWarnFastSamples,
+ MIN_FAST_SAMPLE_COUNT, bytesDelta);
+ updateDb(
+ appItem.key,
+ appWarnSlowAvg, appWarnSlowSamples,
+ appWarnFastAvg, appWarnFastSamples,
+ appWarnActive ? 1 : 0, appItem.total
+ );
+
+ }
+ } else {
+ // not enough BW consumed during this sample - simply update bytes
+ updateDb(appItem.key, appItem.total);
+ }
+ }
+ }
+ cursor.close();
+ }
+
+ long computeAvg(long avg, int samples, int min_samples, long delta) {
+ float temp;
+
+ if (samples < min_samples) {
+ temp = avg * samples;
+ temp += delta;
+ temp /= (samples + 1);
+ return (long)temp;
+ } else {
+ temp = avg * (samples - 1);
+ temp += delta;
+ temp /= samples;
+ return (long)temp;
+ }
+ }
+
+
+ private void updateDb(int uid, long bytes) {
+ ContentValues values = new ContentValues();
+
+ values.put(DataUsageContract.BYTES, bytes);
+ getContentResolver().update(
+ DataUsageContract.CONTENT_URI,
+ values,
+ DataUsageContract.UID + " = ? ",
+ new String[]{String.valueOf(uid)}
+ );
+ }
+
+ private void updateDb(
+ int uid, long slowAvg, int slowSamples, long fastAvg, int fastSamples,
+ int active, long bytes
+ ) {
+ ContentValues values = new ContentValues();
+ String extraInfo = genExtraInfo(bytes);
+ values.put(DataUsageContract.SLOW_AVG, slowAvg);
+ values.put(DataUsageContract.SLOW_SAMPLES, slowSamples);
+ values.put(DataUsageContract.FAST_AVG, fastAvg);
+ values.put(DataUsageContract.FAST_SAMPLES, fastSamples);
+ values.put(DataUsageContract.ACTIVE, active);
+ values.put(DataUsageContract.BYTES, bytes);
+ values.put(DataUsageContract.EXTRA, extraInfo);
+
+ getContentResolver().update(
+ DataUsageContract.CONTENT_URI,
+ values,
+ DataUsageContract.UID + " = ? ",
+ new String[]{String.valueOf(uid)}
+ );
+ }
+
+
+ private String genExtraInfo(long bytes) {
+
+ Gson gson = new Gson();
+
+ DataUsageExtraInfo extraInfo;
+ if (mAppWarnExtra == null || mAppWarnExtra == "") {
+ extraInfo = null;
+ } else {
+ try {
+ extraInfo = gson.fromJson(mAppWarnExtra, DataUsageExtraInfo.class);
+ } catch (Exception e) {
+ extraInfo = null;
+ }
+ }
+
+ if (extraInfo == null) {
+ extraInfo = new DataUsageExtraInfo();
+ extraInfo.samples = new ArrayList<Long>();
+ }
+
+ if (extraInfo.samples.size() == MAX_EXTRA_SAMPLE_COUNT) {
+ extraInfo.samples.remove(0);
+ }
+ extraInfo.samples.add(bytes);
+ String extraInfoJson = gson.toJson(extraInfo);
+ return extraInfoJson;
+ }
+
+
+
+ private void genNotification(long uid, String appTitle, boolean firstTime) {
+ Intent hideIntent = new Intent();
+ hideIntent.setAction(HIDE_ACTION);
+ hideIntent.putExtra(DATA_USAGE_NOTIFICATION_UID, uid);
+ hideIntent.putExtra(DATA_USAGE_NOTIFICATION_TITLE, appTitle);
+ PendingIntent hidePendingIntent = PendingIntent.getBroadcast(
+ mContext, DATA_USAGE_BROADCAST_REQUEST_CODE, hideIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT
+ );
+
+ Intent disableIntent = new Intent();
+ disableIntent.setAction(DISABLE_ACTION);
+ disableIntent.putExtra(DATA_USAGE_NOTIFICATION_UID, uid);
+ disableIntent.putExtra(DATA_USAGE_NOTIFICATION_TITLE, appTitle);
+ PendingIntent disablePendingIntent = PendingIntent.getBroadcast(
+ mContext, DATA_USAGE_BROADCAST_REQUEST_CODE, disableIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT
+ );
+
+ Intent dataUsageIntent = new Intent();
+ dataUsageIntent.setAction(cyanogenmod.content.Intent.ACTION_DATA_USAGE);
+ dataUsageIntent.addCategory(Intent.CATEGORY_DEFAULT);
+ // dataUsageIntent.setData(Uri.parse("package:" + mContext.getPackageName()));
+ dataUsageIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ dataUsageIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
+ dataUsageIntent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+
+ PendingIntent dataUsagePendingIntent = PendingIntent.getActivity(mContext, 0,
+ dataUsageIntent, 0);
+
+ // NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext)
+ Notification.Builder builder = new Notification.Builder(mContext)
+ // .setSmallIcon(R.drawable.data_warning)
+ // .setSmallIcon(R.drawable.ic_sim_card_alert_white_48dp)
+ .setSmallIcon(R.drawable.data_usage_48dp)
+ .setContentTitle(getResources().getString(R.string.data_usage_notify_title))
+ .setAutoCancel(true) // remove notification when clicked on
+ .setContentText(appTitle) // non-expanded view message
+ .setColor(mContext.getColor(R.color.data_usage_notification_icon_color))
+ .setStyle(new Notification.BigTextStyle()
+ .bigText(getResources().getString(R.string.data_usage_notify_big_text, appTitle)));
+
+ if (firstTime) {
+ builder.addAction(
+ // R.drawable.data_warning_disable,
+ // android.R.drawable.stat_sys_data_bluetooth,
+ R.drawable.data_usage_disable_24dp,
+ getResources().getString(R.string.data_usage_disable_long),
+ disablePendingIntent);
+ } else {
+ builder.addAction(
+ // R.drawable.data_warning_disable,
+ // android.R.drawable.stat_sys_data_bluetooth,
+ R.drawable.data_usage_disable_24dp,
+ getResources().getString(R.string.data_usage_disable_short),
+ disablePendingIntent);
+ builder.addAction(
+ // R.drawable.data_warning_hide,
+ // android.R.drawable.stat_sys_download_done,
+ R.drawable.data_usage_hide_24dp,
+ getResources().getString(R.string.data_usage_hide),
+ hidePendingIntent)
+ ;
+ }
+
+ builder.setContentIntent(dataUsagePendingIntent);
+ mNotificationManager.notify(DATA_USAGE_SERVICE_NOTIFICATION_ID, builder.build());
+ }
+
+
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ }
+}
diff --git a/src/org/cyanogenmod/providers/datausage/DataUsageServiceEnableReceiver.java b/src/org/cyanogenmod/providers/datausage/DataUsageServiceEnableReceiver.java
new file mode 100644
index 0000000..38f60f6
--- /dev/null
+++ b/src/org/cyanogenmod/providers/datausage/DataUsageServiceEnableReceiver.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.cyanogenmod.providers.datausage;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.util.Log;
+
+public class DataUsageServiceEnableReceiver extends BroadcastReceiver {
+ private static final String TAG = DataUsageServiceEnableReceiver.class.getSimpleName();
+ public static final String PREF_FILE = "data_usage_service";
+ public static final String PREF_ENB_DATA_USAGE_NOTIFY = "enb_data_usage_notify";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ boolean enb = intent.getBooleanExtra("enable", false);
+ DataUsageUtils.enbDataUsageService(context, enb);
+ }
+}
diff --git a/src/org/cyanogenmod/providers/datausage/DataUsageUtils.java b/src/org/cyanogenmod/providers/datausage/DataUsageUtils.java
new file mode 100644
index 0000000..5b5ce36
--- /dev/null
+++ b/src/org/cyanogenmod/providers/datausage/DataUsageUtils.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.cyanogenmod.providers.datausage;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.UserInfo;
+import android.database.Cursor;
+import android.util.Log;
+
+import org.cyanogenmod.providers.datausage.R;
+
+import cyanogenmod.providers.DataUsageContract;
+
+
+public final class DataUsageUtils {
+
+ /**
+ * Returns a label for the user, in the form of "User: user name" or "Work profile".
+ */
+ public static String getUserLabel(Context context, UserInfo info) {
+ String name = info != null ? info.name : null;
+ if (info.isManagedProfile()) {
+ // We use predefined values for managed profiles
+ return context.getString(R.string.managed_user_title);
+ } else if (info.isGuest()) {
+ name = context.getString(R.string.user_guest);
+ }
+ if (name == null && info != null) {
+ name = Integer.toString(info.id);
+ } else if (info == null) {
+ name = context.getString(R.string.unknown);
+ }
+ return context.getResources().getString(R.string.running_process_item_user_label, name);
+ }
+
+
+ private static final String TAG = DataUsageUtils.class.getSimpleName();
+ private static final int DATAUSAGE_SERVICE_ALARM_ID = 0x102030;
+ private static boolean DEBUG = true;
+
+ public static void addApp(Context context, int uid, String label) {
+ if (DEBUG) {
+ Log.v(TAG, "addApp: uid:" + uid + " label:" + label);
+ }
+
+ ContentValues values = new ContentValues();
+
+ values.put(DataUsageContract.UID, uid);
+ values.put(DataUsageContract.LABEL, label);
+
+ context.getContentResolver().insert(
+ DataUsageContract.CONTENT_URI,
+ values
+ );
+ }
+
+ public static void removeApp(Context context, int uid) {
+ if (DEBUG) {
+ Log.v(TAG, "removeApp: uid:" + uid);
+ }
+ context.getContentResolver().delete(
+ DataUsageContract.CONTENT_URI,
+ DataUsageContract.UID + " = ? ",
+ new String [] { String.valueOf(uid)}
+ );
+ }
+
+ public static void enbApp(Context context, int uid, boolean enb) {
+ enbApp(context, uid, enb, null);
+ }
+
+ public static void enbApp(Context context, int uid, boolean enb, String label) {
+ if (DEBUG) {
+ Log.v(TAG, "enbApp: uid:" + uid + " enb:" + enb + ((label == null) ? "" : (" label:" +
+ label)));
+ }
+ ContentValues values = new ContentValues();
+
+ values.put(DataUsageContract.ENB, enb);
+ if (label != null) {
+ values.put(DataUsageContract.LABEL, label);
+ }
+ context.getContentResolver().update(
+ DataUsageContract.CONTENT_URI,
+ values,
+ DataUsageContract.UID + " = ? ",
+ new String [] { String.valueOf(uid)}
+ );
+ }
+
+ public static boolean getAppEnb(Context context, int uid) {
+ boolean appEnb = false;
+ Cursor cursor = context.getContentResolver().query(
+ DataUsageContract.CONTENT_URI,
+ null,
+ DataUsageContract.UID + " = ? ",
+ new String [] { String.valueOf(uid) },
+ null
+ );
+ if (cursor != null && cursor.getCount() > 0 && cursor.moveToFirst()) {
+ int enbValue = cursor.getInt(DataUsageContract.COLUMN_OF_ENB);
+ if (enbValue == 1) {
+ appEnb = true;
+ }
+ }
+
+ if (cursor != null) {
+ cursor.close();
+ }
+
+ if (DEBUG) {
+ Log.v(TAG, "getAppEnb: uid:" + uid + " enb:" + appEnb);
+ }
+
+ return appEnb;
+ }
+
+ public static final String PREF_FILE = "data_usage_service";
+ public static final String PREF_ENB_DATA_USAGE_NOTIFY = "enb_data_usage_notify";
+
+
+ public static void enbDataUsageService(Context context, boolean enb) {
+ SharedPreferences prefs = context.getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE);
+ prefs.edit().putBoolean(PREF_ENB_DATA_USAGE_NOTIFY, enb).apply();
+ startDataUsageService(context, enb);
+ }
+
+ public static void startDataUsageService(Context context, boolean enb) {
+ Intent dataUsageServiceIntent = new Intent(context, DataUsageService.class);
+ PendingIntent alarmIntent = PendingIntent.getService(
+ context, DATAUSAGE_SERVICE_ALARM_ID, dataUsageServiceIntent, 0);
+ AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
+
+ if (enb) {
+ alarmManager.setRepeating(
+ AlarmManager.ELAPSED_REALTIME,
+ DataUsageService.START_DELAY,
+ DataUsageService.SAMPLE_PERIOD,
+ alarmIntent
+ );
+ } else {
+ alarmManager.cancel(alarmIntent);
+ }
+ if (DEBUG) {
+ Log.v(TAG, "enbDataUsageService: enb:" + enb);
+ }
+ }
+
+ public static void startDataUsageServiceIfEnabled(Context context) {
+ SharedPreferences prefs = context.getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE);
+ boolean enb = prefs.getBoolean(PREF_ENB_DATA_USAGE_NOTIFY, false);
+ if (enb) {
+ startDataUsageService(context, true);
+ }
+ if (DEBUG) {
+ Log.v(TAG, "startDataUsageServiceIfEnabled: enb: " + enb);
+ }
+
+ }
+}
diff --git a/src/org/cyanogenmod/providers/datausage/UidDetail.java b/src/org/cyanogenmod/providers/datausage/UidDetail.java
new file mode 100644
index 0000000..91facca
--- /dev/null
+++ b/src/org/cyanogenmod/providers/datausage/UidDetail.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.cyanogenmod.providers.datausage;
+
+import android.graphics.drawable.Drawable;
+
+public class UidDetail {
+ public CharSequence label;
+}
diff --git a/src/org/cyanogenmod/providers/datausage/UidDetailProvider.java b/src/org/cyanogenmod/providers/datausage/UidDetailProvider.java
new file mode 100644
index 0000000..1d84018
--- /dev/null
+++ b/src/org/cyanogenmod/providers/datausage/UidDetailProvider.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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.cyanogenmod.providers.datausage;
+
+import android.app.AppGlobals;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.IPackageManager;
+import android.content.pm.UserInfo;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.net.TrafficStats;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.SparseArray;
+
+
+import android.content.pm.IPackageManager;
+import android.content.pm.UserInfo;
+
+
+/**
+ * Return details about a specific UID, handling special cases like
+ * {@link TrafficStats#UID_TETHERING} and {@link UserInfo}.
+ */
+public class UidDetailProvider {
+ private static final String TAG = "DataUsage";
+ private final Context mContext;
+ private final SparseArray<UidDetail> mUidDetailCache;
+
+ public static final int OTHER_USER_RANGE_START = -2000;
+
+ public static int buildKeyForUser(int userHandle) {
+ return OTHER_USER_RANGE_START - userHandle;
+ }
+
+ public static boolean isKeyForUser(int key) {
+ return key <= OTHER_USER_RANGE_START;
+ }
+
+ public static int getUserIdForKey(int key) {
+ return OTHER_USER_RANGE_START - key;
+ }
+
+ public UidDetailProvider(Context context) {
+ mContext = context.getApplicationContext();
+ mUidDetailCache = new SparseArray<UidDetail>();
+ }
+
+ public void clearCache() {
+ synchronized (mUidDetailCache) {
+ mUidDetailCache.clear();
+ }
+ }
+
+ /**
+ * Resolve best descriptive label for the given UID.
+ */
+ public UidDetail getUidDetail(int uid, boolean blocking) {
+ UidDetail detail;
+
+ synchronized (mUidDetailCache) {
+ detail = mUidDetailCache.get(uid);
+ }
+
+ if (detail != null) {
+ return detail;
+ } else if (!blocking) {
+ return null;
+ }
+
+ detail = buildUidDetail(uid);
+
+ synchronized (mUidDetailCache) {
+ mUidDetailCache.put(uid, detail);
+ }
+
+ return detail;
+ }
+
+ /**
+ * Build {@link UidDetail} object, blocking until all {@link Drawable}
+ * lookup is finished.
+ */
+ private UidDetail buildUidDetail(int uid) {
+ final Resources res = mContext.getResources();
+ final PackageManager pm = mContext.getPackageManager();
+
+ final UidDetail detail = new UidDetail();
+ detail.label = pm.getNameForUid(uid);
+
+ // handle special case labels
+ switch (uid) {
+ case android.os.Process.SYSTEM_UID:
+ detail.label = res.getString(R.string.process_kernel_label);
+ return detail;
+ case TrafficStats.UID_REMOVED:
+ detail.label = res.getString(UserManager.supportsMultipleUsers()
+ ? R.string.data_usage_uninstalled_apps_users
+ : R.string.data_usage_uninstalled_apps);
+ return detail;
+ }
+
+ final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+
+ // Handle keys that are actually user handles
+ if (isKeyForUser(uid)) {
+ final int userHandle = getUserIdForKey(uid);
+ final UserInfo info = um.getUserInfo(userHandle);
+ if (info != null) {
+ detail.label = DataUsageUtils.getUserLabel(mContext, info);
+ return detail;
+ }
+ }
+
+ // otherwise fall back to using packagemanager labels
+ final String[] packageNames = pm.getPackagesForUid(uid);
+ final int length = packageNames != null ? packageNames.length : 0;
+ try {
+ final int userId = UserHandle.getUserId(uid);
+ UserHandle userHandle = new UserHandle(userId);
+ IPackageManager ipm = AppGlobals.getPackageManager();
+ if (length == 1) {
+ final ApplicationInfo info = ipm.getApplicationInfo(packageNames[0],
+ 0 /* no flags */, userId);
+ if (info != null) {
+ detail.label = info.loadLabel(pm).toString();
+ }
+ } else if (length > 1) {
+ for (int i = 0; i < length; i++) {
+ final String packageName = packageNames[i];
+ final PackageInfo packageInfo = pm.getPackageInfo(packageName, 0);
+ final ApplicationInfo appInfo = ipm.getApplicationInfo(packageName,
+ 0 /* no flags */, userId);
+
+ if (appInfo != null) {
+ if (packageInfo.sharedUserLabel != 0) {
+ detail.label = pm.getText(packageName, packageInfo.sharedUserLabel,
+ packageInfo.applicationInfo).toString();
+ }
+ }
+ }
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "Error while building UI detail for uid "+uid, e);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error while building UI detail for uid "+uid, e);
+ }
+
+ if (TextUtils.isEmpty(detail.label)) {
+ detail.label = Integer.toString(uid);
+ }
+
+ return detail;
+ }
+}