Merge "Allow use of comma to separate search keywords, update keywords"
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 3bbc667..8c9af29 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1468,6 +1468,17 @@
</intent-filter>
</activity>
+ <!-- Note this must not be exported since it authenticates the given user -->
+ <activity android:name="ConfirmDeviceCredentialActivity$InternalActivity"
+ android:exported="false"
+ android:permission="android.permission.MANAGE_USERS"
+ android:theme="@android:style/Theme.NoDisplay">
+ <intent-filter android:priority="1">
+ <action android:name="android.app.action.CONFIRM_DEVICE_CREDENTIAL_WITH_USER" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
<activity android:name=".SetupRedactionInterstitial"
android:taskAffinity="com.android.wizard"
android:theme="@style/SetupWizardDisableAppStartingTheme"/>
@@ -2656,5 +2667,30 @@
</intent-filter>
</activity>
+ <!-- Conditional receivers, only enabled during silenced state, default off-->
+ <receiver
+ android:name=".dashboard.conditional.HotspotCondition$Receiver"
+ android:enabled="false">
+ <intent-filter>
+ <action android:name="android.net.wifi.WIFI_AP_STATE_CHANGED" />
+ </intent-filter>
+ </receiver>
+
+ <receiver
+ android:name=".dashboard.conditional.AirplaneModeCondition$Receiver"
+ android:enabled="false">
+ <intent-filter>
+ <action android:name="android.intent.action.AIRPLANE_MODE" />
+ </intent-filter>
+ </receiver>
+
+ <receiver
+ android:name=".dashboard.conditional.DndCondition$Receiver"
+ android:enabled="false">
+ <intent-filter>
+ <action android:name="android.app.action.INTERRUPTION_FILTER_CHANGED_INTERNAL" />
+ </intent-filter>
+ </receiver>
+
</application>
</manifest>
diff --git a/res/drawable/ic_airplane.xml b/res/drawable/ic_airplane.xml
new file mode 100644
index 0000000..b3e0ba0
--- /dev/null
+++ b/res/drawable/ic_airplane.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:name="root"
+ android:alpha="1.0"
+ android:height="48dp"
+ android:width="48dp"
+ android:viewportHeight="48"
+ android:viewportWidth="48" >
+ <group
+ android:name="ic_signal_airplane"
+ android:translateX="21.9995"
+ android:translateY="25.73401" >
+ <group
+ android:name="ic_signal_airplane_pivot"
+ android:translateX="-23.21545"
+ android:translateY="-18.86649" >
+ <clip-path
+ android:name="mask"
+ android:pathData="M 37.8337860107,-40.4599914551 c 0.0,0.0 -35.8077850342,31.5523681641 -35.8077850342,31.5523681641 c 0.0,0.0 9.55097961426,9.55285644531 9.55097961426,9.55285644531 c 0.0,0.0 -2.61698913574,2.09387207031 -2.61698913574,2.09387207031 c 0.0,0.0 -9.75096130371,-9.56428527832 -9.75096130371,-9.56428527832 c 0.0,0.0 -34.6200408936,25.4699249268 -34.6200408936,25.4699249268 c 0.0,0.0 55.9664764404,69.742401123 55.9664764404,69.742401123 c 0.0,0.0 73.2448120117,-59.1047973633 73.2448120117,-59.1047973633 c 0.0,0.0 -55.9664916992,-69.7423400879 -55.9664916992,-69.7423400879 Z" />
+ <group
+ android:name="cross" >
+ <path
+ android:name="cross_1"
+ android:pathData="M 7.54049682617,3.9430847168 c 0.0,0.0 0.324981689453,0.399978637695 0.324981689453,0.399978637695 "
+ android:strokeColor="#FFFFFFFF"
+ android:strokeAlpha="0"
+ android:strokeWidth="3.5"
+ android:fillColor="#00000000" />
+ </group>
+ <group
+ android:name="plane"
+ android:translateX="23.481"
+ android:translateY="18.71151" >
+ <path
+ android:name="plane_1"
+ android:pathData="M 18.9439849854,7.98849487305 c 0.0,0.0 0.0,-4.0 0.0,-4.0 c 0.0,0.0 -16.0,-10.0 -16.0,-10.0 c 0.0,0.0 0.0,-11.0 0.0,-11.0 c 0.0,-1.70001220703 -1.30000305176,-3.0 -3.0,-3.0 c -1.69999694824,0.0 -3.0,1.29998779297 -3.0,3.0 c 0.0,0.0 0.0,11.0 0.0,11.0 c 0.0,0.0 -16.0,10.0 -16.0,10.0 c 0.0,0.0 0.0,4.0 0.0,4.0 c 0.0,0.0 16.0,-5.0 16.0,-5.0 c 0.0,0.0 0.0,11.0 0.0,11.0 c 0.0,0.0 -4.0,3.0 -4.0,3.0 c 0.0,0.0 0.0,3.0 0.0,3.0 c 0.0,0.0 7.0,-2.0 7.0,-2.0 c 0.0,0.0 7.0,2.0 7.0,2.0 c 0.0,0.0 0.0,-3.0 0.0,-3.0 c 0.0,0.0 -4.0,-3.0 -4.0,-3.0 c 0.0,0.0 0.0,-11.0 0.0,-11.0 c 0.0,0.0 16.0,5.0 16.0,5.0 Z"
+ android:fillColor="#FFFFFFFF"
+ android:fillAlpha="1" />
+ </group>
+ </group>
+ </group>
+</vector>
diff --git a/res/drawable/ic_expand_less.xml b/res/drawable/ic_expand_less.xml
index 3a00faf..7ccc080 100644
--- a/res/drawable/ic_expand_less.xml
+++ b/res/drawable/ic_expand_less.xml
@@ -19,10 +19,11 @@
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
- android:viewportHeight="24">
+ android:viewportHeight="24"
+ android:tint="?android:attr/colorControlNormal">
<path
- android:fillColor="?android:attr/colorControlNormal"
+ android:fillColor="@android:color/white"
android:pathData="M12.0,8.0l-6.0,6.0 1.41,1.41L12.0,10.83l4.59,4.58L18.0,14.0z"/>
</vector>
diff --git a/res/drawable/ic_expand_more.xml b/res/drawable/ic_expand_more.xml
index 64d2242..609cf36 100644
--- a/res/drawable/ic_expand_more.xml
+++ b/res/drawable/ic_expand_more.xml
@@ -19,10 +19,11 @@
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
- android:viewportHeight="24">
+ android:viewportHeight="24"
+ android:tint="?android:attr/colorControlNormal">
<path
- android:fillColor="?android:attr/colorControlNormal"
+ android:fillColor="@android:color/white"
android:pathData="M16.59,8.59L12.0,13.17 7.41,8.59 6.0,10.0l6.0,6.0 6.0,-6.0z"/>
</vector>
diff --git a/res/drawable/ic_hotspot.xml b/res/drawable/ic_hotspot.xml
new file mode 100644
index 0000000..d8528dd
--- /dev/null
+++ b/res/drawable/ic_hotspot.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:name="root"
+ android:alpha="1.0"
+ android:height="48dp"
+ android:width="48dp"
+ android:viewportHeight="48"
+ android:viewportWidth="48" >
+ <group
+ android:name="ic_hotspot"
+ android:translateX="23.9778"
+ android:translateY="24.26443" >
+ <group
+ android:name="ic_hotspot_pivot"
+ android:translateX="-23.21545"
+ android:translateY="-18.86649" >
+ <clip-path
+ android:name="mask"
+ android:pathData="M 38.8337860107,-40.3974914551 c 0.0,0.0 -38.4077911377,30.8523712158 -38.4077911377,30.8523712158 c 0.0,0.0 6.97125244141,7.33258056641 6.97125244141,7.33258056641 c 0.0,0.0 -2.4169921875,2.57838439941 -2.4169921875,2.57838439941 c 0.0,0.0 -6.77128601074,-6.82850646973 -6.77128601074,-6.82850646973 c 0.0,0.0 -32.6199798584,25.1699066162 -32.6199798584,25.1699066162 c 0.0,0.0 55.9664764404,69.742401123 55.9664764404,69.742401123 c 0.0,0.0 27.6589050293,-22.6579437256 27.6589050293,-22.6579437256 c 0.0,0.0 -30.8645172119,-34.00390625 -30.8645172119,-34.00390625 c 0.0,0.0 2.70756530762,-1.99278259277 2.70756530762,-1.99278259277 c 0.0,0.0 1.53030395508,-0.876571655273 1.53030395508,-0.876571655274 c 0.0,0.0 2.85780334473,-3.12069702148 2.85780334473,-3.12069702148 c 0.0,0.0 0.659332275391,0.664688110352 0.659332275391,0.664688110351 c 0.0,0.0 -3.13299560547,2.82977294922 -3.13299560547,2.82977294922 c 0.0,0.0 29.0108337402,34.4080963135 29.0108337402,34.4080963135 c 0.0,0.0 42.8175811768,-34.3554534912 42.8175811768,-34.3554534912 c 0.0,0.0 -55.9664916992,-69.7423400879 -55.9664916992,-69.7423400879 Z" />
+ <group
+ android:name="cross" >
+ <path
+ android:name="cross_1"
+ android:pathData="M 4.44044494629,2.24310302734 c 0.0,0.0 0.0875396728516,0.112457275391 0.0875396728516,0.112457275391 "
+ android:strokeColor="#FFFFFFFF"
+ android:strokeAlpha="0"
+ android:strokeWidth="3.5"
+ android:fillColor="#00000000" />
+ </group>
+ <group
+ android:name="hotspot"
+ android:translateX="23.481"
+ android:translateY="18.71151" >
+ <group
+ android:name="circles"
+ android:translateX="-0.23909"
+ android:translateY="-0.10807" >
+ <path
+ android:name="path_3"
+ android:pathData="M -0.0042724609375,-2.64895629883 c -2.20922851562,0.0 -4.0,1.791015625 -4.0,4.0 c 0.0,2.20922851562 1.79077148438,4.0 4.0,4.0 c 2.208984375,0.0 4.0,-1.79077148438 4.0,-4.0 c 0.0,-2.208984375 -1.791015625,-4.0 -4.0,-4.0 Z M 11.9957275391,1.35104370117 c 0.0,-6.626953125 -5.373046875,-12.0 -12.0,-12.0 c -6.62719726562,0.0 -12.0,5.373046875 -12.0,12.0 c 0.0,4.43603515625 2.41381835938,8.30004882812 5.99194335938,10.3771972656 c 0.0,0.0 2.01586914062,-3.48217773438 2.01586914062,-3.48217773438 c -2.38500976562,-1.38500976562 -4.0078125,-3.93798828125 -4.0078125,-6.89501953125 c 0.0,-4.41796875 3.58178710938,-8.0 8.0,-8.0 c 4.41796875,0.0 8.0,3.58203125 8.0,8.0 c 0.0,2.95703125 -1.623046875,5.51000976562 -4.00805664062,6.89501953125 c 0.0,0.0 2.01586914062,3.48217773438 2.01586914062,3.48217773438 c 3.578125,-2.0771484375 5.9921875,-5.94116210938 5.9921875,-10.3771972656 Z M -0.0042724609375,-18.6489562988 c -11.0451660156,0.0 -20.0,8.9541015625 -20.0,20.0 c 0.0,7.39306640625 4.02099609375,13.8330078125 9.98779296875,17.2951660156 c 0.0,0.0 2.00219726562,-3.458984375 2.00219726562,-3.458984375 c -4.77319335938,-2.77001953125 -7.98999023438,-7.92211914062 -7.98999023438,-13.8361816406 c 0.0,-8.8369140625 7.16381835938,-16.0 16.0,-16.0 c 8.83595275879,0.0 16.0000152588,7.1630859375 16.0000152588,16.0 c 0.0,5.9140625 -3.21704101562,11.0661621094 -7.990234375,13.8361816406 c 0.0,0.0 2.00219726562,3.458984375 2.00219726563,3.458984375 c 5.966796875,-3.46215820312 9.98803710937,-9.90209960938 9.98803710938,-17.2951660156 c 0.0,-11.0458984375 -8.955078125,-20.0 -20.0000152588,-20.0 Z"
+ android:fillColor="#FFFFFFFF"
+ android:fillAlpha="1" />
+ </group>
+ </group>
+ </group>
+ </group>
+</vector>
diff --git a/res/drawable/ic_zen.xml b/res/drawable/ic_zen.xml
new file mode 100644
index 0000000..17ecf21
--- /dev/null
+++ b/res/drawable/ic_zen.xml
@@ -0,0 +1,26 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="24dp"
+ android:viewportHeight="48.0"
+ android:viewportWidth="48.0"
+ android:width="24dp" >
+
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M24.0,4.0C12.95,4.0 4.0,12.95 4.0,24.0s8.95,20.0 20.0,20.0 20.0,-8.95 20.0,-20.0S35.05,4.0 24.0,4.0zm10.0,22.0L14.0,26.0l0.0,-4.0l20.0,0.0l0.0,4.0z" />
+
+</vector>
diff --git a/res/layout/condition_card.xml b/res/layout/condition_card.xml
new file mode 100644
index 0000000..482d5b6
--- /dev/null
+++ b/res/layout/condition_card.xml
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:background="?android:attr/colorAccent"
+ android:elevation="3dp"
+ android:clickable="true">
+
+ <LinearLayout
+ android:id="@+id/collapsed_group"
+ android:layout_width="match_parent"
+ android:layout_height="56dp"
+ android:orientation="horizontal"
+ android:gravity="center">
+
+ <ImageView
+ android:id="@android:id/icon"
+ android:layout_width="24dp"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="36dp"
+ android:tint="?android:attr/textColorPrimaryInverse" />
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textColor="?android:attr/textColorPrimaryInverse" />
+
+ <ImageView
+ android:id="@+id/expand_indicator"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:tint="?android:attr/textColorPrimaryInverse"
+ android:padding="?android:attr/listPreferredItemPaddingEnd"
+ android:clickable="true"
+ android:background="?android:attr/selectableItemBackground" />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/detail_group"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingStart="60dp"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@android:id/summary"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingBottom="16dp"
+ android:textAppearance="?android:attr/textAppearanceListItemSecondary"
+ android:textColor="?android:attr/textColorPrimaryInverse" />
+
+ <!-- TODO: Better background -->
+ <View
+ android:layout_width="match_parent"
+ android:layout_height=".25dp"
+ android:background="@android:color/white" />
+
+ <com.android.internal.widget.ButtonBarLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="8dp"
+ android:paddingBottom="8dp"
+ style="?attr/buttonBarStyle"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <Button
+ android:id="@+id/first_action"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingStart="0dp"
+ android:textColor="?android:attr/textColorPrimaryInverse"
+ style="?android:attr/buttonBarButtonStyle" />
+
+ <Button
+ android:id="@+id/second_action"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="?android:attr/textColorPrimaryInverse"
+ style="?android:attr/buttonBarButtonStyle" />
+
+ </com.android.internal.widget.ButtonBarLayout>
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/res/layout/dashboard.xml b/res/layout/dashboard.xml
index ae5be68..93261a53 100644
--- a/res/layout/dashboard.xml
+++ b/res/layout/dashboard.xml
@@ -14,7 +14,7 @@
limitations under the License.
-->
-<android.support.v7.widget.RecyclerView
+<com.android.settings.dashboard.conditional.FocusRecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/dashboard_container"
android:layout_width="match_parent"
diff --git a/res/layout/fingerprint_enroll_find_sensor_base.xml b/res/layout/fingerprint_enroll_find_sensor_base.xml
index f65e932..3f69a0a 100644
--- a/res/layout/fingerprint_enroll_find_sensor_base.xml
+++ b/res/layout/fingerprint_enroll_find_sensor_base.xml
@@ -22,48 +22,50 @@
android:layout_height="match_parent"
style="?attr/fingerprint_layout_theme">
- <LinearLayout
- style="@style/SuwContentFrame"
+ <FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:clipToPadding="false"
android:clipChildren="false">
- <TextView
- style="@style/TextAppearance.FingerprintMessage"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/suw_description_margin_top"
- android:text="@string/security_settings_fingerprint_enroll_find_sensor_message"/>
-
- <View
- android:layout_height="0dp"
- android:layout_width="match_parent"
- android:layout_weight="1"/>
-
<include
layout="@layout/fingerprint_enroll_find_sensor_graphic"
- android:layout_width="@dimen/fingerprint_find_sensor_graphic_size"
- android:layout_height="@dimen/fingerprint_find_sensor_graphic_size"
- android:layout_gravity="center_horizontal"/>
-
- <View
- android:layout_height="0dp"
android:layout_width="match_parent"
- android:layout_weight="1"/>
+ android:layout_height="match_parent"
+ android:layout_gravity="center_horizontal|bottom"/>
- <Button
- style="@style/Button.FingerprintButton"
- android:id="@+id/next_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginBottom="4dp"
- android:layout_marginEnd="-12dp"
- android:layout_gravity="end"
- android:gravity="end|center_vertical"
- android:text="@string/fingerprint_enroll_button_next" />
+ <LinearLayout
+ style="@style/SuwContentFrame"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:clipToPadding="false"
+ android:clipChildren="false">
- </LinearLayout>
+ <TextView
+ style="@style/TextAppearance.FingerprintMessage"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/suw_description_margin_top"
+ android:text="@string/security_settings_fingerprint_enroll_find_sensor_message"/>
+ <View
+ android:layout_height="0dp"
+ android:layout_width="match_parent"
+ android:layout_weight="1"/>
+
+ <Button
+ style="@style/Button.FingerprintButton"
+ android:id="@+id/next_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="4dp"
+ android:layout_marginEnd="-12dp"
+ android:layout_gravity="end"
+ android:gravity="end|center_vertical"
+ android:text="@string/fingerprint_enroll_button_next" />
+
+ </LinearLayout>
+ </FrameLayout>
</com.android.setupwizardlib.SetupWizardLayout>
diff --git a/res/raw/fingerprint_location_animation.mp4 b/res/raw/fingerprint_location_animation.mp4
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/res/raw/fingerprint_location_animation.mp4
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 86030ff..539dc05 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -101,7 +101,7 @@
<dimen name="dashboard_category_padding_end">0dp</dimen>
<!-- Dashboard category panel elevation -->
- <dimen name="dashboard_category_elevation">4dp</dimen>
+ <dimen name="dashboard_category_elevation">2dp</dimen>
<!-- Dashboard category title layout height -->
<dimen name="dashboard_category_title_height">48dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 9a81bcd..26961c8 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -6689,4 +6689,23 @@
<string name="default_organisation_name">organisation</string>
<string name="default_admin_support_msg">Contact them to learn more.</string>
<string name="list_of_administrators">List of administrators</string>
+
+ <!-- Turn off a conditional state of the device (e.g. airplane mode, or hotspot) [CHAR LIMIT=30] -->
+ <string name="condition_turn_off">Turn off</string>
+
+ <!-- Title of condition that hotspot is on [CHAR LIMIT=30] -->
+ <string name="condition_hotspot_title">Hotspot is on</string>
+
+ <!-- Summary of condition that hotspot is on [CHAR LIMIT=NONE] -->
+ <string name="condition_hotspot_summary">Portable Wi-Fi hotspot <xliff:g name="ap_name" example="AndroidAP">%1$s</xliff:g> is active, Wi-Fi for this device is turned off.</string>
+
+ <!-- Title of condition that airplane mode is on [CHAR LIMIT=30] -->
+ <string name="condition_airplane_title">Airplane mode is on</string>
+
+ <!-- Summary of condition that airplane mode is on [CHAR LIMIT=NONE] -->
+ <string name="condition_airplane_summary">Wi-Fi, Bluetooth, and cellular network are turned off. You can\'t make phone calls or connect to the Internet.</string>
+
+ <!-- Title of condition that do not disturb is on [CHAR LIMIT=30] -->
+ <string name="condition_zen_title">Do not disturb is on (<xliff:g name="zen_mode_type" example="Alarms only">%1$s</xliff:g>)</string>
+
</resources>
diff --git a/src/com/android/settings/ChooseLockSettingsHelper.java b/src/com/android/settings/ChooseLockSettingsHelper.java
index 4c3fb7b..ab81455 100644
--- a/src/com/android/settings/ChooseLockSettingsHelper.java
+++ b/src/com/android/settings/ChooseLockSettingsHelper.java
@@ -21,6 +21,7 @@
import android.app.Fragment;
import android.app.admin.DevicePolicyManager;
import android.content.Intent;
+import android.content.IntentSender;
import com.android.internal.widget.LockPatternUtils;
@@ -114,6 +115,27 @@
/**
* If a pattern, password or PIN exists, prompt the user before allowing them to change it.
+ *
+ * @param title title of the confirmation screen; shown in the action bar
+ * @param header header of the confirmation screen; shown as large text
+ * @param description description of the confirmation screen
+ * @param returnCredentials if true, put credentials into intent. Note that if this is true,
+ * this can only be called internally.
+ * @param external specifies whether this activity is launched externally, meaning that it will
+ * get a dark theme and allow fingerprint authentication
+ * @param userId The userId for whom the lock should be confirmed.
+ * @return true if one exists and we launched an activity to confirm it
+ * @see Activity#onActivityResult(int, int, android.content.Intent)
+ */
+ boolean launchConfirmationActivity(int request, @Nullable CharSequence title,
+ @Nullable CharSequence header, @Nullable CharSequence description,
+ boolean returnCredentials, boolean external, int userId) {
+ return launchConfirmationActivity(request, title, header, description,
+ returnCredentials, external, false, 0, Utils.getSameOwnerUserId(mActivity, userId));
+ }
+
+ /**
+ * If a pattern, password or PIN exists, prompt the user before allowing them to change it.
* @param message optional message to display about the action about to be done
* @param details optional detail message to display
* @param challenge a challenge to be verified against the device credential.
@@ -175,8 +197,18 @@
if (external) {
intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
if (mFragment != null) {
+ IntentSender intentSender = mFragment.getActivity().getIntent()
+ .getParcelableExtra(Intent.EXTRA_INTENT);
+ if (intentSender != null) {
+ intent.putExtra(Intent.EXTRA_INTENT, intentSender);
+ }
mFragment.startActivity(intent);
} else {
+ IntentSender intentSender = mActivity.getIntent()
+ .getParcelableExtra(Intent.EXTRA_INTENT);
+ if (intentSender != null) {
+ intent.putExtra(Intent.EXTRA_INTENT, intentSender);
+ }
mActivity.startActivity(intent);
}
} else {
diff --git a/src/com/android/settings/ConfirmDeviceCredentialActivity.java b/src/com/android/settings/ConfirmDeviceCredentialActivity.java
index 86935c3..c4587eb 100644
--- a/src/com/android/settings/ConfirmDeviceCredentialActivity.java
+++ b/src/com/android/settings/ConfirmDeviceCredentialActivity.java
@@ -20,7 +20,11 @@
import android.app.Activity;
import android.app.KeyguardManager;
import android.content.Intent;
+import android.os.Binder;
import android.os.Bundle;
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.UserManager;
import android.util.Log;
/**
@@ -30,6 +34,9 @@
public class ConfirmDeviceCredentialActivity extends Activity {
public static final String TAG = ConfirmDeviceCredentialActivity.class.getSimpleName();
+ public static class InternalActivity extends ConfirmDeviceCredentialActivity {
+ }
+
public static Intent createIntent(CharSequence title, CharSequence details) {
Intent intent = new Intent();
intent.setClassName("com.android.settings",
@@ -57,13 +64,24 @@
Intent intent = getIntent();
String title = intent.getStringExtra(KeyguardManager.EXTRA_TITLE);
String details = intent.getStringExtra(KeyguardManager.EXTRA_DESCRIPTION);
-
+ int userId = Utils.getEffectiveUserId(this);
+ if (isInternalActivity()) {
+ int givenUserId = intent.getIntExtra(Intent.EXTRA_USER_ID, userId);
+ UserManager userManager = UserManager.get(this);
+ if (userManager.isSameProfileGroup(givenUserId, userId)) {
+ userId = givenUserId;
+ }
+ }
ChooseLockSettingsHelper helper = new ChooseLockSettingsHelper(this);
if (!helper.launchConfirmationActivity(0 /* request code */, null /* title */, title,
- details, false /* returnCredentials */, true /* isExternal */)) {
+ details, false /* returnCredentials */, true /* isExternal */, userId)) {
Log.d(TAG, "No pattern, password or PIN set.");
setResult(Activity.RESULT_OK);
}
finish();
}
+
+ private boolean isInternalActivity() {
+ return this instanceof ConfirmDeviceCredentialActivity.InternalActivity;
+ }
}
diff --git a/src/com/android/settings/ConfirmDeviceCredentialBaseFragment.java b/src/com/android/settings/ConfirmDeviceCredentialBaseFragment.java
index 32ad317..e04f86f 100644
--- a/src/com/android/settings/ConfirmDeviceCredentialBaseFragment.java
+++ b/src/com/android/settings/ConfirmDeviceCredentialBaseFragment.java
@@ -18,6 +18,7 @@
import android.annotation.Nullable;
import android.content.Intent;
+import android.content.IntentSender;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
@@ -124,4 +125,16 @@
public void startEnterAnimation() {
}
+
+ protected void checkForPendingIntent() {
+ IntentSender intentSender = getActivity().getIntent()
+ .getParcelableExtra(Intent.EXTRA_INTENT);
+ if (intentSender != null) {
+ try {
+ getActivity().startIntentSenderForResult(intentSender, -1, null, 0, 0, 0);
+ } catch (IntentSender.SendIntentException e) {
+ /* ignore */
+ }
+ }
+ }
}
diff --git a/src/com/android/settings/ConfirmLockPassword.java b/src/com/android/settings/ConfirmLockPassword.java
index 7ef6a57..53555e0 100644
--- a/src/com/android/settings/ConfirmLockPassword.java
+++ b/src/com/android/settings/ConfirmLockPassword.java
@@ -20,6 +20,7 @@
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentSender;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.CountDownTimer;
@@ -405,6 +406,7 @@
mPasswordEntryInputDisabler.setInputEnabled(true);
if (matched) {
startDisappearAnimation(intent);
+ checkForPendingIntent();
} else {
if (timeoutMs > 0) {
long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
diff --git a/src/com/android/settings/ConfirmLockPattern.java b/src/com/android/settings/ConfirmLockPattern.java
index 44e74c9..6331290 100644
--- a/src/com/android/settings/ConfirmLockPattern.java
+++ b/src/com/android/settings/ConfirmLockPattern.java
@@ -18,6 +18,7 @@
import android.app.Activity;
import android.content.Intent;
+import android.content.IntentSender;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.CountDownTimer;
@@ -473,6 +474,7 @@
mLockPatternView.setEnabled(true);
if (matched) {
startDisappearAnimation(intent);
+ checkForPendingIntent();
} else {
if (timeoutMs > 0) {
long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
diff --git a/src/com/android/settings/CryptKeeper.java b/src/com/android/settings/CryptKeeper.java
index 1b37066..594cd38 100644
--- a/src/com/android/settings/CryptKeeper.java
+++ b/src/com/android/settings/CryptKeeper.java
@@ -799,6 +799,8 @@
// Asynchronously throw up the IME, since there are issues with requesting it to be shown
// immediately.
if (mLockPatternView == null && !mCooldown) {
+ getWindow().setSoftInputMode(
+ WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
mHandler.postDelayed(new Runnable() {
@Override public void run() {
imm.showSoftInputUnchecked(0, null);
diff --git a/src/com/android/settings/CustomListPreference.java b/src/com/android/settings/CustomListPreference.java
index 1d49165..ce37c14 100644
--- a/src/com/android/settings/CustomListPreference.java
+++ b/src/com/android/settings/CustomListPreference.java
@@ -44,6 +44,8 @@
public static class CustomListPreferenceDialogFragment extends ListPreferenceDialogFragment {
+ private int mClickedDialogEntryIndex;
+
public static ListPreferenceDialogFragment newInstance(String key) {
final ListPreferenceDialogFragment fragment = new CustomListPreferenceDialogFragment();
final Bundle b = new Bundle(1);
@@ -59,12 +61,35 @@
@Override
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
super.onPrepareDialogBuilder(builder);
- getCustomizablePreference().onPrepareDialogBuilder(builder, this);
+ mClickedDialogEntryIndex = getCustomizablePreference()
+ .findIndexOfValue(getCustomizablePreference().getValue());
+ getCustomizablePreference().onPrepareDialogBuilder(builder,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ mClickedDialogEntryIndex = which;
+
+ /*
+ * Clicking on an item simulates the positive button
+ * click, and dismisses the dialog.
+ */
+ CustomListPreferenceDialogFragment.this.onClick(dialog,
+ DialogInterface.BUTTON_POSITIVE);
+ dialog.dismiss();
+ }
+ });
}
@Override
public void onDialogClosed(boolean positiveResult) {
getCustomizablePreference().onDialogClosed(positiveResult);
+ final ListPreference preference = getCustomizablePreference();
+ if (positiveResult && mClickedDialogEntryIndex >= 0 &&
+ preference.getEntryValues() != null) {
+ String value = preference.getEntryValues()[mClickedDialogEntryIndex].toString();
+ if (preference.callChangeListener(value)) {
+ preference.setValue(value);
+ }
+ }
}
}
}
diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java
index 5a54ba4..ddea92b 100644
--- a/src/com/android/settings/Utils.java
+++ b/src/com/android/settings/Utils.java
@@ -74,6 +74,7 @@
import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
+import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -83,8 +84,6 @@
import android.widget.ListView;
import android.widget.TabWidget;
import com.android.internal.util.UserIcons;
-import com.android.settingslib.drawer.UserAdapter;
-import com.android.settingslib.drawer.UserAdapter.UserDetails;
import java.io.IOException;
import java.io.InputStream;
@@ -1058,5 +1057,11 @@
return UserHandle.myUserId();
}
}
+
+ public static int resolveResource(Context context, int attr) {
+ TypedValue value = new TypedValue();
+ context.getTheme().resolveAttribute(attr, value, true);
+ return value.resourceId;
+ }
}
diff --git a/src/com/android/settings/dashboard/DashboardAdapter.java b/src/com/android/settings/dashboard/DashboardAdapter.java
index 6228bbc..3e0b9dd 100644
--- a/src/com/android/settings/dashboard/DashboardAdapter.java
+++ b/src/com/android/settings/dashboard/DashboardAdapter.java
@@ -18,7 +18,6 @@
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
-import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
@@ -26,40 +25,52 @@
import android.widget.ImageView;
import android.widget.TextView;
import com.android.internal.util.ArrayUtils;
+import com.android.settings.R;
import com.android.settings.SettingsActivity;
+import com.android.settings.dashboard.conditional.Condition;
+import com.android.settings.dashboard.conditional.ConditionAdapterUtils;
import com.android.settingslib.drawer.DashboardCategory;
import com.android.settingslib.drawer.DashboardTile;
import java.util.ArrayList;
import java.util.List;
-public class DashboardAdapter extends RecyclerView.Adapter<DashboardAdapter.DashboardItemHolder> {
+public class DashboardAdapter extends RecyclerView.Adapter<DashboardAdapter.DashboardItemHolder> implements View.OnClickListener {
public static final String TAG = "DashboardAdapter";
private final List<Object> mItems = new ArrayList<>();
private final List<Integer> mTypes = new ArrayList<>();
private final List<Integer> mIds = new ArrayList<>();
- private final List<DashboardCategory> mCategories;
private final Context mContext;
+ private List<DashboardCategory> mCategories;
+ private List<Condition> mConditions;
+
private boolean mIsShowingAll;
// Used for counting items;
private int mId;
- public DashboardAdapter(Context context, List<DashboardCategory> categories) {
+ private Condition mExpandedCondition = null;
+
+ public DashboardAdapter(Context context) {
mContext = context;
+
+ setHasStableIds(true);
+ }
+
+ public void setCategories(List<DashboardCategory> categories) {
mCategories = categories;
// TODO: Better place for tinting?
TypedValue tintColor = new TypedValue();
- context.getTheme().resolveAttribute(com.android.internal.R.attr.colorAccent,
+ mContext.getTheme().resolveAttribute(com.android.internal.R.attr.colorAccent,
tintColor, true);
for (int i = 0; i < categories.size(); i++) {
for (int j = 0; j < categories.get(i).tiles.size(); j++) {
DashboardTile tile = categories.get(i).tiles.get(j);
- if (!context.getPackageName().equals(
+ if (!mContext.getPackageName().equals(
tile.intent.getComponent().getPackageName())) {
// If this drawable is coming from outside Settings, tint it to match the
// color.
@@ -67,9 +78,12 @@
}
}
}
+ setShowingAll(mIsShowingAll);
+ }
- setShowingAll(false);
- setHasStableIds(true);
+ public void setConditions(List<Condition> conditions) {
+ mConditions = conditions;
+ setShowingAll(mIsShowingAll);
}
public boolean isShowingAll() {
@@ -88,19 +102,21 @@
public void setShowingAll(boolean showingAll) {
mIsShowingAll = showingAll;
reset();
- countItem(null, com.android.settings.R.layout.dashboard_spacer, true);
- for (int i = 0; i < mCategories.size(); i++) {
+ for (int i = 0; mConditions != null && i < mConditions.size(); i++) {
+ countItem(mConditions.get(i), R.layout.condition_card, mConditions.get(i).shouldShow());
+ }
+ countItem(null, R.layout.dashboard_spacer, true);
+ for (int i = 0; mCategories != null && i < mCategories.size(); i++) {
DashboardCategory category = mCategories.get(i);
- countItem(category, com.android.settings.R.layout.dashboard_category, mIsShowingAll);
+ countItem(category, R.layout.dashboard_category, mIsShowingAll);
for (int j = 0; j < category.tiles.size(); j++) {
DashboardTile tile = category.tiles.get(j);
- Log.d(TAG, "Maybe adding " + tile.intent.getComponent().getClassName());
- countItem(tile, com.android.settings.R.layout.dashboard_tile, mIsShowingAll
+ countItem(tile, R.layout.dashboard_tile, mIsShowingAll
|| ArrayUtils.contains(DashboardSummary.INITIAL_ITEMS,
tile.intent.getComponent().getClassName()));
}
}
- countItem(null, com.android.settings.R.layout.see_all, true);
+ countItem(null, R.layout.see_all, true);
notifyDataSetChanged();
}
@@ -129,10 +145,10 @@
@Override
public void onBindViewHolder(DashboardItemHolder holder, int position) {
switch (mTypes.get(position)) {
- case com.android.settings.R.layout.dashboard_category:
+ case R.layout.dashboard_category:
onBindCategory(holder, (DashboardCategory) mItems.get(position));
break;
- case com.android.settings.R.layout.dashboard_tile:
+ case R.layout.dashboard_tile:
final DashboardTile tile = (DashboardTile) mItems.get(position);
onBindTile(holder, tile);
holder.itemView.setOnClickListener(new View.OnClickListener() {
@@ -142,7 +158,7 @@
}
});
break;
- case com.android.settings.R.layout.see_all:
+ case R.layout.see_all:
onBindSeeAll(holder);
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
@@ -151,6 +167,16 @@
}
});
break;
+ case R.layout.condition_card:
+ ConditionAdapterUtils.bindViews((Condition) mItems.get(position), holder,
+ mItems.get(position) == mExpandedCondition, this,
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ onExpandClick(v);
+ }
+ });
+ break;
}
}
@@ -170,8 +196,8 @@
}
private void onBindSeeAll(DashboardItemHolder holder) {
- holder.title.setText(mIsShowingAll ? com.android.settings.R.string.see_less
- : com.android.settings.R.string.see_all);
+ holder.title.setText(mIsShowingAll ? R.string.see_less
+ : R.string.see_all);
}
@Override
@@ -189,10 +215,38 @@
return mIds.size();
}
+ @Override
+ public void onClick(View v) {
+ if (v.getTag() == mExpandedCondition) {
+ mExpandedCondition.onPrimaryClick();
+ } else {
+ mExpandedCondition = (Condition) v.getTag();
+ notifyDataSetChanged();
+ }
+ }
+
+ public void onExpandClick(View v) {
+ if (v.getTag() == mExpandedCondition) {
+ mExpandedCondition = null;
+ } else {
+ mExpandedCondition = (Condition) v.getTag();
+ }
+ notifyDataSetChanged();
+ }
+
+ public Object getItem(long itemId) {
+ for (int i = 0; i < mIds.size(); i++) {
+ if (mIds.get(i) == itemId) {
+ return mItems.get(i);
+ }
+ }
+ return null;
+ }
+
public static class DashboardItemHolder extends RecyclerView.ViewHolder {
- private final ImageView icon;
- private final TextView title;
- private final TextView summary;
+ public final ImageView icon;
+ public final TextView title;
+ public final TextView summary;
public DashboardItemHolder(View itemView) {
super(itemView);
diff --git a/src/com/android/settings/dashboard/DashboardSummary.java b/src/com/android/settings/dashboard/DashboardSummary.java
index 64eb356..95170a4 100644
--- a/src/com/android/settings/dashboard/DashboardSummary.java
+++ b/src/com/android/settings/dashboard/DashboardSummary.java
@@ -18,7 +18,6 @@
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -31,13 +30,17 @@
import com.android.settings.R;
import com.android.settings.Settings;
import com.android.settings.SettingsActivity;
+import com.android.settings.dashboard.conditional.ConditionAdapterUtils;
+import com.android.settings.dashboard.conditional.ConditionManager;
+import com.android.settings.dashboard.conditional.FocusRecyclerView;
import com.android.settingslib.drawer.DashboardCategory;
import com.android.settingslib.drawer.SettingsDrawerActivity;
import java.util.List;
public class DashboardSummary extends InstrumentedFragment
- implements SettingsDrawerActivity.CategoryListener {
+ implements SettingsDrawerActivity.CategoryListener, ConditionManager.ConditionListener,
+ FocusRecyclerView.FocusListener {
public static final boolean DEBUG = false;
private static final boolean DEBUG_TIMING = false;
private static final String TAG = "DashboardSummary";
@@ -51,11 +54,10 @@
Settings.StorageSettingsActivity.class.getName(),
};
- private static final int MSG_REBUILD_UI = 1;
-
- private RecyclerView mDashboard;
+ private FocusRecyclerView mDashboard;
private DashboardAdapter mAdapter;
private SummaryLoader mSummaryLoader;
+ private ConditionManager mConditionManager;
@Override
protected int getMetricsCategory() {
@@ -73,6 +75,7 @@
setHasOptionsMenu(true);
if (DEBUG_TIMING) Log.d(TAG, "onCreate took " + (System.currentTimeMillis() - startTime)
+ " ms");
+ mConditionManager = ConditionManager.get(getContext());
}
@Override
@@ -95,6 +98,7 @@
((SettingsDrawerActivity) getActivity()).addCategoryListener(this);
mSummaryLoader.setListening(true);
+ Log.d(TAG, "onResume");
}
@Override
@@ -106,6 +110,16 @@
}
@Override
+ public void onWindowFocusChanged(boolean hasWindowFocus) {
+ if (hasWindowFocus) {
+ mConditionManager.addListener(this);
+ mConditionManager.refreshAll();
+ } else {
+ mConditionManager.remListener(this);
+ }
+ }
+
+ @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.dashboard, container, false);
@@ -113,11 +127,16 @@
@Override
public void onViewCreated(View view, Bundle bundle) {
- mDashboard = (RecyclerView) view.findViewById(R.id.dashboard_container);
+ mDashboard = (FocusRecyclerView) view.findViewById(R.id.dashboard_container);
LinearLayoutManager llm = new LinearLayoutManager(getContext());
llm.setOrientation(LinearLayoutManager.VERTICAL);
mDashboard.setLayoutManager(llm);
mDashboard.setHasFixedSize(true);
+ mDashboard.setListener(this);
+ mAdapter = new DashboardAdapter(getContext());
+ mAdapter.setConditions(mConditionManager.getConditions());
+ mSummaryLoader.setAdapter(mAdapter);
+ ConditionAdapterUtils.addDismiss(mDashboard);
rebuildUI();
}
@@ -132,10 +151,7 @@
// TODO: Cache summaries from old categories somehow.
List<DashboardCategory> categories =
((SettingsActivity) getActivity()).getDashboardCategories();
- boolean showingAll = mAdapter != null && mAdapter.isShowingAll();
- mAdapter = new DashboardAdapter(getContext(), categories);
- mSummaryLoader.setAdapter(mAdapter);
- mAdapter.setShowingAll(showingAll);
+ mAdapter.setCategories(categories);
mDashboard.setAdapter(mAdapter);
long delta = System.currentTimeMillis() - start;
@@ -146,4 +162,10 @@
public void onCategoriesChanged() {
rebuildUI();
}
+
+ @Override
+ public void onConditionsChanged() {
+ Log.d(TAG, "onConditionsChanged");
+ mAdapter.setConditions(mConditionManager.getConditions());
+ }
}
diff --git a/src/com/android/settings/dashboard/conditional/AirplaneModeCondition.java b/src/com/android/settings/dashboard/conditional/AirplaneModeCondition.java
new file mode 100644
index 0000000..b839631
--- /dev/null
+++ b/src/com/android/settings/dashboard/conditional/AirplaneModeCondition.java
@@ -0,0 +1,95 @@
+/*
+ * 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.dashboard.conditional;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Icon;
+import android.net.ConnectivityManager;
+import com.android.settings.R;
+import com.android.settings.Settings;
+import com.android.settingslib.WirelessUtils;
+
+public class AirplaneModeCondition extends Condition {
+
+ public AirplaneModeCondition(ConditionManager conditionManager) {
+ super(conditionManager);
+ }
+
+ @Override
+ public void refreshState() {
+ setActive(WirelessUtils.isAirplaneModeOn(mManager.getContext()));
+ }
+
+ @Override
+ protected void onSilenceChanged(boolean silenced) {
+ // Only need to listen for airplane mode changes when its been silenced.
+ PackageManager pm = mManager.getContext().getPackageManager();
+ pm.setComponentEnabledSetting(new ComponentName(mManager.getContext(), Receiver.class),
+ silenced ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+ : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP);
+ }
+
+ @Override
+ public Icon getIcon() {
+ return Icon.createWithResource(mManager.getContext(), R.drawable.ic_airplane);
+ }
+
+ @Override
+ public CharSequence getTitle() {
+ return mManager.getContext().getString(R.string.condition_airplane_title);
+ }
+
+ @Override
+ public CharSequence getSummary() {
+ return mManager.getContext().getString(R.string.condition_airplane_summary);
+ }
+
+ @Override
+ public CharSequence[] getActions() {
+ return new CharSequence[] { mManager.getContext().getString(R.string.condition_turn_off) };
+ }
+
+ @Override
+ public void onPrimaryClick() {
+ mManager.getContext().startActivity(new Intent(mManager.getContext(),
+ Settings.WirelessSettings.class));
+ }
+
+ @Override
+ public void onActionClick(int index) {
+ if (index == 0) {
+ ConnectivityManager.from(mManager.getContext()).setAirplaneMode(false);
+ setActive(false);
+ } else {
+ throw new IllegalArgumentException("Unexpected index " + index);
+ }
+ }
+
+ public static class Receiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(intent.getAction())) {
+ ConditionManager.get(context).getCondition(AirplaneModeCondition.class)
+ .refreshState();
+ }
+ }
+ }
+}
diff --git a/src/com/android/settings/dashboard/conditional/Condition.java b/src/com/android/settings/dashboard/conditional/Condition.java
new file mode 100644
index 0000000..7a6fed5
--- /dev/null
+++ b/src/com/android/settings/dashboard/conditional/Condition.java
@@ -0,0 +1,107 @@
+/*
+ * 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.dashboard.conditional;
+
+import android.graphics.drawable.Icon;
+import android.os.PersistableBundle;
+import android.util.Log;
+
+public abstract class Condition {
+
+ private static final String KEY_SILENCE = "silence";
+ private static final String KEY_ACTIVE = "active";
+ private static final String KEY_LAST_STATE = "last_state";
+
+ protected final ConditionManager mManager;
+
+ private boolean mIsSilenced;
+ private boolean mIsActive;
+ private long mLastStateChange;
+
+ public Condition(ConditionManager manager) {
+ mManager = manager;
+ }
+
+ void restoreState(PersistableBundle bundle) {
+ mIsSilenced = bundle.getBoolean(KEY_SILENCE);
+ mIsActive = bundle.getBoolean(KEY_ACTIVE);
+ mLastStateChange = bundle.getLong(KEY_LAST_STATE);
+ }
+
+ void saveState(PersistableBundle bundle) {
+ bundle.putBoolean(KEY_SILENCE, mIsSilenced);
+ bundle.putBoolean(KEY_ACTIVE, mIsActive);
+ bundle.putLong(KEY_LAST_STATE, mLastStateChange);
+ }
+
+ protected void notifyChanged() {
+ mManager.notifyChanged(this);
+ }
+
+ public boolean isSilenced() {
+ return mIsSilenced;
+ }
+
+ public boolean isActive() {
+ return mIsActive;
+ }
+
+ protected void setActive(boolean active) {
+ if (mIsActive == active) {
+ return;
+ }
+ mIsActive = active;
+ mLastStateChange = System.currentTimeMillis();
+ if (mIsSilenced && !active) {
+ mIsSilenced = false;
+ onSilenceChanged(mIsSilenced);
+ }
+ notifyChanged();
+ }
+
+ public void silence() {
+ if (!mIsSilenced) {
+ mIsSilenced = true;
+ onSilenceChanged(mIsSilenced);
+ notifyChanged();
+ }
+ }
+
+ protected void onSilenceChanged(boolean state) {
+ // Optional enable/disable receivers based on silence state.
+ }
+
+ public boolean shouldShow() {
+ return isActive() && !isSilenced();
+ }
+
+ long getLastChange() {
+ return mLastStateChange;
+ }
+
+ // State.
+ public abstract void refreshState();
+
+ // UI.
+ public abstract Icon getIcon();
+ public abstract CharSequence getTitle();
+ public abstract CharSequence getSummary();
+ public abstract CharSequence[] getActions();
+
+ public abstract void onPrimaryClick();
+ public abstract void onActionClick(int index);
+}
diff --git a/src/com/android/settings/dashboard/conditional/ConditionAdapterUtils.java b/src/com/android/settings/dashboard/conditional/ConditionAdapterUtils.java
new file mode 100644
index 0000000..e390782
--- /dev/null
+++ b/src/com/android/settings/dashboard/conditional/ConditionAdapterUtils.java
@@ -0,0 +1,93 @@
+/*
+ * 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.dashboard.conditional;
+
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.helper.ItemTouchHelper;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageView;
+import com.android.settings.R;
+import com.android.settings.dashboard.DashboardAdapter;
+
+public class ConditionAdapterUtils {
+
+ public static void addDismiss(final RecyclerView recyclerView) {
+ ItemTouchHelper.SimpleCallback callback = new ItemTouchHelper.SimpleCallback(0,
+ ItemTouchHelper.START | ItemTouchHelper.END) {
+ @Override
+ public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
+ RecyclerView.ViewHolder target) {
+ return true;
+ }
+
+ @Override
+ public int getSwipeDirs(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
+ return viewHolder.getItemViewType() == R.layout.condition_card
+ ? super.getSwipeDirs(recyclerView, viewHolder) : 0;
+ }
+
+ @Override
+ public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
+ DashboardAdapter adapter = (DashboardAdapter) recyclerView.getAdapter();
+ Object item = adapter.getItem(viewHolder.getItemId());
+ if (item instanceof Condition) {
+ ((Condition) item).silence();
+ }
+ }
+ };
+ ItemTouchHelper itemTouchHelper = new ItemTouchHelper(callback);
+ itemTouchHelper.attachToRecyclerView(recyclerView);
+ }
+
+ public static void bindViews(final Condition condition,
+ DashboardAdapter.DashboardItemHolder view, boolean isExpanded,
+ View.OnClickListener onClickListener, View.OnClickListener onExpandListener) {
+ view.itemView.setTag(condition);
+ view.itemView.setOnClickListener(onClickListener);
+ view.icon.setImageIcon(condition.getIcon());
+ view.title.setText(condition.getTitle());
+ ImageView expand = (ImageView) view.itemView.findViewById(R.id.expand_indicator);
+ expand.setTag(condition);
+ expand.setImageResource(isExpanded ? R.drawable.ic_expand_less : R.drawable.ic_expand_more);
+ expand.setOnClickListener(onExpandListener);
+
+ View detailGroup = view.itemView.findViewById(R.id.detail_group);
+ // TODO: Animate expand/collapse
+ detailGroup.setVisibility(isExpanded ? View.VISIBLE : View.GONE);
+ if (isExpanded) {
+ view.summary.setText(condition.getSummary());
+ CharSequence[] actions = condition.getActions();
+ for (int i = 0; i < 2; i++) {
+ Button button = (Button) detailGroup.findViewById(i == 0
+ ? R.id.first_action : R.id.second_action);
+ if (actions.length > i) {
+ button.setVisibility(View.VISIBLE);
+ button.setText(actions[i]);
+ final int index = i;
+ button.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ condition.onActionClick(index);
+ }
+ });
+ } else {
+ button.setVisibility(View.GONE);
+ }
+ }
+ }
+ }
+}
diff --git a/src/com/android/settings/dashboard/conditional/ConditionManager.java b/src/com/android/settings/dashboard/conditional/ConditionManager.java
new file mode 100644
index 0000000..d710da2
--- /dev/null
+++ b/src/com/android/settings/dashboard/conditional/ConditionManager.java
@@ -0,0 +1,223 @@
+/*
+ * 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.dashboard.conditional;
+
+import android.content.Context;
+import android.os.PersistableBundle;
+import android.util.Log;
+import android.util.Xml;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+public class ConditionManager {
+
+ private static final String TAG = "ConditionManager";
+
+ private static final boolean DEBUG = true;
+
+ private static final String FILE_NAME = "condition_state.xml";
+ private static final String TAG_CONDITIONS = "conditions";
+ private static final String TAG_CONDITION = "condition";
+ private static final String ATTR_CLASS = "class";
+
+ private static ConditionManager sInstance;
+
+ private final Context mContext;
+ private final ArrayList<Condition> mConditions;
+ private final File mXmlFile;
+
+ private final ArrayList<ConditionListener> mListeners = new ArrayList<>();
+
+ private ConditionManager(Context context) {
+ mContext = context;
+ mConditions = new ArrayList<Condition>();
+ mXmlFile = new File(context.getFilesDir(), FILE_NAME);
+ if (mXmlFile.exists()) {
+ readFromXml();
+ }
+ addMissingConditions();
+ }
+
+ public void refreshAll() {
+ final int N = mConditions.size();
+ for (int i = 0; i < N; i++) {
+ mConditions.get(i).refreshState();
+ }
+ }
+
+ private void readFromXml() {
+ if (DEBUG) Log.d(TAG, "Reading from " + mXmlFile.toString());
+ try {
+ XmlPullParser parser = Xml.newPullParser();
+ FileReader in = new FileReader(mXmlFile);
+ parser.setInput(in);
+ int state = parser.getEventType();
+
+ while (state != XmlPullParser.END_DOCUMENT) {
+ if (TAG_CONDITION.equals(parser.getName())) {
+ int depth = parser.getDepth();
+ String clz = parser.getAttributeValue("", ATTR_CLASS);
+ Condition condition = createCondition(Class.forName(clz));
+ PersistableBundle bundle = PersistableBundle.restoreFromXml(parser);
+ if (DEBUG) Log.d(TAG, "Reading " + clz + " -- " + bundle);
+ condition.restoreState(bundle);
+ mConditions.add(condition);
+ while (parser.getDepth() > depth) {
+ parser.next();
+ }
+ }
+ state = parser.next();
+ }
+ in.close();
+ } catch (XmlPullParserException | IOException | ClassNotFoundException e) {
+ Log.w(TAG, "Problem reading " + FILE_NAME, e);
+ }
+ }
+
+ private void saveToXml() {
+ if (DEBUG) Log.d(TAG, "Writing to " + mXmlFile.toString());
+ try {
+ XmlSerializer serializer = Xml.newSerializer();
+ FileWriter writer = new FileWriter(mXmlFile);
+ serializer.setOutput(writer);
+
+ serializer.startDocument("UTF-8", true);
+ serializer.startTag("", TAG_CONDITIONS);
+
+ final int N = mConditions.size();
+ for (int i = 0; i < N; i++) {
+ serializer.startTag("", TAG_CONDITION);
+ serializer.attribute("", ATTR_CLASS, mConditions.get(i).getClass().getName());
+ PersistableBundle bundle = new PersistableBundle();
+ mConditions.get(i).saveState(bundle);
+ bundle.saveToXml(serializer);
+ serializer.endTag("", TAG_CONDITION);
+ }
+
+ serializer.endTag("", TAG_CONDITIONS);
+ serializer.flush();
+ writer.close();
+ } catch (XmlPullParserException | IOException e) {
+ Log.w(TAG, "Problem writing " + FILE_NAME, e);
+ }
+ }
+
+ private void addMissingConditions() {
+ addIfMissing(AirplaneModeCondition.class);
+ addIfMissing(HotspotCondition.class);
+ addIfMissing(DndCondition.class);
+ }
+
+ private void addIfMissing(Class<? extends Condition> clz) {
+ if (getCondition(clz) == null) {
+ if (DEBUG) Log.d(TAG, "Adding missing " + clz.getName());
+ mConditions.add(createCondition(clz));
+ }
+ }
+
+ private Condition createCondition(Class<?> clz) {
+ if (AirplaneModeCondition.class == clz) {
+ return new AirplaneModeCondition(this);
+ } else if (HotspotCondition.class == clz) {
+ return new HotspotCondition(this);
+ } else if (DndCondition.class == clz) {
+ return new DndCondition(this);
+ }
+ try {
+ Constructor<?> constructor = clz.getConstructor(ConditionManager.class);
+ return (Condition) constructor.newInstance(this);
+ } catch (NoSuchMethodException | IllegalAccessException | InstantiationException
+ | InvocationTargetException e) {
+ }
+ return null;
+ }
+
+ Context getContext() {
+ return mContext;
+ }
+
+ public <T extends Condition> T getCondition(Class<T> clz) {
+ final int N = mConditions.size();
+ for (int i = 0; i < N; i++) {
+ if (clz.equals(mConditions.get(i).getClass())) {
+ return (T) mConditions.get(i);
+ }
+ }
+ return null;
+ }
+
+ public List<Condition> getConditions() {
+ return mConditions;
+ }
+
+ public List<Condition> getVisibleConditions() {
+ List<Condition> conditions = new ArrayList<>();
+ final int N = mConditions.size();
+ for (int i = 0; i < N; i++) {
+ if (mConditions.get(i).shouldShow()) {
+ conditions.add(mConditions.get(i));
+ }
+ }
+ Collections.sort(conditions, CONDITION_COMPARATOR);
+ return conditions;
+ }
+
+ public void notifyChanged(Condition condition) {
+ saveToXml();
+ final int N = mListeners.size();
+ for (int i = 0; i < N; i++) {
+ mListeners.get(i).onConditionsChanged();
+ }
+ }
+
+ public void addListener(ConditionListener listener) {
+ mListeners.add(listener);
+ }
+
+ public void remListener(ConditionListener listener) {
+ mListeners.remove(listener);
+ }
+
+ public static ConditionManager get(Context context) {
+ if (sInstance == null) {
+ sInstance = new ConditionManager(context);
+ }
+ return sInstance;
+ }
+
+ public interface ConditionListener {
+ void onConditionsChanged();
+ }
+
+ private static final Comparator<Condition> CONDITION_COMPARATOR = new Comparator<Condition>() {
+ @Override
+ public int compare(Condition lhs, Condition rhs) {
+ return Long.compare(lhs.getLastChange(), rhs.getLastChange());
+ }
+ };
+}
diff --git a/src/com/android/settings/dashboard/conditional/DndCondition.java b/src/com/android/settings/dashboard/conditional/DndCondition.java
new file mode 100644
index 0000000..3cc3cf0
--- /dev/null
+++ b/src/com/android/settings/dashboard/conditional/DndCondition.java
@@ -0,0 +1,133 @@
+/*
+ * 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.dashboard.conditional;
+
+import android.app.ActivityManager;
+import android.app.NotificationManager;
+import android.app.StatusBarManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Icon;
+import android.provider.Settings;
+
+import android.service.notification.ZenModeConfig;
+import com.android.settings.R;
+
+public class DndCondition extends Condition {
+
+ private static final String TAG = "DndCondition";
+
+ private int mZen;
+ private ZenModeConfig mConfig;
+
+ public DndCondition(ConditionManager manager) {
+ super(manager);
+ }
+
+ @Override
+ public void refreshState() {
+ NotificationManager notificationManager =
+ mManager.getContext().getSystemService(NotificationManager.class);
+ mZen = notificationManager.getZenMode();
+ boolean zenModeEnabled = mZen != Settings.Global.ZEN_MODE_OFF;
+ if (zenModeEnabled) {
+ mConfig = notificationManager.getZenModeConfig();
+ } else {
+ mConfig = null;
+ }
+ setActive(zenModeEnabled);
+ }
+
+ @Override
+ protected void onSilenceChanged(boolean silenced) {
+ // Only need to listen for dnd mode changes when its been silenced.
+ PackageManager pm = mManager.getContext().getPackageManager();
+ pm.setComponentEnabledSetting(new ComponentName(mManager.getContext(), Receiver.class),
+ silenced ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+ : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP);
+ }
+
+ private CharSequence getZenState() {
+ switch (mZen) {
+ case Settings.Global.ZEN_MODE_ALARMS:
+ return mManager.getContext().getString(R.string.zen_mode_option_alarms);
+ case Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
+ return mManager.getContext().getString(
+ R.string.zen_mode_option_important_interruptions);
+ case Settings.Global.ZEN_MODE_NO_INTERRUPTIONS:
+ return mManager.getContext().getString(R.string.zen_mode_option_no_interruptions);
+ }
+ return null;
+ }
+
+ @Override
+ public Icon getIcon() {
+ return Icon.createWithResource(mManager.getContext(), R.drawable.ic_zen);
+ }
+
+ @Override
+ public CharSequence getTitle() {
+ return mManager.getContext().getString(R.string.condition_zen_title, getZenState());
+ }
+
+ @Override
+ public CharSequence getSummary() {
+ final boolean isForever = mConfig != null && mConfig.manualRule != null
+ && mConfig.manualRule.conditionId == null;
+ return isForever ? mManager.getContext().getString(com.android.internal.R.string.zen_mode_forever_dnd)
+ : ZenModeConfig.getConditionSummary(mManager.getContext(), mConfig,
+ ActivityManager.getCurrentUser(),
+ false);
+ }
+
+ @Override
+ public CharSequence[] getActions() {
+ return new CharSequence[] { mManager.getContext().getString(R.string.condition_turn_off) };
+ }
+
+ @Override
+ public void onPrimaryClick() {
+ StatusBarManager statusBar = mManager.getContext().getSystemService(StatusBarManager.class);
+ statusBar.expandSettingsPanel("dnd");
+ }
+
+ @Override
+ public void onActionClick(int index) {
+ if (index == 0) {
+ NotificationManager notificationManager = mManager.getContext().getSystemService(
+ NotificationManager.class);
+ notificationManager.setZenMode(Settings.Global.ZEN_MODE_OFF, null, TAG);
+ setActive(false);
+ } else {
+ throw new IllegalArgumentException("Unexpected index " + index);
+ }
+ }
+
+ public static class Receiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED_INTERNAL
+ .equals(intent.getAction())) {
+ ConditionManager.get(context).getCondition(DndCondition.class)
+ .refreshState();
+ }
+ }
+ }
+}
diff --git a/src/com/android/settings/dashboard/conditional/FocusRecyclerView.java b/src/com/android/settings/dashboard/conditional/FocusRecyclerView.java
new file mode 100644
index 0000000..af51ed5
--- /dev/null
+++ b/src/com/android/settings/dashboard/conditional/FocusRecyclerView.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 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.dashboard.conditional;
+
+import android.content.Context;
+import android.support.annotation.Nullable;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+
+/**
+ * Version of RecyclerView that can have listeners for onWindowFocusChanged.
+ */
+public class FocusRecyclerView extends RecyclerView {
+
+ private FocusListener mListener;
+
+ public FocusRecyclerView(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasWindowFocus) {
+ super.onWindowFocusChanged(hasWindowFocus);
+ if (mListener != null) {
+ mListener.onWindowFocusChanged(hasWindowFocus);
+ }
+ }
+
+ public void setListener(FocusListener listener) {
+ mListener = listener;
+ }
+
+ public interface FocusListener {
+ void onWindowFocusChanged(boolean hasWindowFocus);
+ }
+}
diff --git a/src/com/android/settings/dashboard/conditional/HotspotCondition.java b/src/com/android/settings/dashboard/conditional/HotspotCondition.java
new file mode 100644
index 0000000..0f185c6
--- /dev/null
+++ b/src/com/android/settings/dashboard/conditional/HotspotCondition.java
@@ -0,0 +1,111 @@
+/*
+ * 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.dashboard.conditional;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Icon;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import com.android.settings.R;
+import com.android.settings.TetherSettings;
+import com.android.settings.Utils;
+import com.android.settingslib.TetherUtil;
+
+public class HotspotCondition extends Condition {
+
+ private final WifiManager mWifiManager;
+
+ public HotspotCondition(ConditionManager manager) {
+ super(manager);
+ mWifiManager = mManager.getContext().getSystemService(WifiManager.class);
+ }
+
+ @Override
+ public void refreshState() {
+ boolean wifiTetherEnabled = TetherUtil.isWifiTetherEnabled(mManager.getContext());
+ setActive(wifiTetherEnabled);
+ }
+
+ @Override
+ protected void onSilenceChanged(boolean silenced) {
+ // Only need to listen for hotspot changes when hotspot has been silenced.
+ PackageManager pm = mManager.getContext().getPackageManager();
+ pm.setComponentEnabledSetting(new ComponentName(mManager.getContext(), Receiver.class),
+ silenced ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+ : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP);
+ }
+
+ @Override
+ public Icon getIcon() {
+ return Icon.createWithResource(mManager.getContext(), R.drawable.ic_hotspot);
+ }
+
+ private String getSsid() {
+ WifiConfiguration wifiConfig = mWifiManager.getWifiApConfiguration();
+ if (wifiConfig == null) {
+ return mManager.getContext().getString(
+ com.android.internal.R.string.wifi_tether_configure_ssid_default);
+ } else {
+ return wifiConfig.SSID;
+ }
+ }
+
+ @Override
+ public CharSequence getTitle() {
+ return mManager.getContext().getString(R.string.condition_hotspot_title);
+ }
+
+ @Override
+ public CharSequence getSummary() {
+ return mManager.getContext().getString(R.string.condition_hotspot_summary, getSsid());
+ }
+
+ @Override
+ public CharSequence[] getActions() {
+ return new CharSequence[] { mManager.getContext().getString(R.string.condition_turn_off) };
+ }
+
+ @Override
+ public void onPrimaryClick() {
+ Utils.startWithFragment(mManager.getContext(), TetherSettings.class.getName(), null, null,
+ 0, R.string.tether_settings_title_all, null);
+ }
+
+ @Override
+ public void onActionClick(int index) {
+ if (index == 0) {
+ TetherUtil.setWifiTethering(false, mManager.getContext());
+ setActive(false);
+ } else {
+ throw new IllegalArgumentException("Unexpected index " + index);
+ }
+ }
+
+ public static class Receiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (WifiManager.WIFI_AP_STATE_CHANGED_ACTION.equals(intent.getAction())) {
+ ConditionManager.get(context).getCondition(HotspotCondition.class)
+ .refreshState();
+ }
+ }
+ }
+}
diff --git a/src/com/android/settings/fingerprint/FingerprintEnrollFindSensor.java b/src/com/android/settings/fingerprint/FingerprintEnrollFindSensor.java
index 63d9335..b246bdf 100644
--- a/src/com/android/settings/fingerprint/FingerprintEnrollFindSensor.java
+++ b/src/com/android/settings/fingerprint/FingerprintEnrollFindSensor.java
@@ -33,7 +33,7 @@
private static final int ENROLLING = 2;
public static final String EXTRA_KEY_LAUNCHED_CONFIRM = "launched_confirm_lock";
- private FingerprintLocationAnimationView mAnimation;
+ private FingerprintFindSensorAnimation mAnimation;
private boolean mLaunchedConfirmLock;
@Override
@@ -46,7 +46,7 @@
if (mToken == null && !mLaunchedConfirmLock) {
launchConfirmLock();
}
- mAnimation = (FingerprintLocationAnimationView) findViewById(
+ mAnimation = (FingerprintFindSensorAnimation) findViewById(
R.id.fingerprint_sensor_location_animation);
}
diff --git a/src/com/android/settings/fingerprint/FingerprintFindSensorAnimation.java b/src/com/android/settings/fingerprint/FingerprintFindSensorAnimation.java
new file mode 100644
index 0000000..cb254ba
--- /dev/null
+++ b/src/com/android/settings/fingerprint/FingerprintFindSensorAnimation.java
@@ -0,0 +1,35 @@
+/*
+ * 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.fingerprint;
+
+/**
+ * An abstraction for a view that contains an animation that shows the user
+ * where the fingerprint sensor is on the device.
+ */
+public interface FingerprintFindSensorAnimation {
+
+ /**
+ * Start the animation
+ */
+ void startAnimation();
+
+ /**
+ * Stop the animation
+ */
+ void stopAnimation();
+
+}
diff --git a/src/com/android/settings/fingerprint/FingerprintLocationAnimationVideoView.java b/src/com/android/settings/fingerprint/FingerprintLocationAnimationVideoView.java
new file mode 100644
index 0000000..c1fd2c2
--- /dev/null
+++ b/src/com/android/settings/fingerprint/FingerprintLocationAnimationVideoView.java
@@ -0,0 +1,127 @@
+/*
+ * 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.fingerprint;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.SurfaceTexture;
+import android.media.MediaPlayer;
+import android.media.MediaPlayer.OnInfoListener;
+import android.media.MediaPlayer.OnPreparedListener;
+import android.net.Uri;
+import android.util.AttributeSet;
+import android.view.Surface;
+import android.view.TextureView;
+import android.view.View;
+
+import com.android.settings.R;
+
+/**
+ * A view containing a VideoView for showing the user how to enroll a fingerprint
+ */
+public class FingerprintLocationAnimationVideoView extends TextureView
+ implements FingerprintFindSensorAnimation {
+ protected float mAspect = 1.0f; // initial guess until we know
+ protected MediaPlayer mMediaPlayer;
+
+ public FingerprintLocationAnimationVideoView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // Width is driven by measurespec, height is derrived from aspect ratio
+ int originalWidth = MeasureSpec.getSize(widthMeasureSpec);
+ int height = Math.round(mAspect * originalWidth);
+ super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ setSurfaceTextureListener(new SurfaceTextureListener() {
+ @Override
+ public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width,
+ int height) {
+ setVisibility(View.INVISIBLE);
+ Uri videoUri = resourceEntryToUri(mContext, R.raw.fingerprint_location_animation);
+ mMediaPlayer = MediaPlayer.create(mContext, videoUri);
+ mMediaPlayer.setSurface(new Surface(surfaceTexture));
+ mMediaPlayer.setOnPreparedListener(new OnPreparedListener() {
+ @Override
+ public void onPrepared(MediaPlayer mediaPlayer) {
+ mediaPlayer.setLooping(true);
+ }
+ });
+ mMediaPlayer.setOnInfoListener(new OnInfoListener() {
+ @Override
+ public boolean onInfo(MediaPlayer mediaPlayer, int what, int extra) {
+ if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) {
+ // Keep the view hidden until video starts
+ setVisibility(View.VISIBLE);
+ }
+ return false;
+ }
+ });
+ mAspect = (float) mMediaPlayer.getVideoHeight() / mMediaPlayer.getVideoWidth();
+ requestLayout();
+ startAnimation();
+ }
+
+ @Override
+ public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture,
+ int width, int height) {
+ }
+
+ @Override
+ public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
+ return false;
+ }
+
+ @Override
+ public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
+ }
+ });
+ }
+
+ private static Uri resourceEntryToUri (Context context, int id) {
+ Resources res = context.getResources();
+ return Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" +
+ res.getResourcePackageName(id) + '/' +
+ res.getResourceTypeName(id) + '/' +
+ res.getResourceEntryName(id));
+ }
+
+ @Override
+ public void startAnimation() {
+ if (mMediaPlayer != null && !mMediaPlayer.isPlaying()) {
+ mMediaPlayer.start();
+ }
+ }
+
+ @Override
+ public void stopAnimation() {
+ if (mMediaPlayer != null && mMediaPlayer.isPlaying()) {
+ mMediaPlayer.stop();
+ mMediaPlayer.release();
+ mMediaPlayer = null;
+ }
+ }
+
+}
diff --git a/src/com/android/settings/fingerprint/FingerprintLocationAnimationView.java b/src/com/android/settings/fingerprint/FingerprintLocationAnimationView.java
index 65f38bd..c17069f 100644
--- a/src/com/android/settings/fingerprint/FingerprintLocationAnimationView.java
+++ b/src/com/android/settings/fingerprint/FingerprintLocationAnimationView.java
@@ -34,7 +34,8 @@
/**
* View which plays an animation to indicate where the sensor is on the device.
*/
-public class FingerprintLocationAnimationView extends View {
+public class FingerprintLocationAnimationView extends View implements
+ FingerprintFindSensorAnimation {
private static final float MAX_PULSE_ALPHA = 0.15f;
private static final long DELAY_BETWEEN_PHASE = 1000;
@@ -95,10 +96,12 @@
return getHeight() * mFractionCenterY;
}
+ @Override
public void startAnimation() {
startPhase();
}
+ @Override
public void stopAnimation() {
removeCallbacks(mStartPhaseRunnable);
if (mRadiusAnimator != null) {