Security lock pattern in Settings.
Bug: 71642544
Test: Mojave
Change-Id: I21828d495affcd5cd369a47b4e5d3a30a9c03ec0
diff --git a/Android.mk b/Android.mk
index d278268..fdecc14 100644
--- a/Android.mk
+++ b/Android.mk
@@ -53,6 +53,7 @@
include packages/apps/Car/libs/car-list/car-list.mk
include packages/apps/Car/libs/car-apps-common/car-apps-common.mk
include packages/services/Car/car-support-lib/car-support.mk
+ include frameworks/opt/setupwizard/library/common-gingerbread.mk
include frameworks/base/packages/SettingsLib/common.mk
include $(BUILD_PACKAGE)
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 4b7d65f..dbd54b4 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -108,6 +108,22 @@
</intent-filter>
</activity>
+ <activity android:name=".security.ChooseSecurityLockActivity"
+ android:configChanges="orientation|keyboardHidden|screenSize">
+ <intent-filter>
+ <action android:name="android.car.settings.CHOOSE_SECURITY_LOCK" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name=".security.ChooseLockPatternActivity"
+ android:configChanges="orientation|keyboardHidden|screenSize">
+ <intent-filter>
+ <action android:name="android.car.settings.CHOOSE_LOCK_PATTERN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
<service android:name=".bluetooth.BluetoothPairingService" />
<receiver android:name=".bluetooth.BluetoothPairingRequest">
diff --git a/res/color/lock_pattern_regular.xml b/res/color/lock_pattern_regular.xml
new file mode 100644
index 0000000..67e4fba
--- /dev/null
+++ b/res/color/lock_pattern_regular.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2018 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
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="true"
+ android:color="@color/car_body1" />
+ <item android:state_enabled="false"
+ android:alpha="0.5"
+ android:color="@color/car_body1" />
+</selector>
\ No newline at end of file
diff --git a/res/color/lock_pattern_regular_dark.xml b/res/color/lock_pattern_regular_dark.xml
new file mode 100644
index 0000000..d3ba4ed
--- /dev/null
+++ b/res/color/lock_pattern_regular_dark.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2018 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
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="true"
+ android:color="@color/car_body1_dark" />
+ <item android:state_enabled="false"
+ android:alpha="0.5"
+ android:color="@color/car_body1_dark" />
+</selector>
\ No newline at end of file
diff --git a/res/color/lock_pattern_regular_light.xml b/res/color/lock_pattern_regular_light.xml
new file mode 100644
index 0000000..5b47d6b
--- /dev/null
+++ b/res/color/lock_pattern_regular_light.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2018 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
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="true"
+ android:color="@color/car_body1_light" />
+ <item android:state_enabled="false"
+ android:alpha="0.5"
+ android:color="@color/car_body1_light" />
+</selector>
\ No newline at end of file
diff --git a/res/color/lock_pattern_success.xml b/res/color/lock_pattern_success.xml
new file mode 100644
index 0000000..bee9145
--- /dev/null
+++ b/res/color/lock_pattern_success.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2018 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
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="true"
+ android:color="@color/car_blue_500" />
+ <item android:state_enabled="false"
+ android:alpha="0.5"
+ android:color="@color/car_blue_500" />
+</selector>
\ No newline at end of file
diff --git a/res/color/lock_pattern_success_dark.xml b/res/color/lock_pattern_success_dark.xml
new file mode 100644
index 0000000..33ad70e
--- /dev/null
+++ b/res/color/lock_pattern_success_dark.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2018 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
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="true"
+ android:color="@color/car_teal_700" />
+ <item android:state_enabled="false"
+ android:alpha="0.5"
+ android:color="@color/car_teal_700" />
+</selector>
\ No newline at end of file
diff --git a/res/color/lock_pattern_success_light.xml b/res/color/lock_pattern_success_light.xml
new file mode 100644
index 0000000..eb834c4
--- /dev/null
+++ b/res/color/lock_pattern_success_light.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2018 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
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="true"
+ android:color="@color/car_teal_200" />
+ <item android:state_enabled="false"
+ android:alpha="0.5"
+ android:color="@color/car_teal_200" />
+</selector>
\ No newline at end of file
diff --git a/res/drawable/ic_lock.xml b/res/drawable/ic_lock.xml
new file mode 100644
index 0000000..edb617d
--- /dev/null
+++ b/res/drawable/ic_lock.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2018 Google Inc.
+
+ 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="48dp" android:height="48dp" android:viewportWidth="48.0" android:viewportHeight="48.0">
+ <path android:pathData="M36,16h-2v-4c0,-5.52 -4.48,-10 -10,-10S14,6.48 14,12v4h-2c-2.21,0 -4,1.79 -4,4v20c0,2.21 1.79,4 4,4h24c2.21,0 4,-1.79 4,-4L40,20c0,-2.21 -1.79,-4 -4,-4zM24,34c-2.21,0 -4,-1.79 -4,-4s1.79,-4 4,-4 4,1.79 4,4 -1.79,4 -4,4zM30.2,16L17.8,16v-4c0,-3.42 2.78,-6.2 6.2,-6.2 3.42,0 6.2,2.78 6.2,6.2v4z" android:fillColor="#000000"/>
+</vector>
\ No newline at end of file
diff --git a/res/layout/choose_lock_pattern.xml b/res/layout/choose_lock_pattern.xml
new file mode 100644
index 0000000..31737e0
--- /dev/null
+++ b/res/layout/choose_lock_pattern.xml
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2018 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:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginStart="@dimen/car_margin"
+ android:layout_marginEnd="@dimen/car_margin"
+ android:orientation="horizontal"
+ android:paddingTop="@dimen/car_app_bar_height"
+ android:paddingBottom="@dimen/car_app_bar_height">
+
+ <!-- Start side: lock pattern -->
+ <FrameLayout
+ android:layout_weight="@integer/content_weight"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_vertical">
+
+ <com.android.internal.widget.LockPatternView
+ style="@style/LockPatternStyle"
+ android:id="@+id/lockPattern"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"/>
+ </FrameLayout>
+
+ <!-- End side: instructions and messages -->
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="@integer/content_weight"
+ android:layout_gravity="center_vertical"
+ android:orientation="vertical"
+ android:paddingTop="@dimen/car_primary_icon_size">
+
+ <ImageView
+ android:id="@+id/base_icon"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/car_primary_icon_size"
+ android:layout_marginBottom="@dimen/car_padding_2"
+ android:gravity="center"/>
+
+ <TextView
+ android:id="@+id/title_text"
+ android:gravity="center"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/car_padding_2"
+ android:textAppearance="@style/CarTitle" />
+
+ <TextView
+ android:id="@+id/description_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/car_padding_5"
+ android:gravity="center"
+ android:textAppearance="@style/CarBody2" />
+
+ <!-- confirm / restart buttons -->
+ <LinearLayout
+ android:id="@+id/buttonContainer"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:layout_alignParentBottom="true"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:gravity="center"
+ android:orientation="horizontal">
+
+ <!-- left / top button: skip, or re-try -->
+ <Button android:id="@+id/footerSecondaryButton"
+ style="@style/SetupWizardButton.Negative"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="@dimen/car_action1_size"
+ android:layout_marginEnd="@dimen/car_padding_4"
+ android:text="@string/lockpattern_restart_button_text" />
+
+ <!-- right / bottom button: confirm or ok -->
+ <Button android:id="@+id/footerPrimaryButton"
+ style="@style/SetupWizardButton.Positive"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="@dimen/car_action1_size"
+ android:text="@string/lockpattern_confirm_button_text" />
+
+ </LinearLayout>
+ </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/res/values/colors.xml b/res/values/colors.xml
new file mode 100644
index 0000000..d3ae5f0
--- /dev/null
+++ b/res/values/colors.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <color name="lock_pattern_background">#353839</color>
+</resources>
\ No newline at end of file
diff --git a/res/values/integers.xml b/res/values/integers.xml
new file mode 100644
index 0000000..5942f80
--- /dev/null
+++ b/res/values/integers.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <!-- Weight of the main content in the illustration_layout -->
+ <integer name="content_weight">7</integer>
+
+ <!-- Weight of the illustration logo/video in the illustration_layout -->
+ <integer name="illustration_weight">5</integer>
+
+ <!-- Timeout to clear invalid lock pin/pattern/password -->
+ <integer name="clear_content_timeout_ms">2000</integer>
+</resources>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 5eb6e3e..bf438dc 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -393,11 +393,62 @@
<!-- Message to inform user that the newly created user will have permissions to update apps for all other users. [CHAR LIMIT=100] -->
<string name="user_add_user_message_update">Any user can update apps for all other users.</string>
- <!-- generic --><skip />
+ <!-- security lock -->
+ <!-- Title for security settings [CHAR LIMIT=20] -->
+ <string name="security_settings_title">Security</string>
+ <!-- Subtitle for security settings [CHAR LIMIT=20] -->
+ <string name="security_settings_subtitle">Screen lock</string>
+ <!-- Title string shown in choose screen lock for lock pattern [CHAR LIMIT=40] -->
+ <string name="security_lock_pattern">Pattern</string>
+ <!-- Title string shown in choose screen lock for lock pin [CHAR LIMIT=40] -->
+ <string name="security_lock_pin">Pin</string>
+ <!-- Title string shown in choose screen lock for lock password [CHAR LIMIT=40] -->
+ <string name="security_lock_password">Password</string>
+ <!-- Title for security picker to choose the unlock method: None/Pattern/PIN/Password [CHAR LIMIT=22] -->
+ <string name="lock_settings_picker_title">Choose a lock type</string>
+ <!-- Security & location settings screen, change unlock pattern screen button, on bottom of screen. After they draw a pattern and release their finger, we display the pattern so they remember. When they are ready to draw it once again to confirm it, they press this button. -->
+ <string name="lockpattern_confirm_button_text">Confirm</string>
+ <!-- Security & location settings screen, change unlock pattern screen button, on bottom of screen. After they draw a pattern and release their finger, we display the pattern so they remember. If they are not satisfied with this pattern, they click this button to redraw the pattern. -->
+ <string name="lockpattern_restart_button_text">Redraw</string>
+ <!-- Choose unlock pattern screen button. After first valid enter, they will be prompt to confirm page. [CHAR LIMIT=20] -->
+ <string name="lockpattern_continue_button_text">Continue</string>
+ <!-- Title string shown in choose screen lock [CHAR LIMIT=40] -->
+ <string name="set_screen_lock">Set a screen lock</string>
+ <!-- Message on first screen of choose pattern flow [CHAR LIMIT=40] -->
+ <string name="choose_lock_pattern_message">For security, set a pattern</string>
+ <!-- Choose unlock pattern screen button. If they are supposed to enter their current pattern before being able to draw another one, and they made a mistake, they hit this button to try again [CHAR LIMIT=20] -->
+ <string name="lockpattern_retry_button_text">Clear</string>
+ <!-- Choose unlock pattern screen button. They can choose to cancel the setting of lock pattern. [CHAR LIMIT=20] -->
+ <string name="lockpattern_cancel_button_text">Cancel</string>
+ <!-- Choose unlock pattern screen message, confirm pattern success.[CHAR LIMIT=40] -->
+ <string name="lockpattern_pattern_confirmed">Your new unlock pattern</string>
+ <!-- Choose unlock pattern screen instruction on top of screen. When they are supposed to draw a new unlock pattern (for example, if they are changing their unlock patterns). [CHAR LIMIT=40] -->
+ <string name="lockpattern_recording_intro_header">Draw an unlock pattern</string>
+ <!-- Choose unlock pattern screen instruction on top of screen while drawing pattern. [CHAR LIMIT=40] -->
+ <string name="lockpattern_recording_inprogress">Release finger when done</string>
+ <!-- Choose unlock pattern screen message after drawing pattern. [CHAR LIMIT=40] -->
+ <string name="lockpattern_pattern_entered">Pattern recorded</string>
+ <!-- Choose unlock pattern screen instruction to confirm pattern. [CHAR LIMIT=40] -->
+ <string name="lockpattern_need_to_confirm">Draw pattern again to confirm</string>
+ <!-- Choose unlock pattern screen instruction on top of screen if user doesn't connect enough dots. [CHAR LIMIT=40] -->
+ <string name="lockpattern_recording_incorrect_too_short">Connect at least 4 dots. Try again.</string>
+ <!-- Choose unlock pattern screen instruction if user draws incorrect pattern [CHAR LIMIT=30] -->
+ <string name="lockpattern_pattern_wrong">Wrong pattern</string>
+ <!-- Choose unlock pattern screen instruction, the help instructions (an animation) caption. [CHAR LIMIT=40] -->
+ <string name="lockpattern_settings_help_how_to_record">How to draw an unlock pattern</string>
+ <!-- Label for button to confirm picker selection [CHAR_LIMIT=20] -->
+ <string name="okay">OK</string>
+
+ <!-- Label for button in screen lock settings, allowing users to choose other types of screen locks. [CHAR LIMIT=40] -->
+ <string name="setup_lock_settings_options_button_label">Screen lock options</string>
+
+ <!-- generic -->
<!-- Button label for generic forget action [CHAR LIMIT=20] -->
<string name="forget">Forget</string>
<!-- Delete button text [CHAR LIMIT=20] -->
<string name="delete_button">Delete</string>
<!-- Remove button text [CHAR LIMIT=20] -->
<string name="remove_button">Remove</string>
+ <!-- Cancel button text [CHAR LIMIT=20] -->
+ <string name="cancel">Cancel</string>
</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 6835877..8411b79 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -63,6 +63,21 @@
parent="Widget.Car.Button.Borderless.Colored">
<item name="android:minWidth">@dimen/car_button_min_width</item>
<item name="android:fontFamily">roboto-regular</item>
- <item name="android:textColor">@color/action_bar_btn</item>
+ <item name="android:textColor">@color/car_accent</item>
+ </style>
+
+ <style name="SetupWizardButton.Negative" parent="@style/SuwGlifButton.Secondary">
+ <!-- Negative margin to offset for padding of the button itself. We want the label to be
+ aligned with the text above it -->
+ <item name="android:layout_marginStart">-16dp</item>
+ </style>
+
+ <style name="SetupWizardButton.Positive" parent="@style/SuwGlifButton.Primary" />
+
+ <!-- Style for security lock pattern. -->
+ <style name="LockPatternStyle">
+ <item name="*android:regularColor">@color/lock_pattern_regular</item>
+ <item name="*android:successColor">@color/lock_pattern_success</item>
+ <item name="*android:errorColor">?android:attr/colorError</item>
</style>
</resources>
diff --git a/src/com/android/car/settings/home/HomepageFragment.java b/src/com/android/car/settings/home/HomepageFragment.java
index ff80fcd..bd9244e 100644
--- a/src/com/android/car/settings/home/HomepageFragment.java
+++ b/src/com/android/car/settings/home/HomepageFragment.java
@@ -36,6 +36,7 @@
import com.android.car.settings.common.ListSettingsFragment;
import com.android.car.settings.datetime.DatetimeSettingsFragment;
import com.android.car.settings.display.DisplaySettingsFragment;
+import com.android.car.settings.security.ChooseLockTypeFragment;
import com.android.car.settings.sound.SoundSettingsFragment;
import com.android.car.settings.system.SystemSettingsFragment;
import com.android.car.settings.users.UsersListFragment;
@@ -180,6 +181,13 @@
UsersListFragment.newInstance(),
mFragmentController));
lineItems.add(new SimpleIconTransitionLineItem(
+ R.string.security_settings_title,
+ R.drawable.ic_lock,
+ getContext(),
+ null,
+ ChooseLockTypeFragment.newInstance(),
+ mFragmentController));
+ lineItems.add(new SimpleIconTransitionLineItem(
R.string.system_setting_title,
R.drawable.ic_settings_about,
getContext(),
diff --git a/src/com/android/car/settings/security/ChooseLockPatternActivity.java b/src/com/android/car/settings/security/ChooseLockPatternActivity.java
new file mode 100644
index 0000000..c278c82
--- /dev/null
+++ b/src/com/android/car/settings/security/ChooseLockPatternActivity.java
@@ -0,0 +1,521 @@
+/*
+ * Copyright (C) 2018 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.car.settings.security;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.support.v4.app.FragmentActivity;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockPatternView;
+import com.android.internal.widget.LockPatternView.Cell;
+import com.android.internal.widget.LockPatternView.DisplayMode;
+
+import com.android.car.settings.R;
+
+import com.google.android.collect.Lists;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Activity for choosing security lock pattern.
+ */
+public class ChooseLockPatternActivity extends FragmentActivity implements View.OnClickListener {
+ private static final String TAG = "ChooseLockPattern";
+ private static final int ID_EMPTY_MESSAGE = -1;
+ // How long we wait to clear a wrong pattern
+ private int mWrongPatternClearTimeOut;
+ private Stage mUiStage = Stage.Introduction;
+ private LockPatternView mLockPatternView;
+ private TextView mMessageText;
+ private Button mSecondaryButton;
+ private Button mPrimaryButton;
+
+ private List<LockPatternView.Cell> mChosenPattern;
+ private String mCurrentPattern;
+ private SaveAndFinishWorker mSaveAndFinishWorker;
+ private int mUserId;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.choose_lock_pattern);
+
+ ImageView iconImg = (ImageView) findViewById(R.id.base_icon);
+ iconImg.setImageResource(R.drawable.ic_lock);
+
+ TextView titleText = (TextView) findViewById(R.id.title_text);
+ titleText.setText(getString(R.string.set_screen_lock));
+
+ mMessageText = (TextView) findViewById(R.id.description_text);
+ mMessageText.setText(getString(R.string.choose_lock_pattern_message));
+
+ mPrimaryButton = (Button) findViewById(R.id.footerPrimaryButton);
+ mPrimaryButton.setOnClickListener(this);
+ mSecondaryButton = (Button) findViewById(R.id.footerSecondaryButton);
+ mSecondaryButton.setOnClickListener(this);
+
+ mLockPatternView = (LockPatternView) findViewById(R.id.lockPattern);
+ mLockPatternView.setVisibility(View.VISIBLE);
+ mLockPatternView.setEnabled(true);
+ mLockPatternView.clearPattern();
+ mLockPatternView.setOnPatternListener(mChooseNewLockPatternListener);
+
+ mUserId = UserHandle.myUserId();
+ mCurrentPattern = getIntent()
+ .getStringExtra(SaveChosenLockWorkerBase.EXTRA_KEY_PATTERN);
+
+ mWrongPatternClearTimeOut = getResources().getInteger(R.integer.clear_content_timeout_ms);
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (view == mPrimaryButton) {
+ handlePrimaryButtonClick();
+ } else if (view == mSecondaryButton) {
+ handleSecondaryButtonClick();
+ }
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ updateStage(mUiStage);
+
+ if (mSaveAndFinishWorker != null) {
+ setPrimaryButtonEnabled(true);
+ mSaveAndFinishWorker.setListener(mWorkerListener);
+ }
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ if (mSaveAndFinishWorker != null) {
+ mSaveAndFinishWorker.setListener(null);
+ }
+ }
+
+ /**
+ * Keep track internally of where the user is in choosing a pattern.
+ */
+ protected enum Stage {
+
+ /**
+ * Initial stage when first launching choose a lock pattern.
+ * Pattern enabled, secondary button allow for Cancel, primary button disabled.
+ */
+ Introduction(
+ R.string.lockpattern_recording_intro_header,
+ SecondaryButtonState.Cancel, PrimaryButtonState.ContinueDisabled,
+ true /* patternEnabled */ ),
+ /**
+ * Help screen to show how a valid pattern looks like.
+ * Pattern disabled, primary button shows Ok. No secondary button.
+ */
+ HelpScreen(
+ R.string.lockpattern_settings_help_how_to_record,
+ SecondaryButtonState.Gone, PrimaryButtonState.Ok,
+ false /* patternEnabled */ ),
+ /**
+ * Invalid pattern is entered, hint message show required number of dots.
+ * Secondary button allows for Retry, primary button disabled.
+ */
+ ChoiceTooShort(
+ R.string.lockpattern_recording_incorrect_too_short,
+ SecondaryButtonState.Retry, PrimaryButtonState.ContinueDisabled,
+ true /* patternEnabled */ ),
+ /**
+ * First drawing on the pattern is valid, primary button shows Continue,
+ * can proceed to next screen.
+ */
+ FirstChoiceValid(
+ R.string.lockpattern_recording_intro_header,
+ SecondaryButtonState.Retry, PrimaryButtonState.Continue,
+ false /* patternEnabled */ ),
+ /**
+ * Need to draw pattern again to confirm.
+ * Secondary button allows for Cancel, primary button disabled.
+ */
+ NeedToConfirm(
+ R.string.lockpattern_need_to_confirm,
+ SecondaryButtonState.Cancel, PrimaryButtonState.ConfirmDisabled,
+ true /* patternEnabled */ ),
+ /**
+ * Confirmation of previous drawn pattern failed, didn't enter the same pattern.
+ * Need to re-draw the pattern to match the fist pattern.
+ */
+ ConfirmWrong(
+ R.string.lockpattern_pattern_wrong,
+ SecondaryButtonState.Cancel, PrimaryButtonState.ConfirmDisabled,
+ true /* patternEnabled */ ),
+ /**
+ * Pattern is confirmed after drawing the same pattern twice.
+ * Pattern disabled.
+ */
+ ChoiceConfirmed(
+ R.string.lockpattern_pattern_confirmed,
+ SecondaryButtonState.Cancel, PrimaryButtonState.Confirm,
+ false /* patternEnabled */ );
+
+
+ /**
+ * @param message The message displayed as instruction.
+ * @param secondaryButtonState The state of the secondary button.
+ * @param primaryButtonState The state of the primary button.
+ * @param patternEnabled Whether the pattern widget is enabled.
+ */
+ Stage(int messageId,
+ SecondaryButtonState secondaryButtonState,
+ PrimaryButtonState primaryButtonState,
+ boolean patternEnabled) {
+ this.messageId = messageId;
+ this.secondaryButtonState = secondaryButtonState;
+ this.primaryButtonState = primaryButtonState;
+ this.patternEnabled = patternEnabled;
+ }
+
+ final int messageId;
+ final SecondaryButtonState secondaryButtonState;
+ final PrimaryButtonState primaryButtonState;
+ final boolean patternEnabled;
+ }
+
+ /**
+ * Updates the messages and buttons appropriate to what stage the user
+ * is at in choosing a pattern. This doesn't handle clearing out the pattern;
+ * the pattern is expected to be in the right state.
+ * @param stage The stage UI should be updated to match with.
+ */
+ protected void updateStage(Stage stage) {
+ mUiStage = stage;
+
+ // Message text, visibility and
+ // enabled state all known from the stage
+ mMessageText.setText(stage.messageId);
+
+ if (stage.secondaryButtonState == SecondaryButtonState.Gone) {
+ setSecondaryButtonVisible(false);
+ } else {
+ setSecondaryButtonVisible(true);
+ setSecondaryButtonTextId(stage.secondaryButtonState.textResId);
+ setSecondaryButtonEnabled(stage.secondaryButtonState.enabled);
+ }
+
+ setPrimaryButtonTextId(stage.primaryButtonState.text);
+ setPrimaryButtonEnabled(stage.primaryButtonState.enabled);
+
+ // same for whether the pattern is enabled
+ if (stage.patternEnabled) {
+ mLockPatternView.enableInput();
+ } else {
+ mLockPatternView.disableInput();
+ }
+
+ // the rest of the stuff varies enough that it is easier just to handle
+ // on a case by case basis.
+ mLockPatternView.setDisplayMode(DisplayMode.Correct);
+
+ switch (mUiStage) {
+ case Introduction:
+ mLockPatternView.clearPattern();
+ break;
+ case HelpScreen:
+ mLockPatternView.setPattern(DisplayMode.Animate, mAnimatePattern);
+ break;
+ case ChoiceTooShort:
+ mLockPatternView.setDisplayMode(DisplayMode.Wrong);
+ postClearPatternRunnable();
+ break;
+ case FirstChoiceValid:
+ break;
+ case NeedToConfirm:
+ mLockPatternView.clearPattern();
+ break;
+ case ConfirmWrong:
+ mLockPatternView.setDisplayMode(DisplayMode.Wrong);
+ postClearPatternRunnable();
+ break;
+ case ChoiceConfirmed:
+ break;
+ default:
+ // Do nothing.
+ }
+ }
+
+ /**
+ * The states of the secondary footer button.
+ */
+ enum SecondaryButtonState {
+ Cancel(R.string.lockpattern_cancel_button_text, true),
+ CancelDisabled(R.string.lockpattern_cancel_button_text, false),
+ Retry(R.string.lockpattern_retry_button_text, true),
+ RetryDisabled(R.string.lockpattern_retry_button_text, false),
+ Gone(ID_EMPTY_MESSAGE, false);
+
+ /**
+ * @param text The displayed text for this mode.
+ * @param enabled Whether the button should be enabled.
+ */
+ SecondaryButtonState(int textId, boolean enabled) {
+ this.textResId = textId;
+ this.enabled = enabled;
+ }
+
+ final int textResId;
+ final boolean enabled;
+ }
+
+ /**
+ * The states of the primary footer button.
+ */
+ enum PrimaryButtonState {
+ Continue(R.string.lockpattern_continue_button_text, true),
+ ContinueDisabled(R.string.lockpattern_continue_button_text, false),
+ Confirm(R.string.lockpattern_confirm_button_text, true),
+ ConfirmDisabled(R.string.lockpattern_confirm_button_text, false),
+ Ok(R.string.okay, true);
+
+ /**
+ * @param text The displayed text for this mode.
+ * @param enabled Whether the button should be enabled.
+ */
+ PrimaryButtonState(int text, boolean enabled) {
+ this.text = text;
+ this.enabled = enabled;
+ }
+
+ final int text;
+ final boolean enabled;
+ }
+
+ // The pattern listener that responds according to a user choosing a new
+ // lock pattern.
+ private final LockPatternView.OnPatternListener mChooseNewLockPatternListener =
+ new LockPatternView.OnPatternListener() {
+ @Override
+ public void onPatternStart() {
+ mLockPatternView.removeCallbacks(mClearPatternRunnable);
+ updateUIWhenPatternInProgress();
+ }
+
+ @Override
+ public void onPatternCleared() {
+ mLockPatternView.removeCallbacks(mClearPatternRunnable);
+ }
+
+ @Override
+ public void onPatternDetected(List<LockPatternView.Cell> pattern) {
+ switch(mUiStage) {
+ case Introduction:
+ case ChoiceTooShort:
+ handlePatternEntered(pattern);
+ break;
+ case ConfirmWrong:
+ case NeedToConfirm:
+ handleConfirmPattern(pattern);
+ break;
+ default:
+ throw new IllegalStateException("Unexpected stage " + mUiStage
+ + " when entering the pattern.");
+ }
+ }
+
+ @Override
+ public void onPatternCellAdded(List<Cell> pattern) {}
+
+ private void handleConfirmPattern(List<LockPatternView.Cell> pattern) {
+ if (mChosenPattern == null) {
+ throw new IllegalStateException(
+ "null chosen pattern in stage 'need to confirm");
+ }
+ if (mChosenPattern.equals(pattern)) {
+ updateStage(Stage.ChoiceConfirmed);
+ } else {
+ updateStage(Stage.ConfirmWrong);
+ }
+ }
+
+ private void handlePatternEntered(List<LockPatternView.Cell> pattern) {
+ if (pattern.size() < LockPatternUtils.MIN_LOCK_PATTERN_SIZE) {
+ updateStage(Stage.ChoiceTooShort);
+ } else {
+ mChosenPattern = new ArrayList<LockPatternView.Cell>(pattern);
+ updateStage(Stage.FirstChoiceValid);
+ }
+ }
+ };
+
+ private void updateUIWhenPatternInProgress() {
+ mMessageText.setText(R.string.lockpattern_recording_inprogress);
+ setPrimaryButtonEnabled(false);
+ setSecondaryButtonEnabled(false);
+ }
+
+ // clear the wrong pattern unless they have started a new one
+ // already
+ private void postClearPatternRunnable() {
+ mLockPatternView.removeCallbacks(mClearPatternRunnable);
+ mLockPatternView.postDelayed(mClearPatternRunnable, mWrongPatternClearTimeOut);
+ }
+
+ private void setPrimaryButtonEnabled(boolean enabled) {
+ mPrimaryButton.setEnabled(enabled);
+ }
+
+ private void setPrimaryButtonTextId(int textId) {
+ mPrimaryButton.setText(textId);
+ }
+
+ private void setSecondaryButtonVisible(boolean visible) {
+ mSecondaryButton.setVisibility(visible ? View.VISIBLE : View.GONE);
+ }
+
+ private void setSecondaryButtonEnabled(boolean enabled) {
+ mSecondaryButton.setEnabled(enabled);
+ }
+
+ private void setSecondaryButtonTextId(int textId) {
+ mSecondaryButton.setText(textId);
+ }
+
+ /**
+ * The patten used during the help screen to show how to draw a pattern.
+ */
+ private final List<LockPatternView.Cell> mAnimatePattern =
+ Collections.unmodifiableList(Lists.newArrayList(
+ LockPatternView.Cell.of(0, 0),
+ LockPatternView.Cell.of(0, 1),
+ LockPatternView.Cell.of(1, 1),
+ LockPatternView.Cell.of(2, 1)
+ ));
+
+ private Runnable mClearPatternRunnable = () -> mLockPatternView.clearPattern();
+
+ // Update display message and proceed to next step according to the different text on
+ // the secondary button.
+ private void handleSecondaryButtonClick() {
+ switch(mUiStage.secondaryButtonState) {
+ case Retry:
+ mChosenPattern = null;
+ mLockPatternView.clearPattern();
+ updateStage(Stage.Introduction);
+ break;
+ case Cancel:
+ finish();
+ break;
+ default:
+ throw new IllegalStateException("secondary footer button pressed, but stage of " +
+ mUiStage + " doesn't make sense");
+ }
+ }
+
+ // Update display message and decide on next step according to the different text
+ // on the primary button
+ private void handlePrimaryButtonClick() {
+ switch(mUiStage.primaryButtonState) {
+ case Continue:
+ if (mUiStage != Stage.FirstChoiceValid) {
+ throw new IllegalStateException("expected ui stage "
+ + Stage.FirstChoiceValid + " when button is "
+ + PrimaryButtonState.Continue);
+ }
+ updateStage(Stage.NeedToConfirm);
+ break;
+ case Confirm:
+ if (mUiStage != Stage.ChoiceConfirmed) {
+ throw new IllegalStateException("expected ui stage " + Stage.ChoiceConfirmed
+ + " when button is " + PrimaryButtonState.Confirm);
+ }
+ startSaveAndFinish();
+ break;
+ case Ok:
+ if (mUiStage != Stage.HelpScreen) {
+ throw new IllegalStateException("Help screen is only mode with ok button, "
+ + "but stage is " + mUiStage);
+ }
+ mLockPatternView.clearPattern();
+ mLockPatternView.setDisplayMode(DisplayMode.Correct);
+ updateStage(Stage.Introduction);
+ break;
+ default:
+ // Do nothing.
+ }
+ }
+
+ // Save recorded pattern as an async task and proceed to next
+ private void startSaveAndFinish() {
+ if (mSaveAndFinishWorker != null) {
+ Log.v(TAG, "startSaveAndFinish with an existing SaveAndFinishWorker.");
+ return;
+ }
+
+ setPrimaryButtonEnabled(false);
+
+ mSaveAndFinishWorker = new SaveAndFinishWorker();
+ mSaveAndFinishWorker.setListener(mWorkerListener);
+ mSaveAndFinishWorker.start(new LockPatternUtils(this),
+ mChosenPattern, mCurrentPattern, mUserId);
+ }
+
+ private SaveAndFinishWorker.Listener mWorkerListener = (resultData) -> finish();
+
+ /**
+ * Async task to save the chosen lock pattern.
+ */
+ public static class SaveAndFinishWorker extends SaveChosenLockWorkerBase {
+ private List<LockPatternView.Cell> mChosenPattern;
+ private String mCurrentPattern;
+
+ public void start(LockPatternUtils utils,
+ List<LockPatternView.Cell> chosenPattern, String currentPattern, int userId) {
+ prepare(utils, userId);
+
+ mCurrentPattern = currentPattern;
+ mChosenPattern = chosenPattern;
+ mUserId = userId;
+
+ start();
+ }
+
+ @Override
+ protected Intent saveAndVerifyInBackground() {
+ Intent result = null;
+ final int userId = mUserId;
+ mUtils.saveLockPattern(mChosenPattern, mCurrentPattern, userId);
+ return result;
+ }
+
+ @Override
+ protected void finish(Intent resultData) {
+ if (!mUtils.isPatternEverChosen(mUserId)) {
+ mUtils.setVisiblePatternEnabled(true, mUserId);
+ }
+
+ super.finish(resultData);
+ }
+ }
+}
diff --git a/src/com/android/car/settings/security/ChooseLockTypeFragment.java b/src/com/android/car/settings/security/ChooseLockTypeFragment.java
new file mode 100644
index 0000000..9292407
--- /dev/null
+++ b/src/com/android/car/settings/security/ChooseLockTypeFragment.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2018 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.car.settings.security;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.ListItemSettingsFragment;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import androidx.car.widget.ListItemProvider;
+import androidx.car.widget.ListItem;
+import androidx.car.widget.TextListItem;
+
+/**
+ * Give user choices of lock screen type: Pin/Pattern/Password or None.
+ */
+public class ChooseLockTypeFragment extends ListItemSettingsFragment {
+ static final String LOCK_PATTERN = "lockPattern";
+ static final String LOCK_PIN = "lockPin";
+ static final String LOCK_PASSWORD = "lockPassword";
+ private final List<ListItem> mItems = new ArrayList<>();
+ private ListItemProvider mItemProvider;
+
+ public static ChooseLockTypeFragment newInstance() {
+ ChooseLockTypeFragment chooseLockTypeFragment = new ChooseLockTypeFragment();
+ Bundle bundle = ListItemSettingsFragment.getBundle();
+ bundle.putInt(EXTRA_TITLE_ID, R.string.lock_settings_picker_title);
+ bundle.putInt(EXTRA_ACTION_BAR_LAYOUT, R.layout.action_bar_with_button);
+ chooseLockTypeFragment.setArguments(bundle);
+ return chooseLockTypeFragment;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ }
+
+ @Override
+ public ListItemProvider getItemProvider() {
+ if (mItemProvider == null) {
+ mItemProvider = new ListItemProvider.ListProvider(getListItems());
+ }
+ return mItemProvider;
+ }
+
+ private List<ListItem> getListItems() {
+ List<ListItem> items = new ArrayList<>();
+ items.add(createLockPatternLineItem());
+ return items;
+ }
+
+ private ListItem createLockPatternLineItem() {
+ TextListItem item = new TextListItem(getContext());
+ item.setTitle(getString(R.string.security_lock_pattern));
+ item.setOnClickListener(view -> startChooseSecurityLockActivity(LOCK_PATTERN));
+ return item;
+ }
+
+ private void startChooseSecurityLockActivity(String lockType) {
+ Intent intent = new Intent(getContext(), ChooseSecurityLockActivity.class);
+ intent.putExtra(ChooseSecurityLockActivity.EXTRA_LOCK_TYPE, lockType);
+ getContext().startActivity(intent);
+ }
+}
diff --git a/src/com/android/car/settings/security/ChooseSecurityLockActivity.java b/src/com/android/car/settings/security/ChooseSecurityLockActivity.java
new file mode 100644
index 0000000..7246fba
--- /dev/null
+++ b/src/com/android/car/settings/security/ChooseSecurityLockActivity.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2018 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.car.settings.security;
+
+import android.app.Activity;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * Bring up different activities to handle choice of security lock types.
+ * i.e. Lock pattern, pin, password.
+ */
+public class ChooseSecurityLockActivity extends Activity {
+ /**
+ * A string to keep the security lock type being chosen.
+ */
+ static final String EXTRA_LOCK_TYPE = "lockType";
+ // Arbitrary request code for choose security lock activity.
+ private static final int REQUEST_CHOOSE_LOCK = 10001;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Intent intent = new Intent(this, ChooseLockPatternActivity.class);
+ startActivityForResult(intent, REQUEST_CHOOSE_LOCK);
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ finish();
+ }
+}
diff --git a/src/com/android/car/settings/security/SaveChosenLockWorkerBase.java b/src/com/android/car/settings/security/SaveChosenLockWorkerBase.java
new file mode 100644
index 0000000..ff8bd85
--- /dev/null
+++ b/src/com/android/car/settings/security/SaveChosenLockWorkerBase.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2018 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.car.settings.security;
+
+import android.annotation.WorkerThread;
+import android.app.Fragment;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Bundle;
+
+import com.android.internal.widget.LockPatternUtils;
+
+/**
+ * An invisible retained worker fragment to track the AsyncWork that saves
+ * the chosen lock credential (pattern/pin/password).
+ */
+abstract class SaveChosenLockWorkerBase extends Fragment {
+
+ public static final String EXTRA_KEY_PATTERN = "pattern";
+ public static final String EXTRA_KEY_PASSWORD = "password";
+
+ private Listener mListener;
+ private boolean mFinished;
+ private Intent mResultData;
+
+ protected LockPatternUtils mUtils;
+ protected int mUserId;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setRetainInstance(true);
+ }
+
+ /**
+ * Set the listener to get callback when finished saving the chosen lock.
+ */
+ public void setListener(Listener listener) {
+ if (mListener == listener) {
+ return;
+ }
+
+ mListener = listener;
+ if (mFinished && mListener != null) {
+ mListener.onChosenLockSaveFinished(mResultData);
+ }
+ }
+
+ /**
+ * Set the initial state for the async task.
+ */
+ protected void prepare(LockPatternUtils utils, int userId) {
+ mUtils = utils;
+ mUserId = userId;
+
+ mFinished = false;
+ mResultData = null;
+ }
+
+ /**
+ * Start executing the async task.
+ */
+ protected void start() {
+ new Task().execute();
+ }
+
+ /**
+ * Executes the save and verify work in background.
+ */
+ @WorkerThread
+ protected abstract Intent saveAndVerifyInBackground();
+
+ /**
+ * Send result data via the listener when task finishes.
+ */
+ protected void finish(Intent resultData) {
+ mFinished = true;
+ mResultData = resultData;
+ if (mListener != null) {
+ mListener.onChosenLockSaveFinished(mResultData);
+ }
+ }
+
+ // Save chosen lock task.
+ private class Task extends AsyncTask<Void, Void, Intent> {
+ @Override
+ protected Intent doInBackground(Void... params){
+ return saveAndVerifyInBackground();
+ }
+
+ @Override
+ protected void onPostExecute(Intent resultData) {
+ finish(resultData);
+ }
+ }
+
+ /**
+ * Call back when finishing save the chosen lock.
+ */
+ interface Listener {
+ public void onChosenLockSaveFinished(Intent resultData);
+ }
+}
\ No newline at end of file