summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVineet Patil <vpatil@cyngn.com>2014-11-12 17:14:27 -0800
committerGerrit Code Review <gerrit@cyanogenmod.org>2015-11-24 12:44:15 -0800
commit5f350518a2aec87c057152191eddddad21083d1c (patch)
treebe00269646217027b249e0ab56a20832d60b793b
parent99d6fd01a334b4057e7b56ce3ece09c3e6124bf9 (diff)
downloadpackages_apps_Settings-5f350518a2aec87c057152191eddddad21083d1c.zip
packages_apps_Settings-5f350518a2aec87c057152191eddddad21083d1c.tar.gz
packages_apps_Settings-5f350518a2aec87c057152191eddddad21083d1c.tar.bz2
Re-Implementation of Protected App Settings
Protected App [2/3] Protected Apps Settings -> Apps: - Added Receiver which can send in a call to PackageManager to toggle a components protected status. - Add Protected Apps activity (available from Apps fragment) - Reads from ApplicationInfo state - Requires Pattern Lock to view/modify protected apps - Updates Settings Secure DB with protected components - Support resetting protected apps pattern lock Change-Id: If07a7b69ac963ffae855621881e1944fc8754782 Protected Apps: - App Info for protected Apps doesn't allow Uninstall or Clear data - App Info menu item for launching into Protected Apps when looking at protected components - Prevent process dialog from getting dismissed on touch or back key (can cause odd behavior if cancelled mid-protect) Conflicts: src/com/android/settings/applications/InstalledAppDetails.java Change-Id: I64104d7ff3fbf9d8c393ebf262d4de0b28abbc5c Reset Pattern: - If user cancels while creating new pattern, old pattern is restored Change-Id: I55955b1ffadca2ba712c40c7d443c4fc4b0f528c Clean up protected apps code (1/2) - Work with actual ComponentNames instead of converting them between String and ComponentName all the time - Name protection state parameter in methods 'state' instead of 'protect', as a value of true actually means it's not protected. Also consistently use the respective constants for its values. - Some misc. cleanup Change-Id: I2855978c8aef3cfa14249e3398039c7cdd145ede Settings: Create a security fallback on protected apps. -- Allow a user to bypass pattern lock on protected apps by inputting their primary account information for the device. Conflicts: res/values/cm_dimens.xml res/values/cm_strings.xml Change-Id: I39e5a89a8699cfd2ffaba8aea2daa4f477f2cc9b settings: reset protected apps view instead of finish the activity Finish the app after resettings the account, will lead to the protected app selection without a valid pattern. Instead this should reset the pattern view to create a new pattern prior to go to protected apps. Change-Id: Ida41a29f4f8787940f803a23014a68a2f8beb969 Signed-off-by: Jorge Ruesga <jorge@ruesga.com> Settings: Don't show protected apps options in restricted profiles. Change-Id: I38c2e8fd3508d360f0e23703ce6c939f02f3714e JIRA: 4668 Issue: https://jira.cyanogenmod.org/browse/CYAN-4668 Follow normal lock screen convention - allow user to retry when creating pattern lock Change-Id: I6ad39b07b8de3de03146322b37b60e3f846093d1 Fix: Even when the list is entirely unchecked there may still be components which are still protected and need to cleared when Reset is triggered. https://jira.cyanogenmod.org/browse/BACON-679 https://jira.cyanogenmod.org/browse/CYAN-4835 Change-Id: Ifee3e8b87be39769aedfed0f9a5bda366c67ee45 ProtectedApps pattern lock for landscape mode. Change-Id: I9ef70a0e363d4d17510188d24f4742f458fba38e Launch Protected Apps Change-Id: I2dc2e1e05c1979118d5324c3c05adfcc6f7ee22a Protected Apps: update protected apps screens UI Change-Id: I2dd922956f8ffd9ed153c3d1aa1f9161a127e4c6 Remove unnecessary drawables for launching Protected Apps Change-Id: I58f471ef9d64c7ced79befbfc30b94d75a0085a2 ProtectedAppsReceiver: fix NPE when components are null * To reproduce: create a folder at Trebuchet, lock it and leave the screen Change-Id: I49a2e76fdaa3e375b0ea5aa2bb05eaa92528dd19 Protected Apps: Show state by component instead of by app Change-Id: Idbe1d69b376fc3f42980404d9448152f606e7f8e (cherry picked from commit 56e11c8640cb9e34d07eb11fb1b67f3283b2f6f6) Protected Apps: Increase hit target of the launch app button Change-Id: I38355aca37079d43975d287ee9d81c3a500c3575 (cherry picked from commit 47e8e46f53cdf4f536845d57cc383d3c5f0ca893) Protected Apps: Monitor unlock status Monitor the Activities unlock status so that we can rotate the screen in this activity without having to unlock again. Change-Id: I8feab5cb4d55c4df0d0d1475ab6646c046f01925 Protected Apps: add some side padding in app list view Change-Id: I2549fd8f8b662e2e2c542e67cbc20e6a366fba42 Conflicts: AndroidManifest.xml res/values/cm_dimens.xml res/values/cm_strings.xml res/values/strings.xml src/com/android/settings/applications/ApplicationsState.java src/com/android/settings/applications/InstalledAppDetails.java src/com/android/settings/applications/ManageApplications.java Conflicts: res/values/cm_dimens.xml Conflicts: res/values/cm_strings.xml . Change-Id: I065ac7965a8c7253ad67a20806201fe8fc6cec84
-rwxr-xr-x[-rw-r--r--]AndroidManifest.xml47
-rw-r--r--res/drawable-hdpi/folder_lock.pngbin0 -> 461 bytes
-rw-r--r--res/drawable-mdpi/folder_lock.pngbin0 -> 372 bytes
-rw-r--r--res/drawable-xhdpi/folder_lock.pngbin0 -> 601 bytes
-rw-r--r--res/drawable-xxhdpi/folder_lock.pngbin0 -> 746 bytes
-rw-r--r--res/drawable/ic_launch_app.xml31
-rw-r--r--res/drawable/ic_settings_lockscreen.xml27
-rw-r--r--res/drawable/ic_settings_protected.pngbin0 -> 750 bytes
-rw-r--r--res/drawable/launch_app.xml17
-rw-r--r--res/layout-land/patternlock.xml90
-rw-r--r--res/layout/hidden_apps_list.xml21
-rw-r--r--res/layout/hidden_apps_list_item.xml77
-rw-r--r--res/layout/patternlock.xml92
-rw-r--r--res/layout/protected_account_view.xml71
-rw-r--r--res/values/cm_dimens.xml4
-rw-r--r--res/values/cm_strings.xml18
-rwxr-xr-x[-rw-r--r--]res/values/strings.xml7
-rw-r--r--src/com/android/settings/Utils.java5
-rw-r--r--src/com/android/settings/applications/AppInfoBase.java3
-rwxr-xr-xsrc/com/android/settings/applications/InstalledAppDetails.java51
-rw-r--r--src/com/android/settings/applications/LockPatternActivity.java339
-rw-r--r--src/com/android/settings/applications/ProtectedAppsActivity.java481
-rw-r--r--src/com/android/settings/cyanogenmod/ProtectedAccountView.java281
-rw-r--r--src/com/android/settings/cyanogenmod/ProtectedAppsReceiver.java118
-rw-r--r--src/com/android/settings/widget/CheckableLinearLayout.java64
-rw-r--r--src/com/android/settings/widget/InertCheckBox.java84
26 files changed, 1927 insertions, 1 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 3b1a447..1b1159f 100644..100755
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -84,6 +84,7 @@
<uses-permission android:name="cyanogenmod.permission.HARDWARE_ABSTRACTION_ACCESS" />
<uses-permission android:name="cyanogenmod.permission.FINISH_SETUP" />
<uses-permission android:name="cyanogenmod.permission.MODIFY_PROFILES" />
+ <uses-permission android:name="cyanogenmod.permission.PROTECTED_APP" />
<application android:label="@string/settings_label"
android:icon="@mipmap/ic_launcher_settings"
@@ -2554,6 +2555,25 @@
<activity android:name=".cyanogenmod.PrivacySettings" />
+ <activity android:name=".applications.ProtectedAppsActivity"
+ android:label="@string/protected_apps"
+ android:excludeFromRecents="true"
+ android:configChanges="orientation|keyboardHidden|screenSize">
+ <intent-filter>
+ <action android:name=".applications.ProtectedAppsActivity" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name=".applications.LockPatternActivity"
+ android:label="@string/protected_apps"
+ android:excludeFromRecents="true">
+ <intent-filter>
+ <action android:name=".applications.LockPatternActivity" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
<receiver android:name=".widget.SettingsAppWidgetProvider"
android:label="@string/gadget_title"
android:exported="false"
@@ -2750,5 +2770,32 @@
android:name=".cyanogenmod.LtoService">
</service>
+ <receiver
+ android:name=".cyanogenmod.ProtectedAppsReceiver"
+ android:permission="cyanogenmod.permission.PROTECTED_APP">
+ <intent-filter>
+ <action android:name="cyanogenmod.intent.action.PACKAGE_PROTECTED" />
+ </intent-filter>
+ </receiver>
+
+ <activity android:name=".applications.ProtectedAppsActivity"
+ android:label="@string/protected_apps"
+ android:excludeFromRecents="true"
+ android:configChanges="orientation">
+ <intent-filter>
+ <action android:name=".applications.ProtectedAppsActivity" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name=".applications.LockPatternActivity"
+ android:label="@string/protected_apps"
+ android:excludeFromRecents="true">
+ <intent-filter>
+ <action android:name=".applications.LockPatternActivity" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
</application>
</manifest>
diff --git a/res/drawable-hdpi/folder_lock.png b/res/drawable-hdpi/folder_lock.png
new file mode 100644
index 0000000..ca849e8
--- /dev/null
+++ b/res/drawable-hdpi/folder_lock.png
Binary files differ
diff --git a/res/drawable-mdpi/folder_lock.png b/res/drawable-mdpi/folder_lock.png
new file mode 100644
index 0000000..293444e
--- /dev/null
+++ b/res/drawable-mdpi/folder_lock.png
Binary files differ
diff --git a/res/drawable-xhdpi/folder_lock.png b/res/drawable-xhdpi/folder_lock.png
new file mode 100644
index 0000000..4342c50
--- /dev/null
+++ b/res/drawable-xhdpi/folder_lock.png
Binary files differ
diff --git a/res/drawable-xxhdpi/folder_lock.png b/res/drawable-xxhdpi/folder_lock.png
new file mode 100644
index 0000000..4226f6a
--- /dev/null
+++ b/res/drawable-xxhdpi/folder_lock.png
Binary files differ
diff --git a/res/drawable/ic_launch_app.xml b/res/drawable/ic_launch_app.xml
new file mode 100644
index 0000000..58cb777
--- /dev/null
+++ b/res/drawable/ic_launch_app.xml
@@ -0,0 +1,31 @@
+<?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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="96"
+ android:viewportHeight="96">
+ <path
+ android:fillColor="#009688"
+ android:pathData="M81.3,12.7H44.8c-0.8,0-1.5,0.5-1.8,1.2c-0.3,0.7-0.1,1.6,0.4,2.2L57.4,30L27.6,59.7
+ c-2.4,2.4-2.4,6.2,0,8.6c1.2,1.2,2.8,1.8,4.3,1.8c1.6,0,3.1-0.6,4.3-1.8L66,38.6l13.9,13.9c0.4,0.4,0.9,0.6,1.4,0.6
+ c0.3,0,0.5,0,0.8-0.2c0.7-0.3,1.2-1,1.2-1.8V14.7C83.3,13.6,82.4,12.7,81.3,12.7z"/>
+
+ <path
+ android:fillColor="#009688"
+ android:pathData="M68.9,50.3c-1.9,0-3.4,1.5-3.4,3.4v22.2H20.1V30.5h20.7c1.9,0,3.4-1.5,3.4-3.4s-1.5-3.4-3.4-3.4H16.6
+ c-1.9,0-3.4,1.5-3.4,3.4v52.3c0,1.9,1.5,3.4,3.4,3.4h52.3c1.9,0,3.4-1.5,3.4-3.4V53.7C72.4,51.8,70.8,50.3,68.9,50.3z"/>
+</vector>
diff --git a/res/drawable/ic_settings_lockscreen.xml b/res/drawable/ic_settings_lockscreen.xml
new file mode 100644
index 0000000..390f812
--- /dev/null
+++ b/res/drawable/ic_settings_lockscreen.xml
@@ -0,0 +1,27 @@
+<?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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="64dp"
+ android:height="64dp"
+ android:viewportWidth="48"
+ android:viewportHeight="48">
+
+ <path
+ android:fillColor="#009688"
+ android:pathData="M36,16h-2v-4c0-5.5-4.5-10-10-10S14,6.5,14,12h3.8c0-3.4,2.8-6.2,6.2-6.2
+s6.2,2.8,6.2,6.2v4H12c-2.2,0-4,1.8-4,4v20c0,2.2,1.8,4,4,4h24c2.2,0,4-1.8,4-4V20C40,17.8,38.2,16,36,16Z
+M36,40H12V20L36,40z" />
+</vector>
diff --git a/res/drawable/ic_settings_protected.png b/res/drawable/ic_settings_protected.png
new file mode 100644
index 0000000..16726d3
--- /dev/null
+++ b/res/drawable/ic_settings_protected.png
Binary files differ
diff --git a/res/drawable/launch_app.xml b/res/drawable/launch_app.xml
new file mode 100644
index 0000000..ae5a91b
--- /dev/null
+++ b/res/drawable/launch_app.xml
@@ -0,0 +1,17 @@
+<?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.
+-->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="@android:color/darker_gray"/>
diff --git a/res/layout-land/patternlock.xml b/res/layout-land/patternlock.xml
new file mode 100644
index 0000000..03efa10
--- /dev/null
+++ b/res/layout-land/patternlock.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2014, 2014 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.
+*/
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:fitsSystemWindows="true" >
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="0dip"
+ android:layout_height="match_parent"
+ android:layout_weight="1.0">
+
+ <!-- header message -->
+ <TextView android:id="@+id/pattern_lock_header"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:textSize="18sp"
+ android:paddingTop="20dp"
+ android:text="@string/lockpattern_recording_intro_header"/>
+
+ <!-- footer can show a message, or confirm / restart buttons -->
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/buttonContainer"
+ android:layout_centerHorizontal="true"
+ android:layout_alignParentBottom="true"
+ android:orientation="horizontal">
+
+ <Button
+ android:id="@+id/pattern_lock_btn_cancel"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:text="@string/cancel"
+ android:layout_weight="1"/>
+
+ <Button
+ android:id="@+id/pattern_lock_btn_continue"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:text="@string/lockpattern_continue_button_text"
+ android:layout_weight="1"/>
+ </LinearLayout>
+ </RelativeLayout>
+ </LinearLayout>
+
+ <View
+ android:background="@*android:drawable/code_lock_top"
+ android:layout_width="2dip"
+ android:layout_height="match_parent" />
+
+ <com.android.internal.widget.LockPatternView
+ android:id="@+id/lock_pattern_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center" />
+
+ <com.android.settings.cyanogenmod.ProtectedAccountView
+ android:id="@+id/lock_account_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:visibility="gone" >
+ <include layout="@layout/protected_account_view" />
+ </com.android.settings.cyanogenmod.ProtectedAccountView>
+
+</LinearLayout>
diff --git a/res/layout/hidden_apps_list.xml b/res/layout/hidden_apps_list.xml
new file mode 100644
index 0000000..fdc1fbf
--- /dev/null
+++ b/res/layout/hidden_apps_list.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+<ListView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/protected_apps_list"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="5dp"
+ android:choiceMode="multipleChoice" />
diff --git a/res/layout/hidden_apps_list_item.xml b/res/layout/hidden_apps_list_item.xml
new file mode 100644
index 0000000..4bbb168
--- /dev/null
+++ b/res/layout/hidden_apps_list_item.xml
@@ -0,0 +1,77 @@
+<?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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+
+ <LinearLayout
+ android:id="@+id/app_item"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:minHeight="48dp"
+ android:orientation="horizontal"
+ android:gravity="center_vertical"
+ android:background="?android:attr/selectableItemBackground"
+ android:descendantFocusability="blocksDescendants">
+
+ <ImageView android:id="@+id/icon"
+ android:layout_width="40dp"
+ android:layout_height="40dp"
+ android:layout_gravity="center" />
+
+ <TextView android:id="@+id/title"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="15dip"
+ android:layout_marginRight="6dip"
+ android:layout_marginTop="6dip"
+ android:layout_marginBottom="6dip"
+ android:layout_weight="1"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal" />
+
+ <com.android.settings.widget.InertCheckBox android:id="@+id/checkbox"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <LinearLayout
+ android:id="@+id/launch_app"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:background="@drawable/launch_app"
+ android:visibility="gone" >
+
+ <View
+ android:layout_width="1dp"
+ android:layout_height="match_parent"
+ android:background="@android:color/darker_gray" />
+
+ <ImageView
+ android:layout_width="30dp"
+ android:layout_height="30dp"
+ android:src="@drawable/ic_launch_app"
+ android:layout_gravity="center"
+ android:layout_marginRight="?android:attr/scrollbarSize"
+ android:paddingLeft="5dp" />
+ </LinearLayout>
+ </LinearLayout>
+</LinearLayout>
diff --git a/res/layout/patternlock.xml b/res/layout/patternlock.xml
new file mode 100644
index 0000000..46d1393
--- /dev/null
+++ b/res/layout/patternlock.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2014, 2014 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.
+*/
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:fitsSystemWindows="true" >
+
+ <Space
+ android:layout_width="1dp"
+ android:layout_height="0dip"
+ android:layout_weight=".5" />
+
+ <TextView
+ android:id="@+id/pattern_lock_header"
+ android:layout_width="wrap_content"
+ android:layout_height="0dip"
+ android:text="@string/lockpattern_recording_intro_header"
+ android:layout_weight="1"
+ android:layout_gravity="center_horizontal"
+ android:textSize="18sp" />
+
+ <View
+ android:background="@*android:drawable/code_lock_top"
+ android:layout_width="match_parent"
+ android:layout_height="2dip" />
+
+ <Space
+ android:layout_width="1dp"
+ android:layout_height="0dip"
+ android:layout_weight=".5" />
+
+ <com.android.internal.widget.LockPatternView
+ android:id="@+id/lock_pattern_view"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_gravity="center"
+ android:layout_weight="4" />
+
+ <com.android.settings.cyanogenmod.ProtectedAccountView
+ android:id="@+id/lock_account_view"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_gravity="center"
+ android:layout_weight="4"
+ android:visibility="gone" >
+ <include layout="@layout/protected_account_view" />
+ </com.android.settings.cyanogenmod.ProtectedAccountView>
+
+ <Space
+ android:layout_width="1dp"
+ android:layout_height="0dip"
+ android:layout_weight="1" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_alignParentBottom="true">
+
+ <Button
+ android:id="@+id/pattern_lock_btn_cancel"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/cancel"/>
+
+ <Button
+ android:id="@+id/pattern_lock_btn_continue"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/lockpattern_continue_button_text"/>
+ </LinearLayout>
+</LinearLayout> \ No newline at end of file
diff --git a/res/layout/protected_account_view.xml b/res/layout/protected_account_view.xml
new file mode 100644
index 0000000..e49c329
--- /dev/null
+++ b/res/layout/protected_account_view.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2014, 2014 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.
+*/
+-->
+<com.android.settings.cyanogenmod.ProtectedAccountView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/pa_account_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1">
+
+ <EditText
+ android:id="@+id/login"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dip"
+ android:layout_marginStart="7dip"
+ android:layout_marginEnd="7dip"
+ android:layout_alignParentTop="true"
+ android:hint="@string/pa_login_username_hint"
+ android:inputType="textEmailAddress"
+ />
+
+ <EditText
+ android:id="@+id/password"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/login"
+ android:layout_toLeftOf="@+id/ok"
+ android:layout_marginTop="15dip"
+ android:layout_marginStart="7dip"
+ android:layout_marginEnd="7dip"
+ android:inputType="textPassword"
+ android:hint="@string/pa_login_password_hint"
+ android:nextFocusRight="@+id/ok"
+ android:nextFocusDown="@+id/ok"
+ />
+
+ <!-- ok below password, aligned to right of screen -->
+ <Button
+ android:id="@+id/ok"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="7dip"
+ android:layout_alignParentEnd="true"
+ android:layout_below="@id/login"
+ android:text="@string/pa_login_submit_button"
+ />
+
+ </RelativeLayout>
+
+</com.android.settings.cyanogenmod.ProtectedAccountView>
diff --git a/res/values/cm_dimens.xml b/res/values/cm_dimens.xml
index 373a98e..4298d79 100644
--- a/res/values/cm_dimens.xml
+++ b/res/values/cm_dimens.xml
@@ -18,4 +18,8 @@
<dimen name="profile_instruction_padding">8dp</dimen>
<dimen name="fab_line_width">2dp</dimen>
<dimen name="oval_notification_size">26dp</dimen>
+ <!-- Width of the sliding KeyguardSecurityContainer (includes 2x keyguard_security_view_margin) -->
+ <dimen name="pa_security_width">320dp</dimen>
+ <!-- Height of the sliding KeyguardSecurityContainer (includes 2x keyguard_security_view_margin) -->
+ <dimen name="pa_security_height">400dp</dimen>
</resources>
diff --git a/res/values/cm_strings.xml b/res/values/cm_strings.xml
index 2050c63..156d9e8 100644
--- a/res/values/cm_strings.xml
+++ b/res/values/cm_strings.xml
@@ -794,4 +794,22 @@
<string name="preview_carrier_title">Carrier</string>
<string name="stats_collection_title">Stats collection</string>
<string name="stats_collection_summary">When enabled, allows metrics collection.</string>
+
+ <!-- Protected apps lockpattern reset button -->
+ <string name="lockpattern_reset_button">Reset pattern</string>
+ <string name="lockpattern_settings_reset_summary">Enter primary email account and associated password to reset pattern lock</string>
+
+ <!-- Protected Apps -->
+ <string name="menu_hidden_apps_delete">Reset</string>
+ <string name="menu_hidden_apps_reset_lock">Reset pattern lock</string>
+ <string name="protected_apps">Protected apps</string>
+ <string name="saving_protected_components">Saving component state\u2026</string>
+ <string name="launch_app">Launch app</string>
+
+ <string name="pa_login_username_hint">Username (email)</string>
+ <string name="pa_login_password_hint">Password</string>
+ <string name="pa_login_submit_button">Sign in</string>
+ <string name="pa_login_checking_password">Checking account\u2026</string>
+ <string name="pa_login_incorrect_login">Login was incorrect</string>
+
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index fde5c9b..74f1362 100644..100755
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -7062,4 +7062,11 @@
<!-- Description of feature to change color setting for the display [CHAR LIMIT=NONE] -->
<string name="picture_color_mode_desc">Use sRGB</string>
+
+ <!-- Protected Apps -->
+ <string name="menu_hidden_apps_delete">Reset</string>
+ <string name="menu_hidden_apps_reset_lock">Reset pattern lock</string>
+ <string name="protected_apps">Protected apps</string>
+ <string name="saving_protected_components">Saving component state</string>
+
</resources>
diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java
index 5d3980f..34a27fa 100644
--- a/src/com/android/settings/Utils.java
+++ b/src/com/android/settings/Utils.java
@@ -721,6 +721,11 @@ public final class Utils {
.getUsers().size() > 1;
}
+ public static boolean isRestrictedProfile(Context context) {
+ UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ return um.getUserInfo(um.getUserHandle()).isRestricted();
+ }
+
private static int getScreenType(Context context) {
if (sDeviceType == -1) {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
diff --git a/src/com/android/settings/applications/AppInfoBase.java b/src/com/android/settings/applications/AppInfoBase.java
index ff618c2..9528ec1 100644
--- a/src/com/android/settings/applications/AppInfoBase.java
+++ b/src/com/android/settings/applications/AppInfoBase.java
@@ -123,7 +123,8 @@ public abstract class AppInfoBase extends SettingsPreferenceFragment
mPackageInfo = mPm.getPackageInfo(mAppEntry.info.packageName,
PackageManager.GET_DISABLED_COMPONENTS |
PackageManager.GET_UNINSTALLED_PACKAGES |
- PackageManager.GET_SIGNATURES);
+ PackageManager.GET_SIGNATURES |
+ PackageManager.GET_ACTIVITIES);
} catch (NameNotFoundException e) {
Log.e(TAG, "Exception when retrieving package:" + mAppEntry.info.packageName, e);
}
diff --git a/src/com/android/settings/applications/InstalledAppDetails.java b/src/com/android/settings/applications/InstalledAppDetails.java
index 8b6fe53..b5e24e5 100755
--- a/src/com/android/settings/applications/InstalledAppDetails.java
+++ b/src/com/android/settings/applications/InstalledAppDetails.java
@@ -28,6 +28,7 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.Loader;
+import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -63,6 +64,7 @@ import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
+import com.android.settings.cyanogenmod.ProtectedAppsReceiver;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.os.BatterySipper;
@@ -106,10 +108,12 @@ public class InstalledAppDetails extends AppInfoBase
// Menu identifiers
public static final int UNINSTALL_ALL_USERS_MENU = 1;
public static final int UNINSTALL_UPDATES = 2;
+ public static final int OPEN_PROTECTED_APPS = 3;
// Result code identifiers
public static final int REQUEST_UNINSTALL = 0;
private static final int SUB_INFO_FRAGMENT = 1;
+ public static final int REQUEST_TOGGLE_PROTECTION = 3;
private static final int LOADER_CHART_DATA = 2;
@@ -236,6 +240,12 @@ public class InstalledAppDetails extends AppInfoBase
enabled = false;
}
+ // This is a protected app component.
+ // You cannot a uninstall a protected component
+ if (mPackageInfo.applicationInfo.protect) {
+ enabled = false;
+ }
+
mUninstallButton.setEnabled(enabled);
if (enabled) {
// Register listener
@@ -375,6 +385,9 @@ public class InstalledAppDetails extends AppInfoBase
.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
menu.add(0, UNINSTALL_ALL_USERS_MENU, 1, R.string.uninstall_all_users_text)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
+ menu.add(0, OPEN_PROTECTED_APPS, Menu.NONE, R.string.protected_apps)
+ .setIcon(getResources().getDrawable(R.drawable.folder_lock))
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
}
@Override
@@ -399,6 +412,9 @@ public class InstalledAppDetails extends AppInfoBase
menu.findItem(UNINSTALL_ALL_USERS_MENU).setVisible(showIt);
mUpdatedSysApp = (mAppEntry.info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
menu.findItem(UNINSTALL_UPDATES).setVisible(mUpdatedSysApp && !mAppControlRestricted);
+
+ menu.findItem(OPEN_PROTECTED_APPS).setVisible(mPackageInfo.applicationInfo.protect);
+
}
@Override
@@ -410,6 +426,10 @@ public class InstalledAppDetails extends AppInfoBase
case UNINSTALL_UPDATES:
showDialogInner(DLG_FACTORY_RESET, 0);
return true;
+ case OPEN_PROTECTED_APPS:
+ // Verify protection for toggling protected component status
+ Intent protectedApps = new Intent(getActivity(), LockPatternActivity.class);
+ startActivityForResult(protectedApps, REQUEST_TOGGLE_PROTECTION);
}
return false;
}
@@ -435,6 +455,37 @@ public class InstalledAppDetails extends AppInfoBase
if (!refreshUi()) {
setIntentAndFinish(true, true);
}
+ } else if (requestCode == REQUEST_TOGGLE_PROTECTION) {
+ switch (resultCode) {
+ case Activity.RESULT_OK:
+ new ToggleProtectedAppComponents().execute();
+ break;
+ case Activity.RESULT_CANCELED:
+ // User failed to enter/confirm a lock pattern, do nothing
+ break;
+ }
+ }
+ }
+
+ private class ToggleProtectedAppComponents extends AsyncTask<Void, Void, Void> {
+ @Override
+ protected void onPostExecute(Void aVoid) {
+ getActivity().invalidateOptionsMenu();
+ if (!refreshUi()) {
+ setIntentAndFinish(true, true);
+ }
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ ArrayList<ComponentName> components = new ArrayList<ComponentName>();
+ for (ActivityInfo aInfo : mPackageInfo.activities) {
+ components.add(new ComponentName(aInfo.packageName, aInfo.name));
+ }
+
+ ProtectedAppsReceiver.updateProtectedAppComponentsAndNotify(getActivity(),
+ components, PackageManager.COMPONENT_VISIBLE_STATUS);
+ return null;
}
}
diff --git a/src/com/android/settings/applications/LockPatternActivity.java b/src/com/android/settings/applications/LockPatternActivity.java
new file mode 100644
index 0000000..76ae423
--- /dev/null
+++ b/src/com/android/settings/applications/LockPatternActivity.java
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2014 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 com.android.settings.applications;
+
+import android.app.Activity;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.util.Base64;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+import android.widget.Toast;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockPatternView;
+import com.android.settings.R;
+import com.android.settings.cyanogenmod.ProtectedAccountView;
+import com.android.settings.cyanogenmod.ProtectedAccountView.OnNotifyAccountReset;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.List;
+
+public class LockPatternActivity extends Activity implements OnNotifyAccountReset {
+ public static final String PATTERN_LOCK_PROTECTED_APPS = "pattern_lock_protected_apps";
+ public static final String RECREATE_PATTERN = "recreate_pattern_lock";
+
+ private static final int MIN_PATTERN_SIZE = 4;
+ private static final int MAX_PATTERN_RETRY = 5;
+ private static final int PATTERN_CLEAR_TIMEOUT_MS = 2000;
+
+ private static final int MENU_RESET = 0;
+
+ LockPatternView mLockPatternView;
+ ProtectedAccountView mAccountView;
+
+ TextView mPatternLockHeader;
+ MenuItem mItem;
+ Button mCancel;
+ Button mContinue;
+ byte[] mPatternHash;
+
+ int mRetry = 0;
+
+ boolean mCreate;
+ boolean mRetryPattern = true;
+ boolean mConfirming = false;
+
+ Runnable mCancelPatternRunnable = new Runnable() {
+ public void run() {
+ mLockPatternView.clearPattern();
+ mContinue.setEnabled(false);
+
+ if (mCreate) {
+ if (mConfirming) {
+ mPatternLockHeader.setText(getResources()
+ .getString(R.string.lockpattern_need_to_confirm));
+ } else {
+ mPatternLockHeader.setText(getResources()
+ .getString(R.string.lockpattern_recording_intro_header));
+ mCancel.setText(getResources().getString(R.string.cancel));
+ }
+ } else {
+ mPatternLockHeader.setText(getResources()
+ .getString(R.string.lockpattern_settings_enable_summary));
+ }
+ }
+ };
+
+ View.OnClickListener mCancelOnClickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mCreate && !mConfirming && !mRetryPattern) {
+ // Retry
+ mRetryPattern = true;
+ resetPatternState(true);
+ return;
+ }
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+ };
+
+ View.OnClickListener mContinueOnClickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Button btn = (Button) v;
+ if (mConfirming) {
+ SharedPreferences prefs = PreferenceManager
+ .getDefaultSharedPreferences(getApplicationContext());
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putString(PATTERN_LOCK_PROTECTED_APPS,
+ Base64.encodeToString(mPatternHash, Base64.DEFAULT));
+ editor.commit();
+ setResult(RESULT_OK);
+ finish();
+ } else {
+ mConfirming = true;
+ mCancel.setText(getResources().getString(R.string.cancel));
+ mLockPatternView.clearPattern();
+
+ mPatternLockHeader.setText(getResources().getString(
+ R.string.lockpattern_need_to_confirm));
+ btn.setText(getResources().getString(R.string.lockpattern_confirm_button_text));
+ btn.setEnabled(false);
+ }
+ }
+ };
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ menu.clear();
+ if (!mCreate) {
+ menu.add(0, MENU_RESET, 0, R.string.lockpattern_reset_button)
+ .setIcon(R.drawable.ic_lockscreen_ime)
+ .setAlphabeticShortcut('r')
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM |
+ MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+ mItem = menu.findItem(0);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case MENU_RESET:
+ if (mAccountView.getVisibility() == View.VISIBLE) {
+ switchToPattern(false);
+ } else {
+ switchToAccount();
+ }
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public void onNotifyAccountReset() {
+ switchToPattern(true);
+ }
+
+ private void switchToPattern(boolean reset) {
+ if (reset) {
+ resetPatternState(false);
+ }
+ mPatternLockHeader.setText(getResources()
+ .getString(R.string.lockpattern_settings_enable_summary));
+ mItem.setIcon(R.drawable.ic_lockscreen_ime);
+ mAccountView.clearFocusOnInput();
+ mAccountView.setVisibility(View.GONE);
+ mLockPatternView.setVisibility(View.VISIBLE);
+ }
+
+ private void switchToAccount() {
+ mPatternLockHeader.setText(getResources()
+ .getString(R.string.lockpattern_settings_reset_summary));
+ mItem.setIcon(R.drawable.ic_settings_lockscreen);
+ mAccountView.setVisibility(View.VISIBLE);
+ mLockPatternView.setVisibility(View.GONE);
+ }
+
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.patternlock);
+
+ mPatternLockHeader = (TextView) findViewById(R.id.pattern_lock_header);
+ mCancel = (Button) findViewById(R.id.pattern_lock_btn_cancel);
+ mCancel.setOnClickListener(mCancelOnClickListener);
+ mContinue = (Button) findViewById(R.id.pattern_lock_btn_continue);
+ mContinue.setOnClickListener(mContinueOnClickListener);
+
+ mAccountView = (ProtectedAccountView) findViewById(R.id.lock_account_view);
+ mAccountView.setOnNotifyAccountResetCb(this);
+ mLockPatternView = (LockPatternView) findViewById(R.id.lock_pattern_view);
+
+ resetPatternState(false);
+
+ //Setup Pattern Lock View
+ mLockPatternView.setSaveEnabled(false);
+ mLockPatternView.setFocusable(false);
+ mLockPatternView.setOnPatternListener(new UnlockPatternListener());
+
+ }
+
+ private void resetPatternState(boolean clear) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+ String pattern = prefs.getString(PATTERN_LOCK_PROTECTED_APPS, null);
+ mCreate = pattern == null || RECREATE_PATTERN.equals(getIntent().getAction())
+ || clear;
+
+ mPatternHash = null;
+ if (pattern != null) {
+ mPatternHash = Base64.decode(pattern, Base64.DEFAULT);
+ }
+
+ mContinue.setEnabled(!mCreate);
+ mCancel.setVisibility(mCreate ? View.VISIBLE : View.GONE);
+ mCancel.setText(getResources().getString(R.string.cancel));
+ mContinue.setVisibility(mCreate ? View.VISIBLE : View.GONE);
+ mPatternLockHeader.setText(mCreate
+ ? getResources().getString(R.string.lockpattern_recording_intro_header)
+ : getResources().getString(R.string.lockpattern_settings_enable_summary));
+ mLockPatternView.clearPattern();
+
+ invalidateOptionsMenu();
+ }
+
+ private class UnlockPatternListener implements LockPatternView.OnPatternListener {
+
+ public void onPatternStart() {
+ mLockPatternView.removeCallbacks(mCancelPatternRunnable);
+
+ mPatternLockHeader.setText(getResources().getText(
+ R.string.lockpattern_recording_inprogress));
+ mContinue.setEnabled(false);
+ }
+
+ public void onPatternCleared() {
+ }
+
+ public void onPatternDetected(List<LockPatternView.Cell> pattern) {
+ //Check inserted Pattern
+ if (mCreate) {
+ if (pattern.size() < MIN_PATTERN_SIZE) {
+ mPatternLockHeader.setText(getResources().getString(
+ R.string.lockpattern_recording_incorrect_too_short,
+ LockPatternUtils.MIN_LOCK_PATTERN_SIZE));
+
+ mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
+ mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS);
+ mCancel.setText(getResources()
+ .getString(R.string.lockpattern_retry_button_text));
+ mRetryPattern = false;
+ return;
+ }
+
+ if (mConfirming) {
+ if (Arrays.equals(mPatternHash, patternToHash(pattern))) {
+ mContinue.setText(getResources()
+ .getString(R.string.lockpattern_confirm_button_text));
+ mContinue.setEnabled(true);
+ mPatternLockHeader.setText(getResources().getString(
+ R.string.lockpattern_pattern_confirmed_header));
+ } else {
+ mContinue.setEnabled(false);
+
+ mPatternLockHeader.setText(getResources().getString(
+ R.string.lockpattern_need_to_unlock_wrong));
+ mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
+ mLockPatternView.postDelayed(mCancelPatternRunnable,
+ PATTERN_CLEAR_TIMEOUT_MS);
+ }
+ } else {
+ //Save pattern, user needs to redraw to confirm
+ mCancel.setText(getResources()
+ .getString(R.string.lockpattern_retry_button_text));
+ mRetryPattern = false;
+
+ mPatternHash = patternToHash(pattern);
+
+ mPatternLockHeader.setText(getResources().getString(
+ R.string.lockpattern_pattern_entered_header));
+ mContinue.setEnabled(true);
+ }
+ } else {
+ //Check against existing pattern
+ if (Arrays.equals(mPatternHash, patternToHash(pattern))) {
+ setResult(RESULT_OK);
+ finish();
+ } else {
+ mRetry++;
+ mPatternLockHeader.setText(getResources().getString(
+ R.string.lockpattern_need_to_unlock_wrong));
+
+ mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
+ mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS);
+
+ if (mRetry >= MAX_PATTERN_RETRY) {
+ mLockPatternView.removeCallbacks(mCancelPatternRunnable);
+ Toast.makeText(getApplicationContext(),
+ getResources().getString(
+ R.string.lockpattern_too_many_failed_confirmation_attempts),
+ Toast.LENGTH_SHORT).show();
+ switchToAccount();
+ }
+ }
+ }
+ }
+
+ public void onPatternCellAdded(List<LockPatternView.Cell> pattern) {}
+ }
+
+ /*
+ * Generate an SHA-1 hash for the pattern. Not the most secure, but it is
+ * at least a second level of protection. First level is that the file
+ * is in a location only readable by the system process.
+ * @param pattern the gesture pattern.
+ * @return the hash of the pattern in a byte array.
+ */
+ public byte[] patternToHash(List<LockPatternView.Cell> pattern) {
+ if (pattern == null) {
+ return null;
+ }
+
+ final int patternSize = pattern.size();
+ byte[] res = new byte[patternSize];
+ for (int i = 0; i < patternSize; i++) {
+ LockPatternView.Cell cell = pattern.get(i);
+ res[i] = (byte) (cell.getRow() * 3 + cell.getColumn());
+ }
+ try {
+ MessageDigest md = MessageDigest.getInstance("SHA-1");
+ byte[] hash = md.digest(res);
+ return hash;
+ } catch (NoSuchAlgorithmException nsa) {
+ return res;
+ }
+ }
+}
diff --git a/src/com/android/settings/applications/ProtectedAppsActivity.java b/src/com/android/settings/applications/ProtectedAppsActivity.java
new file mode 100644
index 0000000..5ff459a
--- /dev/null
+++ b/src/com/android/settings/applications/ProtectedAppsActivity.java
@@ -0,0 +1,481 @@
+/*
+ * Copyright (C) 2015 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 com.android.settings.applications;
+
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.CheckBox;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+import com.android.settings.R;
+import com.android.settings.cyanogenmod.ProtectedAppsReceiver;
+
+import cyanogenmod.providers.CMSettings;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class ProtectedAppsActivity extends Activity {
+ private static final int REQ_ENTER_PATTERN = 1;
+ private static final int REQ_RESET_PATTERN = 2;
+
+ private static final String NEEDS_UNLOCK = "needs_unlock";
+
+ private ListView mListView;
+
+ private static final int MENU_RESET = 0;
+ private static final int MENU_RESET_LOCK = 1;
+
+ private PackageManager mPackageManager;
+
+ private AppsAdapter mAppsAdapter;
+
+ private ArrayList<ComponentName> mProtect;
+
+ private boolean mWaitUserAuth = false;
+ private boolean mUserIsAuth = false;
+
+ private HashSet<ComponentName> mProtectedApps = new HashSet<ComponentName>();
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setTitle(R.string.protected_apps);
+ setContentView(R.layout.hidden_apps_list);
+
+ mPackageManager = getPackageManager();
+ mAppsAdapter = new AppsAdapter(this, R.layout.hidden_apps_list_item);
+ mAppsAdapter.setNotifyOnChange(true);
+
+ mListView = (ListView) findViewById(R.id.protected_apps_list);
+ mListView.setAdapter(mAppsAdapter);
+
+ mProtect = new ArrayList<ComponentName>();
+
+ if (savedInstanceState != null) {
+ mUserIsAuth = savedInstanceState.getBoolean(NEEDS_UNLOCK);
+ }
+
+ if (!mUserIsAuth) {
+ // Require unlock
+ Intent lockPattern = new Intent(this, LockPatternActivity.class);
+ startActivityForResult(lockPattern, REQ_ENTER_PATTERN);
+ }
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putBoolean(NEEDS_UNLOCK, mUserIsAuth);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ AsyncTask<Void, Void, List<AppEntry>> refreshAppsTask =
+ new AsyncTask<Void, Void, List<AppEntry>>() {
+
+ @Override
+ protected void onPostExecute(List<AppEntry> apps) {
+ mAppsAdapter.clear();
+ mAppsAdapter.addAll(apps);
+ }
+
+ @Override
+ protected List<AppEntry> doInBackground(Void... params) {
+ return refreshApps();
+ }
+ };
+ refreshAppsTask.execute(null, null, null);
+
+ getActionBar().setDisplayHomeAsUpEnabled(true);
+
+ // Update Protected Apps list
+ updateProtectedComponentsList();
+ }
+
+ private void updateProtectedComponentsList() {
+ String protectedComponents = CMSettings.System.getString(getContentResolver(),
+ CMSettings.Secure.PROTECTED_COMPONENTS);
+ protectedComponents = protectedComponents == null ? "" : protectedComponents;
+ String [] flattened = protectedComponents.split("\\|");
+ mProtectedApps = new HashSet<ComponentName>(flattened.length);
+ for (String flat : flattened) {
+ ComponentName cmp = ComponentName.unflattenFromString(flat);
+ if (cmp != null) {
+ mProtectedApps.add(cmp);
+ }
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+
+ // Don't stick around
+ if (mWaitUserAuth && !mUserIsAuth) {
+ finish();
+ }
+ }
+
+ private boolean getProtectedStateFromComponentName(ComponentName componentName) {
+ return mProtectedApps.contains(componentName);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+ case REQ_ENTER_PATTERN:
+ mWaitUserAuth = true;
+ switch (resultCode) {
+ case RESULT_OK:
+ //Nothing to do, proceed!
+ mUserIsAuth = true;
+ break;
+ case RESULT_CANCELED:
+ // user failed to define a pattern, do not lock the folder
+ finish();
+ break;
+ }
+ break;
+ case REQ_RESET_PATTERN:
+ mWaitUserAuth = true;
+ mUserIsAuth = false;
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ menu.add(0, MENU_RESET, 0, R.string.menu_hidden_apps_delete)
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
+ menu.add(0, MENU_RESET_LOCK, 0, R.string.menu_hidden_apps_reset_lock)
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
+ return true;
+ }
+
+ private void reset() {
+ ArrayList<ComponentName> componentsList = new ArrayList<ComponentName>();
+
+ // Check to see if any components that have been protected that aren't present in
+ // the ListView. This can happen if there are components which have been protected
+ // but do not respond to the queryIntentActivities for Launcher Category
+ ContentResolver resolver = getContentResolver();
+ String hiddenComponents = CMSettings.System.getString(resolver,
+ CMSettings.Secure.PROTECTED_COMPONENTS);
+
+ if (hiddenComponents != null && !hiddenComponents.equals("")) {
+ for (String flattened : hiddenComponents.split("\\|")) {
+ ComponentName cmp = ComponentName.unflattenFromString(flattened);
+
+ if (!componentsList.contains(cmp)) {
+ componentsList.add(cmp);
+ }
+ }
+ }
+
+ AppProtectList list = new AppProtectList(componentsList,
+ PackageManager.COMPONENT_VISIBLE_STATUS);
+ StoreComponentProtectedStatus task = new StoreComponentProtectedStatus(this);
+ task.execute(list);
+ }
+
+ private void resetLock() {
+ mWaitUserAuth = false;
+ Intent lockPattern = new Intent(LockPatternActivity.RECREATE_PATTERN, null,
+ this, LockPatternActivity.class);
+ startActivityForResult(lockPattern, REQ_RESET_PATTERN);
+ }
+
+ private List<AppEntry> refreshApps() {
+ Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
+ mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+ List<ResolveInfo> apps = mPackageManager.queryIntentActivities(mainIntent, 0);
+ Collections.sort(apps, new ResolveInfo.DisplayNameComparator(mPackageManager));
+ List<AppEntry> appEntries = new ArrayList<AppEntry>(apps.size());
+ for (ResolveInfo info : apps) {
+ appEntries.add(new AppEntry(info));
+ }
+ return appEntries;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case MENU_RESET:
+ reset();
+ return true;
+ case MENU_RESET_LOCK:
+ resetLock();
+ return true;
+ case android.R.id.home:
+ finish();
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ private final class AppEntry {
+ public final ComponentName componentName;
+ public final String title;
+
+ public AppEntry(ResolveInfo info) {
+ ActivityInfo aInfo = info.activityInfo;
+ componentName = new ComponentName(aInfo.packageName, aInfo.name);
+ title = info.loadLabel(mPackageManager).toString();
+ }
+ }
+
+ private final class AppProtectList {
+ public final ArrayList<ComponentName> componentNames;
+ public final boolean state;
+
+ public AppProtectList(ArrayList<ComponentName> componentNames, boolean state) {
+ this.componentNames = new ArrayList<ComponentName>();
+ for (ComponentName cn : componentNames) {
+ this.componentNames.add(cn.clone());
+ }
+
+ this.state = state;
+ }
+ }
+
+ public class StoreComponentProtectedStatus extends AsyncTask<AppProtectList, Void, Void> {
+ private ProgressDialog mDialog;
+ private Context mContext;
+
+ public StoreComponentProtectedStatus(Context context) {
+ mContext = context;
+ mDialog = new ProgressDialog(mContext);
+ }
+
+ @Override
+ protected void onPreExecute() {
+ mDialog.setMessage(getResources().getString(R.string.saving_protected_components));
+ mDialog.setCancelable(false);
+ mDialog.setCanceledOnTouchOutside(false);
+ mDialog.show();
+ }
+
+ @Override
+ protected void onPostExecute(Void aVoid) {
+ if (mDialog.isShowing()) {
+ mDialog.dismiss();
+ }
+
+ mAppsAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ protected Void doInBackground(final AppProtectList... args) {
+ for (AppProtectList appList : args) {
+ ProtectedAppsReceiver.updateProtectedAppComponentsAndNotify(mContext,
+ appList.componentNames, appList.state);
+ }
+
+ updateProtectedComponentsList();
+ return null;
+ }
+ }
+
+ /**
+ * App view holder used to reuse the views inside the list.
+ */
+ private static class AppViewHolder {
+ public final View container;
+ public final TextView title;
+ public final ImageView icon;
+ public final View launch;
+ public final CheckBox checkBox;
+
+ public AppViewHolder(View parentView) {
+ container = parentView.findViewById(R.id.app_item);
+ icon = (ImageView) parentView.findViewById(R.id.icon);
+ title = (TextView) parentView.findViewById(R.id.title);
+ launch = parentView.findViewById(R.id.launch_app);
+ checkBox = (CheckBox) parentView.findViewById(R.id.checkbox);
+ }
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ }
+
+ public class AppsAdapter extends ArrayAdapter<AppEntry> {
+
+ private final LayoutInflater mInflator;
+
+ private ConcurrentHashMap<String, Drawable> mIcons;
+ private Drawable mDefaultImg;
+ private List<AppEntry> mApps;
+
+ public AppsAdapter(Context context, int textViewResourceId) {
+ super(context, textViewResourceId);
+
+ mApps = new ArrayList<AppEntry>();
+
+ mInflator = LayoutInflater.from(context);
+
+ // set the default icon till the actual app icon is loaded in async task
+ mDefaultImg = context.getResources().getDrawable(android.R.mipmap.sym_def_app_icon);
+ mIcons = new ConcurrentHashMap<String, Drawable>();
+ }
+
+ @Override
+ public View getView(final int position, View convertView, ViewGroup parent) {
+ AppViewHolder viewHolder;
+
+ if (convertView == null) {
+ convertView = mInflator.inflate(R.layout.hidden_apps_list_item, parent, false);
+ viewHolder = new AppViewHolder(convertView);
+ convertView.setTag(viewHolder);
+ } else {
+ viewHolder = (AppViewHolder) convertView.getTag();
+ }
+
+ AppEntry app = getItem(position);
+
+ viewHolder.title.setText(app.title);
+
+ Drawable icon = mIcons.get(app.componentName.getPackageName());
+ viewHolder.icon.setImageDrawable(icon != null ? icon : mDefaultImg);
+
+ boolean state = getProtectedStateFromComponentName(app.componentName);
+ viewHolder.checkBox.setChecked(state);
+ if (state) {
+ viewHolder.launch.setVisibility(View.VISIBLE);
+ viewHolder.launch.setTag(app);
+ viewHolder.launch.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ ComponentName cName = ((AppEntry)v.getTag()).componentName;
+ Intent intent = new Intent();
+ intent.setClassName(cName.getPackageName(), cName.getClassName());
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(intent);
+ }
+ });
+ } else {
+ viewHolder.launch.setVisibility(View.GONE);
+ }
+
+ viewHolder.container.setTag(position);
+ viewHolder.container.setOnClickListener(mAppClickListener);
+ return convertView;
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ @Override
+ public void notifyDataSetChanged() {
+ super.notifyDataSetChanged();
+ // If we have new items, we have to load their icons
+ // If items were deleted, remove them from our mApps
+ List<AppEntry> newApps = new ArrayList<AppEntry>(getCount());
+ List<AppEntry> oldApps = new ArrayList<AppEntry>(getCount());
+ for (int i = 0; i < getCount(); i++) {
+ AppEntry app = getItem(i);
+ if (mApps.contains(app)) {
+ oldApps.add(app);
+ } else {
+ newApps.add(app);
+ }
+ }
+
+ if (newApps.size() > 0) {
+ new LoadIconsTask().execute(newApps.toArray(new AppEntry[] {}));
+ newApps.addAll(oldApps);
+ mApps = newApps;
+ } else {
+ mApps = oldApps;
+ }
+ }
+
+ /**
+ * An asynchronous task to load the icons of the installed applications.
+ */
+ private class LoadIconsTask extends AsyncTask<AppEntry, Void, Void> {
+ @Override
+ protected Void doInBackground(AppEntry... apps) {
+ for (AppEntry app : apps) {
+ try {
+ String packageName = app.componentName.getPackageName();
+ if (mIcons.containsKey(packageName)) {
+ continue;
+ }
+ Drawable icon = mPackageManager.getApplicationIcon(packageName);
+ mIcons.put(packageName, icon);
+ publishProgress();
+ } catch (PackageManager.NameNotFoundException e) {
+ // ignored; app will show up with default image
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ protected void onProgressUpdate(Void... progress) {
+ notifyDataSetChanged();
+ }
+ }
+ }
+
+ private View.OnClickListener mAppClickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ int position = (Integer) v.getTag();
+ ComponentName cn = mAppsAdapter.getItem(position).componentName;
+ ArrayList<ComponentName> componentsList = new ArrayList<ComponentName>();
+ componentsList.add(cn);
+ boolean state = getProtectedStateFromComponentName(cn);
+
+ AppProtectList list = new AppProtectList(componentsList, state);
+ StoreComponentProtectedStatus task =
+ new StoreComponentProtectedStatus(ProtectedAppsActivity.this);
+ task.execute(list);
+ }
+ };
+}
diff --git a/src/com/android/settings/cyanogenmod/ProtectedAccountView.java b/src/com/android/settings/cyanogenmod/ProtectedAccountView.java
new file mode 100644
index 0000000..4b9e14e
--- /dev/null
+++ b/src/com/android/settings/cyanogenmod/ProtectedAccountView.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2014 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 com.android.settings.cyanogenmod;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AccountManagerCallback;
+import android.accounts.AccountManagerFuture;
+import android.accounts.AuthenticatorException;
+import android.accounts.OperationCanceledException;
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.preference.PreferenceManager;
+import android.text.InputFilter;
+import android.text.LoginFilter;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.Toast;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.settings.R;
+import com.android.settings.applications.LockPatternActivity;
+
+import java.io.IOException;
+
+/**
+ * When the user forgets their password a bunch of times, we fall back on their
+ * account's login/password to unlock protected apps (and reset their lock pattern).
+ */
+public class ProtectedAccountView extends LinearLayout implements View.OnClickListener {
+
+ public static interface OnNotifyAccountReset {
+ void onNotifyAccountReset();
+ }
+
+ private EditText mLogin;
+ private EditText mPassword;
+ private Button mOk;
+ private Context mContext;
+ private LockPatternUtils mLockPatternUtils;
+ private OnNotifyAccountReset mNotifyAccountResetCb;
+
+ /**
+ * Shown while making asynchronous check of password.
+ */
+ private ProgressDialog mCheckingDialog;
+
+ public ProtectedAccountView(Context context) {
+ this(context, null);
+ }
+
+ public ProtectedAccountView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ProtectedAccountView(Context context, AttributeSet st, int ds) {
+ super(context, st, ds);
+ mContext = context;
+ mLockPatternUtils = new LockPatternUtils(mContext);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ mLogin = (EditText) findViewById(R.id.login);
+ mLogin.setFilters(new InputFilter[] { new LoginFilter.UsernameFilterGeneric() } );
+ mPassword = (EditText) findViewById(R.id.password);
+
+ mOk = (Button) findViewById(R.id.ok);
+ mOk.setOnClickListener(this);
+
+ reset();
+ }
+
+ @Override
+ protected boolean onRequestFocusInDescendants(int direction,
+ Rect previouslyFocusedRect) {
+ // send focus to the login field
+ return mLogin.requestFocus(direction, previouslyFocusedRect);
+ }
+
+ public boolean needsInput() {
+ return true;
+ }
+
+ public void setOnNotifyAccountResetCb(OnNotifyAccountReset callback) {
+ this.mNotifyAccountResetCb = callback;
+ }
+
+ public void clearFocusOnInput() {
+ mLogin.clearFocus();
+ mPassword.clearFocus();
+
+ // hide keyboard
+ final InputMethodManager imm = (InputMethodManager)
+ mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.hideSoftInputFromWindow(mLogin.getWindowToken(), 0);
+ imm.hideSoftInputFromWindow(mPassword.getWindowToken(), 0);
+ }
+
+ public void reset() {
+ mLogin.setText("");
+ mPassword.setText("");
+ mLogin.requestFocus();
+ }
+
+ /** {@inheritDoc} */
+ public void cleanUp() {
+ if (mCheckingDialog != null) {
+ mCheckingDialog.hide();
+ }
+ }
+
+ public void onClick(View v) {
+ if (v == mOk) {
+ asyncCheckPassword();
+ }
+ }
+
+ private void postOnCheckPasswordResult(final boolean success) {
+ // ensure this runs on UI thread
+ mLogin.post(new Runnable() {
+ public void run() {
+ if (success) {
+
+ Activity baseActivity = (Activity) mContext;
+
+ if (!baseActivity.isFinishing()) {
+ // Remove pattern
+ SharedPreferences prefs = PreferenceManager
+ .getDefaultSharedPreferences(mContext);
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.remove(LockPatternActivity.PATTERN_LOCK_PROTECTED_APPS);
+ editor.commit();
+
+ if (mNotifyAccountResetCb != null) {
+ mNotifyAccountResetCb.onNotifyAccountReset();
+ } else {
+ baseActivity.setResult(Activity.RESULT_OK);
+ baseActivity.finish();
+ }
+ }
+ } else {
+ Toast.makeText(mContext,
+ getResources().getString(
+ R.string.pa_login_incorrect_login),
+ Toast.LENGTH_SHORT).show();
+ mPassword.setText("");
+ }
+ }
+ });
+ }
+
+ /**
+ * Given the string the user entered in the 'username' field, find
+ * the stored account that they probably intended. Prefer, in order:
+ *
+ * - an exact match for what was typed, or
+ * - a case-insensitive match for what was typed, or
+ * - if they didn't include a domain, an exact match of the username, or
+ * - if they didn't include a domain, a case-insensitive
+ * match of the username.
+ *
+ * If there is a tie for the best match, choose neither --
+ * the user needs to be more specific.
+ *
+ * @return an account name from the database, or null if we can't
+ * find a single best match.
+ */
+ private Account findIntendedAccount(String username) {
+ Account[] accounts = AccountManager.get(mContext).getAccountsByTypeAsUser("com.google",
+ new UserHandle(ActivityManager.getCurrentUser()));
+
+ // Try to figure out which account they meant if they
+ // typed only the username (and not the domain), or got
+ // the case wrong.
+
+ Account bestAccount = null;
+ int bestScore = 0;
+ for (Account a: accounts) {
+ int score = 0;
+ if (username.equals(a.name)) {
+ score = 4;
+ } else if (username.equalsIgnoreCase(a.name)) {
+ score = 3;
+ } else if (username.indexOf('@') < 0) {
+ int i = a.name.indexOf('@');
+ if (i >= 0) {
+ String aUsername = a.name.substring(0, i);
+ if (username.equals(aUsername)) {
+ score = 2;
+ } else if (username.equalsIgnoreCase(aUsername)) {
+ score = 1;
+ }
+ }
+ }
+ if (score > bestScore) {
+ bestAccount = a;
+ bestScore = score;
+ } else if (score == bestScore) {
+ bestAccount = null;
+ }
+ }
+ return bestAccount;
+ }
+
+ private void asyncCheckPassword() {
+ final String login = mLogin.getText().toString();
+ final String password = mPassword.getText().toString();
+ Account account = findIntendedAccount(login);
+ if (account == null) {
+ postOnCheckPasswordResult(false);
+ return;
+ }
+ getProgressDialog().show();
+ Bundle options = new Bundle();
+ options.putString(AccountManager.KEY_PASSWORD, password);
+ AccountManager.get(mContext).confirmCredentialsAsUser(account, options, null /* activity */,
+ new AccountManagerCallback<Bundle>() {
+ public void run(AccountManagerFuture<Bundle> future) {
+ try {
+ final Bundle result = future.getResult();
+ final boolean verified = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT);
+ postOnCheckPasswordResult(verified);
+ } catch (OperationCanceledException e) {
+ postOnCheckPasswordResult(false);
+ } catch (IOException e) {
+ postOnCheckPasswordResult(false);
+ } catch (AuthenticatorException e) {
+ postOnCheckPasswordResult(false);
+ } finally {
+ mLogin.post(new Runnable() {
+ public void run() {
+ getProgressDialog().hide();
+ }
+ });
+ }
+ }
+ }, null /* handler */, new UserHandle(ActivityManager.getCurrentUser()));
+ }
+
+ private Dialog getProgressDialog() {
+ if (mCheckingDialog == null) {
+ mCheckingDialog = new ProgressDialog(mContext);
+ mCheckingDialog.setMessage(
+ mContext.getString(R.string.pa_login_checking_password));
+ mCheckingDialog.setIndeterminate(true);
+ mCheckingDialog.setCancelable(false);
+ mCheckingDialog.getWindow().setType(
+ WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+ }
+ return mCheckingDialog;
+ }
+}
+
diff --git a/src/com/android/settings/cyanogenmod/ProtectedAppsReceiver.java b/src/com/android/settings/cyanogenmod/ProtectedAppsReceiver.java
new file mode 100644
index 0000000..0b2e56f
--- /dev/null
+++ b/src/com/android/settings/cyanogenmod/ProtectedAppsReceiver.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2015 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 com.android.settings.cyanogenmod;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.provider.Settings;
+import android.util.Log;
+
+import static cyanogenmod.content.Intent.ACTION_PROTECTED;
+import static cyanogenmod.content.Intent.ACTION_PROTECTED_CHANGED;
+import static cyanogenmod.content.Intent.EXTRA_PROTECTED_COMPONENTS;
+import static cyanogenmod.content.Intent.EXTRA_PROTECTED_STATE;
+
+import cyanogenmod.providers.CMSettings;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+
+public class ProtectedAppsReceiver extends BroadcastReceiver {
+ private static final String TAG = "ProtectedAppsReceiver";
+
+ private static final String PROTECTED_APP_PERMISSION = cyanogenmod.platform.Manifest
+ .permission.PROTECTED_APP;
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (ACTION_PROTECTED.equals(intent.getAction())) {
+ boolean protect = intent.getBooleanExtra(EXTRA_PROTECTED_STATE,
+ PackageManager.COMPONENT_VISIBLE_STATUS);
+ ArrayList<ComponentName> components =
+ intent.getParcelableArrayListExtra(EXTRA_PROTECTED_COMPONENTS);
+ if (components != null) {
+ updateProtectedAppComponentsAndNotify(context, components, protect);
+ }
+ }
+ }
+
+ public static void updateProtectedAppComponentsAndNotify(Context context,
+ ArrayList<ComponentName> components, boolean state) {
+ updateProtectedAppComponents(context, components, state);
+ updateSettingsSecure(context, components, state);
+ notifyProtectedChanged(context, components, state);
+ }
+
+ public static void updateProtectedAppComponents(Context context,
+ ArrayList<ComponentName> components, boolean state) {
+ PackageManager pm = context.getPackageManager();
+
+ for (ComponentName component : components) {
+ try {
+ pm.setComponentProtectedSetting(component, state);
+ } catch (NoSuchMethodError nsm) {
+ Log.e(TAG, "Unable to protected app via PackageManager");
+ }
+ }
+ }
+
+ public static void updateSettingsSecure(Context context,
+ ArrayList<ComponentName> components, boolean state) {
+ ContentResolver resolver = context.getContentResolver();
+ String hiddenComponents = CMSettings.System.getString(resolver,
+ CMSettings.Secure.PROTECTED_COMPONENTS);
+ HashSet<ComponentName> newComponentList = new HashSet<ComponentName>();
+
+ if (hiddenComponents != null) {
+ for (String flattened : hiddenComponents.split("\\|")) {
+ ComponentName cmp = ComponentName.unflattenFromString(flattened);
+ if (cmp != null) {
+ newComponentList.add(cmp);
+ }
+ }
+ }
+
+ boolean update = state == PackageManager.COMPONENT_PROTECTED_STATUS
+ ? newComponentList.addAll(components)
+ : newComponentList.removeAll(components);
+
+ if (update) {
+ StringBuilder flattenedList = new StringBuilder();
+ for (ComponentName cmp : newComponentList) {
+ if (flattenedList.length() > 0) {
+ flattenedList.append("|");
+ }
+ flattenedList.append(cmp.flattenToString());
+ }
+ CMSettings.System.putString(resolver, CMSettings.Secure.PROTECTED_COMPONENTS,
+ flattenedList.toString());
+ }
+ }
+
+ public static void notifyProtectedChanged(Context context,
+ ArrayList<ComponentName> components, boolean state) {
+ Intent intent = new Intent(ACTION_PROTECTED_CHANGED);
+ intent.putExtra(EXTRA_PROTECTED_STATE, state);
+ intent.putExtra(EXTRA_PROTECTED_COMPONENTS, components);
+
+ context.sendBroadcast(intent, PROTECTED_APP_PERMISSION);
+ }
+}
diff --git a/src/com/android/settings/widget/CheckableLinearLayout.java b/src/com/android/settings/widget/CheckableLinearLayout.java
new file mode 100644
index 0000000..a4b9a7d
--- /dev/null
+++ b/src/com/android/settings/widget/CheckableLinearLayout.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2015 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 com.android.settings.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.CheckBox;
+import android.widget.Checkable;
+import android.widget.LinearLayout;
+import com.android.settings.R;
+
+/*
+ * This class is useful for using inside of ListView that needs to have checkable items.
+ */
+public class CheckableLinearLayout extends LinearLayout implements Checkable {
+ private CheckBox mCheckBox;
+
+ public CheckableLinearLayout(Context context) {
+ super(context);
+ }
+
+ public CheckableLinearLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public CheckableLinearLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ mCheckBox = (CheckBox) findViewById(R.id.checkbox);
+ }
+
+ @Override
+ public boolean isChecked() {
+ return mCheckBox.isChecked();
+ }
+
+ @Override
+ public void setChecked(boolean checked) {
+ mCheckBox.setChecked(checked);
+ }
+
+ @Override
+ public void toggle() {
+ mCheckBox.toggle();
+ }
+} \ No newline at end of file
diff --git a/src/com/android/settings/widget/InertCheckBox.java b/src/com/android/settings/widget/InertCheckBox.java
new file mode 100644
index 0000000..82a376f
--- /dev/null
+++ b/src/com/android/settings/widget/InertCheckBox.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2015 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 com.android.settings.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.widget.CheckBox;
+
+
+// CheckBox that does not react to any user event in order to let the container handle them.
+public class InertCheckBox extends CheckBox {
+
+ @SuppressWarnings("unused")
+ public InertCheckBox(Context context) {
+ super(context);
+ }
+
+ @SuppressWarnings("unused")
+ public InertCheckBox(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @SuppressWarnings("unused")
+ public InertCheckBox(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ // Make the checkbox not respond to any user event
+ return false;
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ // Make the checkbox not respond to any user event
+ return false;
+ }
+
+ @Override
+ public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
+ // Make the checkbox not respond to any user event
+ return false;
+ }
+
+ @Override
+ public boolean onKeyPreIme(int keyCode, KeyEvent event) {
+ // Make the checkbox not respond to any user event
+ return false;
+ }
+
+ @Override
+ public boolean onKeyShortcut(int keyCode, KeyEvent event) {
+ // Make the checkbox not respond to any user event
+ return false;
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ // Make the checkbox not respond to any user event
+ return false;
+ }
+
+ @Override
+ public boolean onTrackballEvent(MotionEvent event) {
+ // Make the checkbox not respond to any user event
+ return false;
+ }
+} \ No newline at end of file