From 5d7d1665fecd9f0dba90ccf5e827d3a295dafdcc Mon Sep 17 00:00:00 2001 From: Josh Hou Date: Mon, 9 Jan 2023 18:18:11 +0800 Subject: [PATCH 1/4] Fix the security vulnerability issue in AppLocalePickerActivity Examine whether the packages is allowed to display app locales list when creating the AppLocalePickerActivity, and examine whether the target user is the same as the calling user. Bug: 257954050 Test: Follows the test step listed in b/257954050#comment14 Change-Id: I2e25a308bcba6ea0edee89c7a78465f766bdbeac Merged-In: I2e25a308bcba6ea0edee89c7a78465f766bdbeac --- .../localepicker/AppLocalePickerActivity.java | 19 +- .../AppLocalePickerActivityTest.java | 172 +++++++++++++++++- 2 files changed, 183 insertions(+), 8 deletions(-) diff --git a/src/com/android/settings/localepicker/AppLocalePickerActivity.java b/src/com/android/settings/localepicker/AppLocalePickerActivity.java index b7fef30a58e..691344da60f 100644 --- a/src/com/android/settings/localepicker/AppLocalePickerActivity.java +++ b/src/com/android/settings/localepicker/AppLocalePickerActivity.java @@ -19,6 +19,7 @@ package com.android.settings.localepicker; import android.app.FragmentTransaction; import android.app.LocaleManager; import android.content.Context; +import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; import android.os.LocaleList; @@ -34,6 +35,7 @@ import com.android.internal.app.LocalePickerWithRegion; import com.android.internal.app.LocaleStore; import com.android.settings.R; import com.android.settings.applications.AppInfoBase; +import com.android.settings.applications.AppLocaleUtil; import com.android.settings.applications.appinfo.AppLocaleDetails; import com.android.settings.core.SettingsBaseActivity; @@ -64,12 +66,17 @@ public class AppLocalePickerActivity extends SettingsBaseActivity } mContextAsUser = this; if (getIntent().hasExtra(AppInfoBase.ARG_PACKAGE_UID)) { - int userId = getIntent().getIntExtra(AppInfoBase.ARG_PACKAGE_UID, -1); - if (userId != -1) { - UserHandle userHandle = UserHandle.getUserHandleForUid(userId); + int uid = getIntent().getIntExtra(AppInfoBase.ARG_PACKAGE_UID, -1); + if (uid != -1) { + UserHandle userHandle = UserHandle.getUserHandleForUid(uid); mContextAsUser = createContextAsUser(userHandle, 0); } } + if (!canDisplayLocaleUi() || mContextAsUser.getUserId() != UserHandle.myUserId()) { + Log.w(TAG, "Not allow to display Locale Settings UI."); + finish(); + return; + } setTitle(R.string.app_locale_picker_title); getActionBar().setDisplayHomeAsUpEnabled(true); @@ -160,4 +167,10 @@ public class AppLocalePickerActivity extends SettingsBaseActivity .replace(R.id.content_frame, mLocalePickerWithRegion) .commit(); } + + private boolean canDisplayLocaleUi() { + return AppLocaleUtil.canDisplayLocaleUi(mContextAsUser, mPackageName, + mContextAsUser.getPackageManager().queryIntentActivities( + AppLocaleUtil.LAUNCHER_ENTRY_INTENT, PackageManager.GET_META_DATA)); + } } diff --git a/tests/robotests/src/com/android/settings/localepicker/AppLocalePickerActivityTest.java b/tests/robotests/src/com/android/settings/localepicker/AppLocalePickerActivityTest.java index 332a39b3436..77b2abb76dc 100644 --- a/tests/robotests/src/com/android/settings/localepicker/AppLocalePickerActivityTest.java +++ b/tests/robotests/src/com/android/settings/localepicker/AppLocalePickerActivityTest.java @@ -18,25 +18,35 @@ package com.android.settings.localepicker; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.annotation.Nullable; import android.app.Activity; import android.app.ApplicationPackageManager; +import android.app.LocaleConfig; import android.content.Context; import android.content.Intent; +import android.content.pm.ApplicationInfo; import android.content.pm.InstallSourceInfo; +import android.content.pm.PackageInfo; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; import android.net.Uri; +import android.os.LocaleList; import android.os.Process; import android.os.UserHandle; import android.telephony.TelephonyManager; +import androidx.annotation.ArrayRes; + import com.android.internal.app.LocaleStore; import com.android.settings.applications.AppInfoBase; -import java.util.Locale; - +import org.junit.After; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -45,17 +55,26 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; import org.robolectric.Shadows; import org.robolectric.android.controller.ActivityController; import org.robolectric.annotation.Config; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; +import org.robolectric.shadows.ShadowPackageManager; import org.robolectric.shadows.ShadowTelephonyManager; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + @RunWith(RobolectricTestRunner.class) @Config( shadows = { AppLocalePickerActivityTest.ShadowApplicationPackageManager.class, + AppLocalePickerActivityTest.ShadowResources.class, + AppLocalePickerActivityTest.ShadowUserHandle.class, + AppLocalePickerActivityTest.ShadowLocaleConfig.class, }) public class AppLocalePickerActivityTest { private static final String TEST_PACKAGE_NAME = "com.android.settings"; @@ -67,21 +86,99 @@ public class AppLocalePickerActivityTest { @Rule public MockitoRule rule = MockitoJUnit.rule(); + private Context mContext; + private ShadowPackageManager mPackageManager; + + @Before + public void setUp() { + mContext = spy(RuntimeEnvironment.application); + mPackageManager = Shadows.shadowOf(mContext.getPackageManager()); + } + + @After + public void tearDown() { + mPackageManager.removePackage(TEST_PACKAGE_NAME); + ShadowResources.setDisAllowPackage(false); + ShadowApplicationPackageManager.setNoLaunchEntry(false); + ShadowUserHandle.setUserId(0); + ShadowLocaleConfig.setStatus(LocaleConfig.STATUS_SUCCESS); + } + @Test public void launchAppLocalePickerActivity_hasPackageName_success() { ActivityController controller = initActivityController(true); - controller.create(); assertThat(controller.get().isFinishing()).isFalse(); } + @Test + public void launchAppLocalePickerActivity_appNoLocaleConfig_failed() { + ShadowLocaleConfig.setStatus(LocaleConfig.STATUS_NOT_SPECIFIED); + + ActivityController controller = + initActivityController(true); + controller.create(); + + assertThat(controller.get().isFinishing()).isTrue(); + } + + @Test + public void launchAppLocalePickerActivity_appSignPlatformKey_failed() { + final ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_SIGNED_WITH_PLATFORM_KEY; + applicationInfo.packageName = TEST_PACKAGE_NAME; + + final PackageInfo packageInfo = new PackageInfo(); + packageInfo.packageName = TEST_PACKAGE_NAME; + packageInfo.applicationInfo = applicationInfo; + mPackageManager.installPackage(packageInfo); + + ActivityController controller = + initActivityController(true); + controller.create(); + + assertThat(controller.get().isFinishing()).isTrue(); + } + + @Test + public void launchAppLocalePickerActivity_appMatchDisallowedPackage_failed() { + ShadowResources.setDisAllowPackage(true); + + ActivityController controller = + initActivityController(true); + controller.create(); + + assertThat(controller.get().isFinishing()).isTrue(); + } + + @Test + public void launchAppLocalePickerActivity_appNoLaunchEntry_failed() { + ShadowApplicationPackageManager.setNoLaunchEntry(true); + + ActivityController controller = + initActivityController(true); + controller.create(); + + assertThat(controller.get().isFinishing()).isTrue(); + } + + @Test + public void launchAppLocalePickerActivity_modifyAppLocalesOfAnotherUser_failed() { + ShadowUserHandle.setUserId(10); + + ActivityController controller = + initActivityController(true); + controller.create(); + + assertThat(controller.get().isFinishing()).isTrue(); + } + @Test public void launchAppLocalePickerActivity_intentWithoutPackageName_failed() { ActivityController controller = initActivityController(false); - controller.create(); assertThat(controller.get().isFinishing()).isTrue(); @@ -125,7 +222,7 @@ public class AppLocalePickerActivityTest { if (hasPackageName) { data.setData(TEST_PACKAGE_URI); } - data.putExtra(AppInfoBase.ARG_PACKAGE_UID, UserHandle.getUserId(Process.myUid())); + data.putExtra(AppInfoBase.ARG_PACKAGE_UID, Process.myUid()); ActivityController activityController = Robolectric.buildActivity(TestAppLocalePickerActivity.class, data); Activity activity = activityController.get(); @@ -149,10 +246,75 @@ public class AppLocalePickerActivityTest { @Implements(ApplicationPackageManager.class) public static class ShadowApplicationPackageManager extends org.robolectric.shadows.ShadowApplicationPackageManager { + private static boolean sNoLaunchEntry = false; @Implementation protected Object getInstallSourceInfo(String packageName) { return new InstallSourceInfo("", null, null, ""); } + + @Implementation + protected List queryIntentActivities(Intent intent, int flags) { + if (sNoLaunchEntry) { + return new ArrayList(); + } else { + return super.queryIntentActivities(intent, flags); + } + } + + private static void setNoLaunchEntry(boolean noLaunchEntry) { + sNoLaunchEntry = noLaunchEntry; + } + } + + @Implements(Resources.class) + public static class ShadowResources extends + org.robolectric.shadows.ShadowResources { + private static boolean sDisAllowPackage = false; + + @Implementation + public String[] getStringArray(@ArrayRes int id) { + if (sDisAllowPackage) { + return new String[]{TEST_PACKAGE_NAME}; + } else { + return new String[0]; + } + } + + private static void setDisAllowPackage(boolean disAllowPackage) { + sDisAllowPackage = disAllowPackage; + } + } + + @Implements(UserHandle.class) + public static class ShadowUserHandle { + private static int sUserId = 0; + private static void setUserId(int userId) { + sUserId = userId; + } + + @Implementation + public static int getUserId(int userId) { + return sUserId; + } + } + + @Implements(LocaleConfig.class) + public static class ShadowLocaleConfig { + private static int sStatus = 0; + + @Implementation + public @Nullable LocaleList getSupportedLocales() { + return LocaleList.forLanguageTags("en-US"); + } + + private static void setStatus(@LocaleConfig.Status int status) { + sStatus = status; + } + + @Implementation + public @LocaleConfig.Status int getStatus() { + return sStatus; + } } } From 33ef83795e44f9360d9113df0f309e99f47be23e Mon Sep 17 00:00:00 2001 From: Josh Hou Date: Mon, 9 Jan 2023 18:18:11 +0800 Subject: [PATCH 2/4] Fix the security vulnerability issue in AppLocalePickerActivity Examine whether the packages is allowed to display app locales list when creating the AppLocalePickerActivity, and examine whether the target user is the same as the calling user. Bug: 257954050 Test: Follows the test step listed in b/257954050#comment14 Change-Id: I2e25a308bcba6ea0edee89c7a78465f766bdbeac Merged-In: I2e25a308bcba6ea0edee89c7a78465f766bdbeac --- .../localepicker/AppLocalePickerActivity.java | 19 +- .../AppLocalePickerActivityTest.java | 172 +++++++++++++++++- 2 files changed, 183 insertions(+), 8 deletions(-) diff --git a/src/com/android/settings/localepicker/AppLocalePickerActivity.java b/src/com/android/settings/localepicker/AppLocalePickerActivity.java index b7fef30a58e..691344da60f 100644 --- a/src/com/android/settings/localepicker/AppLocalePickerActivity.java +++ b/src/com/android/settings/localepicker/AppLocalePickerActivity.java @@ -19,6 +19,7 @@ package com.android.settings.localepicker; import android.app.FragmentTransaction; import android.app.LocaleManager; import android.content.Context; +import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; import android.os.LocaleList; @@ -34,6 +35,7 @@ import com.android.internal.app.LocalePickerWithRegion; import com.android.internal.app.LocaleStore; import com.android.settings.R; import com.android.settings.applications.AppInfoBase; +import com.android.settings.applications.AppLocaleUtil; import com.android.settings.applications.appinfo.AppLocaleDetails; import com.android.settings.core.SettingsBaseActivity; @@ -64,12 +66,17 @@ public class AppLocalePickerActivity extends SettingsBaseActivity } mContextAsUser = this; if (getIntent().hasExtra(AppInfoBase.ARG_PACKAGE_UID)) { - int userId = getIntent().getIntExtra(AppInfoBase.ARG_PACKAGE_UID, -1); - if (userId != -1) { - UserHandle userHandle = UserHandle.getUserHandleForUid(userId); + int uid = getIntent().getIntExtra(AppInfoBase.ARG_PACKAGE_UID, -1); + if (uid != -1) { + UserHandle userHandle = UserHandle.getUserHandleForUid(uid); mContextAsUser = createContextAsUser(userHandle, 0); } } + if (!canDisplayLocaleUi() || mContextAsUser.getUserId() != UserHandle.myUserId()) { + Log.w(TAG, "Not allow to display Locale Settings UI."); + finish(); + return; + } setTitle(R.string.app_locale_picker_title); getActionBar().setDisplayHomeAsUpEnabled(true); @@ -160,4 +167,10 @@ public class AppLocalePickerActivity extends SettingsBaseActivity .replace(R.id.content_frame, mLocalePickerWithRegion) .commit(); } + + private boolean canDisplayLocaleUi() { + return AppLocaleUtil.canDisplayLocaleUi(mContextAsUser, mPackageName, + mContextAsUser.getPackageManager().queryIntentActivities( + AppLocaleUtil.LAUNCHER_ENTRY_INTENT, PackageManager.GET_META_DATA)); + } } diff --git a/tests/robotests/src/com/android/settings/localepicker/AppLocalePickerActivityTest.java b/tests/robotests/src/com/android/settings/localepicker/AppLocalePickerActivityTest.java index 332a39b3436..77b2abb76dc 100644 --- a/tests/robotests/src/com/android/settings/localepicker/AppLocalePickerActivityTest.java +++ b/tests/robotests/src/com/android/settings/localepicker/AppLocalePickerActivityTest.java @@ -18,25 +18,35 @@ package com.android.settings.localepicker; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.annotation.Nullable; import android.app.Activity; import android.app.ApplicationPackageManager; +import android.app.LocaleConfig; import android.content.Context; import android.content.Intent; +import android.content.pm.ApplicationInfo; import android.content.pm.InstallSourceInfo; +import android.content.pm.PackageInfo; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; import android.net.Uri; +import android.os.LocaleList; import android.os.Process; import android.os.UserHandle; import android.telephony.TelephonyManager; +import androidx.annotation.ArrayRes; + import com.android.internal.app.LocaleStore; import com.android.settings.applications.AppInfoBase; -import java.util.Locale; - +import org.junit.After; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -45,17 +55,26 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; import org.robolectric.Shadows; import org.robolectric.android.controller.ActivityController; import org.robolectric.annotation.Config; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; +import org.robolectric.shadows.ShadowPackageManager; import org.robolectric.shadows.ShadowTelephonyManager; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + @RunWith(RobolectricTestRunner.class) @Config( shadows = { AppLocalePickerActivityTest.ShadowApplicationPackageManager.class, + AppLocalePickerActivityTest.ShadowResources.class, + AppLocalePickerActivityTest.ShadowUserHandle.class, + AppLocalePickerActivityTest.ShadowLocaleConfig.class, }) public class AppLocalePickerActivityTest { private static final String TEST_PACKAGE_NAME = "com.android.settings"; @@ -67,21 +86,99 @@ public class AppLocalePickerActivityTest { @Rule public MockitoRule rule = MockitoJUnit.rule(); + private Context mContext; + private ShadowPackageManager mPackageManager; + + @Before + public void setUp() { + mContext = spy(RuntimeEnvironment.application); + mPackageManager = Shadows.shadowOf(mContext.getPackageManager()); + } + + @After + public void tearDown() { + mPackageManager.removePackage(TEST_PACKAGE_NAME); + ShadowResources.setDisAllowPackage(false); + ShadowApplicationPackageManager.setNoLaunchEntry(false); + ShadowUserHandle.setUserId(0); + ShadowLocaleConfig.setStatus(LocaleConfig.STATUS_SUCCESS); + } + @Test public void launchAppLocalePickerActivity_hasPackageName_success() { ActivityController controller = initActivityController(true); - controller.create(); assertThat(controller.get().isFinishing()).isFalse(); } + @Test + public void launchAppLocalePickerActivity_appNoLocaleConfig_failed() { + ShadowLocaleConfig.setStatus(LocaleConfig.STATUS_NOT_SPECIFIED); + + ActivityController controller = + initActivityController(true); + controller.create(); + + assertThat(controller.get().isFinishing()).isTrue(); + } + + @Test + public void launchAppLocalePickerActivity_appSignPlatformKey_failed() { + final ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_SIGNED_WITH_PLATFORM_KEY; + applicationInfo.packageName = TEST_PACKAGE_NAME; + + final PackageInfo packageInfo = new PackageInfo(); + packageInfo.packageName = TEST_PACKAGE_NAME; + packageInfo.applicationInfo = applicationInfo; + mPackageManager.installPackage(packageInfo); + + ActivityController controller = + initActivityController(true); + controller.create(); + + assertThat(controller.get().isFinishing()).isTrue(); + } + + @Test + public void launchAppLocalePickerActivity_appMatchDisallowedPackage_failed() { + ShadowResources.setDisAllowPackage(true); + + ActivityController controller = + initActivityController(true); + controller.create(); + + assertThat(controller.get().isFinishing()).isTrue(); + } + + @Test + public void launchAppLocalePickerActivity_appNoLaunchEntry_failed() { + ShadowApplicationPackageManager.setNoLaunchEntry(true); + + ActivityController controller = + initActivityController(true); + controller.create(); + + assertThat(controller.get().isFinishing()).isTrue(); + } + + @Test + public void launchAppLocalePickerActivity_modifyAppLocalesOfAnotherUser_failed() { + ShadowUserHandle.setUserId(10); + + ActivityController controller = + initActivityController(true); + controller.create(); + + assertThat(controller.get().isFinishing()).isTrue(); + } + @Test public void launchAppLocalePickerActivity_intentWithoutPackageName_failed() { ActivityController controller = initActivityController(false); - controller.create(); assertThat(controller.get().isFinishing()).isTrue(); @@ -125,7 +222,7 @@ public class AppLocalePickerActivityTest { if (hasPackageName) { data.setData(TEST_PACKAGE_URI); } - data.putExtra(AppInfoBase.ARG_PACKAGE_UID, UserHandle.getUserId(Process.myUid())); + data.putExtra(AppInfoBase.ARG_PACKAGE_UID, Process.myUid()); ActivityController activityController = Robolectric.buildActivity(TestAppLocalePickerActivity.class, data); Activity activity = activityController.get(); @@ -149,10 +246,75 @@ public class AppLocalePickerActivityTest { @Implements(ApplicationPackageManager.class) public static class ShadowApplicationPackageManager extends org.robolectric.shadows.ShadowApplicationPackageManager { + private static boolean sNoLaunchEntry = false; @Implementation protected Object getInstallSourceInfo(String packageName) { return new InstallSourceInfo("", null, null, ""); } + + @Implementation + protected List queryIntentActivities(Intent intent, int flags) { + if (sNoLaunchEntry) { + return new ArrayList(); + } else { + return super.queryIntentActivities(intent, flags); + } + } + + private static void setNoLaunchEntry(boolean noLaunchEntry) { + sNoLaunchEntry = noLaunchEntry; + } + } + + @Implements(Resources.class) + public static class ShadowResources extends + org.robolectric.shadows.ShadowResources { + private static boolean sDisAllowPackage = false; + + @Implementation + public String[] getStringArray(@ArrayRes int id) { + if (sDisAllowPackage) { + return new String[]{TEST_PACKAGE_NAME}; + } else { + return new String[0]; + } + } + + private static void setDisAllowPackage(boolean disAllowPackage) { + sDisAllowPackage = disAllowPackage; + } + } + + @Implements(UserHandle.class) + public static class ShadowUserHandle { + private static int sUserId = 0; + private static void setUserId(int userId) { + sUserId = userId; + } + + @Implementation + public static int getUserId(int userId) { + return sUserId; + } + } + + @Implements(LocaleConfig.class) + public static class ShadowLocaleConfig { + private static int sStatus = 0; + + @Implementation + public @Nullable LocaleList getSupportedLocales() { + return LocaleList.forLanguageTags("en-US"); + } + + private static void setStatus(@LocaleConfig.Status int status) { + sStatus = status; + } + + @Implementation + public @LocaleConfig.Status int getStatus() { + return sStatus; + } } } From 1021b58ae539fe32c1b6d5433acc8039ad6d30ef Mon Sep 17 00:00:00 2001 From: Diya Bera Date: Thu, 12 Jan 2023 18:18:09 +0000 Subject: [PATCH 3/4] Tests for SUW in work mode Bug: 247049655 Test: m RunSettingsRoboTests -j30 ROBOTEST_FILTER=MultiBiometricEnrollHelperTest Change-Id: Ifabb96a90c340bc9306cc0f033ba103c614f32a9 --- .../MultiBiometricEnrollHelper.java | 80 ++++++---- .../MultiBiometricEnrollHelperTest.java | 148 ++++++++++++++++++ 2 files changed, 199 insertions(+), 29 deletions(-) create mode 100644 tests/robotests/src/com/android/settings/biometrics/MultiBiometricEnrollHelperTest.java diff --git a/src/com/android/settings/biometrics/MultiBiometricEnrollHelper.java b/src/com/android/settings/biometrics/MultiBiometricEnrollHelper.java index a994b9564d9..d85f446d1a3 100644 --- a/src/com/android/settings/biometrics/MultiBiometricEnrollHelper.java +++ b/src/com/android/settings/biometrics/MultiBiometricEnrollHelper.java @@ -24,8 +24,11 @@ import android.hardware.fingerprint.FingerprintManager; import androidx.annotation.NonNull; import androidx.fragment.app.FragmentActivity; +import com.android.internal.annotations.VisibleForTesting; import com.android.settings.password.ChooseLockSettingsHelper; +import java.util.function.Function; + /** * Helper for {@link BiometricEnrollActivity} when multiple sensors exist on a device. */ @@ -45,14 +48,39 @@ public class MultiBiometricEnrollHelper { private final int mUserId; private final boolean mRequestEnrollFace; private final boolean mRequestEnrollFingerprint; + private final FingerprintManager mFingerprintManager; + private final FaceManager mFaceManager; + private final Intent mFingerprintEnrollIntroductionIntent; + private final Intent mFaceEnrollIntroductionIntent; + private Function mGatekeeperHatSupplier; + @VisibleForTesting MultiBiometricEnrollHelper(@NonNull FragmentActivity activity, int userId, - boolean enrollFace, boolean enrollFingerprint, long gkPwHandle) { + boolean enrollFace, boolean enrollFingerprint, long gkPwHandle, + FingerprintManager fingerprintManager, + FaceManager faceManager, Intent fingerprintEnrollIntroductionIntent, + Intent faceEnrollIntroductionIntent, Function gatekeeperHatSupplier) { mActivity = activity; mUserId = userId; mGkPwHandle = gkPwHandle; mRequestEnrollFace = enrollFace; mRequestEnrollFingerprint = enrollFingerprint; + mFingerprintManager = fingerprintManager; + mFaceManager = faceManager; + mFingerprintEnrollIntroductionIntent = fingerprintEnrollIntroductionIntent; + mFaceEnrollIntroductionIntent = faceEnrollIntroductionIntent; + mGatekeeperHatSupplier = gatekeeperHatSupplier; + } + + MultiBiometricEnrollHelper(@NonNull FragmentActivity activity, int userId, + boolean enrollFace, boolean enrollFingerprint, long gkPwHandle) { + this(activity, userId, enrollFace, enrollFingerprint, gkPwHandle, + activity.getSystemService(FingerprintManager.class), + activity.getSystemService(FaceManager.class), + BiometricUtils.getFingerprintIntroIntent(activity, activity.getIntent()), + BiometricUtils.getFaceIntroIntent(activity, activity.getIntent()), + (challenge) -> BiometricUtils.requestGatekeeperHat(activity, gkPwHandle, + userId, challenge)); } void startNextStep() { @@ -67,45 +95,39 @@ public class MultiBiometricEnrollHelper { } private void launchFaceEnroll() { - final FaceManager faceManager = mActivity.getSystemService(FaceManager.class); - faceManager.generateChallenge(mUserId, (sensorId, userId, challenge) -> { - final byte[] hardwareAuthToken = BiometricUtils.requestGatekeeperHat(mActivity, - mGkPwHandle, mUserId, challenge); - final Intent faceIntent = BiometricUtils.getFaceIntroIntent(mActivity, - mActivity.getIntent()); - faceIntent.putExtra(BiometricEnrollBase.EXTRA_KEY_SENSOR_ID, sensorId); - faceIntent.putExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, challenge); - BiometricUtils.launchEnrollForResult(mActivity, faceIntent, REQUEST_FACE_ENROLL, - hardwareAuthToken, mGkPwHandle, mUserId); + mFaceManager.generateChallenge(mUserId, (sensorId, userId, challenge) -> { + final byte[] hardwareAuthToken = mGatekeeperHatSupplier.apply(challenge); + mFaceEnrollIntroductionIntent.putExtra( + BiometricEnrollBase.EXTRA_KEY_SENSOR_ID, sensorId); + mFaceEnrollIntroductionIntent.putExtra( + BiometricEnrollBase.EXTRA_KEY_CHALLENGE, challenge); + BiometricUtils.launchEnrollForResult(mActivity, mFaceEnrollIntroductionIntent, + REQUEST_FACE_ENROLL, hardwareAuthToken, mGkPwHandle, mUserId); }); } private void launchFingerprintEnroll() { - final FingerprintManager fingerprintManager = mActivity - .getSystemService(FingerprintManager.class); - fingerprintManager.generateChallenge(mUserId, ((sensorId, userId, challenge) -> { - final byte[] hardwareAuthToken = BiometricUtils.requestGatekeeperHat(mActivity, - mGkPwHandle, mUserId, challenge); - final Intent intent = BiometricUtils.getFingerprintIntroIntent(mActivity, - mActivity.getIntent()); - intent.putExtra(BiometricEnrollBase.EXTRA_KEY_SENSOR_ID, sensorId); - intent.putExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, challenge); + mFingerprintManager.generateChallenge(mUserId, ((sensorId, userId, challenge) -> { + final byte[] hardwareAuthToken = mGatekeeperHatSupplier.apply(challenge); + mFingerprintEnrollIntroductionIntent.putExtra( + BiometricEnrollBase.EXTRA_KEY_SENSOR_ID, sensorId); + mFingerprintEnrollIntroductionIntent.putExtra( + BiometricEnrollBase.EXTRA_KEY_CHALLENGE, challenge); if (mRequestEnrollFace) { // Give FingerprintEnroll a pendingIntent pointing to face enrollment, so that it // can be started when user skips or finishes fingerprint enrollment. // FLAG_UPDATE_CURRENT ensures it is launched with the most recent values. - final Intent faceIntent = BiometricUtils.getFaceIntroIntent(mActivity, - mActivity.getIntent()); - faceIntent.putExtra(Intent.EXTRA_USER_ID, mUserId); - faceIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, - mGkPwHandle); + mFaceEnrollIntroductionIntent.putExtra(Intent.EXTRA_USER_ID, mUserId); + mFaceEnrollIntroductionIntent.putExtra( + ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, mGkPwHandle); final PendingIntent faceAfterFp = PendingIntent.getActivity(mActivity, - 0 /* requestCode */, faceIntent, + 0 /* requestCode */, mFaceEnrollIntroductionIntent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT); - intent.putExtra(EXTRA_ENROLL_AFTER_FINGERPRINT, faceAfterFp); + mFingerprintEnrollIntroductionIntent.putExtra(EXTRA_ENROLL_AFTER_FINGERPRINT, + faceAfterFp); } - BiometricUtils.launchEnrollForResult(mActivity, intent, REQUEST_FINGERPRINT_ENROLL, - hardwareAuthToken, mGkPwHandle, mUserId); + BiometricUtils.launchEnrollForResult(mActivity, mFingerprintEnrollIntroductionIntent, + REQUEST_FINGERPRINT_ENROLL, hardwareAuthToken, mGkPwHandle, mUserId); })); } } diff --git a/tests/robotests/src/com/android/settings/biometrics/MultiBiometricEnrollHelperTest.java b/tests/robotests/src/com/android/settings/biometrics/MultiBiometricEnrollHelperTest.java new file mode 100644 index 00000000000..03b3b48461d --- /dev/null +++ b/tests/robotests/src/com/android/settings/biometrics/MultiBiometricEnrollHelperTest.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2022 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.biometrics; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.robolectric.Shadows.shadowOf; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.hardware.face.FaceManager; +import android.hardware.fingerprint.FingerprintManager; +import android.os.RemoteException; + +import androidx.fragment.app.FragmentActivity; + +import com.android.settings.biometrics.face.FaceEnrollIntroduction; +import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroduction; +import com.android.settings.password.ChooseLockSettingsHelper; +import com.android.settings.testutils.shadow.ShadowLockPatternUtils; +import com.android.settings.testutils.shadow.ShadowRestrictedLockUtilsInternal; +import com.android.settings.testutils.shadow.ShadowSensorPrivacyManager; +import com.android.settings.testutils.shadow.ShadowUserManager; +import com.android.settings.testutils.shadow.ShadowUtils; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.android.controller.ActivityController; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowPackageManager; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = { + ShadowUtils.class, + ShadowUserManager.class, + ShadowRestrictedLockUtilsInternal.class, + ShadowSensorPrivacyManager.class, + ShadowLockPatternUtils.class +}) +public class MultiBiometricEnrollHelperTest { + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private FragmentActivity mActivity; + @Mock + private FingerprintManager mFingerprintManager; + @Mock + private FaceManager mFaceManager; + + private Context mContext; + + @Captor + private ArgumentCaptor mFingerprintCaptor; + + private final int mUserId = 10; + private final long mChallenge = 0L; + private final int mSensorId = 0; + private final long mGkPwHandle = 0L; + + private MultiBiometricEnrollHelper mMultiBiometricEnrollHelper; + private Intent mFingerprintIntent; + private Intent mFaceIntent; + + @Before + public void setUp() throws RemoteException { + mContext = RuntimeEnvironment.application.getApplicationContext(); + mFingerprintIntent = new Intent(mContext, FingerprintEnrollIntroduction.class); + mFaceIntent = new Intent(mContext, FaceEnrollIntroduction.class); + mMultiBiometricEnrollHelper = new MultiBiometricEnrollHelper( + mActivity, mUserId, true /* enrollFace */, true /* enrollFingerprint */, + mGkPwHandle, mFingerprintManager, mFaceManager, mFingerprintIntent, mFaceIntent, + (challenge) -> null); + } + + @Test + public void launchFaceAndFingerprintEnroll_testFingerprint() { + mMultiBiometricEnrollHelper.startNextStep(); + + verify(mFingerprintManager).generateChallenge(anyInt(), mFingerprintCaptor.capture()); + + FingerprintManager.GenerateChallengeCallback generateChallengeCallback = + mFingerprintCaptor.getValue(); + generateChallengeCallback.onChallengeGenerated(mSensorId, mUserId, mChallenge); + + assertThat(mFingerprintIntent.hasExtra( + MultiBiometricEnrollHelper.EXTRA_ENROLL_AFTER_FINGERPRINT)).isTrue(); + assertThat(mFingerprintIntent.getExtra(BiometricEnrollBase.EXTRA_KEY_SENSOR_ID, + -1 /* defaultValue */)).isEqualTo(mSensorId); + assertThat(mFingerprintIntent.getExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, + -1 /* defaultValue */)).isEqualTo(mChallenge); + assertThat(mFingerprintIntent.getExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, + -1 /* defaultValue */)).isEqualTo(mGkPwHandle); + } + + @Test + public void launchFaceAndFingerprintEnroll_testFace() { + mMultiBiometricEnrollHelper.startNextStep(); + + verify(mFingerprintManager).generateChallenge(anyInt(), mFingerprintCaptor.capture()); + + FingerprintManager.GenerateChallengeCallback fingerprintGenerateChallengeCallback = + mFingerprintCaptor.getValue(); + fingerprintGenerateChallengeCallback.onChallengeGenerated( + mSensorId, mUserId, mChallenge); + + assertThat(mFaceIntent.getExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, + -1 /* defaultValue */)).isEqualTo(mGkPwHandle); + assertThat(mFaceIntent.getIntExtra(Intent.EXTRA_USER_ID, -1 /* defaultValue */)) + .isEqualTo(mUserId); + + final ShadowPackageManager shadowPackageManager = shadowOf(mContext.getPackageManager()); + shadowPackageManager.setSystemFeature(PackageManager.FEATURE_FACE, true); + ShadowUtils.setFaceManager(mFaceManager); + ActivityController.of(new FaceEnrollIntroduction(), mFaceIntent) + .create(mFaceIntent.getExtras()).get(); + + verify(mFaceManager).generateChallenge(eq(mUserId), any()); + } +} From 296f4c4a6fe861ac14b6b83453059907b921d495 Mon Sep 17 00:00:00 2001 From: Edgar Wang Date: Thu, 2 Feb 2023 09:17:40 +0800 Subject: [PATCH 4/4] Update illustration for tablet Fixes: 244271773 Test: visual Change-Id: Ic8968a951fc6c9b8023aa522aabdac3f1bcb84d3 --- res/raw-sw600dp/lottie_button_nav_menu.json | 1 + res/xml/button_navigation_settings.xml | 1 + 2 files changed, 2 insertions(+) create mode 100644 res/raw-sw600dp/lottie_button_nav_menu.json diff --git a/res/raw-sw600dp/lottie_button_nav_menu.json b/res/raw-sw600dp/lottie_button_nav_menu.json new file mode 100644 index 00000000000..16b5ef31ca0 --- /dev/null +++ b/res/raw-sw600dp/lottie_button_nav_menu.json @@ -0,0 +1 @@ +{"v":"5.8.1","fr":60,"ip":0,"op":300,"w":412,"h":300,"nm":"Home Button","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":125,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":132,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":220,"s":[100]},{"t":227,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":115,"s":[206,192.75,0],"to":[0,-2.583,0],"ti":[0,2.583,0]},{"i":{"x":0.3,"y":0.3},"o":{"x":0.7,"y":0.7},"t":155,"s":[206,177.25,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.3,"y":1},"o":{"x":0.8,"y":0},"t":210,"s":[206,177.25,0],"to":[0,3.417,0],"ti":[0,-3.417,0]},{"t":235,"s":[206,197.75,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[40,40,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[4.636,0],[0,4.636],[0,0],[-5.694,-0.823],[0,0],[0,0],[0,0],[0,5.93]],"o":[[0,4.636],[-4.636,0],[0,0],[0,5.93],[0,0],[0,0],[0,0],[5.694,-0.823],[0,0]],"v":[[8.399,61.968],[0,70.367],[-8.399,61.968],[-11.758,61.968],[-1.68,73.592],[-1.68,78.766],[1.68,78.766],[1.68,73.592],[11.758,61.968]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[-2.788,0],[0,2.788],[0,0],[2.788,0],[0,-2.788],[0,0]],"o":[[2.788,0],[0,0],[0,-2.788],[-2.788,0],[0,0],[0,2.788]],"v":[[0,67.007],[5.039,61.968],[5.039,51.89],[0,46.85],[-5.039,51.89],[-5.039,61.968]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":70,"op":1534,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":3,"nm":"Null 1","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[208.25,164,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[113.25,113.25,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":1500,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Shape Layer 3","parent":2,"td":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":110,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":118.334,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":231.666,"s":[100]},{"t":240,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-1.987,43.709,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[88.3,88.3,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":110,"s":[{"i":[[0,-5.523],[0,0],[5.523,0],[0,0],[0,5.523],[0,0],[-5.523,0],[0,0]],"o":[[0,0],[0,5.523],[0,0],[-5.523,0],[0,0],[0,-5.523],[0,0],[5.523,0]],"v":[[161,5.5],[161,27],[151,37],[-151,37],[-161,27],[-161,5.5],[-151,-4.5],[151,-4.5]],"c":true}]},{"i":{"x":0.3,"y":0.3},"o":{"x":0.7,"y":0.7},"t":143,"s":[{"i":[[0,-5.523],[0,0],[5.523,0],[0,0],[0,5.523],[0,0],[-5.523,0],[0,0]],"o":[[0,0],[0,5.523],[0,0],[-5.523,0],[0,0],[0,-5.523],[0,0],[5.523,0]],"v":[[161,-19.5],[161,27],[151,37],[-151,37],[-161,27],[-161,-19.5],[-151,-29.5],[151,-29.5]],"c":true}]},{"i":{"x":0.3,"y":1},"o":{"x":0.8,"y":0},"t":215,"s":[{"i":[[0,-5.523],[0,0],[5.523,0],[0,0],[0,5.523],[0,0],[-5.523,0],[0,0]],"o":[[0,0],[0,5.523],[0,0],[-5.523,0],[0,0],[0,-5.523],[0,0],[5.523,0]],"v":[[161,-19.5],[161,27],[151,37],[-151,37],[-161,27],[-161,-19.5],[-151,-29.5],[151,-29.5]],"c":true}]},{"t":240,"s":[{"i":[[0,-5.523],[0,0],[5.523,0],[0,0],[0,5.523],[0,0],[0,0.25],[0,0]],"o":[[0,0],[0,5.523],[0,0],[-5.523,0],[0,0],[0,-5.523],[0,0],[-1.125,0]],"v":[[161,5.5],[161,27],[151,37],[-151,37],[-161,27],[-161,5.5],[-161,-4.5],[161,-4.5]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1500,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"NGA Line Outlines","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1.5,49.474,0],"ix":2,"l":2},"a":{"a":0,"k":[45.896,0.896,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":115,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[185.882,0.94],[208.382,0.94]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":139,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[42.387,0.911],[63.59,0.911]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.7,"y":0},"t":155,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[42.385,0.887],[70.714,0.923]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.8,"y":0},"t":210,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[42.416,0.911],[70.714,0.923]],"c":false}]},{"t":235,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[185.992,0.94],[208.492,0.94]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.258823543787,0.521568655968,0.956862747669,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1.793,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Yellow","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":115,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[208.382,0.94],[230.882,0.94]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":139,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[63.59,0.911],[87.443,0.911]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.7,"y":0},"t":155,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[70.714,0.923],[98.844,0.923]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.8,"y":0},"t":210,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[70.714,0.923],[98.844,0.923]],"c":false}]},{"t":235,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[208.492,0.94],[230.992,0.94]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.258823543787,0.521568655968,0.956862747669,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1.793,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Green","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":115,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-123.053,0.94],[-100.553,0.94]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":139,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[21.184,0.911],[42.387,0.911]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.7,"y":0},"t":155,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[14.13,0.887],[42.385,0.887]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.8,"y":0},"t":210,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[14.13,0.887],[42.416,0.911]],"c":false}]},{"t":235,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-123.273,0.94],[-100.773,0.94]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.258823543787,0.521568655968,0.956862747669,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1.793,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Red","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":115,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-145.553,0.94],[-123.053,0.94]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":139,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-1.972,0.911],[21.184,0.911]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.7,"y":0},"t":155,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-14.06,0.911],[14.13,0.887]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.8,"y":0},"t":210,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-14.06,0.911],[14.13,0.887]],"c":false}]},{"t":235,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-145.773,0.94],[-123.273,0.94]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.258999992819,0.522000002394,0.957000014361,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1.793,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Blue","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":115,"op":236,"st":35,"bm":0},{"ddd":0,"ind":8,"ty":3,"nm":"Null 8","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[319.75,114.25,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":1500,"st":0,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Buttons","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[319.843,229.28,0],"ix":2,"l":2},"a":{"a":0,"k":[319.843,229.28,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[4.355,-4.355],[-4.355,-4.355],[-4.355,4.355],[4.355,4.355]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.737254917622,0.745098054409,0.752941191196,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[345.684,229.275],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 8","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.402,0],[0,2.402],[2.403,0],[0,-2.403]],"o":[[2.403,0],[0,-2.403],[-2.402,0],[0,2.402]],"v":[[0,4.35],[4.35,0],[0,-4.35],[-4.35,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.737254917622,0.745098054409,0.752941191196,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":80,"s":[100,100]},{"i":{"x":[0.3,0.3],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":90,"s":[0,0]},{"i":{"x":[0.3,0.3],"y":[1,1]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":107,"s":[0,0]},{"t":116,"s":[100,100]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[319.548,229.28],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 7","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-3.77,-0.005],[3.77,-4.355],[3.77,4.355],[-3.77,0.005]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.737254917622,0.745098054409,0.752941191196,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[293.417,229.285],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 6","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1500,"st":0,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Gesture Tap","parent":8,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,114.812,0],"ix":2,"l":2},"a":{"a":0,"k":[273.205,92.869,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-5.226],[-5.226,0],[0,5.226],[5.226,0]],"o":[[0,5.226],[5.226,0],[0,-5.226],[-5.226,0]],"v":[[-9.463,0],[0,9.463],[9.463,0],[0,-9.463]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[273.205,92.869],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.239,0.239],"y":[0.677,0.677]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":40,"s":[0,0]},{"i":{"x":[0.596,0.596],"y":[1,1]},"o":{"x":[0.182,0.182],"y":[0.207,0.207]},"t":55,"s":[80,80]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.793,0.793],"y":[0,0]},"t":75,"s":[120,120]},{"t":85,"s":[0,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":40,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-8.26],[-8.26,0],[0,8.26],[8.26,0]],"o":[[0,8.26],[8.26,0],[0,-8.26],[-8.26,0]],"v":[[-14.957,0],[0,14.957],[14.957,0],[0,-14.957]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[273.205,92.869],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.304,0.304],"y":[0.802,0.802]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":30,"s":[0,0]},{"i":{"x":[0.709,0.709],"y":[0.997,0.997]},"o":{"x":[0.196,0.196],"y":[0.446,0.446]},"t":45,"s":[80,80]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.609,0.609],"y":[0,0]},"t":75,"s":[100,100]},{"t":85,"s":[0,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":40,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1500,"st":0,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Blue_Dot","parent":8,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":80,"s":[0.025]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":90,"s":[-5.218]},{"i":{"x":[0.999],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":102,"s":[-15.702]},{"t":111,"s":[0.025]}],"ix":3},"y":{"a":0,"k":114.925,"ix":4}},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[34.95,34.95,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.4,0.4],"y":[0,0]},"t":80,"s":[10,10]},{"t":93,"s":[8,8]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.101960785687,0.450980395079,0.909803926945,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":80,"op":118,"st":10,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Yellow_Dot","parent":8,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":90,"s":[0.025]},{"i":{"x":[0.999],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":102,"s":[5.267]},{"t":110,"s":[0.025]}],"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":80,"s":[114.925]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":90,"s":[120.168]},{"t":98,"s":[114.925]}],"ix":4}},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[34.95,34.95,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.4,0.4],"y":[0,0]},"t":80,"s":[10,10]},{"t":93,"s":[8,8]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.976470589638,0.670588254929,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":80,"op":112,"st":10,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"Red_Dot","parent":8,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":90,"s":[0.025]},{"i":{"x":[0.999],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":102,"s":[-5.218]},{"t":110,"s":[0.025]}],"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":80,"s":[114.925]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":90,"s":[109.683]},{"t":98,"s":[114.925]}],"ix":4}},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[34.95,34.95,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.4,0.4],"y":[0,0]},"t":80,"s":[10,10]},{"t":93,"s":[8,8]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980401039,0.188235297799,0.145098045468,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":80,"op":111,"st":10,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Green_Dot","parent":8,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":80,"s":[0.025]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":90,"s":[5.267]},{"i":{"x":[0.999],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":102,"s":[15.752]},{"t":111,"s":[0.025]}],"ix":3},"y":{"a":0,"k":114.925,"ix":4}},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[34.95,34.95,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.4,0.4],"y":[0,0]},"t":80,"s":[10,10]},{"t":93,"s":[8,8]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.117647059262,0.556862771511,0.243137255311,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":80,"op":112,"st":10,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":".black","cl":"black","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":110,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":118.334,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":231.666,"s":[100]},{"t":240,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,213.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":110,"s":[{"i":[[0,-5.523],[0,0],[5.523,0],[0,0],[0,5.523],[0,0],[0,0.25],[0,0]],"o":[[0,0],[0,5.523],[0,0],[-5.523,0],[0,0],[0,-5.523],[0,0],[-1.125,0]],"v":[[161,5.5],[161,27],[151,37],[-151,37],[-161,27],[-161,5.5],[-161,-4.5],[161,-4.5]],"c":true}]},{"i":{"x":0.3,"y":0.3},"o":{"x":0.7,"y":0.7},"t":143,"s":[{"i":[[0,-5.523],[0,0],[5.523,0],[0,0],[0,5.523],[0,0],[-5.523,0],[0,0]],"o":[[0,0],[0,5.523],[0,0],[-5.523,0],[0,0],[0,-5.523],[0,0],[5.523,0]],"v":[[161,-19.5],[161,27],[151,37],[-151,37],[-161,27],[-161,-19.5],[-151,-29.5],[151,-29.5]],"c":true}]},{"i":{"x":0.3,"y":1},"o":{"x":0.8,"y":0},"t":215,"s":[{"i":[[0,-5.523],[0,0],[5.523,0],[0,0],[0,5.523],[0,0],[-5.523,0],[0,0]],"o":[[0,0],[0,5.523],[0,0],[-5.523,0],[0,0],[0,-5.523],[0,0],[5.523,0]],"v":[[161,-19.5],[161,27],[151,37],[-151,37],[-161,27],[-161,-19.5],[-151,-29.5],[151,-29.5]],"c":true}]},{"t":240,"s":[{"i":[[0,-5.523],[0,0],[5.523,0],[0,0],[0,5.523],[0,0],[0,0.25],[0,0]],"o":[[0,0],[0,5.523],[0,0],[-5.523,0],[0,0],[0,-5.523],[0,0],[-1.125,0]],"v":[[161,5.5],[161,27],[151,37],[-151,37],[-161,27],[-161,5.5],[-161,-4.5],[161,-4.5]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1500,"st":0,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":".grey600","cl":"grey600","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206.01,149.85,0],"ix":2,"l":2},"a":{"a":0,"k":[206.01,149.85,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.28,0],[0,0],[0,-1.28]],"o":[[0,-1.28],[0,0],[-1.28,0],[0,0]],"v":[[6.935,1.155],[4.625,-1.155],[-4.625,-1.155],[-6.935,1.155]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960784314,0.525490196078,0.545098039216,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[337.493,48.485],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 4","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.27,0],[0,0],[0,-1.28],[0,0]],"o":[[0,-1.28],[0,0],[-1.28,0],[0,0],[0,0]],"v":[[13.86,1.155],[11.55,-1.155],[-11.56,-1.155],[-13.87,1.155],[13.87,1.155]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960784314,0.525490196078,0.545098039216,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[305.976,48.485],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 3","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[4.15,0],[0,0],[0,4.15],[0,0],[-4.15,0],[0,0],[0,-4.15],[0,0]],"o":[[0,4.15],[0,0],[-4.15,0],[0,0],[0,-4.15],[0,0],[4.15,0],[0,0],[0,0]],"v":[[159.899,92.5],[151.998,100.02],[-151.979,100.02],[-159.879,92.5],[-159.879,-90.09],[-151.979,-97.99],[151.998,-97.99],[159.899,-90.09],[159.899,92.49]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[5.671,0],[0,0],[0,-5.67],[0,0],[-5.67,0],[0,0],[0,5.67],[0,0]],"o":[[0,0],[0,-5.67],[0,0],[-5.67,0],[0,0],[0,5.67],[0,0],[5.671,0],[0,0],[0,0]],"v":[[162.259,-7.17],[162.259,-90.09],[151.998,-100.35],[-151.979,-100.35],[-162.239,-90.09],[-162.239,92.11],[-151.979,102.37],[151.998,102.37],[162.259,92.11],[162.259,-7.18]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960784314,0.525490196078,0.545098039216,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 2","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1500,"st":0,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":".grey700","cl":"grey700","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,130.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,-5.523],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[5.523,0]],"o":[[-5.523,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,-5.523],[0,0]],"v":[[-151,-78.5],[-161,-68.5],[-161,-52.5],[-161,68.5],[-161,78.5],[-151,78.5],[151,78.5],[161,78.5],[161,68.5],[161,-52.5],[161,-68.5],[151,-78.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1500,"st":0,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".black","cl":"black","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[15.291,0],[0,0],[0,15.652],[0,0],[-15.291,0],[0,0],[0,-15.652],[0,0]],"o":[[0,0],[-15.291,0],[0,0],[0,-15.652],[0,0],[15.185,0],[0,0],[0,15.544]],"v":[[178.179,150],[-178.179,150],[-206,121.522],[-206,-121.522],[-178.179,-150],[178.286,-150],[206,-121.522],[206,121.63]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1500,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/res/xml/button_navigation_settings.xml b/res/xml/button_navigation_settings.xml index 1153c6767ad..2459b602d1c 100644 --- a/res/xml/button_navigation_settings.xml +++ b/res/xml/button_navigation_settings.xml @@ -26,6 +26,7 @@