cmparts: Privacy Guard settings
* Refactoring PG settings to live in CMParts
* Includes general infrastructure and top-level settings
Change-Id: If031b683bcef119852553c21b10078ddf6c46ad8
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..0ffb101
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,13 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CMParts
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_STATIC_JAVA_LIBRARIES := org.cyanogenmod.platform.internal
+LOCAL_CERTIFICATE := platform
+LOCAL_PRIVILEGED_MODULE := true
+LOCAL_MODULE_TAGS := optional
+LOCAL_PROGUARD_FLAG_FILES := proguard.flags
+
+include $(BUILD_PACKAGE)
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
new file mode 100644
index 0000000..0d8d72b
--- /dev/null
+++ b/AndroidManifest.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright 2016, The CyanogenMod 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.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ package="org.cyanogenmod.cmparts"
+ android:versionCode="1"
+ android:versionName="1.0"
+ android:sharedUserId="android.uid.system">
+
+ <uses-sdk android:minSdkVersion="24" android:targetSdkVersion="24" />
+
+ <application android:label="@string/cmparts_title"
+ android:theme="@style/Theme.Settings"
+ android:hardwareAccelerated="true"
+ android:supportsRtl="true"
+ android:defaultToDeviceProtectedStorage="true"
+ android:directBootAware="true">
+
+ <activity
+ android:name=".PrivacySettings"
+ android:label="@string/privacy_settings_title">
+ <intent-filter android:priority="1">
+ <action android:name="com.android.settings.action.EXTRA_SETTINGS" />
+ </intent-filter>
+ <meta-data
+ android:name="com.android.settings.category"
+ android:value="com.android.settings.category.personal" />
+ <meta-data
+ android:name="com.android.settings.icon"
+ android:resource="@drawable/ic_settings_privacy" />
+ </activity>
+
+ </application>
+</manifest>
+
+
diff --git a/proguard.flags b/proguard.flags
new file mode 100644
index 0000000..ecbb6e0
--- /dev/null
+++ b/proguard.flags
@@ -0,0 +1,60 @@
+-optimizationpasses 5
+-dontusemixedcaseclassnames
+-dontskipnonpubliclibraryclasses
+-dontpreverify
+-verbose
+-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
+
+-keep public class * extends android.app.Activity
+-keep public class * extends android.app.Application
+-keep public class * extends android.app.Service
+-keep public class * extends android.content.BroadcastReceiver
+-keep public class * extends android.content.ContentProvider
+-keep public class * extends android.app.backup.BackupAgentHelper
+-keep public class * extends android.preference.Preference
+-keep public class android.support.v7.preference.Preference {
+ public <init>(android.content.Context, android.util.AttributeSet);
+}
+-keep public class * extends android.support.v7.preference.Preference {
+ public <init>(android.content.Context, android.util.AttributeSet);
+}
+-keep public class com.android.vending.licensing.ILicensingService
+
+-keepclasseswithmembernames class * {
+ native <methods>;
+}
+
+-keepclasseswithmembers class * {
+ public <init>(android.content.Context, android.util.AttributeSet);
+}
+
+-keepclasseswithmembers class * {
+ public <init>(android.content.Context, android.util.AttributeSet, int);
+}
+
+-keepclassmembers class * extends android.app.Activity {
+ public void *(android.view.View);
+}
+
+-keepclassmembers enum * {
+ public static **[] values();
+ public static ** valueOf(java.lang.String);
+}
+
+-keep class * implements android.os.Parcelable {
+ public static final android.os.Parcelable$Creator *;
+}
+
+-keep @android.support.annotation.Keep class *
+-keepclassmembers class * {
+ @android.support.annotation.Keep *;
+}
+
+-dontwarn org.bouncycastle.x509.util.LDAPStoreHelper
+-dontwarn org.bouncycastle.jce.provider.X509LDAPCertStoreSpi
+-dontwarn org.bouncycastle.util.io.pem.AllTests
+-dontwarn org.bouncycastle.util.AllTests
+-dontwarn android.support.v13.app.FragmentCompatICSMR1
+-dontwarn android.support.v4.view.ViewCompatJellybeanMr1
+-dontwarn org.bouncycastle.x509.X509V3CertificateGenerator
+-dontwarn org.bouncycastle.jce.provider.BouncyCastleProvider
diff --git a/res/drawable-hdpi/ic_privacy_guard.png b/res/drawable-hdpi/ic_privacy_guard.png
new file mode 100644
index 0000000..0f58fce
--- /dev/null
+++ b/res/drawable-hdpi/ic_privacy_guard.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_privacy_guard_off.png b/res/drawable-hdpi/ic_privacy_guard_off.png
new file mode 100644
index 0000000..f5dce61
--- /dev/null
+++ b/res/drawable-hdpi/ic_privacy_guard_off.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_settings_backup_restore.png b/res/drawable-hdpi/ic_settings_backup_restore.png
new file mode 100644
index 0000000..6bb2548
--- /dev/null
+++ b/res/drawable-hdpi/ic_settings_backup_restore.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_privacy_guard.png b/res/drawable-mdpi/ic_privacy_guard.png
new file mode 100644
index 0000000..a749b20
--- /dev/null
+++ b/res/drawable-mdpi/ic_privacy_guard.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_privacy_guard_off.png b/res/drawable-mdpi/ic_privacy_guard_off.png
new file mode 100644
index 0000000..8d9db2e
--- /dev/null
+++ b/res/drawable-mdpi/ic_privacy_guard_off.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_privacy_guard.png b/res/drawable-xhdpi/ic_privacy_guard.png
new file mode 100644
index 0000000..f7b4a4c
--- /dev/null
+++ b/res/drawable-xhdpi/ic_privacy_guard.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_privacy_guard_off.png b/res/drawable-xhdpi/ic_privacy_guard_off.png
new file mode 100644
index 0000000..6425549
--- /dev/null
+++ b/res/drawable-xhdpi/ic_privacy_guard_off.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_settings_backup_restore.png b/res/drawable-xhdpi/ic_settings_backup_restore.png
new file mode 100644
index 0000000..9f975d3
--- /dev/null
+++ b/res/drawable-xhdpi/ic_settings_backup_restore.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_privacy_guard.png b/res/drawable-xxhdpi/ic_privacy_guard.png
new file mode 100644
index 0000000..b432ed4
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_privacy_guard.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_privacy_guard_off.png b/res/drawable-xxhdpi/ic_privacy_guard_off.png
new file mode 100644
index 0000000..c1964f4
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_privacy_guard_off.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_settings_backup_restore.png b/res/drawable-xxhdpi/ic_settings_backup_restore.png
new file mode 100644
index 0000000..d8effc6
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_settings_backup_restore.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_settings_backup_restore.png b/res/drawable-xxxhdpi/ic_settings_backup_restore.png
new file mode 100644
index 0000000..f5773c9
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_settings_backup_restore.png
Binary files differ
diff --git a/res/drawable/ic_privacy_guard_on.xml b/res/drawable/ic_privacy_guard_on.xml
new file mode 100644
index 0000000..66bd107
--- /dev/null
+++ b/res/drawable/ic_privacy_guard_on.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+ android:src="@drawable/ic_privacy_guard"
+ android:tint="@color/theme_accent">
+</bitmap>
diff --git a/res/drawable/ic_settings_privacy.xml b/res/drawable/ic_settings_privacy.xml
new file mode 100644
index 0000000..d7578d3
--- /dev/null
+++ b/res/drawable/ic_settings_privacy.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:fillColor="#009688"
+ android:pathData="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 10.99h7c-.53
+4.12-3.28 7.79-7 8.94V12H5V6.3l7-3.11v8.8z" />
+</vector>
diff --git a/res/drawable/preference_background.xml b/res/drawable/preference_background.xml
new file mode 100644
index 0000000..12bdea8
--- /dev/null
+++ b/res/drawable/preference_background.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+
+<color xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:attr/colorBackground" />
diff --git a/res/layout/privacy_guard_manager.xml b/res/layout/privacy_guard_manager.xml
new file mode 100644
index 0000000..6cf90b0
--- /dev/null
+++ b/res/layout/privacy_guard_manager.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 Slimroms 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <FrameLayout
+ android:id="@+id/privacy_guard_prefs"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="@android:color/darker_gray"/>
+ <TextView
+ android:id="@+id/error"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="20dip"
+ android:layout_gravity="center"
+ android:gravity="center_horizontal"
+ android:textColor="@android:color/white"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:visibility="gone" />
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1">
+
+ <ListView android:id="@+id/apps_list"
+ android:drawSelectorOnTop="false"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipToPadding="false"
+ android:scrollbarStyle="@*android:integer/preference_fragment_scrollbarStyle"
+ android:visibility="gone"/>
+
+ <LinearLayout android:id="@+id/loading_container"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone"
+ android:gravity="center">
+
+ <ProgressBar style="?android:attr/progressBarStyleLarge"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <TextView android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:text="@string/loading"
+ android:paddingTop="4dip"
+ android:singleLine="true" />
+
+ </LinearLayout>
+ </FrameLayout>
+
+</LinearLayout>
diff --git a/res/layout/privacy_guard_manager_list_row.xml b/res/layout/privacy_guard_manager_list_row.xml
new file mode 100644
index 0000000..eb6a327
--- /dev/null
+++ b/res/layout/privacy_guard_manager_list_row.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 Slimroms 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.
+-->
+
+<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingTop="8dip"
+ android:paddingBottom="8dip" >
+ <ImageView
+ android:id="@+id/app_icon"
+ android:layout_width="36dip"
+ android:layout_height="36dip"
+ android:layout_marginEnd="8dip"
+ android:layout_gravity="center_vertical"
+ android:scaleType="centerInside"
+ android:contentDescription="@null" />
+ <TextView
+ android:id="@+id/app_title"
+ android:layout_width="0dip"
+ android:layout_gravity="fill_horizontal|center_vertical"
+ android:layout_marginTop="2dip"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textAlignment="viewStart" />
+ <ImageView
+ android:id="@+id/app_privacy_guard_icon"
+ android:layout_width="@android:dimen/app_icon_size"
+ android:layout_height="@android:dimen/app_icon_size"
+ android:layout_gravity="center_vertical"
+ android:scaleType="centerInside"
+ android:contentDescription="@null" />
+</GridLayout>
diff --git a/res/menu/privacy_guard_manager.xml b/res/menu/privacy_guard_manager.xml
new file mode 100644
index 0000000..66f5862
--- /dev/null
+++ b/res/menu/privacy_guard_manager.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 Slimroms 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.
+-->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@+id/reset"
+ android:showAsAction="always"
+ android:icon="@drawable/ic_settings_backup_restore"
+ android:title="@string/privacy_guard_reset_title" />
+ <item android:id="@+id/show_system_apps"
+ android:title="@string/privacy_guard_manager_show_system_apps"
+ android:checkable="true" />
+ <item android:id="@+id/advanced"
+ android:title="@string/privacy_guard_advanced_settings_title" />
+ <item android:id="@+id/help"
+ android:title="@string/privacy_guard_help_title" />
+</menu>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
new file mode 100644
index 0000000..eb01190
--- /dev/null
+++ b/res/values/attrs.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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.
+-->
+
+<resources>
+ <attr name="preferenceBackgroundColor" format="color" />
+</resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
new file mode 100644
index 0000000..655ff74
--- /dev/null
+++ b/res/values/colors.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The CyanogenMod 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.
+-->
+<resources>
+ <color name="theme_accent">#ff009688</color>
+</resources>
+
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
new file mode 100644
index 0000000..a14d36b
--- /dev/null
+++ b/res/values/dimens.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<resources>
+ <!-- ActionBar height -->
+ <dimen name="actionbar_size">56dip</dimen>
+
+ <dimen name="settings_side_margin">0dip</dimen>
+
+</resources>
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
new file mode 100644
index 0000000..9a0d65e
--- /dev/null
+++ b/res/values/strings.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 The CyanogenMod 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.
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+ <string name="cmparts_title">CyanogenMod Settings</string>
+ <string name="loading">Loading\u2026</string>
+ <string name="dlg_ok">OK</string>
+ <string name="cancel">Cancel</string>
+
+ <!-- Privacy Settings Header item -->
+ <string name="privacy_settings_title">Privacy</string>
+ <string name="privacy_settings_category" translatable="false">CyanogenMod</string>
+
+ <!-- Privacy Guard -->
+ <string name="privacy_guard_switch_label">Enable Privacy Guard</string>
+ <string name="privacy_guard_dlg_title">Enable Privacy Guard?</string>
+ <string name="privacy_guard_dlg_text">When Privacy Guard is enabled, the app will not be able to access personal data such as contacts, messages or call logs.</string>
+ <string name="privacy_guard_dlg_system_app_text">When enabling Privacy Guard for a built-in app, the app will not be able to access or provide personal data. This may cause other apps to misbehave.</string>
+ <string name="privacy_guard_default_title">Enabled by default</string>
+ <string name="privacy_guard_default_summary">Enable by default for newly-installed apps</string>
+ <string name="privacy_guard_manager_title">Privacy Guard</string>
+ <string name="privacy_guard_manager_summary">Manage which apps have access to your personal data</string>
+ <string name="privacy_guard_no_user_apps">No apps are installed</string>
+ <string name="privacy_guard_help_title">Help</string>
+ <string name="privacy_guard_reset_title">Reset</string>
+ <string name="privacy_guard_reset_text">Reset permissions?</string>
+ <string name="privacy_guard_help_text">In this screen you can choose which apps Privacy Guard should be active for by simply tapping on them. Selected apps will not be able to access your personal data such as contacts, messages or call logs. Long pressing an app\'s entry opens its app details screen.\n\nBuilt-in apps are not shown by default but can be revealed by selecting the respective menu option.</string>
+ <string name="privacy_guard_manager_show_system_apps">Show built-in apps</string>
+ <string name="privacy_guard_advanced_settings_title">Advanced</string>
+ <string name="privacy_guard_notification_title">Show notification</string>
+
+</resources>
+
diff --git a/res/values/styles.xml b/res/values/styles.xml
new file mode 100644
index 0000000..7835732
--- /dev/null
+++ b/res/values/styles.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2006 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.
+-->
+
+<resources>
+ <style name="PreferenceHeaderPanelSinglePane">
+ <item name="android:layout_marginStart">0dp</item>
+ <item name="android:layout_marginEnd">0dp</item>
+ <item name="android:background">@null</item>
+ </style>
+
+ <style name="PreferencePanelSinglePane" parent="@*android:style/PreferencePanel">
+ <item name="android:layout_marginStart">0dp</item>
+ <item name="android:layout_marginEnd">0dp</item>
+ <item name="android:paddingStart">0dp</item>
+ <item name="android:paddingEnd">0dp</item>
+ <item name="android:background">@null</item>
+ <item name="android:scrollbarStyle">outsideOverlay</item>
+ </style>
+
+ <style name="PreferenceHeaderListSinglePane" parent="@*android:style/PreferenceHeaderList">
+ <item name="android:paddingStart">@dimen/settings_side_margin</item>
+ <item name="android:paddingEnd">@dimen/settings_side_margin</item>
+ <item name="android:paddingTop">0dp</item>
+ <item name="android:paddingBottom">0dp</item>
+ <item name="android:layout_marginStart">0dp</item>
+ <item name="android:layout_marginEnd">0dp</item>
+ <item name="android:layout_marginTop">0dp</item>
+ <item name="android:layout_marginBottom">0dp</item>
+ <item name="android:scrollbarStyle">outsideOverlay</item>
+ </style>
+
+ <style name="PreferenceFragmentListSinglePane" parent="@*android:style/PreferenceFragmentList">
+ <item name="android:paddingStart">@dimen/settings_side_margin</item>
+ <item name="android:paddingEnd">@dimen/settings_side_margin</item>
+ <item name="android:layout_marginStart">0dp</item>
+ <item name="android:layout_marginEnd">0dp</item>
+ <item name="android:scrollbarStyle">outsideOverlay</item>
+ </style>
+
+
+</resources>
diff --git a/res/values/themes.xml b/res/values/themes.xml
new file mode 100644
index 0000000..8c10990
--- /dev/null
+++ b/res/values/themes.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<resources>
+ <style name="Theme.SettingsBase" parent="@android:style/Theme.Material.Settings" />
+
+ <style name="Theme.Settings" parent="Theme.SettingsBase">
+ <item name="@*android:preferenceHeaderPanelStyle">@style/PreferenceHeaderPanelSinglePane</item>
+ <item name="@*android:preferencePanelStyle">@style/PreferencePanelSinglePane</item>
+ <item name="@*android:preferenceListStyle">@style/PreferenceHeaderListSinglePane</item>
+ <item name="@*android:preferenceFragmentListStyle">@style/PreferenceFragmentListSinglePane</item>
+ <item name="@*android:preferenceFragmentPaddingSide">@dimen/settings_side_margin</item>
+
+ <!-- Redefine the ActionBar style for contentInsetStart -->
+ <item name="@*android:actionBarSize">@dimen/actionbar_size</item>
+
+ <item name="preferenceBackgroundColor">@drawable/preference_background</item>
+ </style>
+</resources>
+
diff --git a/res/xml/privacy_guard_prefs.xml b/res/xml/privacy_guard_prefs.xml
new file mode 100644
index 0000000..d5f961d
--- /dev/null
+++ b/res/xml/privacy_guard_prefs.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 Slimroms 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.
+-->
+
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <SwitchPreference
+ android:key="privacy_guard_default"
+ android:title="@string/privacy_guard_default_title"
+ android:summary="@string/privacy_guard_default_summary"
+ android:persistent="false" />
+
+ <org.cyanogenmod.cmparts.CMSecureSettingSwitchPreference
+ android:key="privacy_guard_notification"
+ android:title="@string/privacy_guard_notification_title"
+ android:defaultValue="true" />
+
+</PreferenceScreen>
diff --git a/res/xml/privacy_settings.xml b/res/xml/privacy_settings.xml
new file mode 100644
index 0000000..2c4009a
--- /dev/null
+++ b/res/xml/privacy_settings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The CyanogenMod 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.
+-->
+
+<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <header
+ android:title="@string/privacy_guard_manager_title"
+ android:summary="@string/privacy_guard_manager_summary"
+ android:fragment="org.cyanogenmod.cmparts.privacyguard.PrivacyGuardManager" />
+
+</preference-headers>
diff --git a/src/org/cyanogenmod/cmparts/PrivacySettings.java b/src/org/cyanogenmod/cmparts/PrivacySettings.java
new file mode 100644
index 0000000..4ca6e0b
--- /dev/null
+++ b/src/org/cyanogenmod/cmparts/PrivacySettings.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod 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.cmparts;
+
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceActivity.Header;
+
+import java.util.List;
+
+/**
+ * Privacy settings
+ */
+public class PrivacySettings extends PreferenceActivity {
+
+ @Override
+ public void onBuildHeaders(List<Header> target) {
+ loadHeadersFromResource(R.xml.privacy_settings, target);
+ }
+
+ @Override
+ public boolean isValidFragment(String fragmentName) {
+ return fragmentName.startsWith("org.cyanogenmod.cmparts.privacy");
+ }
+}
diff --git a/src/org/cyanogenmod/cmparts/privacyguard/AppInfoLoader.java b/src/org/cyanogenmod/cmparts/privacyguard/AppInfoLoader.java
new file mode 100644
index 0000000..f9785b8
--- /dev/null
+++ b/src/org/cyanogenmod/cmparts/privacyguard/AppInfoLoader.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2015 The CyanogenMod 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.cmparts.privacyguard;
+
+import android.app.AppOpsManager;
+import android.content.AsyncTaskLoader;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageInfo;
+
+import org.cyanogenmod.cmparts.privacyguard.PrivacyGuardManager.AppInfo;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * An asynchronous loader implementation that loads AppInfo structures.
+ */
+/* package */ class AppInfoLoader extends AsyncTaskLoader<List<AppInfo>> {
+ private PackageManager mPm;
+ private boolean mShowSystemApps;
+ private AppOpsManager mAppOps;
+ private static final String[] BLACKLISTED_PACKAGES = {
+ "com.android.systemui"
+ };
+
+ public AppInfoLoader(Context context, boolean showSystemApps) {
+ super(context);
+ mPm = context.getPackageManager();
+ mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
+ mShowSystemApps = showSystemApps;
+ }
+
+ @Override
+ public List<AppInfo> loadInBackground() {
+ return loadInstalledApps();
+ }
+
+ @Override
+ public void onStartLoading() {
+ forceLoad();
+ }
+
+ @Override
+ public void onStopLoading() {
+ cancelLoad();
+ }
+
+ @Override
+ protected void onReset() {
+ cancelLoad();
+ }
+
+ private boolean isBlacklisted(String packageName) {
+ for (String pkg : BLACKLISTED_PACKAGES) {
+ if (pkg.equals(packageName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Uses the package manager to query for all currently installed apps
+ * for the list.
+ *
+ * @return the complete List off installed applications (@code PrivacyGuardAppInfo)
+ */
+ private List<AppInfo> loadInstalledApps() {
+ List<AppInfo> apps = new ArrayList<AppInfo>();
+ List<PackageInfo> packages = mPm.getInstalledPackages(
+ PackageManager.GET_PERMISSIONS | PackageManager.GET_SIGNATURES);
+
+ for (PackageInfo info : packages) {
+ final ApplicationInfo appInfo = info.applicationInfo;
+
+ // skip all system apps if they shall not be included
+ if ((!mShowSystemApps && (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0)
+ || (appInfo.uid == android.os.Process.SYSTEM_UID)
+ || isBlacklisted(appInfo.packageName)) {
+ continue;
+ }
+
+ AppInfo app = new AppInfo();
+ app.title = appInfo.loadLabel(mPm).toString();
+ app.packageName = info.packageName;
+ app.enabled = appInfo.enabled;
+ app.uid = info.applicationInfo.uid;
+ app.privacyGuardEnabled = mAppOps.getPrivacyGuardSettingForPackage(
+ app.uid, app.packageName);
+ apps.add(app);
+ }
+
+ // sort the apps by their enabled state, then by title
+ Collections.sort(apps, new Comparator<AppInfo>() {
+ @Override
+ public int compare(AppInfo lhs, AppInfo rhs) {
+ if (lhs.enabled != rhs.enabled) {
+ return lhs.enabled ? -1 : 1;
+ }
+ return lhs.title.compareToIgnoreCase(rhs.title);
+ }
+ });
+
+ return apps;
+ }
+
+}
diff --git a/src/org/cyanogenmod/cmparts/privacyguard/PrivacyGuardAppListAdapter.java b/src/org/cyanogenmod/cmparts/privacyguard/PrivacyGuardAppListAdapter.java
new file mode 100644
index 0000000..2d1654f
--- /dev/null
+++ b/src/org/cyanogenmod/cmparts/privacyguard/PrivacyGuardAppListAdapter.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2013 SlimRoms Project
+ * Copyright (C) 2016 The CyanogenMod 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.cmparts.privacyguard;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.SectionIndexer;
+import android.widget.TextView;
+
+import org.cyanogenmod.cmparts.R;
+import org.cyanogenmod.cmparts.privacyguard.PrivacyGuardManager.AppInfo;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class PrivacyGuardAppListAdapter extends BaseAdapter implements SectionIndexer {
+
+ private LayoutInflater mInflater;
+ private PackageManager mPm;
+
+ private List<AppInfo> mApps;
+ private String[] mSections;
+ private int[] mPositions;
+ private ConcurrentHashMap<String, Drawable> mIcons;
+ private Drawable mDefaultImg;
+
+ private Context mContext;
+
+ //constructor
+ public PrivacyGuardAppListAdapter(Context context, List<AppInfo> apps,
+ List<String> sections, List<Integer> positions) {
+ mContext = context;
+ mInflater = LayoutInflater.from(mContext);
+ mPm = context.getPackageManager();
+
+ mApps = apps;
+ mSections = sections.toArray(new String[sections.size()]);
+ mPositions = new int[positions.size()];
+ for (int i = 0; i < positions.size(); i++) {
+ mPositions[i] = positions.get(i);
+ }
+
+ // set the default icon till the actual app icon is loaded in async task
+ mDefaultImg = mContext.getResources().getDrawable(android.R.mipmap.sym_def_app_icon);
+ mIcons = new ConcurrentHashMap<String, Drawable>();
+
+ new LoadIconsTask().execute(apps.toArray(new PrivacyGuardManager.AppInfo[]{}));
+ }
+
+ @Override
+ public int getCount() {
+ return mApps.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return mApps.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ PrivacyGuardAppViewHolder appHolder;
+
+ if (convertView == null) {
+ convertView = mInflater.inflate(R.layout.privacy_guard_manager_list_row, null);
+
+ // creates a ViewHolder and children references
+ appHolder = new PrivacyGuardAppViewHolder();
+ appHolder.title = (TextView) convertView.findViewById(R.id.app_title);
+ appHolder.icon = (ImageView) convertView.findViewById(R.id.app_icon);
+ appHolder.privacyGuardIcon = (ImageView) convertView.findViewById(R.id.app_privacy_guard_icon);
+ convertView.setTag(appHolder);
+ } else {
+ appHolder = (PrivacyGuardAppViewHolder) convertView.getTag();
+ }
+
+ PrivacyGuardManager.AppInfo app = mApps.get(position);
+
+ appHolder.title.setText(app.title);
+
+ Drawable icon = mIcons.get(app.packageName);
+ appHolder.icon.setImageDrawable(icon != null ? icon : mDefaultImg);
+
+ int privacyGuardDrawableResId = app.privacyGuardEnabled
+ ? R.drawable.ic_privacy_guard_on :
+ R.drawable.ic_privacy_guard_off;
+ appHolder.privacyGuardIcon.setImageResource(privacyGuardDrawableResId);
+
+ return convertView;
+ }
+
+ @Override
+ public int getPositionForSection(int section) {
+ if (section < 0 || section >= mSections.length) {
+ return -1;
+ }
+
+ return mPositions[section];
+ }
+
+ @Override
+ public int getSectionForPosition(int position) {
+ if (position < 0 || position >= getCount()) {
+ return -1;
+ }
+
+ int index = Arrays.binarySearch(mPositions, position);
+
+ /*
+ * Consider this example: section positions are 0, 3, 5; the supplied
+ * position is 4. The section corresponding to position 4 starts at
+ * position 3, so the expected return value is 1. Binary search will not
+ * find 4 in the array and thus will return -insertPosition-1, i.e. -3.
+ * To get from that number to the expected value of 1 we need to negate
+ * and subtract 2.
+ */
+ return index >= 0 ? index : -index - 2;
+ }
+
+ @Override
+ public Object[] getSections() {
+ return mSections;
+ }
+
+ /**
+ * An asynchronous task to load the icons of the installed applications.
+ */
+ private class LoadIconsTask extends AsyncTask<PrivacyGuardManager.AppInfo, Void, Void> {
+ @Override
+ protected Void doInBackground(PrivacyGuardManager.AppInfo... apps) {
+ for (PrivacyGuardManager.AppInfo app : apps) {
+ try {
+ Drawable icon = mPm.getApplicationIcon(app.packageName);
+ mIcons.put(app.packageName, icon);
+ publishProgress();
+ } catch (PackageManager.NameNotFoundException e) {
+ // ignored; app will show up with default image
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ protected void onProgressUpdate(Void... progress) {
+ notifyDataSetChanged();
+ }
+ }
+
+ /**
+ * App view holder used to reuse the views inside the list.
+ */
+ public static class PrivacyGuardAppViewHolder {
+ TextView title;
+ ImageView icon;
+ ImageView privacyGuardIcon;
+ }
+}
diff --git a/src/org/cyanogenmod/cmparts/privacyguard/PrivacyGuardManager.java b/src/org/cyanogenmod/cmparts/privacyguard/PrivacyGuardManager.java
new file mode 100644
index 0000000..3e7951f
--- /dev/null
+++ b/src/org/cyanogenmod/cmparts/privacyguard/PrivacyGuardManager.java
@@ -0,0 +1,399 @@
+/*
+ * Copyright (C) 2013 SlimRoms Project
+ * Copyright (C) 2016 The CyanogenMod 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.cmparts.privacyguard;
+
+import android.app.FragmentTransaction;
+import android.view.animation.AnimationUtils;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.AppOpsManager;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.LoaderManager;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.Loader;
+import android.content.SharedPreferences;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.preference.PreferenceManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.AdapterView.OnItemLongClickListener;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import org.cyanogenmod.cmparts.R;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class PrivacyGuardManager extends Fragment
+ implements OnItemClickListener, OnItemLongClickListener,
+ LoaderManager.LoaderCallbacks<List<PrivacyGuardManager.AppInfo>> {
+
+ private static final String TAG = "PrivacyGuardManager";
+
+ private TextView mNoUserAppsInstalled;
+ private ListView mAppsList;
+ private View mLoadingContainer;
+ private PrivacyGuardAppListAdapter mAdapter;
+ private List<AppInfo> mApps;
+
+ private Activity mActivity;
+
+ private SharedPreferences mPreferences;
+ private AppOpsManager mAppOps;
+
+ private int mSavedFirstVisiblePosition = AdapterView.INVALID_POSITION;
+ private int mSavedFirstItemOffset;
+
+ // keys for extras and icicles
+ private final static String LAST_LIST_POS = "last_list_pos";
+ private final static String LAST_LIST_OFFSET = "last_list_offset";
+
+ // Privacy Guard Fragment
+ private final static String PRIVACY_GUARD_FRAGMENT_TAG = "privacy_guard_fragment";
+
+ // holder for package data passed into the adapter
+ public static final class AppInfo {
+ String title;
+ String packageName;
+ boolean enabled;
+ boolean privacyGuardEnabled;
+ int uid;
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ mActivity = getActivity();
+ mAppOps = (AppOpsManager)getActivity().getSystemService(Context.APP_OPS_SERVICE);
+
+ View hostView = inflater.inflate(R.layout.privacy_guard_manager, container, false);
+
+ Fragment privacyGuardPrefs = PrivacyGuardPrefs.newInstance();
+ FragmentTransaction fragmentTransaction = getChildFragmentManager().beginTransaction();
+ fragmentTransaction.replace(R.id.privacy_guard_prefs, privacyGuardPrefs,
+ PRIVACY_GUARD_FRAGMENT_TAG);
+ fragmentTransaction.commit();
+ return hostView;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ mNoUserAppsInstalled = (TextView) mActivity.findViewById(R.id.error);
+
+ mAppsList = (ListView) mActivity.findViewById(R.id.apps_list);
+ mAppsList.setOnItemClickListener(this);
+ mAppsList.setOnItemLongClickListener(this);
+
+ mLoadingContainer = mActivity.findViewById(R.id.loading_container);
+
+ // get shared preference
+ mPreferences = mActivity.getSharedPreferences("privacy_guard_manager", Activity.MODE_PRIVATE);
+ if (savedInstanceState == null && !mPreferences.getBoolean("first_help_shown", false)) {
+ showHelp();
+ }
+
+ if (savedInstanceState != null) {
+ mSavedFirstVisiblePosition = savedInstanceState.getInt(LAST_LIST_POS,
+ AdapterView.INVALID_POSITION);
+ mSavedFirstItemOffset = savedInstanceState.getInt(LAST_LIST_OFFSET, 0);
+ } else {
+ mSavedFirstVisiblePosition = AdapterView.INVALID_POSITION;
+ mSavedFirstItemOffset = 0;
+ }
+
+ // load apps and construct the list
+ scheduleAppsLoad();
+
+ setHasOptionsMenu(true);
+ }
+
+ @Override
+ public void onViewStateRestored(Bundle savedInstanceState) {
+ super.onViewStateRestored(savedInstanceState);
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+
+ outState.putInt(LAST_LIST_POS, mSavedFirstVisiblePosition);
+ outState.putInt(LAST_LIST_OFFSET, mSavedFirstItemOffset);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+
+ // Remember where the list is scrolled to so we can restore the scroll position
+ // when we come back to this activity and *after* we complete querying for the
+ // conversations.
+ mSavedFirstVisiblePosition = mAppsList.getFirstVisiblePosition();
+ View firstChild = mAppsList.getChildAt(0);
+ mSavedFirstItemOffset = (firstChild == null) ? 0 : firstChild.getTop();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ // rebuild the list; the user might have changed settings inbetween
+ scheduleAppsLoad();
+ }
+
+ @Override
+ public Loader<List<AppInfo>> onCreateLoader(int id, Bundle args) {
+ mLoadingContainer.startAnimation(AnimationUtils.loadAnimation(
+ mActivity, android.R.anim.fade_in));
+ mAppsList.startAnimation(AnimationUtils.loadAnimation(
+ mActivity, android.R.anim.fade_out));
+
+ mAppsList.setVisibility(View.INVISIBLE);
+ mLoadingContainer.setVisibility(View.VISIBLE);
+ return new AppInfoLoader(mActivity, shouldShowSystemApps());
+ }
+
+ @Override
+ public void onLoadFinished(Loader<List<AppInfo>> loader, List<AppInfo> apps) {
+ mApps = apps;
+ prepareAppAdapter();
+
+ mLoadingContainer.startAnimation(AnimationUtils.loadAnimation(
+ mActivity, android.R.anim.fade_out));
+ mAppsList.startAnimation(AnimationUtils.loadAnimation(
+ mActivity, android.R.anim.fade_in));
+
+ if (mSavedFirstVisiblePosition != AdapterView.INVALID_POSITION) {
+ mAppsList.setSelectionFromTop(mSavedFirstVisiblePosition, mSavedFirstItemOffset);
+ mSavedFirstVisiblePosition = AdapterView.INVALID_POSITION;
+ }
+
+ mLoadingContainer.setVisibility(View.INVISIBLE);
+ mAppsList.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void onLoaderReset(Loader<List<AppInfo>> loader) {
+ }
+
+ private void scheduleAppsLoad() {
+ getLoaderManager().restartLoader(0, null, this);
+ }
+
+ private void prepareAppAdapter() {
+ // if app list is empty inform the user
+ // else go ahead and construct the list
+ if (mApps == null || mApps.isEmpty()) {
+ mNoUserAppsInstalled.setText(R.string.privacy_guard_no_user_apps);
+ mNoUserAppsInstalled.setVisibility(View.VISIBLE);
+ mAppsList.setVisibility(View.GONE);
+ mAppsList.setAdapter(null);
+ } else {
+ mNoUserAppsInstalled.setVisibility(View.GONE);
+ mAppsList.setVisibility(View.VISIBLE);
+ mAdapter = createAdapter();
+ mAppsList.setAdapter(mAdapter);
+ mAppsList.setFastScrollEnabled(true);
+ }
+ }
+
+ private PrivacyGuardAppListAdapter createAdapter() {
+ String lastSectionIndex = null;
+ ArrayList<String> sections = new ArrayList<String>();
+ ArrayList<Integer> positions = new ArrayList<Integer>();
+ int count = mApps.size(), offset = 0;
+
+ for (int i = 0; i < count; i++) {
+ AppInfo app = mApps.get(i);
+ String sectionIndex;
+
+ if (!app.enabled) {
+ sectionIndex = "--"; //XXX
+ } else if (app.title.isEmpty()) {
+ sectionIndex = "";
+ } else {
+ sectionIndex = app.title.substring(0, 1).toUpperCase();
+ }
+
+ if (lastSectionIndex == null ||
+ !TextUtils.equals(sectionIndex, lastSectionIndex)) {
+ sections.add(sectionIndex);
+ positions.add(offset);
+ lastSectionIndex = sectionIndex;
+ }
+ offset++;
+ }
+
+ return new PrivacyGuardAppListAdapter(mActivity, mApps, sections, positions);
+ }
+
+ private void resetPrivacyGuard() {
+ if (mApps == null || mApps.isEmpty()) {
+ return;
+ }
+ showResetDialog();
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ // on click change the privacy guard status for this item
+ final AppInfo app = (AppInfo) parent.getItemAtPosition(position);
+
+ app.privacyGuardEnabled = !app.privacyGuardEnabled;
+ mAppOps.setPrivacyGuardSettingForPackage(app.uid, app.packageName, app.privacyGuardEnabled);
+
+ mAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
+ // on long click open app details window
+ final AppInfo app = (AppInfo) parent.getItemAtPosition(position);
+
+ Bundle args = new Bundle();
+ args.putString("package", app.packageName);
+
+ final Intent i = new Intent(Settings.ACTION_APP_OPS_DETAILS_SETTINGS,
+ Uri.fromParts("package", app.packageName, null));
+ mActivity.startActivityForResult(i, 0);
+ return true;
+ }
+
+
+ private boolean shouldShowSystemApps() {
+ return mPreferences.getBoolean("show_system_apps", false);
+ }
+
+ public static class HelpDialogFragment extends DialogFragment {
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ return new AlertDialog.Builder(getActivity())
+ .setTitle(R.string.privacy_guard_help_title)
+ .setMessage(R.string.privacy_guard_help_text)
+ .setNegativeButton(R.string.dlg_ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.cancel();
+ }
+ })
+ .create();
+ }
+
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ getActivity().getSharedPreferences("privacy_guard_manager", Activity.MODE_PRIVATE)
+ .edit()
+ .putBoolean("first_help_shown", true)
+ .commit();
+ }
+ }
+
+ private void showHelp() {
+ HelpDialogFragment fragment = new HelpDialogFragment();
+ fragment.show(getFragmentManager(), "help_dialog");
+ }
+
+ public static class ResetDialogFragment extends DialogFragment {
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ return new AlertDialog.Builder(getActivity())
+ .setTitle(R.string.privacy_guard_reset_title)
+ .setMessage(R.string.privacy_guard_reset_text)
+ .setPositiveButton(R.string.dlg_ok,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ ((PrivacyGuardManager)getTargetFragment()).doReset();
+ }
+ })
+ .setNegativeButton(R.string.cancel,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ // Do nothing
+ }
+ })
+ .create();
+ }
+ }
+
+ private void doReset() {
+ // turn off privacy guard for all apps shown in the current list
+ for (AppInfo app : mApps) {
+ app.privacyGuardEnabled = false;
+ }
+ mAppOps.resetAllModes();
+ mAdapter.notifyDataSetChanged();
+ }
+
+ private void showResetDialog() {
+ ResetDialogFragment dialog = new ResetDialogFragment();
+ dialog.setTargetFragment(this, 0);
+ dialog.show(getFragmentManager(), "reset_dialog");
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ super.onCreateOptionsMenu(menu, inflater);
+ inflater.inflate(R.menu.privacy_guard_manager, menu);
+ menu.findItem(R.id.show_system_apps).setChecked(shouldShowSystemApps());
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.help:
+ showHelp();
+ return true;
+ case R.id.reset:
+ resetPrivacyGuard();
+ return true;
+ case R.id.show_system_apps:
+ final String prefName = "show_system_apps";
+ // set the menu checkbox and save it in
+ // shared preference and rebuild the list
+ item.setChecked(!item.isChecked());
+ mPreferences.edit().putBoolean(prefName, item.isChecked()).commit();
+ scheduleAppsLoad();
+ return true;
+ case R.id.advanced:
+ Intent i = new Intent("android.settings.APP_OPS_SETTINGS");
+ mActivity.startActivityForResult(i, 0);
+ return true;
+ default:
+ return super.onContextItemSelected(item);
+ }
+ }
+}
diff --git a/src/org/cyanogenmod/cmparts/privacyguard/PrivacyGuardPrefs.java b/src/org/cyanogenmod/cmparts/privacyguard/PrivacyGuardPrefs.java
new file mode 100644
index 0000000..f2b6985
--- /dev/null
+++ b/src/org/cyanogenmod/cmparts/privacyguard/PrivacyGuardPrefs.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2013 Slimroms
+ * Copyright (C) 2016 The CyanogenMod 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.cmparts.privacyguard;
+
+import android.os.Bundle;
+import android.provider.Settings;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.preference.PreferenceFragment;
+import android.preference.SwitchPreference;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ListView;
+
+import org.cyanogenmod.cmparts.R;
+
+import cyanogenmod.providers.CMSettings;
+
+public class PrivacyGuardPrefs extends PreferenceFragment implements
+ OnPreferenceChangeListener {
+
+ private static final String TAG = "PrivacyGuardPrefs";
+
+ private static final String KEY_PRIVACY_GUARD_DEFAULT = "privacy_guard_default";
+
+ private SwitchPreference mPrivacyGuardDefault;
+
+ public static PrivacyGuardPrefs newInstance() {
+ PrivacyGuardPrefs privacyGuardFragment = new PrivacyGuardPrefs();
+ return privacyGuardFragment;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ addPreferencesFromResource(R.xml.privacy_guard_prefs);
+
+ mPrivacyGuardDefault = (SwitchPreference) findPreference(KEY_PRIVACY_GUARD_DEFAULT);
+ mPrivacyGuardDefault.setOnPreferenceChangeListener(this);
+
+ mPrivacyGuardDefault.setChecked(CMSettings.Secure.getInt(
+ getActivity().getContentResolver(),
+ CMSettings.Secure.PRIVACY_GUARD_DEFAULT, 0) == 1);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater,
+ ViewGroup container, Bundle savedInstanceState) {
+ final View view = super.onCreateView(inflater, container, savedInstanceState);
+ final ListView list = (ListView) view.findViewById(android.R.id.list);
+ // our container already takes care of the padding
+ int paddingTop = list.getPaddingTop();
+ int paddingBottom = list.getPaddingBottom();
+ list.setPadding(0, paddingTop, 0, paddingBottom);
+ return view;
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ if (preference == mPrivacyGuardDefault) {
+ boolean value = (Boolean) newValue;
+ CMSettings.Secure.putInt(getActivity().getContentResolver(),
+ CMSettings.Secure.PRIVACY_GUARD_DEFAULT, value ? 1 : 0);
+ return true;
+ }
+ return false;
+ }
+}