Merge "[Physical Keyboard Setting] Update layout for one pane land screen" into main
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index cbc0d8e..9a8aa75 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -818,6 +818,10 @@
android:permission="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS"
android:theme="@style/Theme.AlertDialog.SimConfirmDialog"/>
+ <activity android:name=".network.telephony.EuiccRacConnectivityDialogActivity"
+ android:exported="false"
+ android:theme="@style/Theme.AlertDialog.SimConfirmDialog"/>
+
<activity
android:name="Settings$TetherSettingsActivity"
android:label="@string/tether_settings_title_all"
@@ -2505,6 +2509,22 @@
</activity>
<activity
+ android:name="Settings$ColorContrastActivity"
+ android:exported="true"
+ android:label="@string/accessibility_color_contrast_title">
+ <intent-filter>
+ <action android:name="android.settings.ACCESSIBILITY_COLOR_CONTRAST_SETTINGS" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
+ android:value="com.android.settings.accessibility.ColorContrastFragment" />
+ <meta-data android:name="com.android.settings.HIGHLIGHT_MENU_KEY"
+ android:value="@string/menu_key_accessibility"/>
+ <meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
+ android:value="true" />
+ </activity>
+
+ <activity
android:name="Settings$TextToSpeechSettingsActivity"
android:exported="true"
android:label="@string/tts_settings">
diff --git a/aconfig/accessibility/accessibility_flags.aconfig b/aconfig/accessibility/accessibility_flags.aconfig
index 24e108e..2b843cd 100644
--- a/aconfig/accessibility/accessibility_flags.aconfig
+++ b/aconfig/accessibility/accessibility_flags.aconfig
@@ -30,3 +30,10 @@
description: "Don't show quick settings tooltip in SUW, since the user can't use quick settings there."
bug: "294560581"
}
+
+flag {
+ name: "enable_color_contrast_control"
+ namespace: "accessibility"
+ description: "Allows users to control color contrast in the Accessibility settings page."
+ bug: "246577325"
+}
diff --git a/aconfig/settings_connecteddevice_flag_declarations.aconfig b/aconfig/settings_connecteddevice_flag_declarations.aconfig
index 84bb578..49dd4b4 100644
--- a/aconfig/settings_connecteddevice_flag_declarations.aconfig
+++ b/aconfig/settings_connecteddevice_flag_declarations.aconfig
@@ -9,20 +9,6 @@
}
flag {
- name: "enable_le_audio_sharing"
- namespace: "pixel_cross_device_control"
- description: "Gates whether to enable LE audio sharing"
- bug: "305620450"
-}
-
-flag {
- name: "enable_le_audio_qr_code_private_broadcast_sharing"
- namespace: "pixel_cross_device_control"
- description: "Gates whether to enable LE audio private broadcast sharing via QR code"
- bug: "308368124"
-}
-
-flag {
name: "enable_auth_challenge_for_usb_preferences"
namespace: "safety_center"
description: "Gates whether to require an auth challenge for changing USB preferences"
@@ -35,10 +21,3 @@
description: "Order the saved bluetooth devices by most recently connected."
bug: "306160434"
}
-
-flag {
- name: "enable_hide_exclusively_managed_bluetooth_device"
- namespace: "dck_framework"
- description: "Hide exclusively managed Bluetooth devices in BT settings menu."
- bug: "322285078"
-}
\ No newline at end of file
diff --git a/res/drawable-night/ic_contrast_high.xml b/res/drawable-night/ic_contrast_high.xml
new file mode 100644
index 0000000..4d54df8
--- /dev/null
+++ b/res/drawable-night/ic_contrast_high.xml
@@ -0,0 +1,28 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/system_accent1_200"
+ android:pathData="M12,4C7,4 2.73,7.11 1,11.5C2.73,15.89 7,19 12,19s9.27,-3.11 11,-7.5C21.27,7.11 17,4 12,4zM12,16c-2.48,0 -4.5,-2.02 -4.5,-4.5S9.52,7 12,7s4.5,2.02 4.5,4.5S14.48,16 12,16z"/>
+ <path
+ android:fillColor="@android:color/system_accent1_50"
+ android:pathData="M12,11.5m-2.7,0a2.7,2.7 0,1 1,5.4 0a2.7,2.7 0,1 1,-5.4 0"/>
+</vector>
diff --git a/res/drawable-night/ic_contrast_medium.xml b/res/drawable-night/ic_contrast_medium.xml
new file mode 100644
index 0000000..dc8ddae
--- /dev/null
+++ b/res/drawable-night/ic_contrast_medium.xml
@@ -0,0 +1,28 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/system_accent1_400"
+ android:pathData="M12,4C7,4 2.73,7.11 1,11.5C2.73,15.89 7,19 12,19s9.27,-3.11 11,-7.5C21.27,7.11 17,4 12,4zM12,16c-2.48,0 -4.5,-2.02 -4.5,-4.5S9.52,7 12,7s4.5,2.02 4.5,4.5S14.48,16 12,16z"/>
+ <path
+ android:fillColor="@android:color/system_accent1_100"
+ android:pathData="M12,11.5m-2.7,0a2.7,2.7 0,1 1,5.4 0a2.7,2.7 0,1 1,-5.4 0"/>
+</vector>
diff --git a/res/drawable-night/ic_contrast_standard.xml b/res/drawable-night/ic_contrast_standard.xml
new file mode 100644
index 0000000..cdf61cf
--- /dev/null
+++ b/res/drawable-night/ic_contrast_standard.xml
@@ -0,0 +1,28 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/system_accent1_700"
+ android:pathData="M12,4C7,4 2.73,7.11 1,11.5C2.73,15.89 7,19 12,19s9.27,-3.11 11,-7.5C21.27,7.11 17,4 12,4zM12,16c-2.48,0 -4.5,-2.02 -4.5,-4.5S9.52,7 12,7s4.5,2.02 4.5,4.5S14.48,16 12,16z"/>
+ <path
+ android:fillColor="@android:color/system_accent1_200"
+ android:pathData="M12,11.5m-2.7,0a2.7,2.7 0,1 1,5.4 0a2.7,2.7 0,1 1,-5.4 0"/>
+</vector>
diff --git a/res/drawable/accessibility_contrast_button_background.xml b/res/drawable/accessibility_contrast_button_background.xml
new file mode 100644
index 0000000..281fcef
--- /dev/null
+++ b/res/drawable/accessibility_contrast_button_background.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 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"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+
+ <item android:state_selected="true">
+ <layer-list>
+ <item>
+ <shape android:shape="rectangle">
+ <solid android:color="?androidprv:attr/materialColorSurfaceContainerHighest" />
+ <stroke
+ android:color="?androidprv:attr/materialColorSecondary"
+ android:width="@dimen/contrast_button_stroke_width" />
+ <corners android:radius="@dimen/contrast_button_radius"/>
+ </shape>
+ </item>
+ <item
+ android:width="24dp"
+ android:height="24dp"
+ android:left="57dp"
+ android:top="57dp">
+ <vector
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="?androidprv:attr/materialColorPrimary"
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10c5.52,0 10,-4.48 10,-10S17.52,2 12,2zM10.59,16.6l-4.24,-4.24l1.41,-1.41l2.83,2.83l5.66,-5.66l1.41,1.41L10.59,16.6z"/>
+ </vector>
+ </item>
+ </layer-list>
+ </item>
+
+ <item>
+ <layer-list>
+ <item android:top="@dimen/contrast_button_stroke_width"
+ android:bottom="@dimen/contrast_button_stroke_width"
+ android:left="@dimen/contrast_button_stroke_width"
+ android:right="@dimen/contrast_button_stroke_width">
+ <shape android:shape="rectangle">
+ <solid android:color="?androidprv:attr/materialColorSurfaceContainerHighest" />
+ <corners android:radius="@dimen/contrast_button_radius"/>
+ </shape>
+ </item>
+ </layer-list>
+ </item>
+</selector>
\ No newline at end of file
diff --git a/res/drawable/audio_sharing_guidance.png b/res/drawable/audio_sharing_guidance.png
deleted file mode 100644
index c0ab637..0000000
--- a/res/drawable/audio_sharing_guidance.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/audio_sharing_rounded_bg_ripple.xml b/res/drawable/audio_sharing_rounded_bg_ripple.xml
deleted file mode 100644
index 18696c6..0000000
--- a/res/drawable/audio_sharing_rounded_bg_ripple.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 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.
- -->
-
-<ripple xmlns:android="http://schemas.android.com/apk/res/android"
- android:color="?android:attr/colorControlHighlight">
- <item android:drawable="@drawable/audio_sharing_rounded_bg"/>
-</ripple>
\ No newline at end of file
diff --git a/res/drawable/audio_sharing_rounded_bg.xml b/res/drawable/color_contrast_preview_background.xml
similarity index 71%
copy from res/drawable/audio_sharing_rounded_bg.xml
copy to res/drawable/color_contrast_preview_background.xml
index db1e1bb..51d0ade 100644
--- a/res/drawable/audio_sharing_rounded_bg.xml
+++ b/res/drawable/color_contrast_preview_background.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2023 The Android Open Source Project
+ ~ Copyright (C) 2024 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.
@@ -14,9 +14,9 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <solid android:color="?android:colorButtonNormal" />
- <corners android:radius="12dp" />
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle" >
+ <solid android:color="?androidprv:attr/materialColorSurfaceContainerHigh"/>
+ <corners android:radius="20dp" />
</shape>
\ No newline at end of file
diff --git a/res/drawable/audio_sharing_rounded_bg.xml b/res/drawable/color_contrast_preview_bottom_appbar_background.xml
similarity index 62%
copy from res/drawable/audio_sharing_rounded_bg.xml
copy to res/drawable/color_contrast_preview_bottom_appbar_background.xml
index db1e1bb..f3392fb 100644
--- a/res/drawable/audio_sharing_rounded_bg.xml
+++ b/res/drawable/color_contrast_preview_bottom_appbar_background.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2023 The Android Open Source Project
+ ~ Copyright (C) 2024 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.
@@ -14,9 +14,13 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <solid android:color="?android:colorButtonNormal" />
- <corners android:radius="12dp" />
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle" >
+ <solid android:color="?androidprv:attr/materialColorSurfaceContainer"/>
+ <corners
+ android:bottomLeftRadius="20dp"
+ android:bottomRightRadius="20dp"
+ android:topLeftRadius="0dp"
+ android:topRightRadius="0dp" />
</shape>
\ No newline at end of file
diff --git a/res/drawable/audio_sharing_rounded_bg.xml b/res/drawable/color_contrast_preview_button_background.xml
similarity index 71%
copy from res/drawable/audio_sharing_rounded_bg.xml
copy to res/drawable/color_contrast_preview_button_background.xml
index db1e1bb..8b92087 100644
--- a/res/drawable/audio_sharing_rounded_bg.xml
+++ b/res/drawable/color_contrast_preview_button_background.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2023 The Android Open Source Project
+ ~ Copyright (C) 2024 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.
@@ -14,9 +14,9 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <solid android:color="?android:colorButtonNormal" />
- <corners android:radius="12dp" />
-</shape>
\ No newline at end of file
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle" >
+ <solid android:color="?androidprv:attr/materialColorTertiaryContainer"/>
+ <corners android:radius="7dp" />
+</shape>
diff --git a/res/drawable/audio_sharing_rounded_bg.xml b/res/drawable/color_contrast_preview_dialog_background.xml
similarity index 74%
rename from res/drawable/audio_sharing_rounded_bg.xml
rename to res/drawable/color_contrast_preview_dialog_background.xml
index db1e1bb..f60a271 100644
--- a/res/drawable/audio_sharing_rounded_bg.xml
+++ b/res/drawable/color_contrast_preview_dialog_background.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2023 The Android Open Source Project
+ ~ Copyright (C) 2024 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.
@@ -14,9 +14,9 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <solid android:color="?android:colorButtonNormal" />
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle" >
+ <solid android:color="?androidprv:attr/materialColorSurfaceContainerLow"/>
<corners android:radius="12dp" />
</shape>
\ No newline at end of file
diff --git a/res/drawable/audio_sharing_rounded_bg.xml b/res/drawable/color_contrast_preview_icon_edit_background.xml
similarity index 71%
copy from res/drawable/audio_sharing_rounded_bg.xml
copy to res/drawable/color_contrast_preview_icon_edit_background.xml
index db1e1bb..14c5f3c 100644
--- a/res/drawable/audio_sharing_rounded_bg.xml
+++ b/res/drawable/color_contrast_preview_icon_edit_background.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2023 The Android Open Source Project
+ ~ Copyright (C) 2024 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.
@@ -14,9 +14,9 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <solid android:color="?android:colorButtonNormal" />
- <corners android:radius="12dp" />
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle" >
+ <solid android:color="?androidprv:attr/materialColorPrimary"/>
+ <corners android:radius="20dp" />
</shape>
\ No newline at end of file
diff --git a/res/drawable/audio_sharing_rounded_bg.xml b/res/drawable/color_contrast_preview_icon_group_background.xml
similarity index 70%
copy from res/drawable/audio_sharing_rounded_bg.xml
copy to res/drawable/color_contrast_preview_icon_group_background.xml
index db1e1bb..b8554c1 100644
--- a/res/drawable/audio_sharing_rounded_bg.xml
+++ b/res/drawable/color_contrast_preview_icon_group_background.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2023 The Android Open Source Project
+ ~ Copyright (C) 2024 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.
@@ -14,9 +14,9 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <solid android:color="?android:colorButtonNormal" />
- <corners android:radius="12dp" />
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="oval" >
+ <solid android:color="?androidprv:attr/materialColorSurfaceContainer"/>
+ <size android:width="36dp" android:height="36dp" />
</shape>
\ No newline at end of file
diff --git a/res/drawable/audio_sharing_rounded_bg.xml b/res/drawable/color_contrast_preview_icon_inbox_background.xml
similarity index 67%
copy from res/drawable/audio_sharing_rounded_bg.xml
copy to res/drawable/color_contrast_preview_icon_inbox_background.xml
index db1e1bb..45d8285 100644
--- a/res/drawable/audio_sharing_rounded_bg.xml
+++ b/res/drawable/color_contrast_preview_icon_inbox_background.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2023 The Android Open Source Project
+ ~ Copyright (C) 2024 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.
@@ -14,9 +14,10 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <solid android:color="?android:colorButtonNormal" />
- <corners android:radius="12dp" />
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle" >
+ <solid android:color="?androidprv:attr/materialColorSecondaryContainer"/>
+ <corners android:radius="20dp" />
+ <size android:height="30dp" android:width="60dp" />
</shape>
\ No newline at end of file
diff --git a/res/drawable/audio_sharing_rounded_bg.xml b/res/drawable/color_contrast_preview_icon_star_background.xml
similarity index 71%
copy from res/drawable/audio_sharing_rounded_bg.xml
copy to res/drawable/color_contrast_preview_icon_star_background.xml
index db1e1bb..335ee88 100644
--- a/res/drawable/audio_sharing_rounded_bg.xml
+++ b/res/drawable/color_contrast_preview_icon_star_background.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2023 The Android Open Source Project
+ ~ Copyright (C) 2024 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.
@@ -14,9 +14,9 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <solid android:color="?android:colorButtonNormal" />
- <corners android:radius="12dp" />
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="oval" >
+ <solid android:color="?androidprv:attr/materialColorPrimary"/>
+ <size android:width="36dp" android:height="36dp" />
</shape>
\ No newline at end of file
diff --git a/res/drawable/audio_sharing_rounded_bg.xml b/res/drawable/color_contrast_preview_tag_background.xml
similarity index 71%
copy from res/drawable/audio_sharing_rounded_bg.xml
copy to res/drawable/color_contrast_preview_tag_background.xml
index db1e1bb..a7b051a 100644
--- a/res/drawable/audio_sharing_rounded_bg.xml
+++ b/res/drawable/color_contrast_preview_tag_background.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2023 The Android Open Source Project
+ ~ Copyright (C) 2024 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.
@@ -14,9 +14,9 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <solid android:color="?android:colorButtonNormal" />
- <corners android:radius="12dp" />
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle" >
+ <solid android:color="?androidprv:attr/materialColorSurfaceContainerHighest"/>
+ <corners android:radius="4dp" />
</shape>
\ No newline at end of file
diff --git a/res/drawable/ic_article_24dp.xml b/res/drawable/ic_article_24dp.xml
new file mode 100644
index 0000000..0b38daa
--- /dev/null
+++ b/res/drawable/ic_article_24dp.xml
@@ -0,0 +1,27 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal"
+ android:autoMirrored="true">
+<path
+ android:fillColor="?androidprv:attr/materialColorOnSurfaceVariant"
+ android:pathData="M19,5v14L5,19L5,5h14m0,-2L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM14,17L7,17v-2h7v2zM17,13L7,13v-2h10v2zM17,9L7,9L7,7h10v2z"/>
+</vector>
diff --git a/res/drawable/ic_article_filled_24dp.xml b/res/drawable/ic_article_filled_24dp.xml
new file mode 100644
index 0000000..e22d151
--- /dev/null
+++ b/res/drawable/ic_article_filled_24dp.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:width="18dp"
+ android:height="18dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="?androidprv:attr/materialColorOnTertiaryContainer"
+ android:pathData="M19,3H5C3.9,3 3,3.9 3,5v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5C21,3.9 20.1,3 19,3zM14,17H7v-2h7V17zM17,13H7v-2h10V13zM17,9H7V7h10V9z"/>
+</vector>
diff --git a/res/drawable/ic_audio_calls_and_alarms.xml b/res/drawable/ic_audio_calls_and_alarms.xml
deleted file mode 100644
index 5da27c6..0000000
--- a/res/drawable/ic_audio_calls_and_alarms.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<!--
- 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.
- -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
- <path
- android:pathData="M3,15V9H7L12,4V20L7,15H3ZM10,15.17V8.83L7.83,11H5V13H7.83L10,15.17Z"
- android:fillType="evenOdd"
- android:fillColor="?android:attr/colorPrimary"/>
- <path
- android:pathData="M16.5,12C16.5,10.23 15.48,8.71 14,7.97V16.02C15.48,15.29 16.5,13.77 16.5,12Z"
- android:fillColor="?android:attr/colorPrimary"/>
- <path
- android:pathData="M14,3.23V5.29C16.89,6.15 19,8.83 19,12C19,15.17 16.89,17.85 14,18.71V20.77C18.01,19.86 21,16.28 21,12C21,7.72 18.01,4.14 14,3.23Z"
- android:fillColor="?android:attr/colorPrimary"/>
-</vector>
diff --git a/res/drawable/ic_audio_play_sample.xml b/res/drawable/ic_audio_play_sample.xml
deleted file mode 100644
index 3666c22..0000000
--- a/res/drawable/ic_audio_play_sample.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<!--
- ~ Copyright (C) 2023 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24"
- android:viewportHeight="24"
- android:tint="?android:attr/colorControlNormal">
- <path
- android:pathData="M14,8C9.6,8 6,11.6 6,16H8C8,12.7 10.7,10 14,10V8Z"
- android:fillColor="#4E4639"/>
- <path
- android:pathData="M14,6V4C7.4,4 2,9.4 2,16H4C4,10.5 8.5,6 14,6Z"
- android:fillColor="#4E4639"/>
- <path
- android:pathData="M16,4V12.6C15.4,12.3 14.7,12 14,12C11.8,12 10,13.8 10,16C10,18.2 11.8,20 14,20C16.2,20 18,18.2 18,16V7H22V4H16ZM14,18C12.9,18 12,17.1 12,16C12,14.9 12.9,14 14,14C15.1,14 16,14.9 16,16C16,17.1 15.1,18 14,18Z"
- android:fillColor="#4E4639"/>
-</vector>
diff --git a/res/drawable/ic_chat_bubble_24dp.xml b/res/drawable/ic_chat_bubble_24dp.xml
new file mode 100644
index 0000000..c7ad6bf
--- /dev/null
+++ b/res/drawable/ic_chat_bubble_24dp.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+<path
+ android:fillColor="?androidprv:attr/materialColorOnSurfaceVariant"
+ android:pathData="M20,2L4,2c-1.1,0 -2,0.9 -2,2v18l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM20,16L4,16L4,4h16v12z"/>
+</vector>
diff --git a/res/drawable/ic_color_contrast.xml b/res/drawable/ic_color_contrast.xml
new file mode 100644
index 0000000..9d56ada
--- /dev/null
+++ b/res/drawable/ic_color_contrast.xml
@@ -0,0 +1,35 @@
+<!--
+ ~ Copyright (C) 2024 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.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <com.android.settingslib.widget.AdaptiveIconShapeDrawable
+ android:width="@dimen/accessibility_icon_size"
+ android:height="@dimen/accessibility_icon_size"
+ android:color="@color/accessibility_feature_background"/>
+ </item>
+ <item android:gravity="center">
+ <vector
+ android:width="@dimen/accessibility_icon_foreground_size"
+ android:height="@dimen/accessibility_icon_foreground_size"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="#ffffff"
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10s10,-4.48 10,-10S17.52,2 12,2zM11,19.93C7.06,19.44 4,16.08 4,12s3.05,-7.44 7,-7.93V19.93zM13,4.07C14.03,4.2 15,4.52 15.87,5H13V4.07zM13,7h5.24c0.25,0.31 0.48,0.65 0.68,1H13V7zM13,10h6.74c0.08,0.33 0.15,0.66 0.19,1H13V10zM13,19.93V19h2.87C15,19.48 14.03,19.8 13,19.93zM18.24,17H13v-1h5.92C18.72,16.35 18.49,16.69 18.24,17zM19.74,14H13v-1h6.93C19.89,13.34 19.82,13.67 19.74,14z"/>
+ </vector>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/res/drawable/ic_contrast_high.xml b/res/drawable/ic_contrast_high.xml
new file mode 100644
index 0000000..363f2a8
--- /dev/null
+++ b/res/drawable/ic_contrast_high.xml
@@ -0,0 +1,28 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/system_accent1_700"
+ android:pathData="M12,4C7,4 2.73,7.11 1,11.5C2.73,15.89 7,19 12,19s9.27,-3.11 11,-7.5C21.27,7.11 17,4 12,4zM12,16c-2.48,0 -4.5,-2.02 -4.5,-4.5S9.52,7 12,7s4.5,2.02 4.5,4.5S14.48,16 12,16z"/>
+ <path
+ android:fillColor="@android:color/system_accent1_900"
+ android:pathData="M12,11.5m-2.7,0a2.7,2.7 0,1 1,5.4 0a2.7,2.7 0,1 1,-5.4 0"/>
+</vector>
diff --git a/res/drawable/ic_contrast_medium.xml b/res/drawable/ic_contrast_medium.xml
new file mode 100644
index 0000000..04e48d0
--- /dev/null
+++ b/res/drawable/ic_contrast_medium.xml
@@ -0,0 +1,28 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/system_accent1_500"
+ android:pathData="M12,4C7,4 2.73,7.11 1,11.5C2.73,15.89 7,19 12,19s9.27,-3.11 11,-7.5C21.27,7.11 17,4 12,4zM12,16c-2.48,0 -4.5,-2.02 -4.5,-4.5S9.52,7 12,7s4.5,2.02 4.5,4.5S14.48,16 12,16z"/>
+ <path
+ android:fillColor="@android:color/system_accent1_700"
+ android:pathData="M12,11.5m-2.7,0a2.7,2.7 0,1 1,5.4 0a2.7,2.7 0,1 1,-5.4 0"/>
+</vector>
diff --git a/res/drawable/ic_contrast_standard.xml b/res/drawable/ic_contrast_standard.xml
new file mode 100644
index 0000000..9f0c5af
--- /dev/null
+++ b/res/drawable/ic_contrast_standard.xml
@@ -0,0 +1,28 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+android:width="48dp"
+android:height="48dp"
+android:viewportWidth="24"
+android:viewportHeight="24"
+android:tint="?attr/colorControlNormal">
+<path
+ android:fillColor="@android:color/system_accent1_100"
+ android:pathData="M12,4C7,4 2.73,7.11 1,11.5C2.73,15.89 7,19 12,19s9.27,-3.11 11,-7.5C21.27,7.11 17,4 12,4zM12,16c-2.48,0 -4.5,-2.02 -4.5,-4.5S9.52,7 12,7s4.5,2.02 4.5,4.5S14.48,16 12,16z"/>
+<path
+ android:fillColor="@android:color/system_accent1_600"
+ android:pathData="M12,11.5m-2.7,0a2.7,2.7 0,1 1,5.4 0a2.7,2.7 0,1 1,-5.4 0"/>
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_edit_24dp.xml b/res/drawable/ic_edit_24dp.xml
new file mode 100644
index 0000000..c9dbfc3
--- /dev/null
+++ b/res/drawable/ic_edit_24dp.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:width="32dp"
+ android:height="32dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+<path
+ android:fillColor="?androidprv:attr/materialColorOnPrimary"
+ android:pathData="M20.41,4.94l-1.35,-1.35c-0.78,-0.78 -2.05,-0.78 -2.83,0L3,16.82L3,21h4.18L20.41,7.77c0.79,-0.78 0.79,-2.05 0,-2.83zM6.41,19.06L5,19v-1.36l9.82,-9.82 1.41,1.41 -9.82,9.83z"/>
+</vector>
diff --git a/res/drawable/ic_group_24dp.xml b/res/drawable/ic_group_24dp.xml
new file mode 100644
index 0000000..92815c2
--- /dev/null
+++ b/res/drawable/ic_group_24dp.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="?androidprv:attr/materialColorSecondary"
+ android:pathData="M15,8c0,-1.42 -0.5,-2.73 -1.33,-3.76C14.09,4.1 14.53,4 15,4c2.21,0 4,1.79 4,4s-1.79,4 -4,4c-0.43,0 -0.84,-0.09 -1.23,-0.21c-0.03,-0.01 -0.06,-0.02 -0.1,-0.03C14.5,10.73 15,9.42 15,8zM16.66,13.13C18.03,14.06 19,15.32 19,17v3h4v-3C23,14.82 19.42,13.53 16.66,13.13zM9,4c2.21,0 4,1.79 4,4s-1.79,4 -4,4s-4,-1.79 -4,-4S6.79,4 9,4zM9,13c2.67,0 8,1.34 8,4v3H1v-3C1,14.34 6.33,13 9,13z"/>
+</vector>
diff --git a/res/drawable/ic_inbox_24dp.xml b/res/drawable/ic_inbox_24dp.xml
new file mode 100644
index 0000000..7800ea1
--- /dev/null
+++ b/res/drawable/ic_inbox_24dp.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+<path
+ android:fillColor="?androidprv:attr/materialColorOnSecondaryContainer"
+ android:pathData="M19,3H5C3.9,3 3,3.9 3,5v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5C21,3.9 20.1,3 19,3zM19,5v9h-4.18c-0.41,1.16 -1.51,2 -2.82,2s-2.4,-0.84 -2.82,-2H5V5H19z"/>
+</vector>
diff --git a/res/drawable/ic_security_privacy_alert_primary.xml b/res/drawable/ic_security_privacy_alert_primary.xml
new file mode 100644
index 0000000..3ec0c93
--- /dev/null
+++ b/res/drawable/ic_security_privacy_alert_primary.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="48"
+ android:viewportHeight="48">
+ <path
+ android:pathData="M22,25.6H26V12.7H22V25.6ZM22,33.9H26V29.6H22V33.9ZM24,44C23.767,44 23.533,43.983 23.3,43.95C23.1,43.883 22.9,43.8 22.7,43.7C17.767,41.167 14.083,38.033 11.65,34.3C9.217,30.533 8,25.967 8,20.6V11.65C8,11.017 8.183,10.45 8.55,9.95C8.917,9.45 9.4,9.083 10,8.85L23,4.15C23.167,4.083 23.333,4.05 23.5,4.05C23.667,4.017 23.833,4 24,4C24.1,4 24.433,4.05 25,4.15L38,8.8C38.6,9.033 39.083,9.417 39.45,9.95C39.817,10.45 40,11.017 40,11.65V20.6C40,25.967 38.75,30.533 36.25,34.3C33.783,38.067 30.117,41.2 25.25,43.7C25.05,43.8 24.85,43.883 24.65,43.95C24.45,43.983 24.233,44 24,44ZM24,41C28.367,38.633 31.617,35.817 33.75,32.55C35.917,29.25 37,25.267 37,20.6V11.65L24,7L11,11.65V20.6C11,25.267 12.1,29.267 14.3,32.6C16.533,35.933 19.767,38.733 24,41Z"
+ android:fillColor="?android:attr/colorAccent"/>
+</vector>
diff --git a/res/drawable/ic_star_24dp.xml b/res/drawable/ic_star_24dp.xml
new file mode 100644
index 0000000..38535e6
--- /dev/null
+++ b/res/drawable/ic_star_24dp.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="?androidprv:attr/materialColorOnPrimary"
+ android:pathData="M14.43,10l-2.43,-8l-2.43,8l-7.57,0l6.18,4.41l-2.35,7.59l6.17,-4.69l6.18,4.69l-2.35,-7.59l6.17,-4.41z"/>
+</vector>
diff --git a/res/drawable/private_space_choose_lock_illustration.xml b/res/drawable/private_space_choose_lock_illustration.xml
deleted file mode 100644
index f943cb8..0000000
--- a/res/drawable/private_space_choose_lock_illustration.xml
+++ /dev/null
@@ -1,84 +0,0 @@
-<!--
- ~ Copyright (C) 2024 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="364dp"
- android:height="266dp"
- android:viewportWidth="364"
- android:viewportHeight="266">
- <group>
- <clip-path
- android:pathData="M0,0.95h364v265.05h-364z"/>
- <path
- android:pathData="M339.42,265.47H24.58C11.07,265.47 0,254.17 0,240.39V26.42C0,12.6 11.07,1.3 24.58,1.3H339.52C352.93,1.3 364,12.6 364,26.38V240.49C364,254.17 352.93,265.47 339.42,265.47Z"
- android:fillColor="#000000"/>
- <path
- android:pathData="M300.32,99.22C310.82,106.16 324.97,103.31 331.93,92.85C338.89,82.39 336.03,68.29 325.54,61.35L301.31,45.32C290.82,38.38 276.67,41.24 269.7,51.69C262.74,62.15 265.6,76.26 276.1,83.19L300.32,99.22Z"
- android:fillColor="#C7EBD4"/>
- <path
- android:pathData="M323.02,78.05L314.03,79.7C313.56,79.79 313.24,80.25 313.33,80.72L314.98,89.71C315.07,90.19 315.52,90.5 316,90.41L324.99,88.76C325.46,88.68 325.78,88.22 325.69,87.75L324.04,78.76C323.95,78.28 323.5,77.97 323.02,78.05Z"
- android:fillColor="#5BB974"/>
- <path
- android:pathData="M310.61,70.73L310.59,70.95C311.1,73.37 309.48,75.68 307.04,76.1L298.41,77.6C295.97,78.01 293.62,76.36 293.19,73.99L293.21,73.77C292.7,71.35 294.32,69.04 296.76,68.63L305.39,67.13C307.83,66.71 310.1,68.31 310.61,70.73Z"
- android:fillColor="#5BB974"/>
- <path
- android:pathData="M280.24,67.28C283.75,69.56 288.46,68.56 290.74,65.05C293.02,61.54 292.02,56.84 288.51,54.56C285,52.28 280.29,53.28 278.02,56.79C275.73,60.3 276.73,65 280.24,67.28Z"
- android:fillColor="#5BB974"/>
- <path
- android:pathData="M68.38,36.61C69.99,35.55 71.95,35.17 73.84,35.55C75.73,35.92 77.39,37.02 78.48,38.6L78.71,38.97L80.09,41.3C80.7,42.37 81.58,43.26 82.64,43.89C83.7,44.52 84.9,44.87 86.14,44.89L88.76,44.96C90.69,45.04 92.52,45.87 93.84,47.28C95.16,48.69 95.87,50.56 95.82,52.49C95.77,52.62 95.77,52.77 95.83,52.91L95.52,55.59C95.39,56.8 95.58,58.03 96.06,59.15C96.54,60.28 97.3,61.26 98.27,62L100.32,63.72C101.82,64.92 102.8,66.65 103.03,68.56C103.26,70.47 102.73,72.39 101.56,73.91C101.44,73.98 101.39,74.18 101.27,74.26L99.55,76.2C98.72,77.1 98.14,78.2 97.87,79.4C97.59,80.59 97.63,81.84 97.99,83.01L98.66,85.58C99.17,87.44 98.92,89.43 97.97,91.11C97.02,92.78 95.44,94.02 93.58,94.55C93.46,94.63 93.26,94.58 93.13,94.66L90.56,95.23C89.36,95.48 88.24,96.04 87.32,96.84C86.4,97.64 85.69,98.67 85.28,99.82L84.38,102.36C83.69,104.16 82.33,105.61 80.58,106.42C78.83,107.22 76.83,107.31 75.02,106.65C74.88,106.64 74.74,106.57 74.64,106.46L72.32,105.41C71.19,104.94 69.96,104.75 68.74,104.85C67.52,104.96 66.34,105.36 65.31,106.01L63.11,107.43C61.5,108.48 59.54,108.86 57.65,108.49C55.76,108.12 54.1,107.02 53.01,105.44L52.78,105.07L51.4,102.75C50.78,101.68 49.89,100.79 48.81,100.17C47.74,99.55 46.52,99.22 45.28,99.22L42.66,99.15C40.72,99.07 38.9,98.24 37.58,96.83C36.25,95.42 35.54,93.55 35.6,91.62C35.64,91.49 35.64,91.34 35.59,91.2L35.89,88.52C36.02,87.31 35.84,86.08 35.36,84.95C34.88,83.83 34.12,82.84 33.15,82.09L31.08,80.46C30.32,79.87 29.68,79.13 29.21,78.28C28.74,77.44 28.43,76.51 28.32,75.56C28.21,74.6 28.29,73.63 28.55,72.7C28.82,71.77 29.27,70.9 29.87,70.15C29.99,70.07 30.04,69.87 30.16,69.79L31.89,67.77C32.72,66.87 33.3,65.76 33.58,64.57C33.85,63.38 33.81,62.13 33.46,60.96L32.79,58.37C32.29,56.51 32.54,54.53 33.49,52.86C34.44,51.18 36.01,49.95 37.87,49.42C37.99,49.34 38.19,49.39 38.32,49.31L40.89,48.73C42.09,48.48 43.21,47.92 44.13,47.12C45.06,46.31 45.76,45.29 46.17,44.13L47.07,41.6C47.75,39.81 49.12,38.35 50.87,37.55C52.62,36.75 54.62,36.66 56.43,37.31C56.57,37.33 56.71,37.4 56.81,37.5L59.2,38.58C60.33,39.04 61.56,39.23 62.78,39.13C64,39.02 65.18,38.63 66.21,37.97L68.38,36.61Z"
- android:fillColor="#CEE3FF"/>
- <path
- android:pathData="M60.5,74.56C59.92,74.56 59.44,74.37 59.05,73.97C58.65,73.58 58.45,73.09 58.45,72.52C58.45,71.95 58.65,71.46 59.05,71.07C59.44,70.67 59.92,70.48 60.5,70.48C61.07,70.48 61.55,70.67 61.95,71.07C62.34,71.46 62.54,71.95 62.54,72.52C62.54,73.09 62.34,73.58 61.95,73.97C61.55,74.37 61.07,74.56 60.5,74.56ZM70.3,74.56C69.73,74.56 69.25,74.37 68.85,73.97C68.46,73.58 68.26,73.09 68.26,72.52C68.26,71.95 68.46,71.46 68.85,71.07C69.25,70.67 69.73,70.48 70.3,70.48C70.88,70.48 71.36,70.67 71.75,71.07C72.15,71.46 72.35,71.95 72.35,72.52C72.35,73.09 72.15,73.58 71.75,73.97C71.36,74.37 70.88,74.56 70.3,74.56ZM65.4,83.96C69.05,83.96 72.14,82.69 74.68,80.16C77.21,77.63 78.48,74.54 78.48,70.89C78.48,70.23 78.43,69.6 78.35,68.99C78.27,68.37 78.12,67.78 77.9,67.21C77.33,67.34 76.76,67.45 76.19,67.51C75.61,67.58 75.02,67.62 74.39,67.62C71.91,67.62 69.57,67.09 67.36,66.02C65.15,64.96 63.27,63.48 61.72,61.57C60.85,63.69 59.6,65.54 57.98,67.11C56.36,68.67 54.48,69.85 52.32,70.64V70.89C52.32,74.54 53.59,77.63 56.12,80.16C58.66,82.69 61.75,83.96 65.4,83.96ZM65.4,87.23C63.14,87.23 61.01,86.8 59.03,85.94C57.04,85.09 55.31,83.92 53.84,82.45C52.36,80.98 51.2,79.25 50.34,77.26C49.48,75.27 49.05,73.15 49.05,70.89C49.05,68.62 49.48,66.5 50.34,64.51C51.2,62.52 52.36,60.79 53.84,59.32C55.31,57.85 57.04,56.69 59.03,55.83C61.01,54.97 63.14,54.54 65.4,54.54C67.66,54.54 69.79,54.97 71.77,55.83C73.76,56.69 75.49,57.85 76.96,59.32C78.43,60.79 79.6,62.52 80.46,64.51C81.32,66.5 81.74,68.62 81.74,70.89C81.74,73.15 81.32,75.27 80.46,77.26C79.6,79.25 78.43,80.98 76.96,82.45C75.49,83.92 73.76,85.09 71.77,85.94C69.79,86.8 67.66,87.23 65.4,87.23ZM63.19,58.01C64.34,59.92 65.89,61.45 67.85,62.61C69.81,63.77 71.99,64.35 74.39,64.35C74.77,64.35 75.14,64.33 75.49,64.29C75.85,64.25 76.21,64.2 76.6,64.14C75.45,62.24 73.9,60.7 71.94,59.55C69.98,58.39 67.8,57.81 65.4,57.81C65.02,57.81 64.65,57.83 64.3,57.87C63.94,57.91 63.57,57.96 63.19,58.01ZM53.02,66.76C54.41,65.97 55.62,64.95 56.66,63.69C57.69,62.44 58.47,61.04 58.98,59.49C57.6,60.28 56.38,61.3 55.35,62.55C54.31,63.8 53.54,65.21 53.02,66.76Z"
- android:fillColor="#559DFD"/>
- <path
- android:pathData="M185.48,137.47C186.07,137.47 186.56,137.28 186.94,136.89C187.33,136.51 187.52,136.02 187.52,135.43C187.52,134.84 187.33,134.35 186.94,133.96C186.56,133.58 186.07,133.39 185.48,133.39C184.88,133.39 184.4,133.58 184.01,133.96C183.62,134.35 183.43,134.84 183.43,135.43C183.43,136.02 183.62,136.51 184.01,136.89C184.4,137.28 184.88,137.47 185.48,137.47Z"
- android:fillColor="#202124"/>
- <path
- android:pathData="M54.83,176.56L60.78,178.1C63.63,178.83 66.64,178.74 69.45,177.82L75.29,175.92C86.88,172.14 98.07,182.63 94.99,194.4L93.44,200.33C92.69,203.18 92.78,206.18 93.69,208.97L95.6,214.79C99.37,226.35 88.83,237.52 77.03,234.47L71.09,232.93C68.23,232.19 65.22,232.29 62.42,233.2L56.58,235.11C44.98,238.89 33.8,228.4 36.88,216.63L38.43,210.7C39.17,207.85 39.08,204.85 38.17,202.06L36.27,196.23C32.49,184.68 43.03,173.51 54.83,176.56Z"
- android:fillColor="#FFF7DD"/>
- <path
- android:pathData="M80.51,190.74m-6.18,0a6.18,6.18 0,1 1,12.37 0a6.18,6.18 0,1 1,-12.37 0"
- android:fillColor="#FFC800"/>
- <path
- android:pathData="M50.47,219.9m-6.18,0a6.18,6.18 0,1 1,12.37 0a6.18,6.18 0,1 1,-12.37 0"
- android:fillColor="#FFC800"/>
- <path
- android:pathData="M80.51,219.9m-6.18,0a6.18,6.18 0,1 1,12.37 0a6.18,6.18 0,1 1,-12.37 0"
- android:fillColor="#FFC800"/>
- <path
- android:pathData="M77.6,193.13L50.21,220.52"
- android:strokeWidth="1.76699"
- android:fillColor="#00000000"
- android:strokeColor="#FFC800"/>
- <path
- android:pathData="M80.07,219.9L50.47,219.9"
- android:strokeWidth="1.76699"
- android:fillColor="#00000000"
- android:strokeColor="#FFC800"/>
- <path
- android:pathData="M311.22,173.59L289.22,173.9C286.98,173.93 284.78,174.55 282.85,175.7C280.92,176.85 279.34,178.48 278.25,180.44L267.53,199.55C266.44,201.5 265.88,203.71 265.91,205.95C265.94,208.18 266.56,210.38 267.72,212.3L278.99,231.12C280.14,233.04 281.78,234.63 283.74,235.72C285.7,236.81 287.91,237.36 290.15,237.33L312.15,237.01C314.39,236.99 316.59,236.37 318.52,235.22C320.45,234.08 322.04,232.45 323.14,230.49L333.84,211.37C334.93,209.41 335.49,207.21 335.45,204.97C335.42,202.73 334.79,200.54 333.64,198.62L322.38,179.8C321.23,177.88 319.59,176.3 317.63,175.21C315.67,174.12 313.46,173.56 311.22,173.59Z"
- android:fillColor="#CEE3FF"/>
- <path
- android:pathData="M285.54,202.36C285.35,202.24 285.23,202.07 285.18,201.88C285.14,201.68 285.19,201.47 285.35,201.25C286.94,198.95 288.98,197.13 291.46,195.8C293.93,194.48 296.6,193.77 299.45,193.68C302.3,193.59 305.02,194.11 307.58,195.24C310.15,196.37 312.32,198.05 314.08,200.28C314.28,200.51 314.35,200.72 314.28,200.91C314.22,201.1 314.11,201.26 313.96,201.4C313.8,201.53 313.61,201.6 313.4,201.59C313.18,201.59 312.99,201.48 312.82,201.27C311.28,199.25 309.32,197.72 306.96,196.69C304.6,195.66 302.11,195.19 299.5,195.27C296.89,195.35 294.46,195.98 292.21,197.15C289.95,198.33 288.11,199.97 286.67,202.09C286.51,202.33 286.33,202.47 286.11,202.51C285.9,202.54 285.71,202.49 285.54,202.36ZM304.89,221.4C302.07,220.8 299.73,219.5 297.89,217.5C296.05,215.5 295.09,213.01 294.99,210.04C294.95,208.71 295.4,207.58 296.34,206.65C297.28,205.72 298.44,205.23 299.81,205.18C301.18,205.14 302.37,205.56 303.37,206.43C304.36,207.3 304.88,208.4 304.92,209.73C304.95,210.6 305.31,211.33 306,211.9C306.69,212.48 307.5,212.76 308.41,212.73C309.33,212.7 310.1,212.38 310.73,211.76C311.35,211.14 311.65,210.39 311.62,209.52C311.53,206.44 310.3,203.89 307.95,201.86C305.6,199.84 302.83,198.87 299.66,198.97C296.48,199.07 293.79,200.21 291.56,202.38C289.34,204.54 288.28,207.15 288.37,210.21C288.39,210.84 288.48,211.64 288.63,212.59C288.78,213.54 289.11,214.65 289.6,215.9C289.69,216.14 289.69,216.35 289.6,216.54C289.51,216.73 289.36,216.87 289.15,216.96C288.94,217.04 288.73,217.04 288.52,216.96C288.32,216.87 288.17,216.72 288.09,216.51C287.65,215.49 287.33,214.47 287.12,213.45C286.92,212.44 286.79,211.38 286.76,210.29C286.65,206.77 287.86,203.77 290.38,201.3C292.9,198.83 295.96,197.54 299.57,197.42C303.2,197.31 306.36,198.4 309.05,200.71C311.73,203.02 313.13,205.94 313.24,209.47C313.28,210.79 312.84,211.92 311.91,212.84C310.98,213.76 309.83,214.24 308.46,214.28C307.09,214.32 305.9,213.91 304.89,213.06C303.88,212.2 303.35,211.1 303.31,209.78C303.28,208.9 302.93,208.18 302.25,207.6C301.57,207.02 300.78,206.75 299.86,206.78C298.95,206.81 298.17,207.13 297.53,207.75C296.89,208.37 296.58,209.11 296.61,209.99C296.69,212.56 297.53,214.69 299.13,216.36C300.73,218.04 302.77,219.18 305.24,219.8C305.48,219.87 305.65,220 305.74,220.18C305.82,220.37 305.84,220.56 305.8,220.78C305.75,220.96 305.64,221.13 305.49,221.27C305.33,221.4 305.13,221.45 304.89,221.4ZM290.53,193.88C290.32,194.02 290.1,194.06 289.89,194C289.67,193.94 289.5,193.81 289.39,193.6C289.27,193.39 289.24,193.2 289.29,193.02C289.34,192.85 289.47,192.69 289.68,192.55C291.16,191.71 292.72,191.05 294.35,190.57C295.97,190.1 297.64,189.83 299.33,189.78C301.05,189.73 302.74,189.88 304.39,190.24C306.05,190.6 307.66,191.14 309.21,191.86C309.46,191.99 309.61,192.14 309.65,192.32C309.7,192.51 309.68,192.7 309.61,192.88C309.53,193.07 309.4,193.22 309.22,193.34C309.03,193.45 308.8,193.44 308.53,193.32C307.08,192.65 305.59,192.14 304.06,191.8C302.53,191.47 300.97,191.32 299.38,191.37C297.82,191.42 296.29,191.65 294.8,192.05C293.3,192.46 291.88,193.07 290.53,193.88ZM296.15,221.12C294.51,219.52 293.24,217.88 292.34,216.2C291.44,214.51 290.95,212.5 290.88,210.17C290.8,207.75 291.63,205.69 293.35,203.97C295.07,202.26 297.19,201.36 299.69,201.28C302.19,201.21 304.37,201.97 306.23,203.57C308.08,205.17 309.05,207.18 309.12,209.6C309.13,209.84 309.06,210.03 308.92,210.18C308.77,210.33 308.58,210.41 308.34,210.42C308.12,210.42 307.93,210.36 307.77,210.22C307.6,210.07 307.52,209.88 307.51,209.65C307.45,207.66 306.65,206.01 305.11,204.72C303.58,203.43 301.79,202.81 299.74,202.88C297.7,202.94 295.96,203.66 294.54,205.05C293.11,206.44 292.43,208.13 292.49,210.12C292.56,212.27 292.99,214.08 293.79,215.55C294.59,217.03 295.74,218.5 297.24,219.97C297.41,220.12 297.5,220.31 297.5,220.52C297.51,220.73 297.44,220.92 297.28,221.08C297.12,221.25 296.94,221.33 296.72,221.34C296.51,221.35 296.32,221.27 296.15,221.12ZM308.25,218.03C305.86,218.11 303.76,217.38 301.94,215.84C300.13,214.3 299.19,212.32 299.11,209.91C299.11,209.7 299.17,209.51 299.32,209.35C299.46,209.18 299.65,209.1 299.89,209.09C300.14,209.08 300.33,209.15 300.49,209.31C300.64,209.46 300.72,209.65 300.73,209.86C300.79,211.85 301.57,213.46 303.06,214.69C304.55,215.91 306.27,216.5 308.2,216.44C308.37,216.43 308.59,216.41 308.89,216.38C309.18,216.34 309.49,216.29 309.81,216.23C310.05,216.17 310.26,216.2 310.44,216.31C310.62,216.42 310.74,216.6 310.8,216.84C310.86,217.05 310.83,217.23 310.7,217.4C310.57,217.56 310.4,217.67 310.18,217.73C309.7,217.88 309.28,217.97 308.92,217.99C308.56,218.01 308.33,218.03 308.25,218.03Z"
- android:fillColor="#669DF6"/>
- <path
- android:pathData="M214.98,100.49V166.46H149.02V100.49H214.98ZM214.98,91.07H149.02C143.83,91.07 139.59,95.31 139.59,100.49V166.46C139.59,171.64 143.83,175.88 149.02,175.88H214.98C220.17,175.88 224.41,171.64 224.41,166.46V100.49C224.41,95.31 220.17,91.07 214.98,91.07Z"
- android:fillColor="#669DF6"/>
- <path
- android:pathData="M186.4,134.74L189.09,149.96H174.95L177.64,134.74C174.62,133.19 172.6,130.03 172.6,126.4C172.6,121.22 176.84,116.98 182.02,116.98C187.2,116.98 191.44,121.22 191.44,126.4C191.44,130.03 189.42,133.19 186.4,134.74Z"
- android:fillColor="#669DF6"/>
- </group>
-</vector>
diff --git a/res/drawable/private_space_illustration.xml b/res/drawable/private_space_illustration.xml
deleted file mode 100644
index 7f2dba8..0000000
--- a/res/drawable/private_space_illustration.xml
+++ /dev/null
@@ -1,82 +0,0 @@
-<!--
- ~ Copyright (C) 2024 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="412dp"
- android:height="300dp"
- android:viewportWidth="364"
- android:viewportHeight="265">
- <group>
- <clip-path
- android:pathData="M0,0h364v265h-364z"/>
- <group>
- <clip-path
- android:pathData="M0,0h364v265.05h-364z"/>
- <path
- android:pathData="M339.44,265.05H24.56C11.04,265.05 0,253.74 0,239.87V25.18C0,11.31 11.04,0 24.56,0H339.53C352.96,0 364,11.31 364,25.18V240.05C364,253.74 352.96,265.05 339.44,265.05Z"
- android:fillColor="#000000"/>
- <path
- android:pathData="M85.7,153.29a17.67,18.11 0,1 0,35.34 0a17.67,18.11 0,1 0,-35.34 0z"
- android:fillColor="#3C4043"/>
- <path
- android:pathData="M155.05,153.29m-18.11,0a18.11,18.11 0,1 1,36.22 0a18.11,18.11 0,1 1,-36.22 0"
- android:fillColor="#3C4043"/>
- <path
- android:pathData="M208.95,153.29m-18.11,0a18.11,18.11 0,1 1,36.22 0a18.11,18.11 0,1 1,-36.22 0"
- android:fillColor="#3C4043"/>
- <path
- android:pathData="M75.1,76.86L288.9,76.86A8.83,8.83 0,0 1,297.74 85.7L297.74,236.78A8.83,8.83 0,0 1,288.9 245.61L75.1,245.61A8.83,8.83 0,0 1,66.26 236.78L66.26,85.7A8.83,8.83 0,0 1,75.1 76.86z"
- android:fillColor="#E8F0FE"/>
- <path
- android:pathData="M288.02,104.25C288.02,112.06 281.69,118.39 273.88,118.39C266.08,118.39 259.75,112.06 259.75,104.25C259.75,96.45 266.08,90.12 273.88,90.12C281.69,90.12 288.02,96.45 288.02,104.25Z"
- android:fillColor="#D2E3FC"/>
- <path
- android:pathData="M275.92,98.6C275.92,97.39 276.99,96.37 278.35,96.37C279.71,96.37 280.78,97.39 280.78,98.6V101.88H282.72V98.6C282.72,96.33 280.74,94.53 278.35,94.53C275.96,94.53 273.98,96.33 273.98,98.6V100.96H267.57C266.18,100.96 265.05,102.03 265.05,103.35V110.7C265.05,112.02 266.18,113.09 267.57,113.09H276.7C278.09,113.09 279.22,112.02 279.22,110.7V103.35C279.22,102.03 278.09,100.96 276.7,100.96H275.92V98.6ZM267.57,102.8C267.25,102.8 266.99,103.05 266.99,103.35V110.7C266.99,111 267.25,111.25 267.57,111.25H276.7C277.02,111.25 277.28,111 277.28,110.7V103.35C277.28,103.05 277.02,102.8 276.7,102.8H267.57ZM272.14,108.68C273.1,108.68 273.88,107.94 273.88,107.03C273.88,106.11 273.1,105.37 272.14,105.37C271.17,105.37 270.39,106.11 270.39,107.03C270.39,107.94 271.17,108.68 272.14,108.68Z"
- android:fillColor="#669DF6"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M102.04,147.1m-19.88,0a19.88,19.88 0,1 1,39.76 0a19.88,19.88 0,1 1,-39.76 0"
- android:fillColor="#669DF6"/>
- <path
- android:pathData="M155.94,147.1m-19.88,0a19.88,19.88 0,1 1,39.76 0a19.88,19.88 0,1 1,-39.76 0"
- android:fillColor="#669DF6"/>
- <path
- android:pathData="M102.04,204.53m-19.88,0a19.88,19.88 0,1 1,39.76 0a19.88,19.88 0,1 1,-39.76 0"
- android:fillColor="#669DF6"/>
- <path
- android:pathData="M155.94,204.53m-19.88,0a19.88,19.88 0,1 1,39.76 0a19.88,19.88 0,1 1,-39.76 0"
- android:fillColor="#669DF6"/>
- <path
- android:pathData="M210.71,203.65m-19.88,0a19.88,19.88 0,1 1,39.76 0a19.88,19.88 0,1 1,-39.76 0"
- android:fillColor="#669DF6"/>
- <path
- android:pathData="M265.49,203.65m-19.88,0a19.88,19.88 0,1 1,39.76 0a19.88,19.88 0,1 1,-39.76 0"
- android:fillColor="#669DF6"/>
- </group>
- <path
- android:pathData="M86.14,46.38m-19.88,0a19.88,19.88 0,1 1,39.76 0a19.88,19.88 0,1 1,-39.76 0"
- android:fillColor="#3C4043"/>
- <path
- android:pathData="M150.64,46.38m-19.88,0a19.88,19.88 0,1 1,39.76 0a19.88,19.88 0,1 1,-39.76 0"
- android:fillColor="#3C4043"/>
- <path
- android:pathData="M213.36,46.38m-19.88,0a19.88,19.88 0,1 1,39.76 0a19.88,19.88 0,1 1,-39.76 0"
- android:fillColor="#3C4043"/>
- <path
- android:pathData="M277.86,46.38m-19.88,0a19.88,19.88 0,1 1,39.76 0a19.88,19.88 0,1 1,-39.76 0"
- android:fillColor="#3C4043"/>
- </group>
-</vector>
diff --git a/res/drawable/private_space_setup_notification_illustration.xml b/res/drawable/private_space_setup_notification_illustration.xml
deleted file mode 100644
index ecdb486..0000000
--- a/res/drawable/private_space_setup_notification_illustration.xml
+++ /dev/null
@@ -1,65 +0,0 @@
-<!--
- ~ Copyright (C) 2024 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="364dp"
- android:height="265dp"
- android:viewportWidth="364"
- android:viewportHeight="265">
- <group>
- <group>
- <clip-path
- android:pathData="M0,0.48h364v265.05h-364z"/>
- <path
- android:pathData="M339.42,264.99H24.58C11.07,264.99 0,253.7 0,239.92V25.94C0,12.12 11.07,0.83 24.58,0.83H339.52C352.93,0.83 364,12.12 364,25.91V240.01C364,253.7 352.93,264.99 339.42,264.99Z"
- android:fillColor="#000000"/>
- </group>
- <path
- android:pathData="M200.49,144.8C200.49,139.45 205.07,134.95 210.92,134.95C216.77,134.95 221.36,139.45 221.36,144.8V159.36H229.71V144.8C229.71,134.78 221.21,126.82 210.92,126.82C200.64,126.82 192.14,134.78 192.14,144.8V155.29H164.58C158.59,155.29 153.73,160.02 153.73,165.86V198.4C153.73,204.24 158.59,208.98 164.58,208.98H203.82C209.82,208.98 214.68,204.24 214.68,198.4V165.86C214.68,160.02 209.82,155.29 203.82,155.29H200.49V144.8ZM164.58,163.42C163.2,163.42 162.08,164.52 162.08,165.86V198.4C162.08,199.75 163.2,200.85 164.58,200.85H203.82C205.21,200.85 206.33,199.75 206.33,198.4V165.86C206.33,164.52 205.21,163.42 203.82,163.42H164.58ZM184.2,189.46C188.35,189.46 191.72,186.18 191.72,182.13C191.72,178.09 188.35,174.81 184.2,174.81C180.05,174.81 176.69,178.09 176.69,182.13C176.69,186.18 180.05,189.46 184.2,189.46Z"
- android:fillColor="#C58AF9"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M163.56,220.47C168.99,220.47 173.39,224.87 173.39,230.3C173.39,235.72 168.99,240.13 163.56,240.13C158.13,240.13 153.73,235.72 153.73,230.3C153.73,224.87 158.13,220.47 163.56,220.47Z"
- android:fillColor="#5F6368"/>
- <path
- android:pathData="M201.15,220.47C206.58,220.47 210.98,224.87 210.98,230.3C210.98,235.72 206.58,240.13 201.15,240.13C195.71,240.13 191.32,235.72 191.32,230.3C191.32,224.87 195.71,220.47 201.15,220.47Z"
- android:fillColor="#C58AF9"/>
- <path
- android:pathData="M89.23,28.75L257.1,28.75A13.25,13.25 0,0 1,270.35 42L270.35,79.99A13.25,13.25 0,0 1,257.1 93.24L89.23,93.24A13.25,13.25 0,0 1,75.98 79.99L75.98,42A13.25,13.25 0,0 1,89.23 28.75z"
- android:fillColor="#E9D2FD"/>
- <path
- android:pathData="M145.78,43.77L241.19,43.77A5.3,5.3 0,0 1,246.49 49.07L246.49,49.07A5.3,5.3 0,0 1,241.19 54.37L145.78,54.37A5.3,5.3 0,0 1,140.48 49.07L140.48,49.07A5.3,5.3 0,0 1,145.78 43.77z"
- android:fillColor="#C58AF9"/>
- <path
- android:pathData="M145.34,64.97L241.64,64.97A4.86,4.86 0,0 1,246.49 69.83L246.49,69.83A4.86,4.86 0,0 1,241.64 74.69L145.34,74.69A4.86,4.86 0,0 1,140.48 69.83L140.48,69.83A4.86,4.86 0,0 1,145.34 64.97z"
- android:fillColor="#C58AF9"/>
- <path
- android:pathData="M108.23,81.76C120.18,81.76 129.87,72.07 129.87,60.11C129.87,48.16 120.18,38.47 108.23,38.47C96.27,38.47 86.58,48.16 86.58,60.11C86.58,72.07 96.27,81.76 108.23,81.76Z"
- android:fillColor="#C58AF9"/>
- <path
- android:pathData="M266.37,112.68C278.33,112.68 288.02,102.99 288.02,91.03C288.02,79.08 278.33,69.39 266.37,69.39C254.42,69.39 244.73,79.08 244.73,91.03C244.73,102.99 254.42,112.68 266.37,112.68Z"
- android:fillColor="#C58AF9"/>
- <path
- android:pathData="M96.3,70.05V66.96H99.39V56.14C99.39,54 100.04,52.1 101.33,50.43C102.61,48.77 104.29,47.68 106.35,47.17V46.09C106.35,45.44 106.58,44.89 107.03,44.44C107.48,43.99 108.03,43.77 108.67,43.77C109.31,43.77 109.86,43.99 110.31,44.44C110.76,44.89 110.99,45.44 110.99,46.09V47.17C113.05,47.68 114.73,48.77 116.01,50.43C117.3,52.1 117.95,54 117.95,56.14V66.96H121.04V70.05H96.3ZM108.67,74.69C107.82,74.69 107.09,74.39 106.49,73.78C105.88,73.18 105.58,72.45 105.58,71.6H111.76C111.76,72.45 111.46,73.18 110.85,73.78C110.25,74.39 109.52,74.69 108.67,74.69ZM102.49,66.96H114.85V56.14C114.85,54.43 114.25,52.98 113.04,51.77C111.83,50.56 110.37,49.95 108.67,49.95C106.97,49.95 105.51,50.56 104.3,51.77C103.09,52.98 102.49,54.43 102.49,56.14V66.96Z"
- android:fillColor="#E9D2FD"/>
- <path
- android:pathData="M276.44,81.86V101.1H257.2V81.86H276.44ZM276.44,79.11H257.2C255.68,79.11 254.45,80.34 254.45,81.86V101.1C254.45,102.61 255.68,103.84 257.2,103.84H276.44C277.95,103.84 279.18,102.61 279.18,101.1V81.86C279.18,80.34 277.95,79.11 276.44,79.11Z"
- android:fillColor="#E9D2FD"/>
- <path
- android:pathData="M268.09,91.85L268.87,96.29H264.75L265.54,91.85C264.65,91.39 264.06,90.47 264.06,89.42C264.06,87.9 265.3,86.67 266.81,86.67C268.33,86.67 269.56,87.9 269.56,89.42C269.56,90.47 268.97,91.39 268.09,91.85Z"
- android:fillColor="#E9D2FD"/>
- </group>
-</vector>
diff --git a/res/drawable/private_space_setup_preinstalled_illustration.xml b/res/drawable/private_space_setup_preinstalled_illustration.xml
deleted file mode 100644
index 2f01f01..0000000
--- a/res/drawable/private_space_setup_preinstalled_illustration.xml
+++ /dev/null
@@ -1,77 +0,0 @@
-<!--
- ~ Copyright (C) 2024 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="364dp"
- android:height="266dp"
- android:viewportWidth="364"
- android:viewportHeight="266">
- <group>
- <clip-path
- android:pathData="M0,0.48h364v265.05h-364z"/>
- <path
- android:pathData="M339.42,264.99H24.58C11.07,264.99 0,253.7 0,239.92V25.94C0,12.13 11.07,0.83 24.58,0.83H339.52C352.93,0.83 364,12.13 364,25.91V240.01C364,253.7 352.93,264.99 339.42,264.99Z"
- android:fillColor="#000000"/>
- <path
- android:pathData="M75.92,137.81L50.21,111.72C50.1,111.61 49.96,111.54 49.81,111.5C49.66,111.47 49.51,111.49 49.36,111.54C49.22,111.6 49.1,111.7 49.01,111.83C48.93,111.95 48.88,112.1 48.88,112.26L48.52,162.99C48.52,163.14 48.56,163.29 48.64,163.42C48.73,163.54 48.84,163.64 48.98,163.7C49.12,163.76 49.27,163.78 49.42,163.76C49.57,163.73 49.71,163.66 49.82,163.56L75.92,137.81Z"
- android:fillColor="#C58AF9"/>
- <path
- android:pathData="M79.16,134.61L87.37,126.51L54.65,107.44C54.48,107.32 54.27,107.28 54.07,107.31C53.87,107.35 53.69,107.46 53.57,107.62C53.45,107.78 53.39,107.99 53.42,108.19C53.44,108.39 53.55,108.58 53.71,108.71L79.16,134.61Z"
- android:fillColor="#C58AF9"/>
- <path
- android:pathData="M53.24,166.57C53.09,166.71 53.01,166.89 52.99,167.08C52.97,167.27 53.02,167.47 53.13,167.63C53.24,167.79 53.41,167.9 53.6,167.95C53.79,167.99 53.99,167.97 54.16,167.88L87.22,149.32L79.13,141.09L53.24,166.57Z"
- android:fillColor="#C58AF9"/>
- <path
- android:pathData="M101.4,134.67L91.43,128.87L82.37,137.83L91.35,146.94L101.4,141.28C101.97,140.94 102.45,140.46 102.78,139.88C103.11,139.3 103.29,138.64 103.29,137.97C103.29,137.31 103.11,136.65 102.78,136.07C102.45,135.49 101.97,135.01 101.4,134.67Z"
- android:fillColor="#C58AF9"/>
- <path
- android:pathData="M263.42,132.67C263.42,136.04 264.61,138.92 267,141.32C269.39,143.71 272.26,144.91 275.61,144.91C278.96,144.91 281.83,143.71 284.22,141.32C286.61,138.92 287.8,136.04 287.8,132.67C287.8,129.31 286.61,126.43 284.22,124.03C281.83,121.63 278.96,120.43 275.61,120.43C272.26,120.43 269.39,121.63 267,124.03C264.61,126.43 263.42,129.31 263.42,132.67ZM275.61,151.03C276.27,151.03 276.9,151.01 277.51,150.95C278.12,150.9 278.73,150.8 279.34,150.65L272.18,163.04C264.51,162.17 258.08,158.87 252.9,153.13C247.72,147.4 245.13,140.58 245.13,132.67C245.13,130.53 245.33,128.45 245.74,126.44C246.14,124.43 246.75,122.5 247.57,120.66L259.76,141.85C261.33,144.6 263.52,146.82 266.31,148.51C269.11,150.19 272.2,151.03 275.61,151.03ZM275.61,114.32C271.55,114.32 267.94,115.5 264.79,117.87C261.64,120.24 259.45,123.26 258.23,126.94L251.07,114.54C253.87,110.77 257.38,107.75 261.63,105.48C265.87,103.21 270.53,102.08 275.61,102.08C280.64,102.08 285.26,103.19 289.48,105.4C293.69,107.62 297.2,110.59 299.99,114.32H275.61ZM303.5,120.43C304.36,122.32 305.01,124.29 305.44,126.32C305.87,128.36 306.09,130.48 306.09,132.67C306.09,140.58 303.51,147.38 298.36,153.1C293.2,158.81 286.84,162.12 279.27,163.04L291.46,141.85C292.22,140.53 292.82,139.09 293.25,137.53C293.68,135.98 293.9,134.36 293.9,132.67C293.9,130.28 293.48,128.04 292.64,125.98C291.8,123.92 290.65,122.07 289.17,120.43H303.5Z"
- android:fillColor="#C58AF9"/>
- <path
- android:pathData="M155.01,119.75h41.52v36.22h-41.52z"
- android:fillColor="#E9D2FD"/>
- <path
- android:pathData="M175.77,149.2C179.47,149.2 182.62,147.91 185.21,145.33C187.8,142.76 189.09,139.63 189.09,135.95C189.09,132.26 187.8,129.13 185.21,126.56C182.62,123.98 179.47,122.69 175.77,122.69C172.07,122.69 168.93,123.98 166.34,126.56C163.75,129.13 162.46,132.26 162.46,135.95C162.46,139.63 163.75,142.76 166.34,145.33C168.93,147.91 172.07,149.2 175.77,149.2ZM175.77,143.31C173.7,143.31 171.95,142.6 170.52,141.17C169.09,139.75 168.38,138.01 168.38,135.95C168.38,133.88 169.09,132.14 170.52,130.72C171.95,129.29 173.7,128.58 175.77,128.58C177.85,128.58 179.6,129.29 181.03,130.72C182.46,132.14 183.17,133.88 183.17,135.95C183.17,138.01 182.46,139.75 181.03,141.17C179.6,142.6 177.85,143.31 175.77,143.31ZM152.1,159.51C150.47,159.51 149.07,158.93 147.92,157.77C146.76,156.62 146.18,155.24 146.18,153.62V118.28C146.18,116.65 146.76,115.27 147.92,114.11C149.07,112.96 150.47,112.39 152.1,112.39H161.42L166.9,106.5H184.65L190.13,112.39H199.45C201.08,112.39 202.47,112.96 203.63,114.11C204.79,115.27 205.37,116.65 205.37,118.28V153.62C205.37,155.24 204.79,156.62 203.63,157.77C202.47,158.93 201.08,159.51 199.45,159.51H152.1Z"
- android:fillColor="#C58AF9"/>
- <path
- android:pathData="M109.51,182.48C121.47,182.48 131.16,172.78 131.16,160.83C131.16,148.88 121.47,139.18 109.51,139.18C97.56,139.18 87.87,148.88 87.87,160.83C87.87,172.78 97.56,182.48 109.51,182.48Z"
- android:fillColor="#E9D2FD"/>
- <path
- android:pathData="M119.57,151.65V170.89H100.33V151.65H119.57ZM119.57,148.9H100.33C98.82,148.9 97.58,150.14 97.58,151.65V170.89C97.58,172.4 98.82,173.64 100.33,173.64H119.57C121.09,173.64 122.32,172.4 122.32,170.89V151.65C122.32,150.14 121.09,148.9 119.57,148.9Z"
- android:fillColor="#C58AF9"/>
- <path
- android:pathData="M111.23,161.64L112.01,166.08H107.89L108.67,161.64C107.79,161.19 107.2,160.27 107.2,159.21C107.2,157.7 108.44,156.46 109.95,156.46C111.46,156.46 112.7,157.7 112.7,159.21C112.7,160.27 112.11,161.19 111.23,161.64Z"
- android:fillColor="#C58AF9"/>
- <path
- android:pathData="M208.46,182.48C220.42,182.48 230.11,172.78 230.11,160.83C230.11,148.88 220.42,139.18 208.46,139.18C196.51,139.18 186.82,148.88 186.82,160.83C186.82,172.78 196.51,182.48 208.46,182.48Z"
- android:fillColor="#E9D2FD"/>
- <path
- android:pathData="M218.52,151.65V170.89H199.29V151.65H218.52ZM218.52,148.9H199.29C197.77,148.9 196.54,150.14 196.54,151.65V170.89C196.54,172.4 197.77,173.64 199.29,173.64H218.52C220.04,173.64 221.27,172.4 221.27,170.89V151.65C221.27,150.14 220.04,148.9 218.52,148.9Z"
- android:fillColor="#C58AF9"/>
- <path
- android:pathData="M210.18,161.64L210.96,166.08H206.84L207.62,161.64C206.74,161.19 206.15,160.27 206.15,159.21C206.15,157.7 207.39,156.46 208.9,156.46C210.41,156.46 211.65,157.7 211.65,159.21C211.65,160.27 211.06,161.19 210.18,161.64Z"
- android:fillColor="#C58AF9"/>
- <path
- android:pathData="M312.72,182.48C324.67,182.48 334.36,172.78 334.36,160.83C334.36,148.88 324.67,139.18 312.72,139.18C300.76,139.18 291.07,148.88 291.07,160.83C291.07,172.78 300.76,182.48 312.72,182.48Z"
- android:fillColor="#E9D2FD"/>
- <path
- android:pathData="M322.78,151.65V170.89H303.54V151.65H322.78ZM322.78,148.9H303.54C302.03,148.9 300.79,150.14 300.79,151.65V170.89C300.79,172.4 302.03,173.64 303.54,173.64H322.78C324.29,173.64 325.53,172.4 325.53,170.89V151.65C325.53,150.14 324.29,148.9 322.78,148.9Z"
- android:fillColor="#C58AF9"/>
- <path
- android:pathData="M314.43,161.64L315.22,166.08H311.09L311.88,161.64C311,161.19 310.41,160.27 310.41,159.21C310.41,157.7 311.64,156.46 313.15,156.46C314.67,156.46 315.9,157.7 315.9,159.21C315.9,160.27 315.31,161.19 314.43,161.64Z"
- android:fillColor="#C58AF9"/>
- </group>
-</vector>
diff --git a/res/drawable/private_space_setup_sharing_illustration.xml b/res/drawable/private_space_setup_sharing_illustration.xml
deleted file mode 100644
index 8cfa77f..0000000
--- a/res/drawable/private_space_setup_sharing_illustration.xml
+++ /dev/null
@@ -1,100 +0,0 @@
-<!--
- ~ Copyright (C) 2024 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="364dp"
- android:height="266dp"
- android:viewportWidth="364"
- android:viewportHeight="266">
- <group>
- <clip-path
- android:pathData="M0,0.48h364v265.05h-364z"/>
- <path
- android:pathData="M339.42,264.99H24.58C11.07,264.99 0,253.7 0,239.92V25.94C0,12.13 11.07,0.83 24.58,0.83H339.52C352.93,0.83 364,12.13 364,25.91V240.01C364,253.7 352.93,264.99 339.42,264.99Z"
- android:fillColor="#000000"/>
- <path
- android:pathData="M201.15,182.48C195.72,182.48 191.32,186.88 191.32,192.31C191.32,197.73 195.72,202.14 201.15,202.14C206.58,202.14 210.98,197.73 210.98,192.31C210.98,186.88 206.58,182.48 201.15,182.48Z"
- android:fillColor="#3C4043"/>
- <path
- android:pathData="M163.56,182.48C158.13,182.48 153.73,186.88 153.73,192.31C153.73,197.73 158.13,202.14 163.56,202.14C168.99,202.14 173.39,197.73 173.39,192.31C173.39,186.88 168.99,182.48 163.56,182.48Z"
- android:fillColor="#669DF6"/>
- <path
- android:pathData="M185.48,136.99C186.07,136.99 186.56,136.8 186.94,136.41C187.33,136.03 187.52,135.54 187.52,134.95C187.52,134.36 187.33,133.88 186.94,133.49C186.56,133.1 186.07,132.91 185.48,132.91C184.88,132.91 184.4,133.1 184.01,133.49C183.62,133.88 183.43,134.36 183.43,134.95C183.43,135.54 183.62,136.03 184.01,136.41C184.4,136.8 184.88,136.99 185.48,136.99Z"
- android:fillColor="#202124"/>
- <path
- android:pathData="M338.13,145.81L322.74,131.98C319.02,128.64 313.3,128.94 309.97,132.66C306.63,136.37 306.93,142.09 310.65,145.43L326.05,159.26C329.76,162.6 335.48,162.3 338.82,158.58C342.15,154.87 341.85,149.15 338.13,145.81Z"
- android:fillColor="#AECBFA"/>
- <path
- android:pathData="M325.81,64.64L295.79,43.72C293.73,42.28 290.89,42.79 289.45,44.85L268.54,74.87C267.1,76.93 267.6,79.77 269.67,81.21L299.69,102.13C301.75,103.56 304.59,103.06 306.02,100.99L326.94,70.97C328.38,68.91 327.87,66.07 325.81,64.64Z"
- android:fillColor="#E8F0FE"/>
- <path
- android:pathData="M297.96,73.56C299.44,74.63 301.2,75.25 303.04,75.33C304.87,75.41 306.69,74.95 308.25,74.01C309.82,73.06 311.07,71.68 311.85,70.03C312.63,68.38 312.89,66.53 312.61,64.73C312.34,62.93 311.53,61.25 310.29,59.91C309.05,58.56 307.44,57.61 305.66,57.18C303.88,56.75 302,56.85 300.28,57.47C298.56,58.09 297.06,59.21 295.97,60.68C294.52,62.65 293.91,65.11 294.28,67.53C294.65,69.94 295.98,72.11 297.96,73.56Z"
- android:fillColor="#669DF6"/>
- <path
- android:pathData="M295.71,77.78C287.35,71.94 278.5,69.93 272.37,77.85L302.63,98.92C308.76,91 304.07,83.58 295.71,77.78Z"
- android:fillColor="#669DF6"/>
- <path
- android:pathData="M84.13,50.89L47.87,55.78C45.37,56.11 43.63,58.4 43.96,60.89L48.85,97.15C49.18,99.65 51.47,101.39 53.97,101.06L90.23,96.17C92.72,95.84 94.47,93.55 94.13,91.05L89.25,54.79C88.91,52.3 86.62,50.55 84.13,50.89Z"
- android:fillColor="#E9D2FD"/>
- <path
- android:pathData="M69.64,76.29C71.46,76.08 73.18,75.35 74.58,74.17C75.99,72.99 77.01,71.42 77.53,69.66C78.05,67.91 78.03,66.04 77.49,64.3C76.94,62.56 75.89,61.02 74.47,59.88C73.05,58.74 71.32,58.05 69.49,57.9C67.67,57.75 65.84,58.14 64.24,59.02C62.63,59.91 61.32,61.24 60.47,62.87C59.62,64.49 59.27,66.33 59.47,68.15C59.73,70.58 60.94,72.81 62.85,74.33C64.76,75.86 67.2,76.56 69.64,76.29Z"
- android:fillColor="#C58AF9"/>
- <path
- android:pathData="M70.84,80.92C60.73,82.28 52.85,86.78 53.69,96.76L90.23,91.82C89.39,81.84 80.92,79.54 70.84,80.92Z"
- android:fillColor="#C58AF9"/>
- <path
- android:pathData="M39.14,170.33L66.27,160.64A11.49,11.49 115.34,0 1,80.95 167.59L89.91,192.65A11.49,11.49 115.34,0 1,82.96 207.33L55.82,217.03A11.49,11.49 115.34,0 1,41.14 210.07L32.19,185.01A11.49,11.49 115.34,0 1,39.14 170.33z"
- android:fillColor="#E8F0FE"/>
- <path
- android:pathData="M66.57,172.29a4.81,4.58 70.34,1 0,8.63 -3.08a4.81,4.58 70.34,1 0,-8.63 3.08z"
- android:fillColor="#669DF6"/>
- <group>
- <clip-path
- android:pathData="M39.14,170.33L66.27,160.64A11.49,11.49 115.34,0 1,80.95 167.59L89.91,192.65A11.49,11.49 115.34,0 1,82.96 207.33L55.82,217.03A11.49,11.49 115.34,0 1,41.14 210.07L32.19,185.01A11.49,11.49 115.34,0 1,39.14 170.33z"/>
- <path
- android:pathData="M57.86,186.51C53.39,183.04 46.83,185.22 45.32,190.67L39.9,210.23C38.18,216.44 44.17,222.01 50.24,219.85L72.6,211.86C72.78,211.79 72.95,211.72 73.12,211.65C73.31,211.59 73.49,211.54 73.68,211.47L88.87,206.04C95.09,203.82 96.05,195.42 90.49,191.86L79.38,184.73C74.99,181.92 69.13,184.01 67.52,188.97L66.21,193L57.86,186.51Z"
- android:fillColor="#669DF6"
- android:fillType="evenOdd"/>
- </group>
- <path
- android:pathData="M297.35,169.44L324.72,178.44A11.49,11.49 63.19,0 1,332.04 192.93L323.74,218.22A11.49,11.49 63.19,0 1,309.24 225.54L281.87,216.55A11.49,11.49 63.19,0 1,274.54 202.05L282.85,176.77A11.49,11.49 63.19,0 1,297.35 169.44z"
- android:fillColor="#E9D2FD"/>
- <path
- android:pathData="M317.81,187.82a4.81,4.58 108.19,1 0,8.71 2.86a4.81,4.58 108.19,1 0,-8.71 -2.86z"
- android:fillColor="#C58AF9"/>
- <group>
- <clip-path
- android:pathData="M297.35,169.44L324.72,178.44A11.49,11.49 63.19,0 1,332.04 192.93L323.74,218.22A11.49,11.49 63.19,0 1,309.24 225.54L281.87,216.55A11.49,11.49 63.19,0 1,274.54 202.05L282.85,176.77A11.49,11.49 63.19,0 1,297.35 169.44z"/>
- <path
- android:pathData="M302.2,193.71C300.8,188.23 294.29,185.92 289.75,189.3L273.46,201.42C268.3,205.27 269.61,213.34 275.73,215.35L298.28,222.76C298.46,222.82 298.64,222.87 298.83,222.92C299.01,222.99 299.19,223.06 299.38,223.12L314.71,228.15C320.98,230.22 326.89,224.17 324.68,217.95L320.28,205.5C318.54,200.59 312.63,198.65 308.32,201.57L304.82,203.95L302.2,193.71Z"
- android:fillColor="#C58AF9"
- android:fillType="evenOdd"/>
- </group>
- </group>
- <path
- android:pathData="M157.77,52.17L131.31,64.89C130.6,65.23 129.79,65.27 129.04,65.01C128.3,64.75 127.69,64.21 127.36,63.5L126.91,62.58C124.89,58.37 124.62,53.52 126.17,49.1C127.72,44.69 130.96,41.07 135.17,39.04C139.39,37.01 144.24,36.74 148.66,38.29C153.07,39.84 156.69,43.08 158.72,47.3L159.16,48.21C159.5,48.92 159.54,49.74 159.28,50.48C159.02,51.22 158.48,51.83 157.77,52.17Z"
- android:fillColor="#669DF6"/>
- <path
- android:pathData="M200.49,106.81C200.49,101.46 205.07,96.96 210.92,96.96C216.77,96.96 221.36,101.46 221.36,106.81V121.37H229.71V106.81C229.71,96.79 221.21,88.83 210.92,88.83C200.64,88.83 192.14,96.79 192.14,106.81V117.3H164.58C158.59,117.3 153.73,122.03 153.73,127.87V160.41C153.73,166.26 158.59,170.99 164.58,170.99H203.82C209.82,170.99 214.68,166.26 214.68,160.41V127.87C214.68,122.03 209.82,117.3 203.82,117.3H200.49V106.81ZM164.58,125.43C163.2,125.43 162.08,126.53 162.08,127.87V160.41C162.08,161.76 163.2,162.85 164.58,162.85H203.82C205.21,162.85 206.33,161.76 206.33,160.41V127.87C206.33,126.53 205.21,125.43 203.82,125.43H164.58ZM184.2,151.47C188.35,151.47 191.72,148.19 191.72,144.14C191.72,140.1 188.35,136.82 184.2,136.82C180.05,136.82 176.69,140.1 176.69,144.14C176.69,148.19 180.05,151.47 184.2,151.47Z"
- android:fillColor="#C58AF9"
- android:fillType="evenOdd"/>
- <path
- android:pathData="M163.56,182.48C168.99,182.48 173.39,186.88 173.39,192.31C173.39,197.73 168.99,202.14 163.56,202.14C158.13,202.14 153.73,197.73 153.73,192.31C153.73,186.88 158.13,182.48 163.56,182.48Z"
- android:fillColor="#5F6368"/>
- <path
- android:pathData="M201.15,182.48C206.58,182.48 210.98,186.88 210.98,192.31C210.98,197.73 206.58,202.14 201.15,202.14C195.71,202.14 191.32,197.73 191.32,192.31C191.32,186.88 195.71,182.48 201.15,182.48Z"
- android:fillColor="#C58AF9"/>
-</vector>
diff --git a/res/drawable/privatespace_placeholder_image.xml b/res/drawable/privatespace_placeholder_image.xml
deleted file mode 100644
index cde503a..0000000
--- a/res/drawable/privatespace_placeholder_image.xml
+++ /dev/null
@@ -1,143 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="364dp"
- android:height="265dp"
- android:viewportWidth="364"
- android:viewportHeight="265">
- <group>
- <clip-path
- android:pathData="M0,0h364v265h-364z"/>
- <path
- android:pathData="M339.42,265H24.58C11.07,265 0,253.67 0,239.84V25.19C0,11.33 11.07,0 24.58,0H339.52C352.93,0 364,11.33 364,25.16V239.94C364,253.67 352.93,265 339.42,265Z"
- android:fillColor="#000000"/>
- <path
- android:pathData="M247.65,93.87V85.83C247.65,84.73 246.73,83.82 245.61,83.82V41.94C245.61,36.42 241.02,32 235.5,32H142.1C136.49,32 132,36.52 132,41.94V233.67C132,239.2 136.59,243.62 142.1,243.62H235.5C241.12,243.62 245.61,239.1 245.61,233.67V135.85C246.73,135.85 247.65,134.95 247.65,133.84V113.86C247.65,112.75 246.73,111.85 245.61,111.85V95.88C246.84,95.88 247.65,94.97 247.65,93.87ZM243.67,233.67C243.67,238.09 240,241.71 235.5,241.71H142.1C137.61,241.71 133.94,238.09 133.94,233.67V41.94C133.94,37.52 137.61,33.91 142.1,33.91H235.5C240,33.91 243.67,37.52 243.67,41.94V233.67Z"
- android:fillColor="#DADCE0"/>
- <path
- android:pathData="M151.68,46.76L225.5,46.76A7.38,7.38 0,0 1,232.89 54.15L232.89,54.15A7.38,7.38 0,0 1,225.5 61.53L151.68,61.53A7.38,7.38 0,0 1,144.3 54.15L144.3,54.15A7.38,7.38 0,0 1,151.68 46.76z"
- android:fillColor="#444746"/>
- <path
- android:pathData="M147.42,187.02L228.54,187.02A8.04,8.04 0,0 1,236.58 195.06L236.58,226.97A8.04,8.04 0,0 1,228.54 235L147.42,235A8.04,8.04 0,0 1,139.38 226.97L139.38,195.06A8.04,8.04 0,0 1,147.42 187.02z"
- android:fillColor="#1F1F1F"/>
- <path
- android:pathData="M154.15,77.52m-7.38,0a7.38,7.38 0,1 1,14.76 0a7.38,7.38 0,1 1,-14.76 0"
- android:fillColor="#D9D9D9"
- android:fillAlpha="0.4"/>
- <path
- android:pathData="M177.52,77.52m-7.38,0a7.38,7.38 0,1 1,14.76 0a7.38,7.38 0,1 1,-14.76 0"
- android:fillColor="#D9D9D9"
- android:fillAlpha="0.4"/>
- <path
- android:pathData="M200.9,77.52m-7.38,0a7.38,7.38 0,1 1,14.76 0a7.38,7.38 0,1 1,-14.76 0"
- android:fillColor="#D9D9D9"
- android:fillAlpha="0.4"/>
- <path
- android:pathData="M224.27,77.52m-7.38,0a7.38,7.38 0,1 1,14.76 0a7.38,7.38 0,1 1,-14.76 0"
- android:fillColor="#D9D9D9"
- android:fillAlpha="0.4"/>
- <path
- android:pathData="M154.15,99.67m-7.38,0a7.38,7.38 0,1 1,14.76 0a7.38,7.38 0,1 1,-14.76 0"
- android:fillColor="#D9D9D9"
- android:fillAlpha="0.4"/>
- <path
- android:pathData="M154.15,121.81m-7.38,0a7.38,7.38 0,1 1,14.76 0a7.38,7.38 0,1 1,-14.76 0"
- android:fillColor="#D9D9D9"
- android:fillAlpha="0.4"/>
- <path
- android:pathData="M154.15,143.96m-7.38,0a7.38,7.38 0,1 1,14.76 0a7.38,7.38 0,1 1,-14.76 0"
- android:fillColor="#D9D9D9"
- android:fillAlpha="0.4"/>
- <path
- android:pathData="M154.15,166.11m-7.38,0a7.38,7.38 0,1 1,14.76 0a7.38,7.38 0,1 1,-14.76 0"
- android:fillColor="#D9D9D9"
- android:fillAlpha="0.4"/>
- <path
- android:pathData="M177.52,99.67m-7.38,0a7.38,7.38 0,1 1,14.76 0a7.38,7.38 0,1 1,-14.76 0"
- android:fillColor="#D9D9D9"
- android:fillAlpha="0.4"/>
- <path
- android:pathData="M177.52,121.81m-7.38,0a7.38,7.38 0,1 1,14.76 0a7.38,7.38 0,1 1,-14.76 0"
- android:fillColor="#D9D9D9"
- android:fillAlpha="0.4"/>
- <path
- android:pathData="M177.52,143.96m-7.38,0a7.38,7.38 0,1 1,14.76 0a7.38,7.38 0,1 1,-14.76 0"
- android:fillColor="#D9D9D9"
- android:fillAlpha="0.4"/>
- <path
- android:pathData="M177.52,166.11m-7.38,0a7.38,7.38 0,1 1,14.76 0a7.38,7.38 0,1 1,-14.76 0"
- android:fillColor="#D9D9D9"
- android:fillAlpha="0.4"/>
- <path
- android:pathData="M200.9,99.67m-7.38,0a7.38,7.38 0,1 1,14.76 0a7.38,7.38 0,1 1,-14.76 0"
- android:fillColor="#D9D9D9"
- android:fillAlpha="0.4"/>
- <path
- android:pathData="M200.9,121.81m-7.38,0a7.38,7.38 0,1 1,14.76 0a7.38,7.38 0,1 1,-14.76 0"
- android:fillColor="#D9D9D9"
- android:fillAlpha="0.4"/>
- <path
- android:pathData="M200.9,143.96m-7.38,0a7.38,7.38 0,1 1,14.76 0a7.38,7.38 0,1 1,-14.76 0"
- android:fillColor="#D9D9D9"
- android:fillAlpha="0.4"/>
- <path
- android:pathData="M200.9,166.11m-7.38,0a7.38,7.38 0,1 1,14.76 0a7.38,7.38 0,1 1,-14.76 0"
- android:fillColor="#D9D9D9"
- android:fillAlpha="0.4"/>
- <path
- android:pathData="M224.27,99.67m-7.38,0a7.38,7.38 0,1 1,14.76 0a7.38,7.38 0,1 1,-14.76 0"
- android:fillColor="#D9D9D9"
- android:fillAlpha="0.4"/>
- <path
- android:pathData="M224.27,121.81m-7.38,0a7.38,7.38 0,1 1,14.76 0a7.38,7.38 0,1 1,-14.76 0"
- android:fillColor="#D9D9D9"
- android:fillAlpha="0.4"/>
- <path
- android:pathData="M224.27,143.96m-7.38,0a7.38,7.38 0,1 1,14.76 0a7.38,7.38 0,1 1,-14.76 0"
- android:fillColor="#D9D9D9"
- android:fillAlpha="0.4"/>
- <path
- android:pathData="M224.27,166.11m-7.38,0a7.38,7.38 0,1 1,14.76 0a7.38,7.38 0,1 1,-14.76 0"
- android:fillColor="#D9D9D9"
- android:fillAlpha="0.4"/>
- <path
- android:pathData="M154.15,199.32m-7.38,0a7.38,7.38 0,1 1,14.76 0a7.38,7.38 0,1 1,-14.76 0"
- android:fillColor="#0B57D0"/>
- <path
- android:pathData="M154.15,220.24m-7.38,0a7.38,7.38 0,1 1,14.76 0a7.38,7.38 0,1 1,-14.76 0"
- android:fillColor="#0B57D0"/>
- <path
- android:pathData="M177.52,199.32m-7.38,0a7.38,7.38 0,1 1,14.76 0a7.38,7.38 0,1 1,-14.76 0"
- android:fillColor="#0B57D0"/>
- <path
- android:pathData="M177.52,220.24m-7.38,0a7.38,7.38 0,1 1,14.76 0a7.38,7.38 0,1 1,-14.76 0"
- android:fillColor="#0B57D0"/>
- <path
- android:pathData="M200.9,199.32m-7.38,0a7.38,7.38 0,1 1,14.76 0a7.38,7.38 0,1 1,-14.76 0"
- android:fillColor="#0B57D0"/>
- <path
- android:pathData="M200.9,220.24m-7.38,0a7.38,7.38 0,1 1,14.76 0a7.38,7.38 0,1 1,-14.76 0"
- android:fillColor="#0B57D0"/>
- <path
- android:pathData="M224.27,199.32m-7.38,0a7.38,7.38 0,1 1,14.76 0a7.38,7.38 0,1 1,-14.76 0"
- android:fillColor="#0B57D0"/>
- <path
- android:pathData="M224.27,220.24m-7.38,0a7.38,7.38 0,1 1,14.76 0a7.38,7.38 0,1 1,-14.76 0"
- android:fillColor="#0B57D0"/>
- </group>
-</vector>
diff --git a/res/layout/accessibility_color_contrast_preview.xml b/res/layout/accessibility_color_contrast_preview.xml
new file mode 100644
index 0000000..2646709
--- /dev/null
+++ b/res/layout/accessibility_color_contrast_preview.xml
@@ -0,0 +1,251 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 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.
+ -->
+
+<androidx.constraintlayout.widget.ConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="24dp"
+ android:paddingRight="24dp"
+ android:paddingBottom="24dp"
+ android:background="@drawable/color_contrast_preview_background">
+
+ <View
+ android:id="@+id/preview_background"
+ android:background="?androidprv:attr/materialColorSurface"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+
+ app:layout_constraintBottom_toTopOf="@+id/bottom_appbar_background"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+
+ <TextView
+ android:id="@+id/preview"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:layout_margin="8dp"
+ android:textSize="14sp"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintRight_toRightOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ android:textColor="?androidprv:attr/textColorPrimary"
+ android:text="@string/color_contrast_preview" />
+
+ <View
+ android:id="@+id/email_background"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_marginLeft="12dp"
+ android:layout_marginRight="12dp"
+ android:layout_marginTop="8dp"
+ android:layout_marginBottom="24dp"
+ android:background="@drawable/color_contrast_preview_dialog_background"
+ app:layout_constraintBottom_toTopOf="@+id/bottom_appbar_background"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/preview" />
+
+ <ImageView
+ android:id="@+id/ic_group"
+ android:contentDescription="@null"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="14dp"
+ android:layout_marginLeft="14dp"
+ android:padding="8dp"
+ android:src="@drawable/ic_group_24dp"
+ android:background="@drawable/color_contrast_preview_icon_group_background"
+ app:layout_constraintStart_toStartOf="@+id/email_background"
+ app:layout_constraintTop_toTopOf="@+id/email_background" />
+
+ <TextView
+ android:id="@+id/sender_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="8dp"
+ android:layout_marginTop="17dp"
+ android:textColor="?androidprv:attr/materialColorOnSurface"
+ android:textSize="12sp"
+ app:layout_constraintStart_toEndOf="@+id/ic_group"
+ app:layout_constraintTop_toTopOf="@+id/email_background"
+ android:text="@string/color_contrast_preview_sender_name" />
+
+ <TextView
+ android:id="@+id/date"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="?androidprv:attr/materialColorOnSurfaceVariant"
+ android:textSize="11sp"
+ app:layout_constraintStart_toStartOf="@+id/sender_name"
+ app:layout_constraintTop_toBottomOf="@+id/sender_name"
+ android:text="@string/color_contrast_preview_email_send_date" />
+
+ <TextView
+ android:id="@+id/email_tag"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="10dp"
+ android:textColor="?androidprv:attr/materialColorOnSurface"
+ android:background="@drawable/color_contrast_preview_tag_background"
+ android:textSize="11sp"
+ app:layout_constraintStart_toEndOf="@+id/sender_name"
+ app:layout_constraintTop_toTopOf="@+id/sender_name"
+ android:text="@string/color_contrast_preview_email_badge" />
+
+ <ImageView
+ android:id="@+id/ic_star"
+ android:contentDescription="@null"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="14dp"
+ android:layout_marginRight="14dp"
+ android:padding="8dp"
+ android:src="@drawable/ic_star_24dp"
+ android:background="@drawable/color_contrast_preview_icon_star_background"
+ app:layout_constraintEnd_toEndOf="@+id/email_background"
+ app:layout_constraintTop_toTopOf="@+id/email_background" />
+
+ <TextView
+ android:id="@+id/email_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="14dp"
+ android:layout_marginTop="62dp"
+ android:textColor="?androidprv:attr/materialColorOnSurface"
+ android:textSize="16sp"
+ app:layout_constraintStart_toStartOf="@+id/email_background"
+ app:layout_constraintTop_toTopOf="@+id/email_background"
+ android:text="@string/color_contrast_preview_email_title" />
+
+ <TextView
+ android:id="@+id/email_body"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="?androidprv:attr/materialColorOnSurfaceVariant"
+ android:textSize="12sp"
+ android:paddingBottom="8dp"
+ android:maxLines="2"
+ app:layout_constraintStart_toStartOf="@+id/email_title"
+ app:layout_constraintTop_toBottomOf="@+id/email_title"
+ android:text="@string/color_contrast_preview_email_body" />
+
+ <View
+ android:id="@+id/attachment_background"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:background="@drawable/color_contrast_preview_button_background"
+ app:layout_constraintBottom_toBottomOf="@+id/ic_article_filled"
+ app:layout_constraintEnd_toEndOf="@+id/email_attachment"
+ app:layout_constraintStart_toStartOf="@+id/email_title"
+ app:layout_constraintTop_toBottomOf="@+id/email_body" />
+
+ <ImageView
+ android:id="@+id/ic_article_filled"
+ android:contentDescription="@null"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_article_filled_24dp"
+ android:padding="6dp"
+ app:layout_constraintStart_toStartOf="@+id/email_title"
+ app:layout_constraintTop_toBottomOf="@+id/email_body" />
+
+ <TextView
+ android:id="@+id/email_attachment"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingRight="8dp"
+ android:textColor="?androidprv:attr/materialColorOnTertiaryContainer"
+ android:textSize="12sp"
+ app:layout_constraintStart_toEndOf="@+id/ic_article_filled"
+ app:layout_constraintTop_toTopOf="@+id/attachment_background"
+ app:layout_constraintBottom_toBottomOf="@+id/attachment_background"
+ android:text="@string/color_contrast_preview_email_attachment_name" />
+
+ <ImageView
+ android:id="@+id/ic_edit"
+ android:contentDescription="@null"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="48dp"
+ android:padding="27dp"
+ android:src="@drawable/ic_edit_24dp"
+ android:background="@drawable/color_contrast_preview_icon_edit_background"
+ app:layout_constraintEnd_toEndOf="@+id/email_background"
+ app:layout_constraintTop_toBottomOf="@+id/ic_star" />
+
+ <View
+ android:id="@+id/bottom_appbar_background"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:background="@drawable/color_contrast_preview_bottom_appbar_background"
+ app:layout_constraintBottom_toBottomOf="@+id/ic_inbox"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="@+id/ic_inbox" />
+
+ <ImageView
+ android:contentDescription="@null"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/color_contrast_preview_icon_inbox_background"
+ app:layout_constraintBottom_toBottomOf="@+id/ic_inbox"
+ app:layout_constraintEnd_toEndOf="@+id/ic_inbox"
+ app:layout_constraintStart_toStartOf="@+id/ic_inbox"
+ app:layout_constraintTop_toTopOf="@+id/ic_inbox" />
+
+ <ImageView
+ android:id="@+id/ic_inbox"
+ android:contentDescription="@null"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="42dp"
+ android:padding="24dp"
+ android:src="@drawable/ic_inbox_24dp"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@+id/ic_article"
+ app:layout_constraintTop_toBottomOf="@+id/ic_article_filled" />
+
+ <ImageView
+ android:id="@+id/ic_article"
+ android:contentDescription="@null"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:padding="24dp"
+ android:src="@drawable/ic_article_24dp"
+ app:layout_constraintStart_toEndOf="@+id/ic_inbox"
+ app:layout_constraintEnd_toStartOf="@+id/ic_chat_bubble"
+ app:layout_constraintTop_toTopOf="@id/ic_inbox"
+ app:layout_constraintBottom_toBottomOf="@id/ic_inbox" />
+
+ <ImageView
+ android:id="@+id/ic_chat_bubble"
+ android:contentDescription="@null"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:padding="24dp"
+ android:src="@drawable/ic_chat_bubble_24dp"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/ic_article"
+ app:layout_constraintTop_toTopOf="@id/ic_inbox"
+ app:layout_constraintBottom_toBottomOf="@id/ic_inbox" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/res/layout/accessibility_color_contrast_selector.xml b/res/layout/accessibility_color_contrast_selector.xml
new file mode 100644
index 0000000..f7ba28b
--- /dev/null
+++ b/res/layout/accessibility_color_contrast_selector.xml
@@ -0,0 +1,147 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 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"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingVertical="@dimen/settingslib_illustration_padding"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart">
+
+ <include layout="@layout/accessibility_color_contrast_preview" />
+ </FrameLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"/>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <FrameLayout
+ android:id="@+id/contrast_button_default"
+ android:layout_width="@dimen/contrast_button_total_size"
+ android:layout_height="@dimen/contrast_button_total_size"
+ android:background="@drawable/accessibility_contrast_button_background">
+
+ <ImageView
+ android:layout_gravity="center"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:contentDescription="@string/contrast_default"
+ android:src="@drawable/ic_contrast_standard"/>
+ </FrameLayout>
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/contrast_button_text_spacing"
+ android:gravity="center_horizontal|top"
+ android:textSize="@dimen/contrast_button_text_size"
+ android:text="@string/contrast_default"
+ android:textColor="?androidprv:attr/materialColorOnSurface"/>
+ </LinearLayout>
+
+ <Space
+ android:layout_width="@dimen/contrast_button_horizontal_spacing"
+ android:layout_height="match_parent" />
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <FrameLayout
+ android:id="@+id/contrast_button_medium"
+ android:layout_width="@dimen/contrast_button_total_size"
+ android:layout_height="@dimen/contrast_button_total_size"
+ android:background="@drawable/accessibility_contrast_button_background">
+
+ <ImageView
+ android:layout_gravity="center"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:contentDescription="@string/contrast_medium"
+ android:src="@drawable/ic_contrast_medium"/>
+ </FrameLayout>
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/contrast_button_text_spacing"
+ android:gravity="center_horizontal|top"
+ android:textSize="@dimen/contrast_button_text_size"
+ android:text="@string/contrast_medium"
+ android:textColor="?androidprv:attr/materialColorOnSurface"/>
+ </LinearLayout>
+
+ <Space
+ android:layout_width="@dimen/contrast_button_horizontal_spacing"
+ android:layout_height="match_parent" />
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <FrameLayout
+ android:id="@+id/contrast_button_high"
+ android:layout_width="@dimen/contrast_button_total_size"
+ android:layout_height="@dimen/contrast_button_total_size"
+ android:background="@drawable/accessibility_contrast_button_background">
+
+ <ImageView
+ android:layout_gravity="center"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:contentDescription="@string/contrast_high"
+ android:src="@drawable/ic_contrast_high"/>
+ </FrameLayout>
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/contrast_button_text_spacing"
+ android:gravity="center_horizontal|top"
+ android:textSize="@dimen/contrast_button_text_size"
+ android:text="@string/contrast_high"
+ android:textColor="?androidprv:attr/materialColorOnSurface"/>
+ </LinearLayout>
+
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"/>
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/res/layout/audio_sharing_device_item.xml b/res/layout/audio_sharing_device_item.xml
deleted file mode 100644
index 04ecdd7..0000000
--- a/res/layout/audio_sharing_device_item.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 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.
- -->
-
-<FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
-
- <Button
- android:id="@+id/device_button"
- style="@style/SettingsLibActionButton"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="4dp"
- android:background="@drawable/audio_sharing_rounded_bg_ripple"
- android:textAlignment="center" />
-
-</FrameLayout>
\ No newline at end of file
diff --git a/res/layout/dialog_audio_sharing.xml b/res/layout/dialog_audio_sharing.xml
deleted file mode 100644
index aace5ab..0000000
--- a/res/layout/dialog_audio_sharing.xml
+++ /dev/null
@@ -1,82 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 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="wrap_content"
- android:orientation="vertical"
- android:paddingHorizontal="?android:dialogPreferredPadding"
- android:paddingBottom="?android:dialogPreferredPadding">
-
- <TextView
- android:id="@+id/share_audio_subtitle1"
- style="@style/DeviceAudioSharingText"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:paddingBottom="14dp"
- android:textFontWeight="500"
- android:visibility="gone" />
-
- <TextView
- android:id="@+id/share_audio_subtitle2"
- style="@style/DeviceAudioSharingText"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:paddingBottom="24dp"
- android:textFontWeight="400" />
-
- <ImageView
- android:id="@+id/share_audio_guidance"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:src="@drawable/audio_sharing_guidance"
- android:visibility="gone" />
-
- <androidx.recyclerview.widget.RecyclerView
- android:id="@+id/btn_list"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:nestedScrollingEnabled="false"
- android:overScrollMode="never"
- android:visibility="gone" />
-
- <Button
- android:id="@+id/share_btn"
- style="@style/SettingsLibActionButton"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:layout_marginTop="4dp"
- android:background="@drawable/audio_sharing_rounded_bg_ripple"
- android:visibility="gone" />
-
- <Button
- android:id="@+id/cancel_btn"
- style="@style/SettingsLibActionButton"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:layout_marginTop="4dp"
- android:background="@drawable/audio_sharing_rounded_bg_ripple"
- android:text="Not now"
- android:visibility="gone" />
-</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/dialog_audio_sharing_disconnect.xml b/res/layout/dialog_audio_sharing_disconnect.xml
deleted file mode 100644
index 592b41b..0000000
--- a/res/layout/dialog_audio_sharing_disconnect.xml
+++ /dev/null
@@ -1,51 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 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="wrap_content"
- android:orientation="vertical"
- android:paddingHorizontal="?android:dialogPreferredPadding"
- android:paddingBottom="?android:dialogPreferredPadding">
-
- <TextView
- android:id="@+id/share_audio_disconnect_description"
- style="@style/DeviceAudioSharingText"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:paddingBottom="24dp" />
-
- <androidx.recyclerview.widget.RecyclerView
- android:id="@+id/device_btn_list"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:nestedScrollingEnabled="false"
- android:overScrollMode="never" />
-
- <Button
- android:id="@+id/cancel_btn"
- style="@style/SettingsLibActionButton"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:layout_marginTop="4dp"
- android:background="@drawable/audio_sharing_rounded_bg_ripple"
- android:text="@string/cancel" />
-</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/dialog_audio_sharing_join.xml b/res/layout/dialog_audio_sharing_join.xml
deleted file mode 100644
index bfd4c77..0000000
--- a/res/layout/dialog_audio_sharing_join.xml
+++ /dev/null
@@ -1,53 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 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="wrap_content"
- android:orientation="vertical"
- android:paddingHorizontal="?android:dialogPreferredPadding"
- android:paddingBottom="?android:dialogPreferredPadding">
-
- <TextView
- android:id="@+id/share_audio_subtitle"
- style="@style/DeviceAudioSharingText"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:paddingBottom="24dp"
- android:textFontWeight="400" />
-
- <Button
- android:id="@+id/share_btn"
- style="@style/SettingsLibActionButton"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:layout_marginTop="4dp"
- android:background="@drawable/audio_sharing_rounded_bg_ripple" />
-
- <Button
- android:id="@+id/cancel_btn"
- style="@style/SettingsLibActionButton"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:layout_marginTop="4dp"
- android:background="@drawable/audio_sharing_rounded_bg_ripple"
- android:text="Not now" />
-</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/dialog_custom_title_audio_sharing.xml b/res/layout/dialog_custom_title_audio_sharing.xml
deleted file mode 100644
index 0513c4b..0000000
--- a/res/layout/dialog_custom_title_audio_sharing.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 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="wrap_content"
- android:orientation="vertical"
- android:padding="?android:dialogPreferredPadding">
-
- <ImageView
- android:id="@+id/title_icon"
- android:layout_width="32dp"
- android:layout_height="32dp"
- android:layout_gravity="center"
- android:tint="?android:attr/colorControlNormal" />
-
- <TextView
- android:id="@+id/title_text"
- style="?android:attr/windowTitleStyle"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:maxLines="2"
- android:paddingTop="14dp"
- android:textAlignment="center"
- android:textSize="24sp" />
-</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/preference_background.xml b/res/layout/preference_background.xml
new file mode 100644
index 0000000..129076a
--- /dev/null
+++ b/res/layout/preference_background.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ Copyright (C) 2024 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.
+ -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingVertical="@dimen/settingslib_switchbar_margin"
+ android:background="@android:color/transparent">
+
+ <LinearLayout
+ android:id="@+id/background"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:paddingStart="@dimen/settingslib_switchbar_padding_left"
+ android:paddingEnd="@dimen/settingslib_switchbar_padding_right"
+ android:orientation="horizontal"
+ android:gravity="start|center_vertical"
+ android:baselineAligned="false">
+
+ <FrameLayout
+ android:id="@+id/icon_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="15dip"
+ android:layout_marginRight="15dip">
+ <androidx.preference.internal.PreferenceImageView
+ android:id="@android:id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:maxWidth="48dp"
+ app:maxHeight="48dp" />
+ </FrameLayout>
+
+ <RelativeLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="6dip"
+ android:layout_marginRight="6dip"
+ android:layout_marginTop="@dimen/settingslib_switch_title_margin"
+ android:layout_marginBottom="@dimen/settingslib_switch_title_margin"
+ android:layout_weight="1">
+
+ <TextView android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceListItem"
+ android:textColor="?android:attr/textColorPrimary"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase" />
+
+ <TextView android:id="@android:id/summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@android:id/title"
+ android:layout_alignStart="@android:id/title"
+ android:layout_alignLeft="@android:id/title"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/textColorSecondary"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:maxLines="4"
+ android:scrollbars="none"/>
+ </RelativeLayout>
+
+ <!-- Preference should place its actual preference widget here. -->
+ <LinearLayout android:id="@android:id/widget_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:orientation="vertical" />
+ </LinearLayout>
+
+</FrameLayout>
\ No newline at end of file
diff --git a/res/layout/preference_widget_arrow.xml b/res/layout/preference_widget_arrow.xml
new file mode 100644
index 0000000..ddeb669
--- /dev/null
+++ b/res/layout/preference_widget_arrow.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 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.
+-->
+
+<ImageView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_vertical"
+ android:contentDescription="@null"
+ android:scaleType="center"
+ android:src="@drawable/ic_arrow_forward" />
\ No newline at end of file
diff --git a/res/layout/private_space_advancing_screen.xml b/res/layout/private_space_advancing_screen.xml
index 35d6462..b3bb26c 100644
--- a/res/layout/private_space_advancing_screen.xml
+++ b/res/layout/private_space_advancing_screen.xml
@@ -25,13 +25,13 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
- <ImageView
- android:id="@+id/setup_advance_image"
+ <com.airbnb.lottie.LottieAnimationView
+ android:id="@+id/lottie_animation"
+ style="@style/SudContentIllustration"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
- android:contentDescription="@null"
- android:src="@null"/>
+ app:lottie_rawRes="@null"/>
<TextView
android:id="@+id/createMessage"
diff --git a/res/layout/private_space_education_screen.xml b/res/layout/private_space_education_screen.xml
index 0f57e1e..8fb486e 100644
--- a/res/layout/private_space_education_screen.xml
+++ b/res/layout/private_space_education_screen.xml
@@ -33,38 +33,40 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
- <ImageView
- android:id="@+id/setup_education_image"
+ <com.airbnb.lottie.LottieAnimationView
+ android:id="@+id/lottie_animation"
style="@style/SudContentIllustration"
android:layout_width="match_parent"
android:layout_marginLeft="24dp"
android:layout_marginRight="24dp"
- android:layout_height="wrap_content"
- android:src="@drawable/private_space_illustration"/>
+ android:layout_height="match_parent"
+ app:lottie_autoPlay="true"
+ app:lottie_loop="true"
+ app:lottie_rawRes="@raw/private_space_illustration"/>
<TextView
style="@style/PrivateSpaceSetupSubHeaderStyle"
- android:text="@string/private_space_how_title"/>
+ android:text="@string/private_space_setup_sub_header"/>
<RelativeLayout
style="@style/PrivateSpaceSetupBulletPointLayoutStyle">
<ImageView
android:id="@+id/lockIcon"
style="@style/PrivateSpaceBulletPointIconStyle"
- android:src="@drawable/ic_lock_closed" />
+ android:src="@drawable/counter_1_24dp" />
<TextView
style="@style/PrivateSpaceBulletPointTextFontStyle"
android:layout_toRightOf="@+id/lockIcon"
- android:text="@string/private_space_protected_lock_text"/>
+ android:text="@string/private_space_separate_account_text"/>
</RelativeLayout>
<RelativeLayout
style="@style/PrivateSpaceSetupBulletPointLayoutStyle">
<ImageView
android:id="@+id/bellIcon"
style="@style/PrivateSpaceBulletPointIconStyle"
- android:src="@drawable/ic_notifications" />
+ android:src="@drawable/counter_2_24dp" />
<TextView
style="@style/PrivateSpaceBulletPointTextFontStyle"
android:layout_toRightOf="@+id/bellIcon"
- android:text="@string/private_space_hidden_notifications_text"/>
+ android:text="@string/private_space_protected_lock_text"/>
</RelativeLayout>
<RelativeLayout
style="@style/PrivateSpaceSetupBulletPointLayoutStyle"
@@ -73,11 +75,11 @@
<ImageView
android:id="@+id/appsIcon"
style="@style/PrivateSpaceBulletPointIconStyle"
- android:src="@drawable/ic_apps" />
+ android:src="@drawable/counter_3_24dp" />
<TextView
style="@style/PrivateSpaceBulletPointTextFontStyle"
android:layout_toRightOf="@+id/appsIcon"
- android:text="@string/private_space_access_bottom_text"/>
+ android:text="@string/private_space_install_apps_text"/>
</RelativeLayout>
<Space
android:layout_width="wrap_content"
diff --git a/res/layout/private_space_gaia_education_screen.xml b/res/layout/private_space_gaia_education_screen.xml
new file mode 100644
index 0000000..e5c0f85
--- /dev/null
+++ b/res/layout/private_space_gaia_education_screen.xml
@@ -0,0 +1,111 @@
+<!--
+ ~ Copyright (C) 2024 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.
+ -->
+
+<com.google.android.setupdesign.GlifLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/ps_account_intro_screen"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:filterTouchesWhenObscured="true">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <ImageView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="24dp"
+ android:layout_marginRight="24dp"
+ android:contentDescription="@null"
+ android:src="@drawable/ic_security_privacy_alert_primary"/>
+ <TextView
+ style="@style/PrivateSpaceSetupTextFontStyle"
+ android:fontFamily="google-sans"
+ android:text="@string/private_space_gaia_education_title"
+ android:layout_margin="24dp"
+ android:layout_marginTop="40dp"
+ android:gravity="center"
+ android:textSize="28sp"/>
+ <TextView
+ style="@style/PrivateSpaceSetupTextFontStyle"
+ android:layout_marginLeft="24dp"
+ android:layout_marginRight="24dp"
+ android:layout_marginBottom="40dp"
+ android:textSize="14sp"
+ android:text="@string/private_space_gaia_education_description"/>
+ <TextView
+ style="@style/PrivateSpaceSetupTextFontStyle"
+ android:layout_marginLeft="24dp"
+ android:layout_marginRight="24dp"
+ android:layout_marginBottom="16dp"
+ android:textSize="14sp"
+ android:text="@string/private_space_gaia_education_header"/>
+ <RelativeLayout
+ style="@style/PrivateSpaceSetupBulletPointLayoutStyle"
+ android:paddingTop="8dp"
+ android:paddingBottom="8dp">
+ <ImageView
+ android:id="@+id/point1"
+ style="@style/PrivateSpaceBulletPointIconStyle"
+ android:contentDescription="@null"
+ android:src="@drawable/ic_text_dot" />
+ <TextView
+ style="@style/PrivateSpaceBulletPointTextFontStyle"
+ android:layout_toRightOf="@+id/point1"
+ android:textSize="14sp"
+ android:text="@string/private_space_gaia_education_bullet1"/>
+ </RelativeLayout>
+ <RelativeLayout
+ style="@style/PrivateSpaceSetupBulletPointLayoutStyle">
+ <ImageView
+ android:id="@+id/point2"
+ style="@style/PrivateSpaceBulletPointIconStyle"
+ android:contentDescription="@null"
+ android:src="@drawable/ic_text_dot" />
+ <TextView
+ style="@style/PrivateSpaceBulletPointTextFontStyle"
+ android:layout_toRightOf="@+id/point2"
+ android:textSize="14sp"
+ android:text="@string/private_space_gaia_education_bullet2"/>
+ </RelativeLayout>
+ <RelativeLayout
+ style="@style/PrivateSpaceSetupBulletPointLayoutStyle">
+ <ImageView
+ android:id="@+id/point3"
+ style="@style/PrivateSpaceBulletPointIconStyle"
+ android:contentDescription="@null"
+ android:src="@drawable/ic_text_dot" />
+ <TextView
+ style="@style/PrivateSpaceBulletPointTextFontStyle"
+ android:layout_toRightOf="@+id/point3"
+ android:textSize="14sp"
+ android:text="@string/private_space_gaia_education_bullet3"/>
+ </RelativeLayout>
+ <RelativeLayout
+ style="@style/PrivateSpaceSetupBulletPointLayoutStyle">
+ <ImageView
+ android:id="@+id/point4"
+ style="@style/PrivateSpaceBulletPointIconStyle"
+ android:contentDescription="@null"
+ android:src="@drawable/ic_text_dot" />
+ <TextView
+ style="@style/PrivateSpaceBulletPointTextFontStyle"
+ android:layout_toRightOf="@+id/point4"
+ android:textSize="14sp"
+ android:text="@string/private_space_gaia_education_bullet4"/>
+ </RelativeLayout>
+ </LinearLayout>
+</com.google.android.setupdesign.GlifLayout>
diff --git a/res/layout/private_space_setlock_screen.xml b/res/layout/private_space_setlock_screen.xml
index 64a2ff2..323c3a1 100644
--- a/res/layout/private_space_setlock_screen.xml
+++ b/res/layout/private_space_setlock_screen.xml
@@ -31,13 +31,14 @@
android:maxWidth="@dimen/animation_max_size"
android:maxHeight="@dimen/animation_max_size">
- <ImageView
- android:id="@+id/privatespace_lock_image"
+ <com.airbnb.lottie.LottieAnimationView
+ android:id="@+id/lottie_animation"
+ style="@style/SudContentIllustration"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:scaleType="fitCenter"
- android:contentDescription="@null"
- android:src="@drawable/private_space_choose_lock_illustration"/>
+ app:lottie_autoPlay="true"
+ app:lottie_loop="true"
+ app:lottie_rawRes="@raw/private_space_use_screen_lock_illustration"/>
</com.google.android.setupdesign.view.FillContentLayout>
</com.google.android.setupdesign.GlifLayout>
\ No newline at end of file
diff --git a/res/layout/private_space_setup_success.xml b/res/layout/private_space_setup_success.xml
index 759373f..676b87f 100644
--- a/res/layout/private_space_setup_success.xml
+++ b/res/layout/private_space_setup_success.xml
@@ -24,13 +24,21 @@
app:sucHeaderText="@string/private_space_success_title"
app:sudDescriptionText="@string/private_space_access_text"
android:icon="@drawable/ic_privatespace_done">
-
- <ImageView
- android:id="@+id/privatespace_success_image"
+ <com.google.android.setupdesign.view.FillContentLayout
+ style="@style/SudContentFrame"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_marginLeft="24dp"
- android:layout_marginRight="24dp"
- android:src="@drawable/private_space_illustration"/>
+ android:maxWidth="@dimen/animation_max_size"
+ android:maxHeight="@dimen/animation_max_size">
+
+ <com.airbnb.lottie.LottieAnimationView
+ android:id="@+id/lottie_animation"
+ style="@style/SudContentIllustration"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ app:lottie_autoPlay="true"
+ app:lottie_loop="true"
+ app:lottie_rawRes="@raw/private_space_illustration"/>
+ </com.google.android.setupdesign.view.FillContentLayout>
</com.google.android.setupdesign.GlifLayout>
diff --git a/res/layout/sim_warning_dialog_wifi_connectivity.xml b/res/layout/sim_warning_dialog_wifi_connectivity.xml
new file mode 100644
index 0000000..c6cdbc7
--- /dev/null
+++ b/res/layout/sim_warning_dialog_wifi_connectivity.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 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="wrap_content"
+ android:orientation="vertical">
+ <ImageView
+ android:src="@drawable/ic_warning_24dp"
+ android:contentDescription="@null"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="16dp"
+ android:gravity="center"
+ android:tint="?android:attr/textColorSecondary"/>
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingEnd="24dp"
+ android:paddingTop="16dp"
+ android:paddingLeft="24dp"
+ android:gravity="center"
+ style="?android:attr/textAppearanceLarge"/>
+
+ <TextView
+ android:id="@+id/msg"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingEnd="24dp"
+ android:paddingTop="16dp"
+ android:paddingStart="24dp"
+ android:paddingBottom="32dp"
+ android:gravity="center"
+ android:textAppearance="@style/TextAppearance.DialogMessage"
+ android:visibility="gone"/>
+</LinearLayout>
diff --git a/res/navigation/privatespace_main_context_nav.xml b/res/navigation/privatespace_main_context_nav.xml
index f76afac..80cd399 100644
--- a/res/navigation/privatespace_main_context_nav.xml
+++ b/res/navigation/privatespace_main_context_nav.xml
@@ -35,6 +35,9 @@
<action
android:id="@+id/action_set_lock_fragment"
app:destination="@id/ps_profile_lock_fragment"/>
+ <action
+ android:id="@+id/action_account_intro_fragment"
+ app:destination="@id/ps_account_intro_fragment"/>
</fragment>
<fragment android:id="@+id/ps_profile_error_fragment"
android:name="com.android.settings.privatespace.PrivateProfileCreationError"
@@ -51,15 +54,25 @@
android:label="fragment_account_error">
<action
android:id="@+id/action_advance_login_error"
- app:destination="@id/ps_account_error_fragment"/>
+ app:destination="@id/ps_account_intro_fragment"/>
<action
- android:id="@+id/action_success_fragment"
- app:destination="@id/ps_profile_success_fragment"/>
+ android:id="@+id/action_skip_account_login"
+ app:destination="@id/ps_profile_lock_fragment"/>
</fragment>
<fragment android:id="@+id/ps_profile_lock_fragment"
android:name="com.android.settings.privatespace.PrivateSpaceSetLockFragment"
android:label="fragment_ps_lock">
<action
+ android:id="@+id/action_lock_success_fragment"
+ app:destination="@id/ps_profile_success_fragment"/>
+ </fragment>
+ <fragment android:id="@+id/ps_account_intro_fragment"
+ android:name="com.android.settings.privatespace.PrivateSpaceGaiaEducationFragment"
+ android:label="fragment_ps_account_intro">
+ <action
+ android:id="@+id/action_account_lock_fragment"
+ app:destination="@id/ps_profile_lock_fragment"/>
+ <action
android:id="@+id/action_advance_login_error"
app:destination="@id/ps_account_error_fragment"/>
</fragment>
diff --git a/res/raw/private_space_hide_when_locked_illustration.json b/res/raw/private_space_hide_when_locked_illustration.json
new file mode 100644
index 0000000..b06a0b2
--- /dev/null
+++ b/res/raw/private_space_hide_when_locked_illustration.json
@@ -0,0 +1 @@
+{"v":"5.12.1","fr":60,"ip":0,"op":844,"w":412,"h":300,"nm":"Private_SpaceB_v1_export","ddd":0,"assets":[{"id":"comp_0","nm":"Private_SpaceB_v1_PreCrop","fr":60,"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"swipe down","refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":180,"ix":10},"p":{"a":0,"k":[340,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[130,130,100],"ix":6,"l":2}},"ao":0,"w":412,"h":300,"ip":678,"op":801,"st":213,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"press 3","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":573,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":583,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":618,"s":[100]},{"t":628,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[329.133,131.167,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.5,0.5,0.667],"y":[1,1,1]},"o":{"x":[0.5,0.5,0.333],"y":[0,0,0]},"t":598,"s":[130,130,100]},{"i":{"x":[0.5,0.5,0.667],"y":[1,1,1]},"o":{"x":[0.5,0.5,0.333],"y":[0,0,0]},"t":608,"s":[110,110,100]},{"t":618,"s":[130,130,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[56,56],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":67,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.101960785687,0.450980395079,0.909803926945,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.541176497936,0.705882370472,0.972549021244,1],"ix":4},"o":{"a":0,"k":50,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.167,"y":0.186},"t":573,"s":[-37,67],"to":[15,-7],"ti":[7,20]},{"t":598,"s":[0,0],"h":1}],"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":"Touch","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":573,"op":629,"st":368,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"press 2","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":275,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":285,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":320,"s":[100]},{"t":330,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[313.1,128.65,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.5,0.5,0.667],"y":[1,1,1]},"o":{"x":[0.5,0.5,0.333],"y":[0,0,0]},"t":300,"s":[130,130,100]},{"i":{"x":[0.5,0.5,0.667],"y":[1,1,1]},"o":{"x":[0.5,0.5,0.333],"y":[0,0,0]},"t":310,"s":[110,110,100]},{"t":320,"s":[130,130,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[56,56],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":67,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.101960785687,0.450980395079,0.909803926945,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.541176497936,0.705882370472,0.972549021244,1],"ix":4},"o":{"a":0,"k":50,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.167,"y":0.186},"t":275,"s":[-37,67],"to":[15,-7],"ti":[7,20]},{"t":300,"s":[0,0],"h":1}],"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":"Touch","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":275,"op":331,"st":70,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"press","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":23,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":33,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":68,"s":[100]},{"t":78,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[318,64,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.5,0.5,0.667],"y":[1,1,1]},"o":{"x":[0.5,0.5,0.333],"y":[0,0,0]},"t":48,"s":[130,130,100]},{"i":{"x":[0.5,0.5,0.667],"y":[1,1,1]},"o":{"x":[0.5,0.5,0.333],"y":[0,0,0]},"t":58,"s":[110,110,100]},{"t":68,"s":[130,130,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[56,56],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":67,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.101960785687,0.450980395079,0.909803926945,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.541176497936,0.705882370472,0.972549021244,1],"ix":4},"o":{"a":0,"k":50,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.167,"y":0.186},"t":23,"s":[-37,67],"to":[15,-7],"ti":[7,20]},{"t":48,"s":[0,0],"h":1}],"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":"Touch","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":23,"op":79,"st":-182,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"G","parent":7,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":322,"s":[100]},{"t":332,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-142,-99,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":[[-1.986,-0.84],[-2.258,0],[-2.884,2.88],[0,4.667],[0.068,0.493],[0.136,0.613],[0,0],[0,0],[0,0],[1.673,-1.267],[2.313,0],[1.905,1.907],[0,2.72],[-1.905,1.907],[-2.721,0],[-1.075,-0.413],[-0.844,-0.773],[0,0],[1.837,0.707],[2.068,0],[1.986,-0.84],[1.469,-1.44],[0.857,-1.947],[0,-2.213],[-0.857,-1.947],[-1.469,-1.44]],"o":[[1.986,0.84],[4.68,0],[2.884,-2.88],[0,-0.587],[-0.068,-0.493],[0,0],[0,0],[0,0],[-0.327,1.973],[-1.673,1.267],[-2.721,0],[-1.905,-1.907],[0,-2.72],[1.905,-1.907],[1.197,0],[1.075,0.413],[0,0],[-1.551,-1.36],[-1.837,-0.707],[-2.258,0],[-1.986,0.84],[-1.469,1.44],[-0.857,1.947],[0,2.213],[0.857,1.947],[1.469,1.44]],"v":[[-6.041,14.74],[0.327,16],[11.673,11.68],[16,0.36],[15.898,-1.26],[15.592,-2.92],[0.327,-2.92],[0.327,3.04],[9.306,3.04],[6.306,7.9],[0.327,9.8],[-6.612,6.94],[-9.469,0],[-6.612,-6.94],[0.327,-9.8],[3.735,-9.18],[6.612,-7.4],[11.265,-11.84],[6.184,-14.94],[0.327,-16],[-6.041,-14.74],[-11.224,-11.32],[-14.714,-6.24],[-16,0],[-14.714,6.24],[-11.224,11.32]],"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":[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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":844,"st":-2,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Rectangle 260863002","parent":7,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":322,"s":[100]},{"t":332,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-114,-113,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":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"t":116.896,"s":[20,29],"h":1},{"t":145.861,"s":[40,29],"h":1},{"t":167.586,"s":[60,29],"h":1},{"t":180.621,"s":[80,29],"h":1},{"t":190.758,"s":[100,29],"h":1},{"t":211,"s":[113,29],"h":1}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":13,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","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":[0,0],"ix":2},"a":{"k":[{"s":[-10,-14.5],"t":145,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-20,-14.5],"t":146,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-20,-14.5],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-30,-14.5],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-30,-14.5],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-40,-14.5],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-40,-14.5],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-50,-14.5],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-50,-14.5],"t":210,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-56.5,-14.5],"t":211,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"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 260863002","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":116,"op":844,"st":116,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":3,"nm":"ZOOM OUT","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":321,"s":[100]},{"t":331,"s":[0]}],"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":1,"k":[{"i":{"x":[0.168,0.168,0.667],"y":[1,1,1]},"o":{"x":[0.096,0.096,0.333],"y":[0,0,0]},"t":321,"s":[100,100,100]},{"t":361,"s":[80,80,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":844,"st":-2,"bm":0},{"ddd":0,"ind":8,"ty":3,"nm":"Search NULL","parent":7,"sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":58,"s":[0,199,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":64.666,"s":[0,115.4,0],"to":[0,0,0],"ti":[0,0,0]},{"t":98,"s":[0,-10,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":844,"st":-2,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"unlock 2","parent":10,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":322,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":332,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":610,"s":[100]},{"t":620,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[2.504,-16.523,0],"ix":2,"l":2},"a":{"a":0,"k":[2.504,-16.523,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,"y":1},"o":{"x":0.167,"y":0},"t":331,"s":[{"i":[[0,0],[0,0],[-1.355,2.927],[-3.702,1.026],[-0.166,-3.033],[0,-3.968],[0,0],[2.992,5.819],[4.706,-0.139],[2.517,-2.626],[0,-6.587],[0,0]],"o":[[0,0],[0,-3.968],[1.344,-1.62],[2.173,0.401],[0.169,3.094],[0,0],[0.17,-3.914],[-2.25,-2.779],[-4.648,0.137],[-4.408,4.835],[0,0],[0,0]],"v":[[7.105,-16.667],[7.105,-26.19],[8.647,-35.414],[12.565,-37.812],[16.291,-33.754],[17.305,-26.171],[25.315,-26.042],[22.307,-42.725],[13.076,-47.489],[2.552,-43.094],[-2.368,-26.19],[-2.368,-16.667]],"c":true}]},{"t":392,"s":[{"i":[[0,0],[0,0],[-2.763,2.778],[-3.947,0],[-2.763,-2.778],[0,-3.968],[0,0],[4.618,4.643],[6.553,0],[4.618,-4.643],[0,-6.587],[0,0]],"o":[[0,0],[0,-3.968],[2.763,-2.778],[3.947,0],[2.763,2.778],[0,0],[0,-6.587],[-4.618,-4.643],[-6.553,0],[-4.618,4.643],[0,0],[0,0]],"v":[[7.112,-15.167],[7.105,-26.19],[11.25,-36.31],[21.316,-40.476],[31.382,-36.31],[35.526,-26.19],[45,-26.19],[38.072,-43.036],[21.316,-50],[4.559,-43.036],[-2.368,-26.19],[-2.362,-15.167]],"c":true}]}],"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":429,"s":[0.909803926945,0.941176474094,0.996078431606,1]},{"t":439,"s":[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":[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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":151,"op":819,"st":151,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"unlock","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":321,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":331,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":610,"s":[100]},{"t":620,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":429,"s":[213.752,151.127,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.448},"t":439,"s":[269.851,188.327,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":471,"s":[354,244.127,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":479.334,"s":[348,197.327,0],"to":[0,0,0],"ti":[0,0,0]},{"t":521,"s":[339,127.127,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":321,"s":[58,58,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.714,0.714,0]},"t":331,"s":[74.8,74.8,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":382,"s":[100,100,100]},{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":429,"s":[100,100,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.448,0.448,0]},"t":439,"s":[72,72,100]},{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":471,"s":[30,30,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":479.334,"s":[34,34,100]},{"t":521,"s":[40,40,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.855,-1.865],[2.605,0],[1.855,1.865],[0,2.619],[-1.855,1.865],[-2.605,0],[-1.855,-1.865],[0,-2.619]],"o":[[-1.855,1.865],[-2.605,0],[-1.855,-1.865],[0,-2.619],[1.855,-1.865],[2.605,0],[1.855,1.865],[0,2.619]],"v":[[-0.414,23.393],[-7.105,26.19],[-13.796,23.393],[-16.579,16.667],[-13.796,9.94],[-7.105,7.143],[-0.414,9.94],[2.368,16.667]],"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":[[-1.855,-1.865],[-2.605,0],[0,0],[-1.855,1.865],[0,2.619],[0,0],[1.855,1.865],[2.605,0],[0,0],[1.855,-1.865],[0,-2.619],[0,0]],"o":[[1.855,1.865],[0,0],[2.605,0],[1.855,-1.865],[0,0],[0,-2.619],[-1.855,-1.865],[0,0],[-2.605,0],[-1.855,1.865],[0,0],[0,2.619]],"v":[[-42.217,47.202],[-35.526,50],[21.316,50],[28.007,47.202],[30.789,40.476],[30.789,-7.143],[28.007,-13.869],[21.316,-16.667],[-35.526,-16.667],[-42.217,-13.869],[-45,-7.143],[-45,40.476]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":429,"s":[0.909803926945,0.941176474094,0.996078431606,1]},{"t":439,"s":[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":[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":"Vector","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":151,"op":819,"st":151,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"blue_circle","parent":10,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":439,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":449,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":610,"s":[100]},{"t":620,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-8.756,10.1,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[332.547,332.547,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-12.32,0],[0,12.32],[12.32,0],[0,-12.32]],"o":[[12.32,0],[0,-12.32],[-12.32,0],[0,12.32]],"v":[[0,22.308],[22.308,0],[0,-22.308],[-22.308,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.823529422283,0.890196084976,0.988235294819,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 7653","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":414,"op":844,"st":456,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"MASK","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":471,"s":[204.212,215.847,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":479.334,"s":[204.212,158.247,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":0.1},"o":{"x":0.167,"y":0.167},"t":521,"s":[204.212,71.847,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":610,"s":[204.212,71.847,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":618.334,"s":[204.212,154.247,0],"to":[0,0,0],"ti":[0,0,0]},{"t":660,"s":[204.212,277.847,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-82.527,-143.957,0],"ix":1,"l":2},"s":{"a":0,"k":[253.623,322.07,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[202.945,64.328],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.005591273308,0.98464493097,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":[-82.527,-112.055],"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":654,"st":-14,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"APPS_PS","tt":1,"tp":13,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":471,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":481,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":610,"s":[100]},{"t":620,"s":[2]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[208.82,174.342,0],"ix":2,"l":2},"a":{"a":0,"k":[203.638,175.979,0],"ix":1,"l":2},"s":{"a":0,"k":[98,98,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[63.508,63.508],"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.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":[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 7653","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[69.868,128.978],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[102.472,102.472],"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 7653","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[63.508,63.508],"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.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":[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 7654","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[158.082,128.978],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[102.472,102.472],"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 7654","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[63.508,63.508],"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.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":[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 7657","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[69.868,222.98],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[102.472,102.472],"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 7657","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[63.508,63.508],"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.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":[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 7658","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[158.082,222.98],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[102.472,102.472],"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 7658","np":1,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[63.508,63.508],"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.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":[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 7655","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[247.745,221.535],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[102.472,102.472],"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 7655","np":1,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[63.508,63.508],"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.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":[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 7656","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[337.409,222.554],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[102.472,102.472],"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 7656","np":1,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":654,"st":-14,"ct":1,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":".blue50","cl":"blue50","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":439,"s":[0]},{"t":449,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":471,"s":[206,246.076,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":479.334,"s":[206,217.275,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":0.1},"o":{"x":0.167,"y":0.167},"t":521,"s":[206,174.076,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":610,"s":[206,174.076,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":618.334,"s":[206,215.275,0],"to":[0,0,0],"ti":[0,0,0]},{"t":660,"s":[206,277.076,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[99.764,99.764,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.8,0.8],"y":[1,0.15]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":471,"s":[366,64]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0,0.7]},"t":479.334,"s":[366,122]},{"i":{"x":[0.833,0.833],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0]},"t":521,"s":[366,209]},{"i":{"x":[0.8,0.8],"y":[1,0.15]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":610,"s":[366,209]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0,0.7]},"t":618.334,"s":[366,125.4]},{"t":660,"s":[366,0]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":20,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803926945,0.941176474094,0.996078431606,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 3470535","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":414,"op":844,"st":456,"ct":1,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":"star","parent":10,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":321,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":331,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":429,"s":[100]},{"t":439,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.8],"y":[0.164]},"o":{"x":[0.3],"y":[0]},"t":321,"s":[-90]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.702]},"t":331,"s":[-54]},{"t":382,"s":[0]}],"ix":10},"p":{"a":0,"k":[-8.102,-0.639,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.8,0.8,0.8],"y":[0.164,0.164,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":321,"s":[63,63,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.711,0.711,0]},"t":331,"s":[77.913,77.913,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":382,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":429,"s":[100,100,100]},{"t":439,"s":[50,50,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-15.299,-35.012],[0,0],[4.671,-10.689],[0,0],[-35.226,15.206],[0,0],[-10.755,-4.642],[0,0],[15.299,35.011],[0,0],[-4.671,10.689],[0,0],[35.226,-15.206],[0,0],[10.755,4.642]],"o":[[-35.226,-15.206],[0,0],[4.671,10.689],[0,0],[-15.299,35.012],[0,0],[10.755,-4.642],[0,0],[35.226,15.206],[0,0],[-4.671,-10.689],[0,0],[15.299,-35.012],[0,0],[-10.755,4.642],[0,0]],"v":[[-22.684,-77.887],[-78.364,-22.546],[-75.835,-16.757],[-75.835,16.757],[-78.364,22.546],[-22.684,77.887],[-16.86,75.373],[16.86,75.373],[22.684,77.887],[78.364,22.546],[75.835,16.757],[75.835,-16.757],[78.364,-22.546],[22.684,-77.887],[16.86,-75.373],[-16.86,-75.373]],"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":[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":"Star 72","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":321,"op":844,"st":151,"ct":1,"bm":0},{"ddd":0,"ind":17,"ty":3,"nm":"Lock","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":210,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":211,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":322,"s":[100]},{"t":332,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,137.943,0],"ix":2,"l":2},"a":{"a":0,"k":[1649.393,265.393,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":844,"st":-2,"ct":1,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":"LOCK","parent":19,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":210,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":211,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":322,"s":[100]},{"t":332,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.565,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":[[-2.248,0],[0,0],[0,-2.248],[0,0],[2.248,0],[0,0],[0,2.248],[0,0]],"o":[[0,0],[2.248,0],[0,0],[0,2.248],[0,0],[-2.248,0],[0,0],[0,-2.248]],"v":[[-14.305,-18.393],[14.305,-18.393],[18.393,-14.305],[18.393,14.305],[14.305,18.393],[-14.305,18.393],[-18.393,14.305],[-18.393,-14.305]],"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],[0,1.574],[2.248,0],[0,-2.248],[-1.308,-0.674],[0,0]],"o":[[0,0],[1.308,-0.674],[0,-2.248],[-2.248,0],[0,1.574],[0,0],[0,0]],"v":[[3.065,7.153],[1.901,0.552],[4.087,-3.065],[0,-7.153],[-4.087,-3.065],[-1.901,0.552],[-3.065,7.153]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.823529422283,0.890196084976,0.988235294819,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":"Vector","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":844,"st":-2,"ct":1,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":"Rectangle 3470535 2","parent":7,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":210,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":211,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":322,"s":[100]},{"t":332,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,-12.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":[{"ty":"rc","d":1,"s":{"a":0,"k":[331,59],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":10,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","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":[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 3470535","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":844,"st":-2,"ct":1,"bm":0},{"ddd":0,"ind":20,"ty":4,"nm":"Rectangle 3470535","parent":8,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":167,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":168,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":210,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":211,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":322,"s":[100]},{"t":332,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.5,127.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":[{"ty":"rc","d":1,"s":{"a":0,"k":[337,49],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":20,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.235294118524,0.250980407,0.262745112181,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 3470535","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":844,"st":-2,"ct":1,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":"Rectangle 3470534","parent":8,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":322,"s":[100]},{"t":332,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.5,65,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":[{"ty":"rc","d":1,"s":{"a":0,"k":[337,48],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":20,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.235294118524,0.250980407,0.262745112181,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 3470534","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":844,"st":-2,"ct":1,"bm":0},{"ddd":0,"ind":22,"ty":4,"nm":"Ellipse 7632","parent":8,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":115,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":116,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":146,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":167,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":168,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":322,"s":[100]},{"t":332,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[131,0,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":[{"d":1,"ty":"el","s":{"a":0,"k":[54,54],"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.235294118524,0.250980407,0.262745112181,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 7632","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":314,"st":-2,"ct":1,"bm":0},{"ddd":0,"ind":23,"ty":4,"nm":"Ellipse 7631","parent":8,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":115,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":116,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":145,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":146,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":322,"s":[100]},{"t":332,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[44,0,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":[{"d":1,"ty":"el","s":{"a":0,"k":[54,54],"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.235294118524,0.250980407,0.262745112181,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 7631","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":314,"st":-2,"ct":1,"bm":0},{"ddd":0,"ind":24,"ty":4,"nm":"Ellipse 7630","parent":8,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":322,"s":[100]},{"t":332,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-44,0,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":[{"d":1,"ty":"el","s":{"a":0,"k":[54,54],"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.235294118524,0.250980407,0.262745112181,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 7630","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":314,"st":-2,"ct":1,"bm":0},{"ddd":0,"ind":25,"ty":4,"nm":"Ellipse 7629","parent":8,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":322,"s":[100]},{"t":332,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-131,0,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":[{"d":1,"ty":"el","s":{"a":0,"k":[54,54],"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.235294118524,0.250980407,0.262745112181,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 7629","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":314,"st":-2,"ct":1,"bm":0},{"ddd":0,"ind":26,"ty":4,"nm":"Union","parent":8,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":322,"s":[100]},{"t":332,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,56.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":[{"ty":"rc","d":1,"s":{"a":0,"k":[360,178],"ix":2},"p":{"a":0,"k":[0,14.5],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"rc","d":1,"s":{"a":0,"k":[360,207],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":18,"ix":4},"nm":"Rectangle Path 2","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.1254902035,0.129411771894,0.141176477075,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":"Union","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":844,"st":-2,"ct":1,"bm":0},{"ddd":0,"ind":27,"ty":4,"nm":"APPS2","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":436,"s":[0]},{"t":446,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":471,"s":[205.5,-25,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":479.334,"s":[205.5,-75.64,0],"to":[0,0,0],"ti":[0,0,0]},{"t":521,"s":[205.5,-151.6,0],"h":1},{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":610,"s":[205.5,-151.6,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":618.334,"s":[205.5,-68,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":660,"s":[205.5,57.4,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.121,"y":1},"o":{"x":0.09,"y":0},"t":728,"s":[205.5,57.4,0],"to":[0,0,0],"ti":[0,0,0]},{"t":768,"s":[205.5,315.4,0]}],"ix":2,"l":2},"a":{"a":0,"k":[205.5,315.5,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":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7640","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[356,262],"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":".grey200","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false,"cl":"grey200"},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7639","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[256,262],"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":".grey200","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false,"cl":"grey200"},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7638","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[155,262],"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":".grey200","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false,"cl":"grey200"},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7637","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[56,262],"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":".grey200","np":1,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false,"cl":"grey200"},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7632","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[356,151],"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":".grey200","np":1,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false,"cl":"grey200"},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7631","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[256,151],"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":".grey200","np":1,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false,"cl":"grey200"},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7630","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[155,151],"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":".grey200","np":1,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false,"cl":"grey200"},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7629","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[56,151],"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":".grey200","np":1,"cix":2,"bm":0,"ix":8,"mn":"ADBE Vector Group","hd":false,"cl":"grey200"},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7648","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[357,371],"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":".grey212","np":1,"cix":2,"bm":0,"ix":9,"mn":"ADBE Vector Group","hd":false,"cl":"grey212"},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7652","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[357,480],"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":".grey207","np":1,"cix":2,"bm":0,"ix":10,"mn":"ADBE Vector Group","hd":false,"cl":"grey207"},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7651","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[255,480],"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":".grey206","np":1,"cix":2,"bm":0,"ix":11,"mn":"ADBE Vector Group","hd":false,"cl":"grey206"},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7647","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[258,371],"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":".grey205","np":1,"cix":2,"bm":0,"ix":12,"mn":"ADBE Vector Group","hd":false,"cl":"grey205"},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7650","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[156,480],"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":".grey204","np":1,"cix":2,"bm":0,"ix":13,"mn":"ADBE Vector Group","hd":false,"cl":"grey204"},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7646","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[156,371],"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":".grey203","np":1,"cix":2,"bm":0,"ix":14,"mn":"ADBE Vector Group","hd":false,"cl":"grey203"},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7649","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[54,480],"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":".grey202","np":1,"cix":2,"bm":0,"ix":15,"mn":"ADBE Vector Group","hd":false,"cl":"grey202"},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7645","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[54,371],"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":".grey201","np":1,"cix":2,"bm":0,"ix":16,"mn":"ADBE Vector Group","hd":false,"cl":"grey201"}],"ip":402,"op":844,"st":446,"ct":1,"bm":0},{"ddd":0,"ind":28,"ty":4,"nm":"G 2","parent":29,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":985,"s":[100]},{"t":995,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-142,-0.444,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":[[-1.986,-0.84],[-2.258,0],[-2.884,2.88],[0,4.667],[0.068,0.493],[0.136,0.613],[0,0],[0,0],[0,0],[1.673,-1.267],[2.313,0],[1.905,1.907],[0,2.72],[-1.905,1.907],[-2.721,0],[-1.075,-0.413],[-0.844,-0.773],[0,0],[1.837,0.707],[2.068,0],[1.986,-0.84],[1.469,-1.44],[0.857,-1.947],[0,-2.213],[-0.857,-1.947],[-1.469,-1.44]],"o":[[1.986,0.84],[4.68,0],[2.884,-2.88],[0,-0.587],[-0.068,-0.493],[0,0],[0,0],[0,0],[-0.327,1.973],[-1.673,1.267],[-2.721,0],[-1.905,-1.907],[0,-2.72],[1.905,-1.907],[1.197,0],[1.075,0.413],[0,0],[-1.551,-1.36],[-1.837,-0.707],[-2.258,0],[-1.986,0.84],[-1.469,1.44],[-0.857,1.947],[0,2.213],[0.857,1.947],[1.469,1.44]],"v":[[-6.041,14.74],[0.327,16],[11.673,11.68],[16,0.36],[15.898,-1.26],[15.592,-2.92],[0.327,-2.92],[0.327,3.04],[9.306,3.04],[6.306,7.9],[0.327,9.8],[-6.612,6.94],[-9.469,0],[-6.612,-6.94],[0.327,-9.8],[3.735,-9.18],[6.612,-7.4],[11.265,-11.84],[6.184,-14.94],[0.327,-16],[-6.041,-14.74],[-11.224,-11.32],[-14.714,-6.24],[-16,0],[-14.714,6.24],[-11.224,11.32]],"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":[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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":661,"op":844,"st":661,"ct":1,"bm":0},{"ddd":0,"ind":29,"ty":4,"nm":"SearchBar 2","parent":30,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":985,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":986,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":995,"s":[0]},{"t":996,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,51.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":[{"ty":"rc","d":1,"s":{"a":0,"k":[362,63],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":50,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803926945,0.941176474094,0.996078431606,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 3470534","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":661,"op":844,"st":662,"ct":1,"bm":0},{"ddd":0,"ind":30,"ty":4,"nm":"APPS 2","parent":27,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":986,"s":[100]},{"t":996,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,206.544,0],"ix":2,"l":2},"a":{"a":0,"k":[206,206.5,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":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7640","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[356,262],"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 7640","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7639","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[256,262],"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 7639","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7638","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[155,262],"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 7638","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7637","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[56,262],"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 7637","np":1,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7632","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[356,151],"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 7632","np":1,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7631","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[256,151],"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 7631","np":1,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7630","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[155,151],"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 7630","np":1,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7629","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[56,151],"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 7629","np":1,"cix":2,"bm":0,"ix":8,"mn":"ADBE Vector Group","hd":false}],"ip":661,"op":844,"st":662,"ct":1,"bm":0},{"ddd":0,"ind":31,"ty":4,"nm":"SearchBar","parent":7,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":321,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":322,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":331,"s":[0]},{"t":332,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,-98.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":[{"ty":"rc","d":1,"s":{"a":0,"k":[362,63],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":50,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803926945,0.941176474094,0.996078431606,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 3470534","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":581,"st":-2,"ct":1,"bm":0},{"ddd":0,"ind":32,"ty":4,"nm":"APPS","parent":7,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":58,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":68,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":322,"s":[100]},{"t":332,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,56.5,0],"ix":2,"l":2},"a":{"a":0,"k":[206,206.5,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":58,"s":[100,100,100]},{"t":79,"s":[90,90,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7640","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[356,262],"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 7640","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7639","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[256,262],"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 7639","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7638","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[155,262],"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 7638","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7637","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[56,262],"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 7637","np":1,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7632","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[356,151],"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 7632","np":1,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7631","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[256,151],"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 7631","np":1,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7630","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[155,151],"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 7630","np":1,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7629","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[56,151],"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 7629","np":1,"cix":2,"bm":0,"ix":8,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":315,"st":-2,"ct":1,"bm":0},{"ddd":0,"ind":33,"ty":4,"nm":"Vector","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.3,0],[0,0],[0,15.5],[0,0],[15.2,0],[0,0],[0,-15.7],[0,0]],"o":[[0,0],[15.3,0],[0,0],[0,-15.7],[0,0],[-15.3,0],[0,0],[0,15.7]],"v":[[-178.2,150],[178.2,150],[206,121.7],[206,-121.5],[178.3,-150],[-178.2,-150],[-206,-121.5],[-206,121.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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":844,"st":-2,"ct":1,"bm":0}]},{"id":"comp_1","nm":"swipe down","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"swipe up","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":472,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":482,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":517,"s":[100]},{"t":527,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":472,"s":[258,176,0],"to":[5.5,9,0],"ti":[-12,-36,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.333,"y":0.333},"t":497,"s":[291,230,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.45,"y":0},"t":507,"s":[291,230,0],"to":[0,0,0],"ti":[0,0,0]},{"t":527,"s":[291,76,0],"h":1}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.5,0.5,0.667],"y":[1,1,1]},"o":{"x":[0.5,0.5,0.333],"y":[0,0,0]},"t":497,"s":[100,100,100]},{"i":{"x":[0.5,0.5,0.667],"y":[1,1,1]},"o":{"x":[0.5,0.5,0.333],"y":[0,0,0]},"t":507,"s":[80,80,100]},{"i":{"x":[0.5,0.5,0.667],"y":[1,1,1]},"o":{"x":[0.5,0.5,0.333],"y":[0,0,0]},"t":517,"s":[80,80,100]},{"t":527,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.5,0.5],"y":[1,1]},"o":{"x":[0.5,0.5],"y":[0,0]},"t":509,"s":[56,56]},{"i":{"x":[0.5,0.5],"y":[1,1]},"o":{"x":[0.5,0.5],"y":[0,0]},"t":519,"s":[56,98]},{"t":529,"s":[56,56]}],"ix":2},"p":{"a":1,"k":[{"i":{"x":0.5,"y":1},"o":{"x":0.5,"y":0},"t":509,"s":[0,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.5,"y":0},"t":519,"s":[0,14],"to":[0,0],"ti":[0,0]},{"t":529,"s":[0,0]}],"ix":3},"r":{"a":0,"k":67,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.101960785687,0.450980395079,0.909803926945,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.541176497936,0.705882370472,0.972549021244,1],"ix":4},"o":{"a":0,"k":50,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":472,"op":530,"st":447,"ct":1,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"black","td":1,"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.3,0],[0,0],[0,15.5],[0,0],[15.2,0],[0,0],[0,-15.7],[0,0]],"o":[[0,0],[15.3,0],[0,0],[0,-15.7],[0,0],[-15.3,0],[0,0],[0,15.7]],"v":[[-178.2,150],[178.2,150],[206,121.7],[206,-121.5],[178.3,-150],[-178.2,-150],[-206,-121.5],[-206,121.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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":844,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"Private_SpaceB_v1_PreCrop","tt":1,"tp":1,"refId":"comp_0","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":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":412,"h":300,"ip":0,"op":844,"st":0,"bm":0}],"markers":[],"props":{}}
\ No newline at end of file
diff --git a/res/raw/private_space_illustration.json b/res/raw/private_space_illustration.json
new file mode 100644
index 0000000..ea14cfe
--- /dev/null
+++ b/res/raw/private_space_illustration.json
@@ -0,0 +1 @@
+{"v":"5.12.1","fr":60,"ip":0,"op":668,"w":412,"h":300,"nm":"Private_SpaceA_v1_export","ddd":0,"assets":[{"id":"comp_0","nm":"Private_Space_PreCrop","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"swipe up","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":43,"s":[100]},{"t":53,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":-2,"s":[258,176,0],"to":[5.5,9,0],"ti":[-12,-36,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.333,"y":0.333},"t":23,"s":[291,230,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.45,"y":0},"t":33,"s":[291,230,0],"to":[0,0,0],"ti":[0,0,0]},{"t":53,"s":[291,76,0],"h":1}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.5,0.5,0.667],"y":[1,1,1]},"o":{"x":[0.5,0.5,0.333],"y":[0,0,0]},"t":23,"s":[130,130,100]},{"i":{"x":[0.5,0.5,0.667],"y":[1,1,1]},"o":{"x":[0.5,0.5,0.333],"y":[0,0,0]},"t":33,"s":[110,110,100]},{"i":{"x":[0.5,0.5,0.667],"y":[1,1,1]},"o":{"x":[0.5,0.5,0.333],"y":[0,0,0]},"t":43,"s":[110,110,100]},{"t":53,"s":[130,130,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.5,0.5],"y":[1,1]},"o":{"x":[0.5,0.5],"y":[0,0]},"t":35,"s":[56,56]},{"i":{"x":[0.5,0.5],"y":[1,1]},"o":{"x":[0.5,0.5],"y":[0,0]},"t":45,"s":[56,98]},{"t":55,"s":[56,56]}],"ix":2},"p":{"a":1,"k":[{"i":{"x":0.5,"y":1},"o":{"x":0.5,"y":0},"t":35,"s":[0,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.5,"y":0},"t":45,"s":[0,14],"to":[0,0],"ti":[0,0]},{"t":55,"s":[0,0]}],"ix":3},"r":{"a":0,"k":67,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.101960785687,0.450980395079,0.909803926945,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.541176497936,0.705882370472,0.972549021244,1],"ix":4},"o":{"a":0,"k":50,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":56,"st":-27,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"swipe down","refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":180,"ix":10},"p":{"a":0,"k":[340,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[130,130,100],"ix":6,"l":2}},"ao":0,"w":412,"h":300,"ip":497,"op":620,"st":32,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"press 3","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":129,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":139,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":174,"s":[100]},{"t":184,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[344,248,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.5,0.5,0.667],"y":[1,1,1]},"o":{"x":[0.5,0.5,0.333],"y":[0,0,0]},"t":154,"s":[130,130,100]},{"i":{"x":[0.5,0.5,0.667],"y":[1,1,1]},"o":{"x":[0.5,0.5,0.333],"y":[0,0,0]},"t":164,"s":[110,110,100]},{"t":174,"s":[130,130,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[56,56],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":67,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.101960785687,0.450980395079,0.909803926945,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.541176497936,0.705882370472,0.972549021244,1],"ix":4},"o":{"a":0,"k":50,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.167,"y":0.186},"t":129,"s":[-37,67],"to":[15,-7],"ti":[7,20]},{"t":154,"s":[0,0],"h":1}],"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":"Touch","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":129,"op":185,"st":-76,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"press 2","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":383,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":393,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":428,"s":[100]},{"t":438,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[340.6,131,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.5,0.5,0.667],"y":[1,1,1]},"o":{"x":[0.5,0.5,0.333],"y":[0,0,0]},"t":408,"s":[130,130,100]},{"i":{"x":[0.5,0.5,0.667],"y":[1,1,1]},"o":{"x":[0.5,0.5,0.333],"y":[0,0,0]},"t":418,"s":[110,110,100]},{"t":428,"s":[130,130,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[56,56],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":67,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.101960785687,0.450980395079,0.909803926945,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.541176497936,0.705882370472,0.972549021244,1],"ix":4},"o":{"a":0,"k":50,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.167,"y":0.186},"t":383,"s":[-37,67],"to":[15,-7],"ti":[7,20]},{"t":408,"s":[0,0],"h":1}],"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":"Touch","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":383,"op":439,"st":178,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":3,"nm":"LOCK ZOOM","parent":6,"sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":170,"s":[145,441,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":180.166,"s":[87,408.6,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":0.1},"o":{"x":0.167,"y":0.167},"t":231,"s":[0,360,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":271,"s":[0,360,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":281,"s":[58,376,0],"to":[0,0,0],"ti":[0,0,0]},{"t":331,"s":[145,400,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":170,"s":[100,100,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":180.166,"s":[186,186,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":231,"s":[315,315,100]},{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":271,"s":[315,315,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":281,"s":[229,229,100]},{"t":331,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":668,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":3,"nm":"SCROLL","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.121,"y":1},"o":{"x":0.09,"y":0},"t":41,"s":[206,150,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.121,"y":0.121},"o":{"x":0.167,"y":0.167},"t":81,"s":[206,-190,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":271,"s":[206,-190,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":281,"s":[206,-225.6,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.562,"y":0.562},"o":{"x":0.192,"y":0.192},"t":331,"s":[206,-279,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":427,"s":[206,-279,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":437,"s":[206,-227,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":0.1},"o":{"x":0.167,"y":0.167},"t":487,"s":[206,-149,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.121,"y":1},"o":{"x":0.167,"y":0},"t":547,"s":[206,-149,0],"to":[0,0,0],"ti":[0,0,0]},{"t":607,"s":[206,191,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":668,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"handle","parent":5,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":170,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":180,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":437,"s":[0]},{"t":447,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-263.5,-3,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":[{"ty":"rc","d":1,"s":{"a":0,"k":[67,10],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":12,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","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":[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":"handle","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":668,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"unlock 2","parent":5,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":180,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":190,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":437,"s":[100]},{"t":447,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":271,"s":[3.176,-11.119,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":281,"s":[-2.024,-5.919,0],"to":[0,0,0],"ti":[0,0,0]},{"t":331,"s":[-9.824,1.881,0]}],"ix":2,"l":2},"a":{"a":0,"k":[2.504,-16.523,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":271,"s":[31.746,31.746,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":281,"s":[35.448,35.448,100]},{"t":331,"s":[41,41,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0},"t":180,"s":[{"i":[[0,0],[0,0],[-1.355,2.927],[-3.702,1.026],[-0.166,-3.033],[0,-3.968],[0,0],[2.992,5.819],[4.706,-0.139],[2.517,-2.626],[0,-6.587],[0,0]],"o":[[0,0],[0,-3.968],[1.344,-1.62],[2.173,0.401],[0.169,3.094],[0,0],[0.17,-3.914],[-2.25,-2.779],[-4.648,0.137],[-4.408,4.835],[0,0],[0,0]],"v":[[7.105,-16.667],[7.105,-26.19],[8.647,-35.414],[12.565,-37.812],[16.291,-33.754],[17.305,-26.171],[25.315,-26.042],[22.307,-42.725],[13.076,-47.489],[2.552,-43.094],[-2.368,-26.19],[-2.368,-16.667]],"c":true}]},{"t":241,"s":[{"i":[[0,0],[0,0],[-2.763,2.778],[-3.947,0],[-2.763,-2.778],[0,-3.968],[0,0],[4.618,4.643],[6.553,0],[4.618,-4.643],[0,-6.587],[0,0]],"o":[[0,0],[0,-3.968],[2.763,-2.778],[3.947,0],[2.763,2.778],[0,0],[0,-6.587],[-4.618,-4.643],[-6.553,0],[-4.618,4.643],[0,0],[0,0]],"v":[[7.112,-15.167],[7.105,-26.19],[11.25,-36.31],[21.316,-40.476],[31.382,-36.31],[35.526,-26.19],[45,-26.19],[38.072,-43.036],[21.316,-50],[4.559,-43.036],[-2.368,-26.19],[-2.362,-15.167]],"c":true}]}],"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":281,"s":[0.909803926945,0.941176474094,0.996078431606,1]},{"t":291,"s":[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":[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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":668,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"LOCK_NEW_CLOSED","parent":5,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":170,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":180,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":437,"s":[0]},{"t":447,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.091,-5.933,0],"ix":2,"l":2},"a":{"a":0,"k":[8.25,10.75,0],"ix":1,"l":2},"s":{"a":0,"k":[150,150,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,0]],"o":[[0,0],[0,0]],"v":[[-6,8.5],[-6,-1.5]],"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],[0,0],[0.583,0.583],[0.833,0],[0.583,-0.583],[0,-0.833]],"o":[[0,0],[0,0],[0,-0.833],[-0.583,-0.583],[-0.833,0],[-0.583,0.583],[0,0]],"v":[[-3,-3.5],[3,-3.5],[3,-5.5],[2.125,-7.625],[0,-8.5],[-2.125,-7.625],[-3,-5.5]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[-0.55,0],[-0.392,0.391],[0,0.55],[0.391,0.392],[0.55,0],[0.392,-0.391],[0,-0.55],[-0.391,-0.392]],"o":[[0.55,0],[0.391,-0.392],[0,-0.55],[-0.392,-0.391],[-0.55,0],[-0.391,0.392],[0,0.55],[0.392,0.391]],"v":[[0,5.5],[1.413,4.913],[2,3.5],[1.413,2.087],[0,1.5],[-1.413,2.087],[-2,3.5],[-1.413,4.913]],"c":true},"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ind":3,"ty":"sh","ix":4,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-6,8.5],[6,8.5],[6,-1.5],[-6,-1.5]],"c":true},"ix":2},"nm":"Path 4","mn":"ADBE Vector Shape - Group","hd":false},{"ind":4,"ty":"sh","ix":5,"ks":{"a":0,"k":{"i":[[0,0],[0.392,0.391],[0,0.55],[0,0],[-0.391,0.392],[-0.55,0],[0,0],[0,0],[-0.974,0.976],[-1.383,0],[-0.975,-0.974],[0,-1.383],[0,0],[0,0],[-0.392,-0.391],[0,-0.55],[0,0],[0.391,-0.392],[0.55,0]],"o":[[-0.55,0],[-0.391,-0.392],[0,0],[0,-0.55],[0.392,-0.391],[0,0],[0,0],[0,-1.383],[0.976,-0.974],[1.383,0],[0.974,0.976],[0,0],[0,0],[0.55,0],[0.391,0.392],[0,0],[0,0.55],[-0.392,0.391],[0,0]],"v":[[-6,10.5],[-7.413,9.913],[-8,8.5],[-8,-1.5],[-7.413,-2.913],[-6,-3.5],[-5,-3.5],[-5,-5.5],[-3.538,-9.038],[0,-10.5],[3.538,-9.038],[5,-5.5],[5,-3.5],[6,-3.5],[7.413,-2.913],[8,-1.5],[8,8.5],[7.413,9.913],[6,10.5]],"c":true},"ix":2},"nm":"Path 5","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":170,"s":[0.40000000596,0.615686297417,0.964705884457,1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":180,"s":[0.909803986549,0.941176533699,0.996078491211,1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":399,"s":[0.909803986549,0.941176533699,0.996078491211,1]},{"t":401,"s":[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":[8.25,10.75],"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":6,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":2404,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"LOCK_NEW","parent":5,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":180,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":190,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":437,"s":[100]},{"t":447,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":271,"s":[2.341,-5.933,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":281,"s":[-2.659,-0.633,0],"to":[0,0,0],"ti":[0,0,0]},{"t":331,"s":[-10.159,7.317,0]}],"ix":2,"l":2},"a":{"a":0,"k":[9.75,10.75,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":271,"s":[150,150,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":281,"s":[162,162,100]},{"t":331,"s":[180,180,100]}],"ix":6,"l":2}},"ao":0,"hasMask":true,"masksProperties":[{"inv":false,"mode":"a","pt":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[17.904,7.298],[-0.382,7.298],[-0.382,22.525],[17.904,22.525]],"c":true},"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":0,"ix":4},"nm":"Mask 1"}],"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0.392,0.391],[0,0.55],[0,0],[-0.391,0.392],[-0.55,0],[0,0],[0,0],[-0.974,0.976],[-1.383,0],[-0.975,-0.974],[0,-1.383],[0,0],[0.583,0.583],[0.833,0],[0.583,-0.583],[0,-0.833],[0,0],[0,0],[-0.392,-0.391],[0,-0.55],[0,0],[0.391,-0.392],[0.55,0]],"o":[[-0.55,0],[-0.391,-0.392],[0,0],[0,-0.55],[0.392,-0.391],[0,0],[0,0],[0,-1.383],[0.976,-0.974],[1.383,0],[0.974,0.976],[0,0],[0,-0.833],[-0.583,-0.583],[-0.833,0],[-0.583,0.583],[0,0],[0,0],[0.55,0],[0.391,0.392],[0,0],[0,0.55],[-0.392,0.391],[0,0]],"v":[[-7.5,10.5],[-8.913,9.913],[-9.5,8.5],[-9.5,-1.5],[-8.913,-2.913],[-7.5,-3.5],[-0.5,-3.5],[-0.5,-5.5],[0.962,-9.038],[4.5,-10.5],[8.038,-9.038],[9.5,-5.5],[7.5,-5.5],[6.625,-7.625],[4.5,-8.5],[2.375,-7.625],[1.5,-5.5],[1.5,-3.5],[4.5,-3.5],[5.913,-2.913],[6.5,-1.5],[6.5,8.5],[5.913,9.913],[4.5,10.5]],"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]],"o":[[0,0],[0,0]],"v":[[-7.5,8.5],[-7.5,-1.5]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[-0.55,0],[-0.392,0.391],[0,0.55],[0.391,0.392],[0.55,0],[0.392,-0.391],[0,-0.55],[-0.391,-0.392]],"o":[[0.55,0],[0.391,-0.392],[0,-0.55],[-0.392,-0.391],[-0.55,0],[-0.391,0.392],[0,0.55],[0.392,0.391]],"v":[[-1.5,5.5],[-0.087,4.913],[0.5,3.5],[-0.087,2.087],[-1.5,1.5],[-2.913,2.087],[-3.5,3.5],[-2.913,4.913]],"c":true},"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ind":3,"ty":"sh","ix":4,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-7.5,8.5],[4.5,8.5],[4.5,-1.5],[-7.5,-1.5]],"c":true},"ix":2},"nm":"Path 4","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":281,"s":[0.909803926945,0.941176474094,0.996078431606,1]},{"t":291,"s":[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":[9.75,10.75],"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":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":2404,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"blue_circle","parent":5,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":170,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":180,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":437,"s":[0]},{"t":447,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.004,-4.067,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":[[-12.32,0],[0,12.32],[12.32,0],[0,-12.32]],"o":[[12.32,0],[0,-12.32],[-12.32,0],[0,12.32]],"v":[[0,22.308],[22.308,0],[0,-22.308],[-22.308,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.823529422283,0.890196084976,0.988235294819,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 7653","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":668,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".blue50","cl":"blue50","parent":5,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":170,"s":[100]},{"t":180,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-145,-4,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":[{"ty":"rc","d":1,"s":{"a":0,"k":[366,64],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":20,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803926945,0.941176474094,0.996078431606,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 3470535","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":209,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"star","parent":5,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":271,"s":[100]},{"t":288,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":170,"s":[-90]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":180.166,"s":[-54]},{"t":231,"s":[0]}],"ix":10},"p":{"a":0,"k":[-0.191,-6.076,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":170,"s":[20,20,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":180.166,"s":[24.734,24.734,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":231,"s":[31.836,31.836,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":271,"s":[31.836,31.836,100]},{"t":288,"s":[14.836,14.836,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-15.299,-35.012],[0,0],[4.671,-10.689],[0,0],[-35.226,15.206],[0,0],[-10.755,-4.642],[0,0],[15.299,35.011],[0,0],[-4.671,10.689],[0,0],[35.226,-15.206],[0,0],[10.755,4.642]],"o":[[-35.226,-15.206],[0,0],[4.671,10.689],[0,0],[-15.299,35.012],[0,0],[10.755,-4.642],[0,0],[35.226,15.206],[0,0],[-4.671,-10.689],[0,0],[15.299,-35.012],[0,0],[-10.755,4.642],[0,0]],"v":[[-22.684,-77.887],[-78.364,-22.546],[-75.835,-16.757],[-75.835,16.757],[-78.364,22.546],[-22.684,77.887],[-16.86,75.373],[16.86,75.373],[22.684,77.887],[78.364,22.546],[75.835,16.757],[75.835,-16.757],[78.364,-22.546],[22.684,-77.887],[16.86,-75.373],[-16.86,-75.373]],"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":[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":"Star 72","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":170,"op":289,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"circle","parent":5,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":288,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":298,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":437,"s":[100]},{"t":447,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-11.5,7.976,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":[[-17.397,0],[0,17.397],[17.397,0],[0,-17.397]],"o":[[17.397,0],[0,-17.397],[-17.397,0],[0,17.397]],"v":[[0,31.5],[31.5,0],[0,-31.5],[-31.5,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.823529422283,0.890196084976,0.988235294819,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 7653","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":668,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":"MASK","parent":5,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-226.788,-50.524,0],"ix":2,"l":2},"a":{"a":0,"k":[-82.527,-143.957,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":427,"s":[253.623,325.717,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":437,"s":[253.623,243.717,100]},{"t":487,"s":[253.623,120.717,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[202.945,64.328],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.005591273308,0.98464493097,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":[-82.527,-112.055],"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":668,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":"Merged Shape Layer","parent":5,"tt":1,"tp":16,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":289,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":294,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":427,"s":[100]},{"t":447,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.154,"y":0},"t":289,"s":[-142.18,-48.897,0],"to":[0,0,0],"ti":[0,0,0]},{"t":329,"s":[-142.18,53.103,0]}],"ix":2,"l":2},"a":{"a":0,"k":[203.638,175.979,0],"ix":1,"l":2},"s":{"a":0,"k":[97.587,97.587,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[63.508,63.508],"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.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":[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 7653","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[69.868,128.978],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[102.472,102.472],"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 7653","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[63.508,63.508],"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.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":[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 7654","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[158.082,128.978],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[102.472,102.472],"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 7654","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[63.508,63.508],"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.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":[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 7657","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[69.868,222.98],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[102.472,102.472],"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 7657","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[63.508,63.508],"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.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":[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 7658","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[158.082,222.98],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[102.472,102.472],"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 7658","np":1,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[63.508,63.508],"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.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":[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 7655","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[247.745,221.535],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[102.472,102.472],"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 7655","np":1,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[63.508,63.508],"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.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":[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 7656","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[337.409,222.554],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[102.472,102.472],"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 7656","np":1,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":668,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":"Rectangle 3470535 2","parent":5,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":289,"s":[0]},{"t":294,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":427,"s":[-145.173,-50.524,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":437,"s":[-145.173,-44.524,0],"to":[0,0,0],"ti":[0,0,0]},{"t":487,"s":[-145.173,-35.524,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.173,-104.024,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.154,0.154],"y":[0,0]},"t":289,"s":[370,107]},{"i":{"x":[0.833,0.833],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0]},"t":329,"s":[370,209]},{"i":{"x":[0.8,0.8],"y":[1,0.15]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":427,"s":[370,209]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0,0.7]},"t":437,"s":[370,150.6]},{"t":487,"s":[370,63]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":20,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803926945,0.941176474094,0.996078431606,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":[-185.83,-104.508],"ix":2},"a":{"k":[{"s":[-185,-53.5],"t":289,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-54.046],"t":290,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-56.161],"t":291,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-60.351],"t":292,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-65.625],"t":293,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-70.507],"t":294,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-74.617],"t":295,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-78.061],"t":296,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-80.988],"t":297,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-83.514],"t":298,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-85.721],"t":299,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-87.671],"t":300,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-89.406],"t":301,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-90.961],"t":302,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-92.362],"t":303,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-93.628],"t":304,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-94.776],"t":305,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-95.82],"t":306,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-96.771],"t":307,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-97.637],"t":308,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-98.427],"t":309,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-99.148],"t":310,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-99.805],"t":311,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-100.403],"t":312,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-100.947],"t":313,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-101.441],"t":314,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-101.887],"t":315,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-102.29],"t":316,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-102.652],"t":317,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-102.975],"t":318,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-103.261],"t":319,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-103.514],"t":320,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-103.734],"t":321,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-103.923],"t":322,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-104.082],"t":323,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-104.214],"t":324,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-104.32],"t":325,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-104.4],"t":326,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-104.5],"t":427,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-104.338],"t":428,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-103.846],"t":429,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-102.996],"t":430,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-101.739],"t":431,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-100.003],"t":432,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-97.676],"t":433,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-94.58],"t":434,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-90.41],"t":435,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-84.548],"t":436,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-75.3],"t":437,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-64.787],"t":438,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-58.094],"t":439,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-53.691],"t":440,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-50.519],"t":441,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-48.083],"t":442,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-46.129],"t":443,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-44.513],"t":444,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-43.146],"t":445,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-41.971],"t":446,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-40.947],"t":447,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-40.046],"t":448,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-39.246],"t":449,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-38.531],"t":450,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-37.889],"t":451,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-37.309],"t":452,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-36.784],"t":453,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-36.305],"t":454,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-35.869],"t":455,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-35.471],"t":456,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-35.105],"t":457,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-34.77],"t":458,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-34.462],"t":459,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-34.179],"t":460,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-33.919],"t":461,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-33.679],"t":462,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-33.458],"t":463,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-33.255],"t":464,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-33.068],"t":465,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-32.896],"t":466,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-32.739],"t":467,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-32.594],"t":468,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-32.461],"t":469,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-32.339],"t":470,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-32.229],"t":471,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-32.128],"t":472,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-32.036],"t":473,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-31.954],"t":474,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-31.88],"t":475,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-31.813],"t":476,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-31.754],"t":477,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-31.702],"t":478,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-31.657],"t":479,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-31.618],"t":480,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-31.585],"t":481,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-31.558],"t":482,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-31.537],"t":483,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-185,-31.509],"t":485,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"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 3470535","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":668,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":".blue400","cl":"blue400","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-287,-540,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":[[-1.986,-0.84],[-2.258,0],[-2.884,2.88],[0,4.667],[0.068,0.493],[0.136,0.613],[0,0],[0,0],[0,0],[1.673,-1.267],[2.313,0],[1.905,1.907],[0,2.72],[-1.905,1.907],[-2.721,0],[-1.075,-0.413],[-0.844,-0.773],[0,0],[1.837,0.707],[2.068,0],[1.986,-0.84],[1.469,-1.44],[0.857,-1.947],[0,-2.213],[-0.857,-1.947],[-1.469,-1.44]],"o":[[1.986,0.84],[4.68,0],[2.884,-2.88],[0,-0.587],[-0.068,-0.493],[0,0],[0,0],[0,0],[-0.327,1.973],[-1.673,1.267],[-2.721,0],[-1.905,-1.907],[0,-2.72],[1.905,-1.907],[1.197,0],[1.075,0.413],[0,0],[-1.551,-1.36],[-1.837,-0.707],[-2.258,0],[-1.986,0.84],[-1.469,1.44],[-0.857,1.947],[0,2.213],[0.857,1.947],[1.469,1.44]],"v":[[-6.041,14.74],[0.327,16],[11.673,11.68],[16,0.36],[15.898,-1.26],[15.592,-2.92],[0.327,-2.92],[0.327,3.04],[9.306,3.04],[6.306,7.9],[0.327,9.8],[-6.612,6.94],[-9.469,0],[-6.612,-6.94],[0.327,-9.8],[3.735,-9.18],[6.612,-7.4],[11.265,-11.84],[6.184,-14.94],[0.327,-16],[-6.041,-14.74],[-11.224,-11.32],[-14.714,-6.24],[-16,0],[-14.714,6.24],[-11.224,11.32]],"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":[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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":668,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":20,"ty":4,"nm":".blue50","cl":"blue50","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-145,-539.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":[{"ty":"rc","d":1,"s":{"a":0,"k":[362,63],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":50,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803926945,0.941176474094,0.996078431606,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 3470534","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":668,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":".grey200","cl":"grey200","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-145.5,-275.5,0],"ix":2,"l":2},"a":{"a":0,"k":[205.5,315.5,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":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7640","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[356,262],"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":".grey200","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false,"cl":"grey200"},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7639","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[256,262],"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":".grey200","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false,"cl":"grey200"},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7638","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[155,262],"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":".grey200","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false,"cl":"grey200"},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7637","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[56,262],"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":".grey200","np":1,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false,"cl":"grey200"},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7632","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[356,151],"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":".grey200","np":1,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false,"cl":"grey200"},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7631","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[256,151],"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":".grey200","np":1,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false,"cl":"grey200"},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7630","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[155,151],"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":".grey200","np":1,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false,"cl":"grey200"},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7629","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[56,151],"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":".grey200","np":1,"cix":2,"bm":0,"ix":8,"mn":"ADBE Vector Group","hd":false,"cl":"grey200"},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7648","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[357,371],"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":".grey212","np":1,"cix":2,"bm":0,"ix":9,"mn":"ADBE Vector Group","hd":false,"cl":"grey212"},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7652","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[357,480],"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":".grey207","np":1,"cix":2,"bm":0,"ix":10,"mn":"ADBE Vector Group","hd":false,"cl":"grey207"},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7651","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[255,480],"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":".grey206","np":1,"cix":2,"bm":0,"ix":11,"mn":"ADBE Vector Group","hd":false,"cl":"grey206"},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7647","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[258,371],"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":".grey205","np":1,"cix":2,"bm":0,"ix":12,"mn":"ADBE Vector Group","hd":false,"cl":"grey205"},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7650","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[156,480],"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":".grey204","np":1,"cix":2,"bm":0,"ix":13,"mn":"ADBE Vector Group","hd":false,"cl":"grey204"},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7646","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[156,371],"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":".grey203","np":1,"cix":2,"bm":0,"ix":14,"mn":"ADBE Vector Group","hd":false,"cl":"grey203"},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7649","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[54,480],"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":".grey202","np":1,"cix":2,"bm":0,"ix":15,"mn":"ADBE Vector Group","hd":false,"cl":"grey202"},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[62,62],"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.235294118524,0.250980407,0.262745112181,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 7645","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[54,371],"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":".grey201","np":1,"cix":2,"bm":0,"ix":16,"mn":"ADBE Vector Group","hd":false,"cl":"grey201"}],"ip":0,"op":668,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":22,"ty":4,"nm":"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.3,0],[0,0],[0,15.5],[0,0],[15.2,0],[0,0],[0,-15.7],[0,0]],"o":[[0,0],[15.3,0],[0,0],[0,-15.7],[0,0],[-15.3,0],[0,0],[0,15.7]],"v":[[-178.2,150],[178.2,150],[206,121.7],[206,-121.5],[178.3,-150],[-178.2,-150],[-206,-121.5],[-206,121.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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":668,"st":0,"ct":1,"bm":0}]},{"id":"comp_1","nm":"swipe down","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"swipe up","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":472,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":482,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":517,"s":[100]},{"t":527,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":472,"s":[258,176,0],"to":[5.5,9,0],"ti":[-12,-36,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.333,"y":0.333},"t":497,"s":[291,230,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.45,"y":0},"t":507,"s":[291,230,0],"to":[0,0,0],"ti":[0,0,0]},{"t":527,"s":[291,76,0],"h":1}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.5,0.5,0.667],"y":[1,1,1]},"o":{"x":[0.5,0.5,0.333],"y":[0,0,0]},"t":497,"s":[100,100,100]},{"i":{"x":[0.5,0.5,0.667],"y":[1,1,1]},"o":{"x":[0.5,0.5,0.333],"y":[0,0,0]},"t":507,"s":[80,80,100]},{"i":{"x":[0.5,0.5,0.667],"y":[1,1,1]},"o":{"x":[0.5,0.5,0.333],"y":[0,0,0]},"t":517,"s":[80,80,100]},{"t":527,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.5,0.5],"y":[1,1]},"o":{"x":[0.5,0.5],"y":[0,0]},"t":509,"s":[56,56]},{"i":{"x":[0.5,0.5],"y":[1,1]},"o":{"x":[0.5,0.5],"y":[0,0]},"t":519,"s":[56,98]},{"t":529,"s":[56,56]}],"ix":2},"p":{"a":1,"k":[{"i":{"x":0.5,"y":1},"o":{"x":0.5,"y":0},"t":509,"s":[0,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.5,"y":0},"t":519,"s":[0,14],"to":[0,0],"ti":[0,0]},{"t":529,"s":[0,0]}],"ix":3},"r":{"a":0,"k":67,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.101960785687,0.450980395079,0.909803926945,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.541176497936,0.705882370472,0.972549021244,1],"ix":4},"o":{"a":0,"k":50,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":472,"op":530,"st":447,"ct":1,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"black","td":1,"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.3,0],[0,0],[0,15.5],[0,0],[15.2,0],[0,0],[0,-15.7],[0,0]],"o":[[0,0],[15.3,0],[0,0],[0,-15.7],[0,0],[-15.3,0],[0,0],[0,15.7]],"v":[[-178.2,150],[178.2,150],[206,121.7],[206,-121.5],[178.3,-150],[-178.2,-150],[-206,-121.5],[-206,121.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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":668,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"Private_Space_PreCrop","tt":1,"tp":1,"refId":"comp_0","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":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":412,"h":300,"ip":0,"op":668,"st":0,"bm":0}],"markers":[{"tm":261,"cm":"Unlock\r","dr":0},{"tm":271,"cm":"1","dr":0},{"tm":667,"cm":"2","dr":0}],"props":{}}
\ No newline at end of file
diff --git a/res/raw/private_space_notifications_illustration.json b/res/raw/private_space_notifications_illustration.json
new file mode 100644
index 0000000..42f4ee3
--- /dev/null
+++ b/res/raw/private_space_notifications_illustration.json
@@ -0,0 +1 @@
+{"v":"5.12.1","fr":60,"ip":0,"op":271,"w":412,"h":300,"nm":"Private_SpaceD_v1_export","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"unlock 2","parent":2,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":1,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":11,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":108,"s":[100]},{"t":118,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[2.504,-16.523,0],"ix":2,"l":2},"a":{"a":0,"k":[2.504,-16.523,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,"y":1},"o":{"x":0.167,"y":0},"t":10,"s":[{"i":[[0,0],[0,0],[-1.355,2.927],[-3.702,1.026],[-0.166,-3.033],[0,-3.968],[0,0],[2.992,5.819],[4.706,-0.139],[2.517,-2.626],[0,-6.587],[0,0]],"o":[[0,0],[0,-3.968],[1.344,-1.62],[2.173,0.401],[0.169,3.094],[0,0],[0.17,-3.914],[-2.25,-2.779],[-4.648,0.137],[-4.408,4.835],[0,0],[0,0]],"v":[[7.105,-16.667],[7.105,-26.19],[8.647,-35.414],[12.565,-37.812],[16.291,-33.754],[17.305,-26.171],[25.315,-26.042],[22.307,-42.725],[13.076,-47.489],[2.552,-43.094],[-2.368,-26.19],[-2.368,-16.667]],"c":true}]},{"t":71,"s":[{"i":[[0,0],[0,0],[-2.763,2.778],[-3.947,0],[-2.763,-2.778],[0,-3.968],[0,0],[4.618,4.643],[6.553,0],[4.618,-4.643],[0,-6.587],[0,0]],"o":[[0,0],[0,-3.968],[2.763,-2.778],[3.947,0],[2.763,2.778],[0,0],[0,-6.587],[-4.618,-4.643],[-6.553,0],[-4.618,4.643],[0,0],[0,0]],"v":[[7.112,-15.167],[7.105,-26.19],[11.25,-36.31],[21.316,-40.476],[31.382,-36.31],[35.526,-26.19],[45,-26.19],[38.072,-43.036],[21.316,-50],[4.559,-43.036],[-2.368,-26.19],[-2.362,-15.167]],"c":true}]}],"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803986549,0.941176533699,0.996078491211,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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-170,"op":498,"st":-170,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"unlock","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":108,"s":[100]},{"t":118,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":108,"s":[213.752,151.127,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":115,"s":[274.251,163.127,0],"to":[0,0,0],"ti":[0,0,0]},{"t":150,"s":[365,181.127,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":0,"s":[58,58,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.714,0.714,0]},"t":10,"s":[74.8,74.8,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":61,"s":[100,100,100]},{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":108,"s":[100,100,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":115,"s":[84,84,100]},{"t":150,"s":[60,60,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.855,-1.865],[2.605,0],[1.855,1.865],[0,2.619],[-1.855,1.865],[-2.605,0],[-1.855,-1.865],[0,-2.619]],"o":[[-1.855,1.865],[-2.605,0],[-1.855,-1.865],[0,-2.619],[1.855,-1.865],[2.605,0],[1.855,1.865],[0,2.619]],"v":[[-0.414,23.393],[-7.105,26.19],[-13.796,23.393],[-16.579,16.667],[-13.796,9.94],[-7.105,7.143],[-0.414,9.94],[2.368,16.667]],"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":[[-1.855,-1.865],[-2.605,0],[0,0],[-1.855,1.865],[0,2.619],[0,0],[1.855,1.865],[2.605,0],[0,0],[1.855,-1.865],[0,-2.619],[0,0]],"o":[[1.855,1.865],[0,0],[2.605,0],[1.855,-1.865],[0,0],[0,-2.619],[-1.855,-1.865],[0,0],[-2.605,0],[-1.855,1.865],[0,0],[0,2.619]],"v":[[-42.217,47.202],[-35.526,50],[21.316,50],[28.007,47.202],[30.789,40.476],[30.789,-7.143],[28.007,-13.869],[21.316,-16.667],[-35.526,-16.667],[-42.217,-13.869],[-45,-7.143],[-45,40.476]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803986549,0.941176533699,0.996078491211,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":"Vector","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-170,"op":498,"st":-170,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"star","parent":2,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":108,"s":[100]},{"t":118,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.8],"y":[0.164]},"o":{"x":[0.3],"y":[0]},"t":0,"s":[-90]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.702]},"t":10,"s":[-54]},{"t":61,"s":[0]}],"ix":10},"p":{"a":0,"k":[-8.102,-0.639,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.8,0.8,0.8],"y":[0.164,0.164,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":0,"s":[63,63,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.711,0.711,0]},"t":10,"s":[77.913,77.913,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":61,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":108,"s":[100,100,100]},{"t":118,"s":[50,50,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-15.299,-35.012],[0,0],[4.671,-10.689],[0,0],[-35.226,15.206],[0,0],[-10.755,-4.642],[0,0],[15.299,35.011],[0,0],[-4.671,10.689],[0,0],[35.226,-15.206],[0,0],[10.755,4.642]],"o":[[-35.226,-15.206],[0,0],[4.671,10.689],[0,0],[-15.299,35.012],[0,0],[10.755,-4.642],[0,0],[35.226,15.206],[0,0],[-4.671,-10.689],[0,0],[15.299,-35.012],[0,0],[-10.755,4.642],[0,0]],"v":[[-22.684,-77.887],[-78.364,-22.546],[-75.835,-16.757],[-75.835,16.757],[-78.364,22.546],[-22.684,77.887],[-16.86,75.373],[16.86,75.373],[22.684,77.887],[78.364,22.546],[75.835,16.757],[75.835,-16.757],[78.364,-22.546],[22.684,-77.887],[16.86,-75.373],[-16.86,-75.373]],"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":[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":"Star 72","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":523,"st":-170,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"lock","parent":2,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":108,"s":[0]},{"t":118,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-7.5,12.288,0],"ix":2,"l":2},"a":{"a":0,"k":[360.5,188.5,0],"ix":1,"l":2},"s":{"a":0,"k":[166.667,166.667,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,1.98],[2.75,0],[0,-2.829],[-1.6,-0.849],[0,0]],"o":[[0,0],[1.6,-0.849],[0,-2.829],[-2.75,0],[0,1.98],[0,0],[0,0]],"v":[[3.75,9],[2.325,0.694],[5,-3.857],[0,-9],[-5,-3.857],[-2.325,0.694],[-3.75,9]],"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":[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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[361,188],"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":"Vector","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.75,0],[0,0],[0,-2.75],[0,0],[2.75,0],[0,0],[0,2.75],[0,0]],"o":[[0,0],[2.75,0],[0,0],[0,2.75],[0,0],[-2.75,0],[0,0],[0,-2.75]],"v":[[-17.5,-22.5],[17.5,-22.5],[22.5,-17.5],[22.5,17.5],[17.5,22.5],[-17.5,22.5],[-22.5,17.5],[-22.5,-17.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803926945,0.941176474094,0.996078431606,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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[360.5,188.5],"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":"Vector","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1800,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"circle","parent":2,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":108,"s":[0]},{"t":118,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-8.333,11.455,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[166.667,166.667,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-20.987,0],[0,20.987],[20.987,0],[0,-20.987]],"o":[[20.987,0],[0,-20.987],[-20.987,0],[0,20.987]],"v":[[0,38],[38,0],[0,-38],[-38,0]],"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":[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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1800,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Notification","parent":2,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[2.368,16.667,0],"ix":2,"l":2},"a":{"a":0,"k":[366.421,191.127,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":108,"s":[0,0,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":115,"s":[66.667,66.667,100]},{"t":150,"s":[166.667,166.667,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],[0,0],[0,0],[2.344,3.064],[3.75,0.95],[0,0],[0.82,0.831],[1.172,0],[0.82,-0.831],[0,-1.188],[0,0],[2.344,-3.064],[0,-3.943],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,-3.943],[-2.344,-3.064],[0,0],[0,-1.188],[-0.82,-0.831],[-1.172,0],[-0.82,0.831],[0,0],[-3.75,0.95],[-2.344,3.064],[0,0],[0,0]],"v":[[-22.5,14.25],[-22.5,19.95],[22.5,19.95],[22.5,14.25],[16.875,14.25],[16.875,-5.7],[13.359,-16.209],[4.219,-22.23],[4.219,-24.225],[2.988,-27.253],[0,-28.5],[-2.988,-27.253],[-4.219,-24.225],[-4.219,-22.23],[-13.359,-16.209],[-16.875,-5.7],[-16.875,14.25]],"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":[[-1.102,-1.116],[-1.547,0],[-1.102,1.116],[0,1.568],[0,0]],"o":[[1.102,1.116],[1.547,0],[1.102,-1.116],[0,0],[0,1.568]],"v":[[-3.973,26.826],[0,28.5],[3.973,26.826],[5.625,22.8],[-5.625,22.8]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[0,0],[0,-3.135],[0,0],[0,0],[0,0],[2.203,2.232]],"o":[[-2.203,2.232],[0,0],[0,0],[0,0],[0,-3.135],[0,0]],"v":[[-7.945,-13.751],[-11.25,-5.7],[-11.25,14.25],[11.25,14.25],[11.25,-5.7],[7.945,-13.751]],"c":false},"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803926945,0.941176474094,0.996078431606,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":"Vector","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[73.5,129.5],"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":"bell","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":[[-21.815,0],[0,22.091],[21.815,0],[0,-22.091]],"o":[[21.815,0],[0,-22.091],[-21.815,0],[0,22.091]],"v":[[0,40],[39.5,0],[0,-40],[-39.5,0]],"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":[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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[72.5,131],"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":"circle","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[193,18],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":13,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","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":[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 260863005","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[227.5,149],"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 260863005","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[193,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":13,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","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":[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 260863002","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[227.5,111],"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 260863002","np":1,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[354,117],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":15,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803926945,0.941176474094,0.996078431606,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 260863003","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[191,132.5],"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 260863003","np":1,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1800,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Vector","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.3,0],[0,0],[0,15.5],[0,0],[15.2,0],[0,0],[0,-15.7],[0,0]],"o":[[0,0],[15.3,0],[0,0],[0,-15.7],[0,0],[-15.3,0],[0,0],[0,15.7]],"v":[[-178.2,150],[178.2,150],[206,121.7],[206,-121.5],[178.3,-150],[-178.2,-150],[-206,-121.5],[-206,121.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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":844,"st":-2,"ct":1,"bm":0}],"markers":[{"tm":0,"cm":"1","dr":0},{"tm":270,"cm":"2","dr":0},{"tm":429,"cm":"4","dr":0},{"tm":500,"cm":"3","dr":0},{"tm":521,"cm":"5","dr":0},{"tm":843,"cm":"6","dr":0}],"props":{}}
\ No newline at end of file
diff --git a/res/raw/private_space_placeholder_illustration.json b/res/raw/private_space_placeholder_illustration.json
new file mode 100644
index 0000000..dcfdae0
--- /dev/null
+++ b/res/raw/private_space_placeholder_illustration.json
@@ -0,0 +1 @@
+{"v":"5.6.6","ip":0,"op":1,"fr":60,"w":412,"h":300,"layers":[{"ind":13065,"nm":"surface73963","ao":0,"ip":0,"op":60,"st":0,"ty":4,"ks":{"ty":"tr","o":{"k":100},"r":{"k":0},"p":{"k":[0,0]},"a":{"k":[0,0]},"s":{"k":[133.33,133.33]},"sk":{"k":0},"sa":{"k":0}},"shapes":[{"ty":"gr","hd":false,"nm":"surface73963","it":[{"ty":"gr","hd":false,"it":[{"ty":"sh","ks":{"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[71.11,117.75],[71.11,105.94],[72.51,105.94],[72.51,117.75]],"c":true}}},{"ty":"sh","ks":{"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[74.87,117.75],[74.87,105.94],[76.27,105.94],[76.27,117.75]],"c":true}}},{"ty":"sh","ks":{"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[78.35,117.75],[78.35,105.94],[79.75,105.94],[79.75,117.75]],"c":true}}},{"ty":"sh","ks":{"k":{"i":[[0,0],[0.53,0.61],[0,1.04],[0,0],[0,0],[0,0],[-0.39,-0.36],[-0.57,0],[-0.36,0.25],[-0.2,0.41],[0,0.44],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0.29,-0.23],[0.36,-0.13],[0.4,0]],"o":[[-1.04,0],[-0.52,-0.61],[0,0],[0,0],[0,0],[0,0.84],[0.38,0.35],[0.5,0],[0.36,-0.26],[0.2,-0.41],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[-0.14,0.27],[-0.27,0.22],[-0.35,0.14],[0,0]],"v":[[84.88,118.02],[82.54,117.11],[81.77,114.63],[81.77,109.34],[83.17,109.34],[83.17,114.42],[83.75,116.21],[85.18,116.74],[86.47,116.36],[87.31,115.36],[87.61,114.09],[87.61,109.34],[89.01,109.34],[89.01,117.75],[87.67,117.75],[87.67,116.53],[87.61,116.53],[86.96,117.27],[86,117.8],[84.88,118.02]],"c":true}}},{"ty":"sh","ks":{"k":{"i":[[0,0],[0.48,0.2],[0.32,0.34],[0.16,0.41],[0,0],[-0.41,-0.27],[-0.54,0],[-0.34,0.21],[0,0.41],[0.14,0.17],[0.28,0.12],[0.39,0.1],[0,0],[0.36,0.2],[0.22,0.31],[0,0.45],[-0.3,0.36],[-0.48,0.19],[-0.55,0],[-0.42,-0.14],[-0.32,-0.27],[-0.16,-0.4],[0,0],[0.35,0.16],[0.43,0],[0.34,-0.21],[0,-0.35],[-0.27,-0.18],[-0.41,-0.11],[0,0],[-0.42,-0.42],[0,-0.59],[0.3,-0.38],[0.51,-0.21],[0.61,0]],"o":[[-0.63,0],[-0.47,-0.21],[-0.31,-0.35],[0,0],[0.22,0.51],[0.42,0.28],[0.52,0],[0.36,-0.21],[0,-0.25],[-0.14,-0.18],[-0.27,-0.12],[0,0],[-0.4,-0.11],[-0.35,-0.2],[-0.21,-0.32],[0,-0.5],[0.3,-0.36],[0.48,-0.2],[0.49,0],[0.43,0.13],[0.32,0.27],[0,0],[-0.21,-0.42],[-0.35,-0.17],[-0.46,0],[-0.34,0.2],[0,0.35],[0.29,0.18],[0,0],[0.82,0.21],[0.42,0.41],[0,0.53],[-0.3,0.39],[-0.5,0.21],[0,0]],"v":[[93.94,118.02],[92.27,117.72],[91.08,116.89],[90.39,115.75],[91.64,115.19],[92.6,116.36],[94.04,116.78],[95.32,116.46],[95.85,115.54],[95.64,114.91],[95.01,114.46],[94,114.14],[92.98,113.87],[91.86,113.41],[91,112.65],[90.68,111.5],[91.13,110.19],[92.3,109.37],[93.85,109.07],[95.21,109.29],[96.33,109.89],[97.05,110.89],[95.83,111.45],[94.99,110.57],[93.82,110.32],[92.62,110.64],[92.11,111.46],[92.52,112.25],[93.55,112.68],[94.78,113],[96.64,113.94],[97.27,115.44],[96.82,116.81],[95.62,117.7],[93.94,118.02]],"c":true}}},{"ty":"sh","ks":{"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[-0.18,-0.24],[-0.42,0],[-0.16,0.05],[-0.12,0.08],[0,0],[0.18,-0.04],[0.29,0],[0.43,0.41],[0,0.74],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0.44],[0.18,0.24],[0.19,0],[0.15,-0.05],[0,0],[-0.14,0.07],[-0.16,0.04],[-0.71,0],[-0.43,-0.42],[0,0],[0,0],[0,0]],"v":[[98.14,109.34],[99.61,109.34],[99.61,106.96],[101.01,106.96],[101.01,109.34],[103.07,109.34],[103.07,110.61],[101.01,110.61],[101.01,115.19],[101.28,116.21],[102.18,116.58],[102.7,116.5],[103.11,116.3],[103.11,117.67],[102.63,117.82],[101.95,117.88],[100.25,117.27],[99.61,115.54],[99.61,110.61],[98.14,110.61]],"c":true}}},{"ty":"sh","ks":{"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[-0.25,0.23],[-0.33,0.13],[-0.33,0],[-0.14,-0.03],[-0.12,-0.05],[0,0],[0.21,0.04],[0.21,0],[0.34,-0.23],[0.21,-0.39],[0,-0.46],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0.11,-0.32],[0.27,-0.24],[0.34,-0.14],[0.25,0],[0.14,0.02],[0,0],[-0.18,-0.09],[-0.2,-0.05],[-0.41,0],[-0.34,0.23],[-0.2,0.38],[0,0],[0,0]],"v":[[104.75,117.75],[104.75,109.34],[106.08,109.34],[106.08,110.69],[106.15,110.69],[106.69,109.86],[107.58,109.3],[108.59,109.09],[109.18,109.14],[109.58,109.25],[109.58,110.77],[109,110.57],[108.39,110.51],[107.27,110.85],[106.45,111.78],[106.15,113.05],[106.15,117.75]],"c":true}}},{"ty":"sh","ks":{"k":{"i":[[0,0],[0.47,0.24],[0.27,0.42],[0,0.54],[-0.32,0.42],[-0.54,0.21],[-0.65,0],[-0.32,-0.07],[-0.23,-0.09],[-0.12,-0.07],[0,0],[0.45,0.38],[0.65,0],[0.41,-0.21],[0.23,-0.36],[0,0],[-0.33,0.24],[-0.42,0.13],[-0.47,0],[-0.65,-0.6],[0,-1.02],[0,0],[0,0],[0,0],[0,0],[0.27,-0.23],[0.36,-0.14],[0.43,0]],"o":[[-0.62,0],[-0.47,-0.24],[-0.27,-0.43],[0,-0.62],[0.32,-0.43],[0.54,-0.22],[0.37,0],[0.32,0.05],[0.24,0.08],[0,0],[0,-0.64],[-0.45,-0.38],[-0.46,0],[-0.39,0.2],[0,0],[0.22,-0.33],[0.33,-0.24],[0.43,-0.13],[1.14,0],[0.65,0.61],[0,0],[0,0],[0,0],[0,0],[-0.14,0.24],[-0.26,0.22],[-0.35,0.14],[0,0]],"v":[[113.3,118.02],[111.65,117.65],[110.53,116.66],[110.13,115.21],[110.61,113.66],[111.89,112.7],[113.68,112.37],[114.72,112.47],[115.54,112.68],[116.09,112.91],[116.09,112.4],[115.41,110.89],[113.76,110.32],[112.46,110.64],[111.52,111.48],[110.46,110.69],[111.29,109.83],[112.41,109.27],[113.76,109.07],[116.45,109.98],[117.42,112.42],[117.42,117.75],[116.09,117.75],[116.09,116.55],[116.02,116.55],[115.41,117.25],[114.47,117.8],[113.3,118.02]],"c":true}}},{"ty":"sh","ks":{"k":{"i":[[0,0],[-0.39,0.24],[-0.24,0.41],[0,0.48],[0.39,0.11],[0.45,0],[0.37,-0.33],[0,-0.48],[-0.35,-0.29],[-0.54,0]],"o":[[0.48,0],[0.41,-0.24],[0.24,-0.41],[-0.25,-0.18],[-0.37,-0.11],[-0.8,0],[-0.38,0.33],[0,0.46],[0.35,0.29],[0,0]],"v":[[113.43,116.78],[114.75,116.41],[115.72,115.44],[116.09,114.1],[115.13,113.68],[113.89,113.51],[112.13,114],[111.57,115.23],[112.09,116.35],[113.43,116.78]],"c":true}}},{"ty":"sh","ks":{"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[-0.18,-0.24],[-0.42,0],[-0.16,0.05],[-0.12,0.08],[0,0],[0.18,-0.04],[0.29,0],[0.43,0.41],[0,0.74],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0.44],[0.18,0.24],[0.19,0],[0.15,-0.05],[0,0],[-0.14,0.07],[-0.16,0.04],[-0.71,0],[-0.43,-0.42],[0,0],[0,0],[0,0]],"v":[[118.64,109.34],[120.11,109.34],[120.11,106.96],[121.51,106.96],[121.51,109.34],[123.57,109.34],[123.57,110.61],[121.51,110.61],[121.51,115.19],[121.77,116.21],[122.68,116.58],[123.19,116.5],[123.6,116.3],[123.6,117.67],[123.12,117.82],[122.45,117.88],[120.75,117.27],[120.11,115.54],[120.11,110.61],[118.64,110.61]],"c":true}}},{"ty":"sh","ks":{"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[125.45,117.75],[125.45,109.34],[126.86,109.34],[126.86,117.75]],"c":true}}},{"ty":"sh","ks":{"k":{"i":[[0,0],[0.2,0.2],[0,0.28],[-0.2,0.19],[-0.27,0],[-0.19,-0.2],[0,-0.29],[0.2,-0.2],[0.29,0]],"o":[[-0.27,0],[-0.2,-0.2],[0,-0.29],[0.2,-0.2],[0.29,0],[0.2,0.19],[0,0.28],[-0.19,0.2],[0,0]],"v":[[126.14,107.79],[125.44,107.49],[125.14,106.78],[125.44,106.07],[126.14,105.77],[126.86,106.07],[127.15,106.78],[126.86,107.49],[126.14,107.79]],"c":true}}},{"ty":"sh","ks":{"k":{"i":[[0,0],[0.65,0.39],[0.37,0.67],[0,0.84],[-0.36,0.67],[-0.65,0.4],[-0.85,0],[-0.65,-0.41],[-0.36,-0.68],[0,-0.82],[0.38,-0.68],[0.65,-0.4],[0.85,0]],"o":[[-0.85,0],[-0.65,-0.4],[-0.36,-0.68],[0,-0.84],[0.37,-0.68],[0.65,-0.39],[0.85,0],[0.65,0.4],[0.38,0.67],[0,0.84],[-0.36,0.67],[-0.65,0.39],[0,0]],"v":[[132.84,118.02],[130.6,117.42],[129.06,115.82],[128.52,113.54],[129.06,111.28],[130.6,109.66],[132.84,109.07],[135.09,109.68],[136.6,111.3],[137.16,113.54],[136.6,115.82],[135.09,117.42],[132.84,118.02]],"c":true}}},{"ty":"sh","ks":{"k":{"i":[[0,0],[-0.44,0.25],[-0.27,0.48],[0,0.67],[0.27,0.47],[0.45,0.25],[0.5,0],[0.45,-0.25],[0.27,-0.48],[0,-0.67],[-0.28,-0.48],[-0.45,-0.25],[-0.51,0]],"o":[[0.5,0],[0.45,-0.25],[0.27,-0.48],[0,-0.67],[-0.27,-0.48],[-0.44,-0.25],[-0.51,0],[-0.45,0.25],[-0.28,0.47],[0,0.67],[0.27,0.48],[0.45,0.25],[0,0]],"v":[[132.84,116.74],[134.26,116.36],[135.35,115.27],[135.76,113.54],[135.35,111.83],[134.26,110.72],[132.84,110.34],[131.41,110.72],[130.32,111.83],[129.9,113.54],[130.32,115.27],[131.41,116.36],[132.84,116.74]],"c":true}}},{"ty":"sh","ks":{"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[-0.5,0.3],[-0.59,0],[-0.52,-0.6],[0,-0.99],[0,0],[0,0],[0,0],[0.39,0.33],[0.62,0],[0.35,-0.27],[0.2,-0.41],[0,-0.45],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0.22,-0.41],[0.5,-0.3],[1.04,0],[0.53,0.6],[0,0],[0,0],[0,0],[0,-0.82],[-0.39,-0.34],[-0.46,0],[-0.35,0.25],[-0.2,0.41],[0,0],[0,0]],"v":[[138.63,117.75],[138.63,109.34],[139.96,109.34],[139.96,110.57],[140.03,110.57],[141.11,109.52],[142.75,109.07],[145.08,109.98],[145.88,112.36],[145.88,117.75],[144.47,117.75],[144.47,112.57],[143.88,110.85],[142.38,110.34],[141.16,110.74],[140.33,111.73],[140.03,113.02],[140.03,117.75]],"c":true}}},{"ty":"sh","ks":{"k":{"i":[[0,0],[0,0],[0,0],[-0.56,-0.3],[-0.33,-0.53],[0,-0.69],[0.34,-0.53],[0.56,-0.3],[0.66,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0.66,0],[0.56,0.3],[0.34,0.52],[0,0.68],[-0.33,0.53],[-0.56,0.3],[0,0],[0,0],[0,0]],"v":[[152,117.75],[152,105.94],[155.97,105.94],[157.8,106.38],[159.14,107.62],[159.65,109.43],[159.14,111.25],[157.8,112.49],[155.97,112.93],[153.4,112.93],[153.4,117.75]],"c":true}}},{"ty":"sh","ks":{"k":{"i":[[0,0],[0,0],[-0.33,0.21],[-0.18,0.33],[0,0.35],[0.18,0.33],[0.33,0.21],[0.46,0],[0,0]],"o":[[0,0],[0.46,0],[0.33,-0.22],[0.18,-0.33],[0,-0.35],[-0.18,-0.33],[-0.33,-0.22],[0,0],[0,0]],"v":[[153.4,111.59],[156,111.59],[157.19,111.28],[157.95,110.46],[158.23,109.43],[157.95,108.41],[157.19,107.6],[156,107.27],[153.4,107.27]],"c":true}}},{"ty":"sh","ks":{"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[161.3,117.75],[161.3,105.94],[162.7,105.94],[162.7,117.75]],"c":true}}},{"ty":"sh","ks":{"k":{"i":[[0,0],[0.47,0.24],[0.28,0.42],[0,0.54],[-0.32,0.42],[-0.54,0.21],[-0.65,0],[-0.32,-0.07],[-0.23,-0.09],[-0.12,-0.07],[0,0],[0.45,0.38],[0.65,0],[0.41,-0.21],[0.23,-0.36],[0,0],[-0.33,0.24],[-0.42,0.13],[-0.47,0],[-0.65,-0.6],[0,-1.02],[0,0],[0,0],[0,0],[0,0],[0.26,-0.23],[0.36,-0.14],[0.43,0]],"o":[[-0.62,0],[-0.47,-0.24],[-0.26,-0.43],[0,-0.62],[0.32,-0.43],[0.54,-0.22],[0.38,0],[0.32,0.05],[0.24,0.08],[0,0],[0,-0.64],[-0.45,-0.38],[-0.46,0],[-0.4,0.2],[0,0],[0.22,-0.33],[0.33,-0.24],[0.43,-0.13],[1.14,0],[0.65,0.61],[0,0],[0,0],[0,0],[0,0],[-0.14,0.24],[-0.27,0.22],[-0.35,0.14],[0,0]],"v":[[167.39,118.02],[165.74,117.65],[164.62,116.66],[164.22,115.21],[164.7,113.66],[165.99,112.7],[167.77,112.37],[168.81,112.47],[169.64,112.68],[170.18,112.91],[170.18,112.4],[169.5,110.89],[167.85,110.32],[166.55,110.64],[165.61,111.48],[164.55,110.69],[165.38,109.83],[166.5,109.27],[167.85,109.07],[170.54,109.98],[171.52,112.42],[171.52,117.75],[170.18,117.75],[170.18,116.55],[170.11,116.55],[169.5,117.25],[168.56,117.8],[167.39,118.02]],"c":true}}},{"ty":"sh","ks":{"k":{"i":[[0,0],[-0.4,0.24],[-0.24,0.41],[0,0.48],[0.38,0.11],[0.45,0],[0.38,-0.33],[0,-0.48],[-0.35,-0.29],[-0.54,0]],"o":[[0.48,0],[0.41,-0.24],[0.24,-0.41],[-0.25,-0.18],[-0.38,-0.11],[-0.8,0],[-0.38,0.33],[0,0.46],[0.35,0.29],[0,0]],"v":[[167.52,116.78],[168.84,116.41],[169.82,115.44],[170.18,114.1],[169.22,113.68],[167.98,113.51],[166.22,114],[165.66,115.23],[166.19,116.35],[167.52,116.78]],"c":true}}},{"ty":"sh","ks":{"k":{"i":[[0,0],[0.65,0.38],[0.36,0.67],[0,0.85],[-0.36,0.67],[-0.64,0.39],[-0.83,0],[-0.61,-0.44],[-0.25,-0.68],[0,0],[0.42,0.27],[0.58,0],[0.43,-0.27],[0.27,-0.48],[0,-0.65],[-0.26,-0.48],[-0.42,-0.27],[-0.5,0],[-0.43,0.27],[-0.21,0.5],[0,0],[0.62,-0.45],[0.95,0]],"o":[[-0.83,0],[-0.64,-0.4],[-0.36,-0.68],[0,-0.86],[0.36,-0.67],[0.65,-0.39],[0.95,0],[0.62,0.43],[0,0],[-0.21,-0.52],[-0.41,-0.27],[-0.5,0],[-0.42,0.26],[-0.26,0.47],[0,0.64],[0.27,0.48],[0.43,0.27],[0.59,0],[0.43,-0.28],[0,0],[-0.28,0.65],[-0.61,0.45],[0,0]],"v":[[177.2,118.02],[174.99,117.44],[173.48,115.84],[172.94,113.54],[173.48,111.25],[174.99,109.66],[177.2,109.07],[179.52,109.73],[180.83,111.4],[179.56,111.93],[178.62,110.75],[177.13,110.34],[175.75,110.74],[174.72,111.86],[174.33,113.54],[174.72,115.23],[175.75,116.35],[177.13,116.74],[178.67,116.33],[179.62,115.16],[180.88,115.69],[179.54,117.34],[177.2,118.02]],"c":true}}},{"ty":"sh","ks":{"k":{"i":[[0,0],[0.64,0.38],[0.36,0.67],[0,0.86],[-0.33,0.68],[-0.61,0.41],[-0.82,0],[-0.6,-0.38],[-0.32,-0.65],[0,-0.84],[0.01,-0.08],[0.01,-0.05],[0,0],[-0.24,-0.42],[-0.43,-0.23],[-0.47,0],[-0.39,0.29],[-0.24,0.43],[0,0],[0.62,-0.42],[0.93,0]],"o":[[-0.81,0],[-0.62,-0.39],[-0.35,-0.67],[0,-0.8],[0.34,-0.68],[0.62,-0.42],[0.84,0],[0.6,0.36],[0.33,0.65],[0,0.07],[0,0.08],[0,0],[0.04,0.61],[0.27,0.47],[0.44,0.23],[0.61,0],[0.41,-0.3],[0,0],[-0.33,0.64],[-0.61,0.42],[0,0]],"v":[[186.06,118.02],[183.88,117.44],[182.39,115.85],[181.87,113.56],[182.36,111.33],[183.78,109.7],[185.94,109.07],[188.09,109.63],[189.47,111.15],[189.97,113.38],[189.95,113.61],[189.94,113.8],[183.27,113.8],[183.68,115.34],[184.74,116.4],[186.11,116.74],[187.62,116.32],[188.6,115.23],[189.79,115.8],[188.37,117.39],[186.06,118.02]],"c":true}}},{"ty":"sh","ks":{"k":{"i":[[0,0],[0,0],[0.1,0.26],[0.21,0.23],[0.32,0.14],[0.46,0],[0.41,-0.29],[0.21,-0.48],[0.05,-0.27]],"o":[[0,0],[-0.01,-0.23],[-0.09,-0.28],[-0.2,-0.24],[-0.31,-0.16],[-0.55,0],[-0.39,0.28],[-0.11,0.23],[0,0]],"v":[[183.35,112.65],[188.48,112.65],[188.32,111.91],[187.88,111.15],[187.1,110.57],[185.94,110.34],[184.51,110.77],[183.6,111.91],[183.35,112.65]],"c":true}}},{"ty":"sh","ks":{"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[-0.5,0.29],[-0.61,0],[-0.46,-0.29],[-0.23,-0.5],[0,-0.64],[0,0],[0,0],[0,0],[0.2,0.32],[0.32,0.14],[0.38,0],[0.36,-0.27],[0.21,-0.42],[0,-0.45],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0.22,-0.42],[0.51,-0.3],[0.71,0],[0.46,0.29],[0.23,0.48],[0,0],[0,0],[0,0],[0,-0.54],[-0.19,-0.33],[-0.31,-0.16],[-0.46,0],[-0.36,0.26],[-0.21,0.42],[0,0],[0,0]],"v":[[191.61,117.75],[191.61,105.94],[193.01,105.94],[193.01,109.42],[192.95,110.57],[193.01,110.57],[194.09,109.52],[195.75,109.07],[197.52,109.5],[198.56,110.67],[198.9,112.36],[198.9,117.75],[197.5,117.75],[197.5,112.57],[197.2,111.28],[196.45,110.57],[195.42,110.34],[194.18,110.74],[193.33,111.76],[193.01,113.06],[193.01,117.75]],"c":true}}},{"ty":"sh","ks":{"k":{"i":[[0,0],[0.65,0.39],[0.38,0.67],[0,0.84],[-0.36,0.67],[-0.65,0.4],[-0.85,0],[-0.65,-0.41],[-0.36,-0.68],[0,-0.82],[0.38,-0.68],[0.65,-0.4],[0.85,0]],"o":[[-0.85,0],[-0.65,-0.4],[-0.36,-0.68],[0,-0.84],[0.38,-0.68],[0.65,-0.39],[0.85,0],[0.65,0.4],[0.38,0.67],[0,0.84],[-0.36,0.67],[-0.65,0.39],[0,0]],"v":[[204.8,118.02],[202.56,117.42],[201.02,115.82],[200.48,113.54],[201.02,111.28],[202.56,109.66],[204.8,109.07],[207.05,109.68],[208.56,111.3],[209.12,113.54],[208.56,115.82],[207.05,117.42],[204.8,118.02]],"c":true}}},{"ty":"sh","ks":{"k":{"i":[[0,0],[-0.44,0.25],[-0.27,0.48],[0,0.67],[0.28,0.47],[0.45,0.25],[0.51,0],[0.45,-0.25],[0.27,-0.48],[0,-0.67],[-0.27,-0.48],[-0.45,-0.25],[-0.5,0]],"o":[[0.51,0],[0.45,-0.25],[0.28,-0.48],[0,-0.67],[-0.27,-0.48],[-0.44,-0.25],[-0.5,0],[-0.45,0.25],[-0.27,0.47],[0,0.67],[0.27,0.48],[0.45,0.25],[0,0]],"v":[[204.8,116.74],[206.22,116.36],[207.31,115.27],[207.72,113.54],[207.31,111.83],[206.22,110.72],[204.8,110.34],[203.37,110.72],[202.28,111.83],[201.86,113.54],[202.28,115.27],[203.37,116.36],[204.8,116.74]],"c":true}}},{"ty":"sh","ks":{"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[210.67,117.75],[210.67,105.94],[212.07,105.94],[212.07,117.75]],"c":true}}},{"ty":"sh","ks":{"k":{"i":[[0,0],[0.62,0.38],[0.35,0.67],[0,0.87],[-0.35,0.67],[-0.61,0.39],[-0.76,0],[-0.38,-0.14],[-0.27,-0.23],[-0.15,-0.27],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0.29,-0.23],[0.38,-0.14],[0.45,0]],"o":[[-0.76,0],[-0.61,-0.39],[-0.35,-0.67],[0,-0.87],[0.35,-0.67],[0.62,-0.38],[0.45,0],[0.38,0.14],[0.29,0.23],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[-0.15,0.25],[-0.27,0.23],[-0.38,0.14],[0,0]],"v":[[217.65,118.02],[215.59,117.44],[214.16,115.85],[213.63,113.54],[214.16,111.23],[215.59,109.65],[217.65,109.07],[218.89,109.29],[219.86,109.85],[220.52,110.59],[220.59,110.59],[220.52,109.42],[220.52,105.94],[221.93,105.94],[221.93,117.75],[220.59,117.75],[220.59,116.51],[220.52,116.51],[219.86,117.24],[218.89,117.8],[217.65,118.02]],"c":true}}},{"ty":"sh","ks":{"k":{"i":[[0,0],[-0.42,0.25],[-0.27,0.47],[0,0.66],[0.26,0.47],[0.43,0.25],[0.48,0],[0.43,-0.27],[0.26,-0.48],[0,-0.66],[-0.27,-0.48],[-0.42,-0.26],[-0.48,0]],"o":[[0.48,0],[0.43,-0.26],[0.26,-0.48],[0,-0.66],[-0.27,-0.48],[-0.42,-0.27],[-0.48,0],[-0.42,0.25],[-0.27,0.47],[0,0.65],[0.26,0.48],[0.43,0.25],[0,0]],"v":[[217.8,116.74],[219.16,116.36],[220.2,115.26],[220.59,113.54],[220.2,111.84],[219.16,110.74],[217.8,110.34],[216.43,110.74],[215.41,111.84],[215.01,113.54],[215.41,115.24],[216.43,116.36],[217.8,116.74]],"c":true}}},{"ty":"sh","ks":{"k":{"i":[[0,0],[0.64,0.38],[0.36,0.67],[0,0.86],[-0.33,0.68],[-0.61,0.41],[-0.82,0],[-0.59,-0.38],[-0.32,-0.65],[0,-0.84],[0.01,-0.08],[0.01,-0.05],[0,0],[-0.24,-0.42],[-0.43,-0.23],[-0.47,0],[-0.39,0.29],[-0.24,0.43],[0,0],[0.62,-0.42],[0.92,0]],"o":[[-0.82,0],[-0.62,-0.39],[-0.35,-0.67],[0,-0.8],[0.34,-0.68],[0.62,-0.42],[0.84,0],[0.61,0.36],[0.33,0.65],[0,0.07],[0,0.08],[0,0],[0.03,0.61],[0.27,0.47],[0.44,0.23],[0.62,0],[0.41,-0.3],[0,0],[-0.33,0.64],[-0.61,0.42],[0,0]],"v":[[227.66,118.02],[225.48,117.44],[224,115.85],[223.47,113.56],[223.97,111.33],[225.39,109.7],[227.55,109.07],[229.69,109.63],[231.08,111.15],[231.57,113.38],[231.56,113.61],[231.54,113.8],[224.88,113.8],[225.29,115.34],[226.34,116.4],[227.71,116.74],[229.23,116.32],[230.2,115.23],[231.39,115.8],[229.97,117.39],[227.66,118.02]],"c":true}}},{"ty":"sh","ks":{"k":{"i":[[0,0],[0,0],[0.1,0.26],[0.21,0.23],[0.32,0.14],[0.46,0],[0.41,-0.29],[0.21,-0.48],[0.05,-0.27]],"o":[[0,0],[-0.01,-0.23],[-0.09,-0.28],[-0.2,-0.24],[-0.31,-0.16],[-0.55,0],[-0.4,0.28],[-0.11,0.23],[0,0]],"v":[[224.96,112.65],[230.09,112.65],[229.92,111.91],[229.48,111.15],[228.7,110.57],[227.55,110.34],[226.11,110.77],[225.2,111.91],[224.96,112.65]],"c":true}}},{"ty":"sh","ks":{"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[-0.25,0.23],[-0.33,0.13],[-0.33,0],[-0.14,-0.03],[-0.12,-0.05],[0,0],[0.21,0.04],[0.21,0],[0.34,-0.23],[0.21,-0.39],[0,-0.46],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0.11,-0.32],[0.26,-0.24],[0.34,-0.14],[0.25,0],[0.14,0.02],[0,0],[-0.18,-0.09],[-0.2,-0.05],[-0.41,0],[-0.34,0.23],[-0.2,0.38],[0,0],[0,0]],"v":[[233.21,117.75],[233.21,109.34],[234.55,109.34],[234.55,110.69],[234.62,110.69],[235.16,109.86],[236.05,109.3],[237.06,109.09],[237.65,109.14],[238.05,109.25],[238.05,110.77],[237.47,110.57],[236.86,110.51],[235.74,110.85],[234.91,111.78],[234.62,113.05],[234.62,117.75]],"c":true}}},{"ty":"fl","o":{"k":100},"c":{"k":[1,1,1,1]}},{"ty":"tr","o":{"k":100},"r":{"k":0},"p":{"k":[0,0]},"a":{"k":[0,0]},"s":{"k":[100,100]},"sk":{"k":0},"sa":{"k":0},"hd":false}]},{"ty":"gr","hd":false,"it":[{"ty":"sh","ks":{"k":{"i":[[0,0],[-0.28,-0.28],[0,-0.4],[0,0],[0.28,-0.28],[0.4,0],[0.28,0.28],[0,0.4],[0,0],[-0.28,0.28],[-0.39,0]],"o":[[0.4,0],[0.28,0.28],[0,0],[0,0.4],[-0.28,0.28],[-0.39,0],[-0.28,-0.28],[0,0],[0,-0.4],[0.28,-0.28],[0,0]],"v":[[152.68,8.9],[153.75,9.34],[154.18,10.4],[154.18,219.91],[153.75,220.97],[152.68,221.41],[151.62,220.97],[151.18,219.91],[151.18,10.4],[151.62,9.34],[152.68,8.9]],"c":true}}},{"ty":"sh","ks":{"k":{"i":[[0,0],[0.7,-0.7],[0,-1],[0,0],[-0.7,-0.7],[-0.99,0],[-0.7,0.7],[0,1],[0,0],[0.7,0.7],[1,0]],"o":[[-0.99,0],[-0.7,0.7],[0,0],[0,1],[0.7,0.7],[1,0],[0.7,-0.7],[0,0],[0,-1],[-0.7,-0.7],[0,0]],"v":[[152.68,6.65],[150.04,7.75],[148.93,10.4],[148.93,219.91],[150.04,222.56],[152.68,223.66],[155.34,222.56],[156.43,219.91],[156.43,10.4],[155.34,7.75],[152.68,6.65]],"c":true}}},{"ty":"fl","o":{"k":100},"c":{"k":[0,0,0,1]}},{"ty":"tr","o":{"k":100},"r":{"k":0},"p":{"k":[0,0]},"a":{"k":[0,0]},"s":{"k":[100,100]},"sk":{"k":0},"sa":{"k":0},"hd":false}]},{"ty":"gr","hd":false,"it":[{"ty":"sh","ks":{"k":{"i":[[0,0],[0.49,-0.49],[0.7,0],[0.49,0.49],[0,0.7],[0,0],[-0.49,0.49],[-0.7,0],[-0.49,-0.49],[0,-0.7]],"o":[[0,0.7],[-0.49,0.49],[-0.7,0],[-0.49,-0.49],[0,0],[0,-0.7],[0.49,-0.49],[0.7,0],[0.49,0.49],[0,0]],"v":[[155.31,219.91],[154.54,221.77],[152.68,222.53],[150.83,221.77],[150.06,219.91],[150.06,10.4],[150.83,8.55],[152.68,7.78],[154.54,8.55],[155.31,10.4]],"c":false}}},{"ty":"fl","o":{"k":100},"c":{"k":[0.5,0.53,0.55,1]}},{"ty":"tr","o":{"k":100},"r":{"k":0},"p":{"k":[0,0]},"a":{"k":[0,0]},"s":{"k":[100,100]},"sk":{"k":0},"sa":{"k":0},"hd":false}]},{"ty":"gr","hd":false,"it":[{"ty":"sh","ks":{"k":{"i":[[0,0],[0.46,-0.2],[0.35,-0.36],[0.19,-0.46],[0,-0.5],[0,0],[-0.19,-0.46],[-0.36,-0.36],[-0.46,-0.19],[-0.5,0],[0,0],[0,0]],"o":[[-0.5,0],[-0.46,0.19],[-0.36,0.36],[-0.19,0.46],[0,0],[0,0.5],[0.19,0.46],[0.36,0.36],[0.46,0.19],[0,0],[0,0],[0,0]],"v":[[143.14,99.62],[141.68,99.91],[140.44,100.75],[139.62,101.99],[139.34,103.45],[139.34,124.53],[139.62,126],[140.46,127.24],[141.7,128.07],[143.17,128.36],[149.38,128.36],[149.38,99.62]],"c":true}}},{"ty":"fl","o":{"k":100},"c":{"k":[0.4,0.62,0.96,1]}},{"ty":"tr","o":{"k":100},"r":{"k":0},"p":{"k":[0,0]},"a":{"k":[0,0]},"s":{"k":[100,100]},"sk":{"k":0},"sa":{"k":0},"hd":false}]},{"ty":"gr","hd":false,"it":[{"ty":"sh","ks":{"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[133.1,173.75],[129.75,173.75],[129.75,175.95],[133.1,175.95]],"c":true}}},{"ty":"fl","o":{"k":100},"c":{"k":[0.93,0.4,0.36,1]}},{"ty":"tr","o":{"k":100},"r":{"k":0},"p":{"k":[0,0]},"a":{"k":[0,0]},"s":{"k":[100,100]},"sk":{"k":0},"sa":{"k":0},"hd":false}]},{"ty":"gr","hd":false,"it":[{"ty":"sh","ks":{"k":{"i":[[0,0],[0,0],[0,-0.31],[0,0],[-0.31,0],[0,0],[0,0.31],[0,0],[0.31,0]],"o":[[0,0],[-0.31,0],[0,0],[0,0.31],[0,0],[0.31,0],[0,0],[0,-0.31],[0,0]],"v":[[139.45,171.75],[128.83,171.75],[128.27,172.31],[128.27,181.27],[128.83,181.83],[139.45,181.83],[140.01,181.27],[140.01,172.31],[139.45,171.75]],"c":true}}},{"ty":"fl","o":{"k":100},"c":{"k":[0,0,0,1]}},{"ty":"tr","o":{"k":100},"r":{"k":0},"p":{"k":[0,0]},"a":{"k":[0,0]},"s":{"k":[100,100]},"sk":{"k":0},"sa":{"k":0},"hd":false}]},{"ty":"gr","hd":false,"it":[{"ty":"sh","ks":{"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0.3,-0.31],[0,-0.44],[0,0],[-0.08,-0.2],[-0.16,-0.15],[-0.2,-0.08],[-0.22,0],[0,0],[-0.31,0.31],[0,0.44],[0,0],[0.3,0.31],[0.43,0.01]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[-0.44,0.01],[-0.31,0.31],[0,0],[0,0.22],[0.09,0.2],[0.15,0.16],[0.2,0.09],[0,0],[0.45,0],[0.31,-0.32],[0,0],[0,-0.43],[-0.3,-0.31],[0,0]],"v":[[140.01,168.45],[140.01,167.25],[138.51,167.25],[138.51,168.44],[129.75,168.44],[129.75,167.25],[128.25,167.25],[128.25,168.45],[127.09,168.95],[126.61,170.11],[126.61,181.79],[126.74,182.43],[127.1,182.97],[127.64,183.34],[128.28,183.46],[139.96,183.46],[141.15,182.98],[141.64,181.79],[141.64,170.11],[141.16,168.95],[140.01,168.45]],"c":true}}},{"ty":"fl","o":{"k":100},"c":{"k":[0.98,0.82,0.81,1]}},{"ty":"tr","o":{"k":100},"r":{"k":0},"p":{"k":[0,0]},"a":{"k":[0,0]},"s":{"k":[100,100]},"sk":{"k":0},"sa":{"k":0},"hd":false}]},{"ty":"gr","hd":false,"it":[{"ty":"sh","ks":{"k":{"i":[[0,0],[0,0],[0,-3.02],[-3.02,0],[0,0],[0,3.02],[3.02,0]],"o":[[0,0],[-3.02,0],[0,3.02],[0,0],[3.02,0],[0,-3.02],[0,0]],"v":[[135.43,141.31],[128.9,141.31],[123.43,146.77],[128.9,152.24],[135.43,152.24],[140.89,146.77],[135.43,141.31]],"c":true}}},{"ty":"fl","o":{"k":100},"c":{"k":[0.24,0.25,0.26,1]}},{"ty":"tr","o":{"k":100},"r":{"k":0},"p":{"k":[0,0]},"a":{"k":[0,0]},"s":{"k":[100,100]},"sk":{"k":0},"sa":{"k":0},"hd":false}]},{"ty":"gr","hd":false,"it":[{"ty":"sh","ks":{"k":{"i":[[0,0],[0.32,0.77],[0.59,0.59],[0.77,0.32],[0.84,0],[0,0],[-0.78,0.32],[-0.59,0.59],[-0.32,0.78],[0,0.84]],"o":[[0,-0.84],[-0.32,-0.77],[-0.59,-0.59],[-0.78,-0.32],[0,0],[0.84,0],[0.77,-0.32],[0.59,-0.59],[0.32,-0.77],[0,0]],"v":[[160.66,109.21],[160.18,106.77],[158.8,104.7],[156.73,103.31],[154.29,102.83],[154.29,115.58],[156.73,115.1],[158.8,113.72],[160.18,111.65],[160.66,109.21]],"c":true}}},{"ty":"fl","o":{"k":100},"c":{"k":[0.63,0.26,0.96,1]}},{"ty":"tr","o":{"k":100},"r":{"k":0},"p":{"k":[0,0]},"a":{"k":[0,0]},"s":{"k":[100,100]},"sk":{"k":0},"sa":{"k":0},"hd":false}]},{"ty":"gr","hd":false,"it":[{"ty":"sh","ks":{"k":{"i":[[0,0],[3.41,3.42],[4.83,0],[0,0],[-2.31,-1.43],[-1.21,-2.43],[0.26,-2.7],[1.65,-2.16],[2.66,0],[0,0],[-2.21,0.92],[-1.69,1.7],[-0.91,2.21],[0,2.39]],"o":[[0,-4.83],[-3.42,-3.41],[0,0],[2.71,-0.01],[2.3,1.43],[1.2,2.43],[-0.26,2.7],[-2.6,-3.17],[0,0],[2.39,0],[2.21,-0.92],[1.69,-1.69],[0.91,-2.21],[0,0]],"v":[[172.5,113.76],[167.16,100.88],[154.29,95.54],[154.29,99.19],[161.97,101.36],[167.34,107.27],[168.79,115.12],[165.87,122.56],[154.29,118.31],[154.29,132],[161.26,130.61],[167.17,126.65],[171.12,120.74],[172.5,113.76]],"c":true}}},{"ty":"fl","o":{"k":100},"c":{"k":[0.63,0.26,0.96,1]}},{"ty":"tr","o":{"k":100},"r":{"k":0},"p":{"k":[0,0]},"a":{"k":[0,0]},"s":{"k":[100,100]},"sk":{"k":0},"sa":{"k":0},"hd":false}]},{"ty":"gr","hd":false,"it":[{"ty":"sh","ks":{"k":{"i":[[0,0],[0,2.16],[2.16,0],[0,-2.17],[-2.17,0]],"o":[[2.16,0],[0,-2.17],[-2.17,0],[0,2.16],[0,0]],"v":[[169.88,76.43],[173.8,72.51],[169.88,68.59],[165.95,72.51],[169.88,76.43]],"c":true}}},{"ty":"fl","o":{"k":100},"c":{"k":[0.24,0.25,0.26,1]}},{"ty":"tr","o":{"k":100},"r":{"k":0},"p":{"k":[0,0]},"a":{"k":[0,0]},"s":{"k":[100,100]},"sk":{"k":0},"sa":{"k":0},"hd":false}]},{"ty":"gr","hd":false,"it":[{"ty":"sh","ks":{"k":{"i":[[0,0],[0,0],[-0.12,0.05],[-0.13,0],[-0.12,-0.05],[-0.09,-0.09],[0,0],[0,-1.54],[1.09,-1.09],[1.54,0],[1.09,1.09],[0,0],[0,0.26],[-0.18,0.18]],"o":[[0,0],[0.09,-0.09],[0.12,-0.05],[0.13,0],[0.12,0.05],[0,0],[1.09,1.09],[0,1.54],[-1.09,1.09],[-1.54,0],[0,0],[-0.18,-0.18],[0,-0.26],[0,0]],"v":[[171.3,184.95],[178.15,178.11],[178.46,177.89],[178.84,177.82],[179.21,177.89],[179.53,178.11],[179.77,178.35],[181.47,182.46],[179.77,186.57],[175.66,188.27],[171.54,186.57],[171.3,186.33],[171.02,185.64],[171.3,184.95]],"c":true}}},{"ty":"fl","o":{"k":100},"c":{"k":[0.24,0.25,0.26,1]}},{"ty":"tr","o":{"k":100},"r":{"k":0},"p":{"k":[0,0]},"a":{"k":[0,0]},"s":{"k":[100,100]},"sk":{"k":0},"sa":{"k":0},"hd":false}]},{"ty":"gr","hd":false,"it":[{"ty":"sh","ks":{"k":{"i":[[0,0],[0,0],[0,-0.25],[-0.12,-0.21],[-0.21,-0.12],[-0.25,0],[0,0],[-0.22,0.12],[-0.12,0.22],[0,0.25],[0.12,0.21],[0,0],[0.21,0.12],[0.25,0],[0.22,-0.12],[0.12,-0.21]],"o":[[0,0],[-0.12,0.21],[0,0.25],[0.12,0.21],[0.21,0.12],[0,0],[0.25,0],[0.21,-0.12],[0.12,-0.21],[0,-0.25],[0,0],[-0.12,-0.21],[-0.21,-0.12],[-0.25,0],[-0.21,0.12],[0,0]],"v":[[174.89,45],[171,51.75],[170.81,52.46],[171,53.16],[171.52,53.68],[172.23,53.87],[180,53.87],[180.71,53.68],[181.23,53.16],[181.41,52.46],[181.22,51.75],[177.34,45],[176.82,44.48],[176.11,44.29],[175.4,44.48],[174.89,45]],"c":true}}},{"ty":"fl","o":{"k":100},"c":{"k":[0.98,0.82,0.81,1]}},{"ty":"tr","o":{"k":100},"r":{"k":0},"p":{"k":[0,0]},"a":{"k":[0,0]},"s":{"k":[100,100]},"sk":{"k":0},"sa":{"k":0},"hd":false}]},{"ty":"gr","hd":false,"it":[{"ty":"sh","ks":{"k":{"i":[[0,0],[0,0],[-0.08,-0.19],[-0.14,-0.15],[-0.19,-0.08],[-0.21,0],[0,0]],"o":[[0,0],[0,0.21],[0.08,0.19],[0.14,0.14],[0.19,0.08],[0,0],[0,0]],"v":[[140.9,52.17],[140.9,55.98],[141.02,56.58],[141.36,57.09],[141.87,57.43],[142.47,57.55],[146.25,57.55]],"c":true}}},{"ty":"fl","o":{"k":100},"c":{"k":[1,0.94,0.76,1]}},{"ty":"tr","o":{"k":100},"r":{"k":0},"p":{"k":[0,0]},"a":{"k":[0,0]},"s":{"k":[100,100]},"sk":{"k":0},"sa":{"k":0},"hd":false}]},{"ty":"gr","hd":false,"it":[{"ty":"sh","ks":{"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[143.15,68.59],[133.32,68.59],[133.32,70.55],[143.15,70.55]],"c":true}}},{"ty":"fl","o":{"k":100},"c":{"k":[0,0,0,1]}},{"ty":"tr","o":{"k":100},"r":{"k":0},"p":{"k":[0,0]},"a":{"k":[0,0]},"s":{"k":[100,100]},"sk":{"k":0},"sa":{"k":0},"hd":false}]},{"ty":"gr","hd":false,"it":[{"ty":"sh","ks":{"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[143.15,64.65],[133.32,64.65],[133.32,66.61],[143.15,66.61]],"c":true}}},{"ty":"fl","o":{"k":100},"c":{"k":[0,0,0,1]}},{"ty":"tr","o":{"k":100},"r":{"k":0},"p":{"k":[0,0]},"a":{"k":[0,0]},"s":{"k":[100,100]},"sk":{"k":0},"sa":{"k":0},"hd":false}]},{"ty":"gr","hd":false,"it":[{"ty":"sh","ks":{"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[143.15,60.72],[133.32,60.72],[133.32,62.68],[143.15,62.68]],"c":true}}},{"ty":"fl","o":{"k":100},"c":{"k":[0,0,0,1]}},{"ty":"tr","o":{"k":100},"r":{"k":0},"p":{"k":[0,0]},"a":{"k":[0,0]},"s":{"k":[100,100]},"sk":{"k":0},"sa":{"k":0},"hd":false}]},{"ty":"gr","hd":false,"it":[{"ty":"sh","ks":{"k":{"i":[[0,0],[0,0],[0.24,-0.1],[0.18,-0.18],[0.1,-0.24],[0,-0.26],[0,0],[-0.1,-0.24],[-0.18,-0.18],[-0.24,-0.1],[-0.26,0],[0,0],[-0.36,0.37],[0,0.52],[0,0]],"o":[[0,0],[-0.26,0],[-0.24,0.1],[-0.18,0.18],[-0.1,0.24],[0,0],[0,0.26],[0.1,0.24],[0.18,0.18],[0.24,0.1],[0,0],[0.52,-0.01],[0.36,-0.37],[0,0],[0,0]],"v":[[140.9,52.17],[132.16,52.17],[131.41,52.32],[130.77,52.75],[130.34,53.38],[130.19,54.13],[130.19,71.84],[130.34,72.59],[130.77,73.22],[131.41,73.65],[132.16,73.8],[144.32,73.8],[145.68,73.21],[146.25,71.84],[146.25,57.59]],"c":true}}},{"ty":"fl","o":{"k":100},"c":{"k":[0.99,0.79,0.2,1]}},{"ty":"tr","o":{"k":100},"r":{"k":0},"p":{"k":[0,0]},"a":{"k":[0,0]},"s":{"k":[100,100]},"sk":{"k":0},"sa":{"k":0},"hd":false}]},{"ty":"gr","hd":false,"it":[{"ty":"sh","ks":{"k":{"i":[[0,0],[0,0.8],[0.8,0],[0,-0.8],[-0.8,0]],"o":[[0.8,0],[0,-0.8],[-0.8,0],[0,0.8],[0,0]],"v":[[182.72,163],[184.18,161.54],[182.72,160.09],[181.27,161.54],[182.72,163]],"c":true}}},{"ty":"fl","o":{"k":100},"c":{"k":[0,0,0,1]}},{"ty":"tr","o":{"k":100},"r":{"k":0},"p":{"k":[0,0]},"a":{"k":[0,0]},"s":{"k":[100,100]},"sk":{"k":0},"sa":{"k":0},"hd":false}]},{"ty":"gr","hd":false,"it":[{"ty":"sh","ks":{"k":{"i":[[0,0],[0,0.8],[0.8,0],[0,-0.8],[-0.8,0]],"o":[[0.8,0],[0,-0.8],[-0.8,0],[0,0.8],[0,0]],"v":[[178.3,163],[179.76,161.54],[178.3,160.09],[176.85,161.54],[178.3,163]],"c":true}}},{"ty":"fl","o":{"k":100},"c":{"k":[0,0,0,1]}},{"ty":"tr","o":{"k":100},"r":{"k":0},"p":{"k":[0,0]},"a":{"k":[0,0]},"s":{"k":[100,100]},"sk":{"k":0},"sa":{"k":0},"hd":false}]},{"ty":"gr","hd":false,"it":[{"ty":"sh","ks":{"k":{"i":[[0,0],[0,0.8],[0.8,0],[0,-0.8],[-0.8,0]],"o":[[0.8,0],[0,-0.8],[-0.8,0],[0,0.8],[0,0]],"v":[[173.88,163],[175.34,161.54],[173.88,160.09],[172.43,161.54],[173.88,163]],"c":true}}},{"ty":"fl","o":{"k":100},"c":{"k":[0,0,0,1]}},{"ty":"tr","o":{"k":100},"r":{"k":0},"p":{"k":[0,0]},"a":{"k":[0,0]},"s":{"k":[100,100]},"sk":{"k":0},"sa":{"k":0},"hd":false}]},{"ty":"gr","hd":false,"it":[{"ty":"sh","ks":{"k":{"i":[[0,0],[0,0],[1.23,-0.83],[0.56,-1.37],[-0.3,-1.45],[-1.06,-1.04],[0,0],[0,0],[0,0],[-1.4,1.4],[0,1.98],[1.4,1.39],[1.98,0]],"o":[[0,0],[-1.48,0],[-1.23,0.83],[-0.56,1.38],[0.3,1.45],[0,0],[0,0],[0,0],[1.98,0],[1.4,-1.39],[0,-1.98],[-1.4,-1.4],[0,0]],"v":[[181.74,154.09],[174.86,154.09],[170.7,155.36],[167.96,158.73],[167.57,163.06],[169.66,166.88],[174.34,171.56],[176.91,169],[181.74,169],[187.01,166.81],[189.2,161.54],[187.01,156.27],[181.74,154.09]],"c":true}}},{"ty":"fl","o":{"k":100},"c":{"k":[0.36,0.73,0.45,1]}},{"ty":"tr","o":{"k":100},"r":{"k":0},"p":{"k":[0,0]},"a":{"k":[0,0]},"s":{"k":[100,100]},"sk":{"k":0},"sa":{"k":0},"hd":false}]},{"ty":"gr","hd":false,"it":[{"ty":"sh","ks":{"k":{"i":[[0,0],[0,0],[1.34,-1.34],[0,-1.89],[0,0],[0,0],[0,0],[-0.77,0.77],[-1.1,0],[0,0],[-0.78,-0.78],[0,-1.1],[0,0],[0,0],[0,0],[1.34,1.34],[1.89,0]],"o":[[0,0],[-1.89,0],[-1.34,1.34],[0,0],[0,0],[0,0],[0,-1.1],[0.77,-0.78],[0,0],[1.1,0],[0.77,0.77],[0,0],[0,0],[0,0],[0,-1.89],[-1.34,-1.34],[0,0]],"v":[[195.3,19.79],[113.25,19.79],[108.21,21.88],[106.12,26.92],[106.12,109.3],[109.12,109.3],[109.12,26.92],[110.33,24],[113.25,22.79],[195.3,22.79],[198.22,24],[199.43,26.92],[199.43,161.52],[202.43,161.52],[202.43,26.92],[200.34,21.88],[195.3,19.79]],"c":true}}},{"ty":"fl","o":{"k":100},"c":{"k":[0.63,0.26,0.96,1]}},{"ty":"tr","o":{"k":100},"r":{"k":0},"p":{"k":[0,0]},"a":{"k":[0,0]},"s":{"k":[100,100]},"sk":{"k":0},"sa":{"k":0},"hd":false}]},{"ty":"gr","hd":false,"it":[{"ty":"sh","ks":{"k":{"i":[[0,0],[0.77,-0.77],[1.1,0],[0,0],[0.77,0.78],[0,1.1],[0,0],[0,0],[0,0],[-1.34,-1.34],[-1.89,0],[0,0],[-1.34,1.34],[0,1.89],[0,0],[0,0]],"o":[[0,1.1],[-0.78,0.78],[0,0],[-1.1,0],[-0.77,-0.77],[0,0],[0,0],[0,0],[0,1.89],[1.34,1.34],[0,0],[1.89,0],[1.34,-1.34],[0,0],[0,0],[0,0]],"v":[[199.43,198.08],[198.22,201],[195.3,202.21],[113.25,202.21],[110.33,201],[109.12,198.08],[109.12,109.3],[106.12,109.3],[106.12,198.08],[108.21,203.12],[113.25,205.21],[195.3,205.21],[200.34,203.12],[202.43,198.08],[202.43,161.52],[199.43,161.52]],"c":true}}},{"ty":"fl","o":{"k":100},"c":{"k":[0.4,0.62,0.96,1]}},{"ty":"tr","o":{"k":100},"r":{"k":0},"p":{"k":[0,0]},"a":{"k":[0,0]},"s":{"k":[100,100]},"sk":{"k":0},"sa":{"k":0},"hd":false}]},{"ty":"gr","hd":false,"it":[{"ty":"sh","ks":{"k":{"i":[[0,0],[0,0],[0.34,0.34],[0.47,0],[0,0],[1.67,1.67],[2.36,0],[0,0],[1.67,-1.67],[0,-2.36],[0,0],[-1.67,-1.67],[-2.36,0],[0,0],[-1.67,1.67],[0,2.36],[0,0],[-0.33,0.33],[0,0.47],[0,0],[0.34,0.34],[0.47,0],[0,0],[-0.21,0.09],[-0.16,0.17],[-0.09,0.22],[0,0.23]],"o":[[0,0],[0,-0.47],[-0.33,-0.34],[0,0],[0,-2.36],[-1.67,-1.67],[0,0],[-2.36,0],[-1.67,1.67],[0,0],[0,2.36],[1.67,1.67],[0,0],[2.36,0],[1.67,-1.67],[0,0],[0.47,0],[0.34,-0.34],[0,0],[0,-0.47],[-0.33,-0.34],[0,0],[0.24,0],[0.22,-0.09],[0.16,-0.17],[0.09,-0.21],[0,0]],"v":[[206,73.27],[206,66.14],[205.47,64.88],[204.21,64.36],[204.21,26.92],[201.6,20.61],[195.3,18],[113.25,18],[106.95,20.61],[104.33,26.92],[104.33,198.08],[106.95,204.39],[113.25,207],[195.3,207],[201.6,204.39],[204.21,198.08],[204.21,110.71],[205.47,110.2],[206,108.94],[206,91.1],[205.47,89.84],[204.21,89.32],[204.21,75.07],[204.9,74.93],[205.48,74.54],[205.86,73.96],[206,73.27]],"c":true}}},{"ty":"sh","ks":{"k":{"i":[[0,0],[1.34,-1.34],[1.89,0],[0,0],[1.34,1.34],[0,1.89],[0,0],[-1.34,1.34],[-1.89,0],[0,0],[-1.34,-1.34],[0,-1.89]],"o":[[0,1.89],[-1.34,1.34],[0,0],[-1.89,0],[-1.34,-1.34],[0,0],[0,-1.89],[1.34,-1.34],[0,0],[1.89,0],[1.34,1.34],[0,0]],"v":[[202.43,198.08],[200.34,203.12],[195.3,205.21],[113.25,205.21],[108.21,203.12],[106.12,198.08],[106.12,26.92],[108.21,21.88],[113.25,19.79],[195.3,19.79],[200.34,21.88],[202.43,26.92]],"c":true}}},{"ty":"fl","o":{"k":100},"c":{"k":[0.5,0.53,0.55,1]}},{"ty":"tr","o":{"k":100},"r":{"k":0},"p":{"k":[0,0]},"a":{"k":[0,0]},"s":{"k":[100,100]},"sk":{"k":0},"sa":{"k":0},"hd":false}]},{"ty":"gr","hd":false,"it":[{"ty":"sh","ks":{"k":{"i":[[0,0],[0,0],[0,11.74],[0,0],[-11.46,0],[0,0],[0,-11.74],[0,0],[11.46,0]],"o":[[0,0],[-11.46,0],[0,0],[0,-11.77],[0,0],[11.38,0],[0,0],[0,11.66],[0,0]],"v":[[288.14,225],[20.86,225],[0,203.64],[0,21.39],[20.86,0],[288.22,0],[309,21.36],[309,203.72],[288.14,225]],"c":true}}},{"ty":"fl","o":{"k":100},"c":{"k":[0,0,0,1]}},{"ty":"tr","o":{"k":100},"r":{"k":0},"p":{"k":[0,0]},"a":{"k":[0,0]},"s":{"k":[100,100]},"sk":{"k":0},"sa":{"k":0},"hd":false}]},{"ty":"tr","o":{"k":100},"r":{"k":0},"p":{"k":[0,0]},"a":{"k":[0,0]},"s":{"k":[100,100]},"sk":{"k":0},"sa":{"k":0},"hd":false}]}]}],"meta":{"g":"LF SVG to Lottie"},"assets":[]}
\ No newline at end of file
diff --git a/res/raw/private_space_share_photos_illustration.json b/res/raw/private_space_share_photos_illustration.json
new file mode 100644
index 0000000..464704d
--- /dev/null
+++ b/res/raw/private_space_share_photos_illustration.json
@@ -0,0 +1 @@
+{"v":"5.12.1","fr":60,"ip":0,"op":275,"w":412,"h":300,"nm":"Private_SpaceE_v1_export","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"unlock 2","parent":2,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":1,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":11,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":110,"s":[100]},{"t":120,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[2.504,-16.523,0],"ix":2,"l":2},"a":{"a":0,"k":[2.504,-16.523,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,"y":1},"o":{"x":0.167,"y":0},"t":10,"s":[{"i":[[0,0],[0,0],[-1.355,2.927],[-3.702,1.026],[-0.166,-3.033],[0,-3.968],[0,0],[2.992,5.819],[4.706,-0.139],[2.517,-2.626],[0,-6.587],[0,0]],"o":[[0,0],[0,-3.968],[1.344,-1.62],[2.173,0.401],[0.169,3.094],[0,0],[0.17,-3.914],[-2.25,-2.779],[-4.648,0.137],[-4.408,4.835],[0,0],[0,0]],"v":[[7.105,-16.667],[7.105,-26.19],[8.647,-35.414],[12.565,-37.812],[16.291,-33.754],[17.305,-26.171],[25.315,-26.042],[22.307,-42.725],[13.076,-47.489],[2.552,-43.094],[-2.368,-26.19],[-2.368,-16.667]],"c":true}]},{"t":71,"s":[{"i":[[0,0],[0,0],[-2.763,2.778],[-3.947,0],[-2.763,-2.778],[0,-3.968],[0,0],[4.618,4.643],[6.553,0],[4.618,-4.643],[0,-6.587],[0,0]],"o":[[0,0],[0,-3.968],[2.763,-2.778],[3.947,0],[2.763,2.778],[0,0],[0,-6.587],[-4.618,-4.643],[-6.553,0],[-4.618,4.643],[0,0],[0,0]],"v":[[7.112,-15.167],[7.105,-26.19],[11.25,-36.31],[21.316,-40.476],[31.382,-36.31],[35.526,-26.19],[45,-26.19],[38.072,-43.036],[21.316,-50],[4.559,-43.036],[-2.368,-26.19],[-2.362,-15.167]],"c":true}]}],"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803986549,0.941176533699,0.996078491211,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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-170,"op":498,"st":-170,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"unlock","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":110,"s":[100]},{"t":120,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[213.752,151.127,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":0,"s":[58,58,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.714,0.714,0]},"t":10,"s":[74.8,74.8,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":61,"s":[100,100,100]},{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":108,"s":[100,100,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":115,"s":[72,72,100]},{"t":150,"s":[30,30,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.855,-1.865],[2.605,0],[1.855,1.865],[0,2.619],[-1.855,1.865],[-2.605,0],[-1.855,-1.865],[0,-2.619]],"o":[[-1.855,1.865],[-2.605,0],[-1.855,-1.865],[0,-2.619],[1.855,-1.865],[2.605,0],[1.855,1.865],[0,2.619]],"v":[[-0.414,23.393],[-7.105,26.19],[-13.796,23.393],[-16.579,16.667],[-13.796,9.94],[-7.105,7.143],[-0.414,9.94],[2.368,16.667]],"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":[[-1.855,-1.865],[-2.605,0],[0,0],[-1.855,1.865],[0,2.619],[0,0],[1.855,1.865],[2.605,0],[0,0],[1.855,-1.865],[0,-2.619],[0,0]],"o":[[1.855,1.865],[0,0],[2.605,0],[1.855,-1.865],[0,0],[0,-2.619],[-1.855,-1.865],[0,0],[-2.605,0],[-1.855,1.865],[0,0],[0,2.619]],"v":[[-42.217,47.202],[-35.526,50],[21.316,50],[28.007,47.202],[30.789,40.476],[30.789,-7.143],[28.007,-13.869],[21.316,-16.667],[-35.526,-16.667],[-42.217,-13.869],[-45,-7.143],[-45,40.476]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803986549,0.941176533699,0.996078491211,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":"Vector","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-170,"op":498,"st":-170,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"star","parent":2,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":110,"s":[100]},{"t":120,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.8],"y":[0.164]},"o":{"x":[0.3],"y":[0]},"t":0,"s":[-90]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.702]},"t":10,"s":[-54]},{"t":61,"s":[0]}],"ix":10},"p":{"a":0,"k":[-8.102,-0.639,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.8,0.8,0.8],"y":[0.164,0.164,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":0,"s":[63,63,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.711,0.711,0]},"t":10,"s":[77.913,77.913,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":61,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":108,"s":[100,100,100]},{"t":118,"s":[50,50,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-15.299,-35.012],[0,0],[4.671,-10.689],[0,0],[-35.226,15.206],[0,0],[-10.755,-4.642],[0,0],[15.299,35.011],[0,0],[-4.671,10.689],[0,0],[35.226,-15.206],[0,0],[10.755,4.642]],"o":[[-35.226,-15.206],[0,0],[4.671,10.689],[0,0],[-15.299,35.012],[0,0],[10.755,-4.642],[0,0],[35.226,15.206],[0,0],[-4.671,-10.689],[0,0],[15.299,-35.012],[0,0],[-10.755,4.642],[0,0]],"v":[[-22.684,-77.887],[-78.364,-22.546],[-75.835,-16.757],[-75.835,16.757],[-78.364,22.546],[-22.684,77.887],[-16.86,75.373],[16.86,75.373],[22.684,77.887],[78.364,22.546],[75.835,16.757],[75.835,-16.757],[78.364,-22.546],[22.684,-77.887],[16.86,-75.373],[-16.86,-75.373]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"rd","nm":"Round Corners 1","r":{"a":0,"k":48,"ix":1},"ix":2,"mn":"ADBE Vector Filter - RC","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":[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":"Star 72","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":523,"st":-170,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"lock 3","parent":6,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[60.711,-20.694,0],"ix":2,"l":2},"a":{"a":0,"k":[165.25,134.607,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,1.416],[1.967,0],[0,-2.023],[-1.144,-0.607],[0,0]],"o":[[0,0],[1.144,-0.607],[0,-2.023],[-1.967,0],[0,1.416],[0,0],[0,0]],"v":[[2.682,6.436],[1.663,0.497],[3.576,-2.758],[0,-6.436],[-3.576,-2.758],[-1.663,0.497],[-2.682,6.436]],"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":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":30,"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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[165.737,134.475],"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":"Vector","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":[[-1.967,0],[0,0],[0,-1.967],[0,0],[1.967,0],[0,0],[0,1.967],[0,0]],"o":[[0,0],[1.967,0],[0,0],[0,1.967],[0,0],[-1.967,0],[0,0],[0,-1.967]],"v":[[-12.515,-16.091],[12.515,-16.091],[16.091,-12.515],[16.091,12.515],[12.515,16.091],[-12.515,16.091],[-16.091,12.515],[-16.091,-12.515]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803926945,0.941176474094,0.996078431606,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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[165.25,134.607],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":30,"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":"Vector","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":108,"op":1908,"st":108,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"circle 3","parent":6,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[60.583,-21.178,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.008,0],[0,15.008],[15.008,0],[0,-15.008]],"o":[[15.008,0],[0,-15.008],[-15.008,0],[0,15.008]],"v":[[0,27.175],[27.175,0],[0,-27.175],[-27.175,0]],"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":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":30,"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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":108,"op":1908,"st":108,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"MASK","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":108,"s":[-49]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":115,"s":[-29.4]},{"t":150,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":108,"s":[204.539,147.3,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":115,"s":[164.539,150.5,0],"to":[0,0,0],"ti":[0,0,0]},{"t":150,"s":[104.539,155.3,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":108,"s":[10,10,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":115,"s":[46,46,100]},{"t":150,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[112.298,112.298],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":13,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803926945,0.941176474094,0.996078431606,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":27.343,"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 260863008","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":108,"op":1908,"st":108,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Union","parent":6,"tt":1,"tp":6,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-20.5,25.975,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":[{"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":[[30.992,8.201],[-16.258,-61.299],[-82.74,27.252],[25.669,27.252]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","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":27.343,"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":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"rd","nm":"Round Corners 1","r":{"a":0,"k":9,"ix":1},"ix":2,"mn":"ADBE Vector Filter - RC","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[3.6,-4.372],[0,0],[-7.606,0],[0,0],[4.835,5.872]],"o":[[-3.6,-4.372],[0,0],[-4.835,5.871],[0,0],[7.606,0],[0,0]],"v":[[25.486,-10.424],[11.591,-10.424],[-28.326,38.051],[-21.378,52.772],[58.455,52.772],[65.403,38.051]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[26,-16],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":27.343,"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 2","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"rd","nm":"Round Corners 2","r":{"a":0,"k":9,"ix":1},"ix":4,"mn":"ADBE Vector Filter - RC","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":[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":"Union","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":108,"op":1908,"st":108,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Rectangle 260863008","parent":6,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,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":[{"ty":"rc","d":1,"s":{"a":0,"k":[112.298,112.298],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":13,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803926945,0.941176474094,0.996078431606,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":27.343,"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 260863008","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":108,"op":1908,"st":108,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"lock 2","parent":11,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[363.716,162.535,0],"ix":2,"l":2},"a":{"a":0,"k":[363.716,162.535,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,1.416],[1.967,0],[0,-2.023],[-1.144,-0.607],[0,0]],"o":[[0,0],[1.144,-0.607],[0,-2.023],[-1.967,0],[0,1.416],[0,0],[0,0]],"v":[[2.682,6.436],[1.663,0.497],[3.576,-2.758],[0,-6.436],[-3.576,-2.758],[-1.663,0.497],[-2.682,6.436]],"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":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":-15,"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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[363.967,162.098],"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":"Vector","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":[[-1.967,0],[0,0],[0,-1.967],[0,0],[1.967,0],[0,0],[0,1.967],[0,0]],"o":[[0,0],[1.967,0],[0,0],[0,1.967],[0,0],[-1.967,0],[0,0],[0,-1.967]],"v":[[-12.515,-16.091],[12.515,-16.091],[16.091,-12.515],[16.091,12.515],[12.515,16.091],[-12.515,16.091],[-16.091,12.515],[-16.091,-12.515]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803926945,0.941176474094,0.996078431606,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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[363.716,162.535],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":-15,"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":"Vector","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":112,"op":1912,"st":112,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"circle 2","parent":11,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[363.283,162.283,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.008,0],[0,15.008],[15.008,0],[0,-15.008]],"o":[[15.008,0],[0,-15.008],[-15.008,0],[0,15.008]],"v":[[0,27.175],[27.175,0],[0,-27.175],[-27.175,0]],"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":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":-15,"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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":112,"op":1912,"st":112,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"photo","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":112,"s":[15]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":119,"s":[9]},{"t":154,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":112,"s":[206.835,156.835,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":119,"s":[240.035,141.635,0],"to":[0,0,0],"ti":[0,0,0]},{"t":154,"s":[289.835,118.835,0]}],"ix":2,"l":2},"a":{"a":0,"k":[289.835,118.835,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":112,"s":[20,20,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":119,"s":[52,52,100]},{"t":154,"s":[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],[-21.534,-14.263],[15.031,-20.433]],"o":[[15.031,-20.433],[21.534,14.183],[0,0]],"v":[[-42.288,-18.843],[16.824,-20.446],[35.655,32.679]],"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":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":-49.181,"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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[299.112,155.893],"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":"Vector","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":[[4.646,0.092],[3.811,2.641],[0,0],[1.094,6.094],[-3.563,5.078],[-4.327,1.682],[-4.539,-0.99],[-3.22,-3.329],[-0.816,-4.545],[1.863,-4.229],[3.914,-2.488]],"o":[[-4.646,-0.092],[0,0],[-5.11,-3.541],[-1.094,-6.094],[2.658,-3.787],[4.327,-1.682],[4.539,0.99],[3.22,3.329],[0.816,4.545],[-1.863,4.229],[-3.914,2.488]],"v":[[-0.47,23.338],[-13.441,19.145],[-13.441,19.145],[-23.129,4.097],[-19.272,-13.35],[-8.559,-21.738],[5.04,-22.799],[16.939,-16.174],[23.129,-4.097],[21.522,9.361],[12.66,19.663]],"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":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":-49.181,"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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[284.287,95.914],"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":"Vector","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":[[6.498,0],[0,0],[0,-6.498],[0,0],[-6.498,0],[0,0],[0,6.498],[0,0]],"o":[[0,0],[-6.498,0],[0,0],[0,6.498],[0,0],[6.498,0],[0,0],[0,-6.498]],"v":[[47.286,-59.053],[-47.286,-59.053],[-59.053,-47.286],[-59.053,47.286],[-47.286,59.053],[47.286,59.053],[59.053,47.286],[59.053,-47.286]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803926945,0.941176474094,0.996078431606,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":-15.709,"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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[289.835,118.835],"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":"Vector","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false}],"ip":112,"op":1912,"st":112,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Vector","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.3,0],[0,0],[0,15.5],[0,0],[15.2,0],[0,0],[0,-15.7],[0,0]],"o":[[0,0],[15.3,0],[0,0],[0,-15.7],[0,0],[-15.3,0],[0,0],[0,15.7]],"v":[[-178.2,150],[178.2,150],[206,121.7],[206,-121.5],[178.3,-150],[-178.2,-150],[-206,-121.5],[-206,121.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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":844,"st":-2,"ct":1,"bm":0}],"markers":[{"tm":0,"cm":"1","dr":0},{"tm":274,"cm":"2","dr":0},{"tm":429,"cm":"4","dr":0},{"tm":500,"cm":"3","dr":0},{"tm":521,"cm":"5","dr":0},{"tm":843,"cm":"6","dr":0}],"props":{}}
\ No newline at end of file
diff --git a/res/raw/private_space_unlock_to_share_illustration.json b/res/raw/private_space_unlock_to_share_illustration.json
new file mode 100644
index 0000000..26e01cc
--- /dev/null
+++ b/res/raw/private_space_unlock_to_share_illustration.json
@@ -0,0 +1 @@
+{"v":"5.12.1","fr":60,"ip":0,"op":191,"w":412,"h":300,"nm":"Private_SpaceF_v1_export","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"lock3","parent":2,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40.445,32,0],"ix":2,"l":2},"a":{"a":0,"k":[370.942,181.501,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":40,"s":[0,0,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":45,"s":[40,40,100]},{"t":70,"s":[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,1.198],[1.711,0],[0,-1.711],[-0.996,-0.513],[0,0]],"o":[[0,0],[0.996,-0.513],[0,-1.711],[-1.711,0],[0,1.198],[0,0],[0,0]],"v":[[2.333,5.444],[1.447,0.42],[3.111,-2.333],[0,-5.444],[-3.111,-2.333],[-1.447,0.42],[-2.333,5.444]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803926945,0.941176474094,0.996078431606,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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[371.439,182.003],"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":"Vector 16","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":[[-1.711,0],[0,0],[0,-1.711],[0,0],[1.711,0],[0,0],[0,1.711],[0,0]],"o":[[0,0],[1.711,0],[0,0],[0,1.711],[0,0],[-1.711,0],[0,0],[0,-1.711]],"v":[[-10.889,-14],[10.889,-14],[14,-10.889],[14,10.889],[10.889,14],[-10.889,14],[-14,10.889],[-14,-10.889]],"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":[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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[371.442,182.001],"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":"Vector 15","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":[[-13.531,0],[0,13.531],[13.531,0],[0,-13.531]],"o":[[13.531,0],[0,-13.531],[-13.531,0],[0,13.531]],"v":[[0,24.5],[24.5,0],[0,-24.5],[-24.5,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803926945,0.941176474094,0.996078431606,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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[370.942,181.501],"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":"Vector 14","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false}],"ip":40,"op":1840,"st":40,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"chrome","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":40,"s":[0]},{"t":50,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":40,"s":[330.496,64.501,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":45,"s":[330.496,98.501,0],"to":[0,0,0],"ti":[0,0,0]},{"t":70,"s":[330.496,149.501,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":[[3.408,3.42],[0,4.803],[-3.408,3.42],[-4.785,0],[-3.408,-3.42],[0,-4.803],[3.408,-3.42],[4.785,0]],"o":[[-3.408,-3.42],[0,-4.803],[3.408,-3.42],[4.785,0],[3.408,3.42],[0,4.803],[-3.408,3.42],[-4.785,0]],"v":[[-12.289,12.499],[-17.4,0.164],[-12.289,-12.171],[0,-17.302],[12.289,-12.171],[17.4,0.164],[12.289,12.499],[0,17.629]],"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.87,-0.073],[0.943,0],[3.988,2.402],[2.247,3.93],[0,0],[0.58,-2.875],[0,-3.056],[-7.395,-8.187],[-10.947,-1.237],[0,0]],"o":[[-0.87,0.073],[-4.857,0],[-3.988,-2.402],[0,0],[-1.16,2.62],[-0.58,2.875],[0,11.28],[7.395,8.187],[0,0],[-0.87,0.218]],"v":[[2.719,26.253],[0,26.362],[-13.268,22.76],[-22.62,13.263],[-40.02,-16.974],[-42.63,-8.733],[-43.5,0.164],[-32.408,29.364],[-4.894,43.5],[5.329,25.816]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[-4.495,3.384],[-5.8,0],[0,0],[6.018,3.166],[7.178,0],[6.054,-3.238],[3.988,-5.385],[0,0]],"o":[[4.495,-3.384],[0,0],[-3.988,-5.312],[-6.018,-3.166],[-7.25,0],[-6.054,3.238],[0,0],[1.74,-5.24]],"v":[[-15.443,-20.959],[0,-26.035],[34.8,-26.035],[19.792,-38.752],[0,-43.5],[-19.956,-38.642],[-35.018,-25.707],[-24.795,-8.023]],"c":true},"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ind":3,"ty":"sh","ix":4,"ks":{"a":0,"k":{"i":[[0.616,2.911],[1.232,2.693],[0,0],[-1.196,-2.947],[0,-3.42],[0.616,-2.22],[1.087,-1.892],[0,0],[-7.359,8.151],[0,11.28]],"o":[[-0.616,-2.911],[0,0],[2.102,2.329],[1.196,2.947],[0,2.402],[-0.616,2.22],[0,0],[10.803,-1.31],[7.359,-8.151],[0,-3.129]],"v":[[42.576,-8.896],[39.803,-17.302],[19.357,-17.302],[24.306,-9.388],[26.1,0.164],[25.176,7.095],[22.62,13.263],[5.22,43.5],[32.462,29.309],[43.5,0.164]],"c":true},"ix":2},"nm":"Path 4","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","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":[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":"Vector","np":6,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":40,"op":1840,"st":40,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"lock2","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[242.943,181.501,0],"ix":2,"l":2},"a":{"a":0,"k":[242.943,181.501,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":20,"s":[0,0,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":25,"s":[40,40,100]},{"t":50,"s":[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,1.198],[1.711,0],[0,-1.711],[-0.996,-0.513],[0,0]],"o":[[0,0],[0.996,-0.513],[0,-1.711],[-1.711,0],[0,1.198],[0,0],[0,0]],"v":[[2.333,5.444],[1.447,0.42],[3.111,-2.333],[0,-5.444],[-3.111,-2.333],[-1.447,0.42],[-2.333,5.444]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803926945,0.941176474094,0.996078431606,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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[243.44,182.003],"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":"Vector 13","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":[[-1.711,0],[0,0],[0,-1.711],[0,0],[1.711,0],[0,0],[0,1.711],[0,0]],"o":[[0,0],[1.711,0],[0,0],[0,1.711],[0,0],[-1.711,0],[0,0],[0,-1.711]],"v":[[-10.889,-14],[10.889,-14],[14,-10.889],[14,10.889],[10.889,14],[-10.889,14],[-14,10.889],[-14,-10.889]],"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":[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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[243.443,182.001],"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":"Vector 12","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":[[-13.531,0],[0,13.531],[13.531,0],[0,-13.531]],"o":[[13.531,0],[0,-13.531],[-13.531,0],[0,13.531]],"v":[[0,24.5],[24.5,0],[0,-24.5],[-24.5,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803926945,0.941176474094,0.996078431606,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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[242.943,181.501],"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":"Vector 11","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false}],"ip":20,"op":1820,"st":20,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"camera","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[0]},{"t":30,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":20,"s":[198.131,65.001,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":25,"s":[198.131,99.001,0],"to":[0,0,0],"ti":[0,0,0]},{"t":50,"s":[198.131,150.001,0]}],"ix":2,"l":2},"a":{"a":0,"k":[198.131,150.001,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":[[4.299,-4.278],[6.142,0],[4.299,4.278],[0,6.111],[-4.299,4.278],[-6.142,0],[-4.299,-4.278],[0,-6.111]],"o":[[-4.299,4.278],[-6.142,0],[-4.299,-4.278],[0,-6.111],[4.299,-4.278],[6.142,0],[4.299,4.278],[0,6.111]],"v":[[15.661,20.472],[0,26.889],[-15.661,20.472],[-22.11,4.889],[-15.661,-10.694],[0,-17.111],[15.661,-10.694],[22.11,4.889]],"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.375,-2.363],[-3.439,0],[-2.375,2.363],[0,3.422],[2.375,2.363],[3.439,0],[2.375,-2.363],[0,-3.422]],"o":[[2.375,2.363],[3.439,0],[2.375,-2.363],[0,-3.422],[-2.375,-2.363],[-3.439,0],[-2.375,2.363],[0,3.422]],"v":[[-8.721,13.567],[0,17.111],[8.721,13.567],[12.283,4.889],[8.721,-3.789],[0,-7.333],[-8.721,-3.789],[-12.283,4.889]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[-1.924,-1.915],[-2.702,0],[0,0],[-1.924,1.915],[0,2.689],[0,0],[1.924,1.915],[2.702,0],[0,0],[0,0],[0,0],[0,0],[0,0],[1.924,-1.915],[0,-2.689],[0,0]],"o":[[1.924,1.915],[0,0],[2.702,0],[1.924,-1.915],[0,0],[0,-2.689],[-1.924,-1.915],[0,0],[0,0],[0,0],[0,0],[0,0],[-2.702,0],[-1.924,1.915],[0,0],[0,2.689]],"v":[[-46.247,41.128],[-39.307,44],[39.307,44],[46.247,41.128],[49.133,34.222],[49.133,-24.444],[46.247,-31.35],[39.307,-34.222],[23.83,-34.222],[14.74,-44],[-14.74,-44],[-23.83,-34.222],[-39.307,-34.222],[-46.247,-31.35],[-49.133,-24.444],[-49.133,34.222]],"c":true},"ix":2},"nm":"Path 3","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":[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":"Vector","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[198.131,150.001],"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":"camera","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[71.148,57.52],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803986549,0.941176533699,0.996078491211,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":[-7.426,5.76],"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},{"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":"Shape Layer 1","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":20,"op":1820,"st":20,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"lock1","parent":6,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[103.856,181.501,0],"ix":2,"l":2},"a":{"a":0,"k":[103.856,181.501,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":5,"s":[40,40,100]},{"t":30,"s":[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,1.198],[1.711,0],[0,-1.711],[-0.996,-0.513],[0,0]],"o":[[0,0],[0.996,-0.513],[0,-1.711],[-1.711,0],[0,1.198],[0,0],[0,0]],"v":[[2.333,5.444],[1.447,0.42],[3.111,-2.333],[0,-5.444],[-3.111,-2.333],[-1.447,0.42],[-2.333,5.444]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803926945,0.941176474094,0.996078431606,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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[104.353,182.003],"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":"Vector 10","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":[[-1.711,0],[0,0],[0,-1.711],[0,0],[1.711,0],[0,0],[0,1.711],[0,0]],"o":[[0,0],[1.711,0],[0,0],[0,1.711],[0,0],[-1.711,0],[0,0],[0,-1.711]],"v":[[-10.889,-14],[10.889,-14],[14,-10.889],[14,10.889],[10.889,14],[-10.889,14],[-14,10.889],[-14,-10.889]],"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":[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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[104.356,182.001],"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":"Vector 9","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":[[-13.531,0],[0,13.531],[13.531,0],[0,-13.531]],"o":[[13.531,0],[0,-13.531],[-13.531,0],[0,13.531]],"v":[[0,24.5],[24.5,0],[0,-24.5],[-24.5,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803926945,0.941176474094,0.996078431606,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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[103.856,181.501],"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":"Vector 8","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1800,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"play","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":10,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":0,"s":[69.611,67.263,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":5,"s":[69.611,101.263,0],"to":[0,0,0],"ti":[0,0,0]},{"t":30,"s":[69.611,152.263,0]}],"ix":2,"l":2},"a":{"a":0,"k":[69.611,152.263,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.867,-0.644],[-0.432,-0.99],[0.117,-1.073],[0.635,-0.873],[0,0],[0,0]],"o":[[0,0],[1.072,0.128],[0.867,0.644],[0.432,0.99],[-0.117,1.073],[0,0],[0,0],[0,0]],"v":[[-8.178,-13.421],[10.356,-11.269],[13.315,-10.091],[15.297,-7.598],[15.777,-4.45],[14.628,-1.48],[3.393,13.421],[-15.814,5.718]],"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":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":23.58,"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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[96.844,153.656],"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":"Vector 5","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.097,0.3],[-0.122,0.29],[0,0],[0,0],[0,0],[0.311,-0.053],[0.269,0.163]],"o":[[-0.097,-0.3],[0,0],[0,0],[0,0],[-0.201,0.243],[-0.311,0.053],[-0.269,-0.163]],"v":[[-19.598,27.267],[-19.559,26.352],[2.339,-28.174],[19.662,-21.211],[-17.339,27.698],[-18.131,28.156],[-19.03,27.984]],"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":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":23.58,"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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[61.412,182.739],"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":"Vector 4","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.169,0.283],[-0.074,0.321],[-0.275,0.181],[-0.324,-0.06]],"o":[[0,0],[0,0],[-0.317,-0.088],[-0.169,-0.283],[0.074,-0.321],[0.275,-0.181],[0,0]],"v":[[31.18,-5.102],[24.251,12.221],[-30.242,-9.711],[-31,-10.288],[-31.148,-11.229],[-30.604,-12.011],[-29.67,-12.2]],"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":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":23.58,"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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[57.54,126.466],"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":"Vector 3","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.229,-0.094],[0,0],[0,0],[0.203,-0.134],[0.243,0],[0.203,0.134],[0.096,0.224],[0,0],[-0.045,0.244],[-0.173,0.177],[-0.243,0.05]],"o":[[0,0],[0,0],[-0.096,0.224],[-0.203,0.134],[-0.243,0],[-0.203,-0.134],[0,0],[-0.099,-0.227],[0.045,-0.244],[0.173,-0.177],[0.243,-0.05]],"v":[[-26.64,-38.879],[28.358,-16.846],[6.325,38.219],[5.866,38.767],[5.182,38.972],[4.497,38.767],[4.038,38.219],[-28.254,-37.23],[-28.337,-37.953],[-28.002,-38.599],[-27.364,-38.947]],"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":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":23.58,"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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[36.906,156.637],"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":"Vector 2","np":1,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1800,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Vector","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.29,0],[0,0],[0,15.488],[0,0],[15.18,0],[0,0],[0,-15.638],[0,0]],"o":[[0,0],[15.29,0],[0,0],[0,-15.598],[0,0],[-15.29,0],[0,0],[0,15.598]],"v":[[-178.18,149.501],[178.18,149.501],[206,121.226],[206,-121.116],[178.29,-149.501],[-178.18,-149.501],[-206,-121.076],[-206,121.116]],"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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1800,"st":0,"ct":1,"bm":0}],"markers":[{"tm":190,"cm":"1","dr":0}],"props":{}}
\ No newline at end of file
diff --git a/res/raw/private_space_use_screen_lock_illustration.json b/res/raw/private_space_use_screen_lock_illustration.json
new file mode 100644
index 0000000..c8590b3
--- /dev/null
+++ b/res/raw/private_space_use_screen_lock_illustration.json
@@ -0,0 +1 @@
+{"v":"5.12.1","fr":60,"ip":0,"op":482,"w":412,"h":300,"nm":"Private_SpaceC_v1_export","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Wiggle_control","sr":1,"ks":{"o":{"a":0,"k":0,"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,"ef":[{"ty":5,"nm":"freq","np":3,"mn":"ADBE Slider Control","ix":1,"en":1,"ef":[{"ty":0,"nm":"Slider","mn":"ADBE Slider Control-0001","ix":1,"v":{"a":0,"k":0.2,"ix":1}}]},{"ty":5,"nm":"amp","np":3,"mn":"ADBE Slider Control","ix":2,"en":1,"ef":[{"ty":0,"nm":"Slider","mn":"ADBE Slider Control-0001","ix":1,"v":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":205,"s":[25]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":228,"s":[0]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.2],"y":[0]},"t":420,"s":[0]},{"t":460,"s":[25]}],"ix":1}}]}],"ip":0,"op":482,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"lock","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[62.04,55.348,0],"ix":2,"l":2},"a":{"a":0,"k":[363.041,208.037,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.871],[-1.244,0],[0,-1.244],[0.724,-0.373]],"o":[[0,0],[0,0],[-0.724,-0.373],[0,-1.244],[1.244,0],[0,0.871],[0,0]],"v":[[1.696,3.958],[-1.696,3.958],[-1.052,0.305],[-2.262,-1.696],[0,-3.958],[2.262,-1.696],[1.052,0.305]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","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":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[363.043,208.036],"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":"Vector","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],[0,-1.244],[0,0],[-1.244,0],[0,0],[0,1.244],[0,0],[1.244,0]],"o":[[-1.244,0],[0,0],[0,1.244],[0,0],[1.244,0],[0,0],[0,-1.244],[0,0]],"v":[[-7.917,-10.179],[-10.179,-7.917],[-10.179,7.917],[-7.917,10.179],[7.917,10.179],[10.179,7.917],[10.179,-7.917],[7.917,-10.179]],"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],[0,0],[0,0.871],[-1.244,0],[0,-1.244],[0.724,-0.373]],"o":[[0,0],[0,0],[-0.724,-0.373],[0,-1.244],[1.244,0],[0,0.871],[0,0]],"v":[[1.696,3.958],[-1.696,3.958],[-1.052,0.305],[-2.262,-1.696],[0,-3.958],[2.262,-1.696],[1.052,0.305]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":205,"s":[0.396078467369,0.611764729023,0.95294123888,1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":215,"s":[0.235294133425,0.250980407,0.262745112181,1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":420,"s":[0.235294133425,0.250980407,0.262745112181,1]},{"t":433,"s":[0.396078467369,0.611764729023,0.95294123888,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":"Vector","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[363.041,208.037],"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":"Vector","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":482,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Ellipse 7656","parent":3,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[362.508,207.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":[{"d":1,"ty":"el","s":{"a":0,"k":[45,45],"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":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":205,"s":[0.823529422283,0.890196084976,0.988235294819,1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":215,"s":[0.1254902035,0.129411771894,0.141176477075,1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":420,"s":[0.1254902035,0.129411771894,0.141176477075,1]},{"t":433,"s":[0.823529422283,0.890196084976,0.988235294819,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 7656","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":482,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"lock","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[299,150.5,0],"ix":2,"l":2},"a":{"k":[{"s":[-2.828,-21.692,0],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-2.83,-21.492,0],"t":1,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-2.837,-21.29,0],"t":2,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-2.85,-21.086,0],"t":3,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-2.868,-20.88,0],"t":4,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-2.891,-20.672,0],"t":5,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-2.92,-20.463,0],"t":6,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-2.953,-20.252,0],"t":7,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-2.992,-20.04,0],"t":8,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.035,-19.827,0],"t":9,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.084,-19.612,0],"t":10,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.137,-19.397,0],"t":11,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.194,-19.181,0],"t":12,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.257,-18.965,0],"t":13,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.323,-18.748,0],"t":14,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.394,-18.531,0],"t":15,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.469,-18.313,0],"t":16,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.548,-18.096,0],"t":17,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.631,-17.879,0],"t":18,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.718,-17.661,0],"t":19,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.809,-17.445,0],"t":20,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.903,-17.228,0],"t":21,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-4,-17.013,0],"t":22,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-4.101,-16.798,0],"t":23,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-4.205,-16.583,0],"t":24,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-4.312,-16.37,0],"t":25,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-4.422,-16.158,0],"t":26,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-4.535,-15.947,0],"t":27,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-4.65,-15.738,0],"t":28,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-4.768,-15.53,0],"t":29,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-4.888,-15.323,0],"t":30,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-5.011,-15.119,0],"t":31,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-5.135,-14.916,0],"t":32,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-5.261,-14.715,0],"t":33,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-5.389,-14.516,0],"t":34,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-5.519,-14.319,0],"t":35,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-5.65,-14.124,0],"t":36,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-5.783,-13.932,0],"t":37,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-5.916,-13.743,0],"t":38,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-6.051,-13.556,0],"t":39,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-6.186,-13.371,0],"t":40,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-6.322,-13.19,0],"t":41,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-6.459,-13.011,0],"t":42,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-6.596,-12.835,0],"t":43,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-6.733,-12.663,0],"t":44,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-6.87,-12.493,0],"t":45,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-7.007,-12.327,0],"t":46,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-7.144,-12.164,0],"t":47,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-7.28,-12.005,0],"t":48,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-7.416,-11.85,0],"t":49,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-7.551,-11.698,0],"t":50,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-7.685,-11.549,0],"t":51,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-7.818,-11.405,0],"t":52,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-7.95,-11.265,0],"t":53,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-8.08,-11.128,0],"t":54,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-8.209,-10.996,0],"t":55,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-8.336,-10.868,0],"t":56,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-8.461,-10.744,0],"t":57,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-8.584,-10.625,0],"t":58,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-8.704,-10.51,0],"t":59,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-8.823,-10.399,0],"t":60,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-8.938,-10.293,0],"t":61,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-9.052,-10.192,0],"t":62,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-9.162,-10.095,0],"t":63,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-9.269,-10.004,0],"t":64,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-9.373,-9.917,0],"t":65,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-9.473,-9.835,0],"t":66,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-9.57,-9.758,0],"t":67,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-9.664,-9.686,0],"t":68,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-9.753,-9.619,0],"t":69,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-9.839,-9.558,0],"t":70,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-9.92,-9.502,0],"t":71,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-9.997,-9.451,0],"t":72,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.069,-9.405,0],"t":73,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.137,-9.365,0],"t":74,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.2,-9.33,0],"t":75,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.258,-9.301,0],"t":76,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.312,-9.277,0],"t":77,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.359,-9.259,0],"t":78,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.402,-9.247,0],"t":79,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.439,-9.24,0],"t":80,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.47,-9.24,0],"t":81,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.496,-9.245,0],"t":82,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.515,-9.255,0],"t":83,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.528,-9.272,0],"t":84,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.536,-9.295,0],"t":85,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.536,-9.323,0],"t":86,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.531,-9.358,0],"t":87,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.518,-9.398,0],"t":88,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.499,-9.445,0],"t":89,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.474,-9.496,0],"t":90,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.453,-9.538,0],"t":91,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.436,-9.568,0],"t":92,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.425,-9.588,0],"t":93,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.415,-9.594,0],"t":95,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.416,-9.581,0],"t":96,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.421,-9.559,0],"t":97,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.43,-9.526,0],"t":98,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.442,-9.484,0],"t":99,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.457,-9.432,0],"t":100,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.476,-9.372,0],"t":101,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.497,-9.302,0],"t":102,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.521,-9.224,0],"t":103,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.547,-9.138,0],"t":104,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.576,-9.043,0],"t":105,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.606,-8.941,0],"t":106,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.639,-8.831,0],"t":107,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.674,-8.714,0],"t":108,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.71,-8.589,0],"t":109,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.748,-8.457,0],"t":110,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.787,-8.319,0],"t":111,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.827,-8.174,0],"t":112,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.869,-8.023,0],"t":113,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.911,-7.865,0],"t":114,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.955,-7.702,0],"t":115,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.999,-7.533,0],"t":116,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-11.044,-7.358,0],"t":117,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-11.089,-7.178,0],"t":118,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-11.135,-6.993,0],"t":119,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-11.182,-6.802,0],"t":120,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-11.231,-6.605,0],"t":121,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-11.282,-6.4,0],"t":122,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-11.335,-6.189,0],"t":123,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-11.39,-5.972,0],"t":124,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-11.448,-5.748,0],"t":125,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-11.506,-5.518,0],"t":126,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-11.567,-5.282,0],"t":127,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-11.629,-5.041,0],"t":128,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-11.692,-4.794,0],"t":129,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-11.757,-4.543,0],"t":130,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-11.823,-4.286,0],"t":131,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-11.89,-4.024,0],"t":132,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-11.957,-3.758,0],"t":133,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-12.026,-3.487,0],"t":134,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-12.095,-3.213,0],"t":135,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-12.165,-2.934,0],"t":136,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-12.235,-2.652,0],"t":137,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-12.306,-2.367,0],"t":138,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-12.377,-2.078,0],"t":139,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-12.448,-1.786,0],"t":140,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-12.519,-1.491,0],"t":141,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-12.59,-1.193,0],"t":142,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-12.661,-0.893,0],"t":143,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-12.732,-0.591,0],"t":144,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-12.803,-0.287,0],"t":145,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-12.873,0.019,0],"t":146,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-12.942,0.327,0],"t":147,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-13.012,0.636,0],"t":148,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-13.08,0.947,0],"t":149,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-13.148,1.258,0],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-13.214,1.571,0],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-13.28,1.884,0],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-13.345,2.197,0],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-13.409,2.511,0],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-13.472,2.825,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-13.533,3.138,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-13.593,3.452,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-13.652,3.765,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-13.709,4.077,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-13.765,4.388,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-13.819,4.698,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-13.872,5.008,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-13.923,5.315,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-13.972,5.621,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.02,5.926,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.065,6.228,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.109,6.528,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.15,6.826,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.19,7.122,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.227,7.414,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.262,7.704,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.295,7.991,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.326,8.275,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.354,8.556,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.38,8.833,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.404,9.106,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.425,9.376,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.444,9.641,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.46,9.903,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.474,10.16,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.485,10.412,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.494,10.66,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.5,10.904,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.503,11.142,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.503,11.375,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.5,11.603,0],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.495,11.826,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.487,12.043,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.476,12.255,0],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.462,12.461,0],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.446,12.661,0],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.426,12.854,0],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.403,13.042,0],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.378,13.223,0],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.349,13.398,0],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.317,13.566,0],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.283,13.727,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.245,13.882,0],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.204,14.029,0],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.16,14.17,0],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.113,14.303,0],"t":201,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.063,14.429,0],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-14.009,14.547,0],"t":203,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-13.953,14.657,0],"t":204,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-13.893,14.76,0],"t":205,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-13.744,14.763,0],"t":206,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-13.383,14.529,0],"t":207,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-12.746,13.981,0],"t":208,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-11.768,13.039,0],"t":209,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.441,11.681,0],"t":210,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-8.894,10.044,0],"t":211,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-7.352,8.378,0],"t":212,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-5.977,6.872,0],"t":213,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-4.815,5.584,0],"t":214,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.853,4.505,0],"t":215,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.06,3.607,0],"t":216,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-2.407,2.859,0],"t":217,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-1.869,2.237,0],"t":218,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-1.428,1.721,0],"t":219,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-1.067,1.295,0],"t":220,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.775,0.946,0],"t":221,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.541,0.665,0],"t":222,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.358,0.442,0],"t":223,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.218,0.272,0],"t":224,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.117,0.147,0],"t":225,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.05,0.063,0],"t":226,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.012,0.015,0],"t":227,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,0,0],"t":228,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,0,0],"t":420,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.057,-0.106,0],"t":421,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.262,-0.507,0],"t":422,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-0.686,-1.375,0],"t":423,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-1.372,-2.846,0],"t":424,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-2.193,-4.71,0],"t":425,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-2.939,-6.534,0],"t":426,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.537,-8.136,0],"t":427,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-4,-9.518,0],"t":428,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-4.357,-10.722,0],"t":429,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-4.633,-11.786,0],"t":430,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-4.843,-12.736,0],"t":431,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-5.002,-13.595,0],"t":432,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-5.12,-14.376,0],"t":433,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-5.203,-15.091,0],"t":434,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-5.258,-15.748,0],"t":435,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-5.288,-16.354,0],"t":436,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-5.299,-16.915,0],"t":437,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-5.292,-17.434,0],"t":438,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-5.27,-17.916,0],"t":439,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-5.237,-18.363,0],"t":440,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-5.193,-18.777,0],"t":441,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-5.14,-19.162,0],"t":442,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-5.079,-19.518,0],"t":443,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-5.012,-19.847,0],"t":444,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-4.94,-20.151,0],"t":445,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-4.863,-20.43,0],"t":446,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-4.783,-20.687,0],"t":447,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-4.701,-20.921,0],"t":448,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-4.616,-21.134,0],"t":449,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-4.53,-21.327,0],"t":450,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-4.443,-21.5,0],"t":451,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-4.355,-21.655,0],"t":452,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-4.267,-21.791,0],"t":453,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-4.18,-21.91,0],"t":454,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-4.093,-22.011,0],"t":455,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-4.007,-22.096,0],"t":456,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.923,-22.165,0],"t":457,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.84,-22.219,0],"t":458,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.758,-22.257,0],"t":459,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.679,-22.281,0],"t":460,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.602,-22.295,0],"t":461,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.529,-22.305,0],"t":462,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.46,-22.309,0],"t":463,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.394,-22.309,0],"t":464,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.332,-22.304,0],"t":465,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.274,-22.294,0],"t":466,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.219,-22.28,0],"t":467,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.168,-22.261,0],"t":468,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.12,-22.238,0],"t":469,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.076,-22.21,0],"t":470,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-3.035,-22.177,0],"t":471,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-2.998,-22.141,0],"t":472,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-2.965,-22.099,0],"t":473,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-2.935,-22.054,0],"t":474,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-2.909,-22.004,0],"t":475,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-2.886,-21.95,0],"t":476,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-2.866,-21.892,0],"t":477,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-2.85,-21.829,0],"t":478,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-2.837,-21.763,0],"t":479,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-2.828,-21.692,0],"t":480,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-2.83,-21.492,0],"t":481,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.2,0.2,0.2],"y":[0,0,0]},"t":205,"s":[100,100,100]},{"i":{"x":[0.2,0.2,0.703],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":205.25,"s":[105,105,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":228,"s":[84,84,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":420,"s":[84,84,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":440,"s":[105,105,100]},{"t":460,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.518,-1.548],[-2.131,0],[0,0],[-1.518,1.548],[0,2.174],[0,0],[1.518,1.548],[2.131,0],[0,0],[0,0],[3.778,3.854],[5.36,0],[3.778,-3.854],[0,-5.467],[0,0],[0,0],[1.518,-1.548],[0,-2.174],[0,0]],"o":[[1.518,1.548],[0,0],[2.131,0],[1.518,-1.548],[0,0],[0,-2.174],[-1.518,-1.548],[0,0],[0,0],[0,-5.467],[-3.778,-3.854],[-5.36,0],[-3.778,3.854],[0,0],[0,0],[-2.131,0],[-1.518,1.548],[0,0],[0,2.174]],"v":[[-28.723,39.178],[-23.25,41.5],[23.25,41.5],[28.723,39.178],[31,33.595],[31,-5.929],[28.723,-11.511],[23.25,-13.833],[19.375,-13.833],[19.375,-21.738],[13.708,-35.72],[0,-41.5],[-13.708,-35.72],[-19.375,-21.738],[-19.375,-13.833],[-23.25,-13.833],[-28.723,-11.511],[-31,-5.929],[-31,33.595]],"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":[[1.518,-1.548],[2.131,0],[1.518,1.548],[0,2.174],[-1.518,1.548],[-2.131,0],[-1.518,-1.548],[0,-2.174]],"o":[[-1.518,1.548],[-2.131,0],[-1.518,-1.548],[0,-2.174],[1.518,-1.548],[2.131,0],[1.518,1.548],[0,2.174]],"v":[[5.473,19.416],[0,21.738],[-5.473,19.416],[-7.75,13.833],[-5.473,8.251],[0,5.929],[5.473,8.251],[7.75,13.833]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[-2.26,2.306],[-3.229,0],[-2.26,-2.306],[0,-3.294]],"o":[[0,0],[0,0],[0,-3.294],[2.26,-2.306],[3.229,0],[2.26,2.306],[0,0]],"v":[[11.625,-13.833],[-11.625,-13.833],[-11.625,-21.738],[-8.234,-30.137],[0,-33.595],[8.234,-30.137],[11.625,-21.738]],"c":true},"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":205,"s":[0.823529422283,0.890196084976,0.988235294819,1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":215,"s":[0.1254902035,0.129411771894,0.141176477075,1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":420,"s":[0.1254902035,0.129411771894,0.141176477075,1]},{"t":433.20703125,"s":[0.823529422283,0.890196084976,0.988235294819,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":"Vector","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":482,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"mat_shape","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[-45]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":228,"s":[90]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":420,"s":[90]},{"t":482,"s":[135]}],"ix":10},"p":{"a":0,"k":[-0.178,0.318,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],[-13.398,-30.851],[0,0],[0,0],[4.094,-9.416],[0,0],[-30.852,13.397],[0,0],[-9.416,-4.094],[0,0],[13.398,30.851],[0,0],[-4.094,9.416],[0,0],[30.852,-13.397],[0,0],[9.416,4.094]],"o":[[-30.852,-13.41],[0,0],[0,0],[4.094,9.416],[0,0],[-13.41,30.851],[0,0],[9.416,-4.094],[0,0],[30.852,13.397],[0,0],[-4.094,-9.416],[0,0],[13.41,-30.851],[0,0],[-9.416,4.094],[0,0]],"v":[[-19.865,-68.631],[-68.635,-19.862],[-68.635,-19.862],[-66.418,-14.766],[-66.418,14.77],[-68.635,19.866],[-19.865,68.635],[-14.769,66.419],[14.769,66.419],[19.865,68.635],[68.635,19.866],[66.418,14.77],[66.418,-14.766],[68.635,-19.862],[19.865,-68.631],[14.769,-66.415],[-14.769,-66.415]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":205,"s":[0.40000000596,0.615686297417,0.964705884457,1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":215,"s":[0.235294133425,0.250980407,0.262745112181,1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":420,"s":[0.235294133425,0.250980407,0.262745112181,1]},{"t":433.20703125,"s":[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":[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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":482,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":3,"nm":"Wiggle_control_2","sr":1,"ks":{"o":{"a":0,"k":0,"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,"ef":[{"ty":5,"nm":"freq","np":3,"mn":"ADBE Slider Control","ix":1,"en":1,"ef":[{"ty":0,"nm":"Slider","mn":"ADBE Slider Control-0001","ix":1,"v":{"a":0,"k":0.2,"ix":1}}]},{"ty":5,"nm":"amp","np":3,"mn":"ADBE Slider Control","ix":2,"en":1,"ef":[{"ty":0,"nm":"Slider","mn":"ADBE Slider Control-0001","ix":1,"v":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":205,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":228,"s":[25]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.2],"y":[0]},"t":420,"s":[25]},{"t":460,"s":[0]}],"ix":1}}]}],"ip":0,"op":482,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"lock","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"k":[{"s":[111,150.5,0],"t":205,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.945,150.211,0],"t":206,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.834,149.618,0],"t":207,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.699,148.881,0],"t":208,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.55,148.057,0],"t":209,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.395,147.178,0],"t":210,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.236,146.26,0],"t":211,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.076,145.316,0],"t":212,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[109.915,144.353,0],"t":213,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[109.755,143.378,0],"t":214,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[109.597,142.397,0],"t":215,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[109.441,141.414,0],"t":216,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[109.287,140.433,0],"t":217,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[109.136,139.459,0],"t":218,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[108.988,138.496,0],"t":219,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[108.844,137.548,0],"t":220,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[108.704,136.62,0],"t":221,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[108.568,135.718,0],"t":222,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[108.437,134.851,0],"t":223,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[108.313,134.029,0],"t":224,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[108.198,133.269,0],"t":225,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[108.096,132.599,0],"t":226,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[108.015,132.074,0],"t":227,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[107.98,131.845,0],"t":228,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[107.989,131.946,0],"t":230,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[107.983,132.058,0],"t":232,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[107.961,132.178,0],"t":234,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[107.925,132.304,0],"t":236,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[107.876,132.436,0],"t":238,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[107.847,132.503,0],"t":239,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[107.815,132.57,0],"t":240,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[107.78,132.639,0],"t":241,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[107.743,132.707,0],"t":242,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[107.703,132.775,0],"t":243,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[107.66,132.843,0],"t":244,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[107.616,132.91,0],"t":245,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[107.52,133.042,0],"t":247,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[107.417,133.169,0],"t":249,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[107.363,133.23,0],"t":250,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[107.307,133.289,0],"t":251,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[107.249,133.346,0],"t":252,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[107.19,133.4,0],"t":253,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[107.13,133.452,0],"t":254,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[107.068,133.501,0],"t":255,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[107.005,133.547,0],"t":256,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[106.941,133.589,0],"t":257,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[106.875,133.627,0],"t":258,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[106.809,133.663,0],"t":259,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[106.737,133.704,0],"t":260,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[106.66,133.752,0],"t":261,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[106.578,133.806,0],"t":262,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[106.492,133.866,0],"t":263,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[106.401,133.933,0],"t":264,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[106.306,134.006,0],"t":265,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[106.206,134.085,0],"t":266,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[106.103,134.169,0],"t":267,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[105.996,134.26,0],"t":268,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[105.886,134.356,0],"t":269,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[105.772,134.458,0],"t":270,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[105.655,134.565,0],"t":271,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[105.536,134.678,0],"t":272,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[105.414,134.796,0],"t":273,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[105.29,134.919,0],"t":274,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[105.163,135.047,0],"t":275,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[105.035,135.18,0],"t":276,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.905,135.318,0],"t":277,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.773,135.461,0],"t":278,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.64,135.608,0],"t":279,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.506,135.76,0],"t":280,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.371,135.916,0],"t":281,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.235,136.076,0],"t":282,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.099,136.241,0],"t":283,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[103.962,136.409,0],"t":284,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[103.825,136.581,0],"t":285,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[103.688,136.758,0],"t":286,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[103.551,136.937,0],"t":287,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[103.415,137.121,0],"t":288,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[103.279,137.307,0],"t":289,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[103.144,137.497,0],"t":290,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[103.01,137.691,0],"t":291,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[102.878,137.887,0],"t":292,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[102.746,138.086,0],"t":293,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[102.616,138.288,0],"t":294,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[102.488,138.493,0],"t":295,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[102.361,138.7,0],"t":296,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[102.237,138.91,0],"t":297,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[102.114,139.122,0],"t":298,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[101.994,139.336,0],"t":299,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[101.877,139.553,0],"t":300,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[101.762,139.771,0],"t":301,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[101.649,139.991,0],"t":302,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[101.54,140.213,0],"t":303,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[101.434,140.436,0],"t":304,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[101.331,140.661,0],"t":305,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[101.232,140.887,0],"t":306,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[101.136,141.115,0],"t":307,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[101.044,141.343,0],"t":308,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[100.956,141.573,0],"t":309,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[100.871,141.803,0],"t":310,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[100.791,142.034,0],"t":311,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[100.715,142.266,0],"t":312,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[100.643,142.498,0],"t":313,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[100.576,142.73,0],"t":314,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[100.513,142.963,0],"t":315,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[100.455,143.196,0],"t":316,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[100.402,143.429,0],"t":317,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[100.354,143.661,0],"t":318,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[100.311,143.894,0],"t":319,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[100.273,144.126,0],"t":320,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[100.241,144.357,0],"t":321,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[100.214,144.588,0],"t":322,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[100.192,144.818,0],"t":323,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[100.176,145.047,0],"t":324,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[100.165,145.275,0],"t":325,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[100.16,145.502,0],"t":326,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[100.162,145.728,0],"t":327,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[100.169,145.952,0],"t":328,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[100.182,146.175,0],"t":329,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[100.201,146.396,0],"t":330,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[100.226,146.616,0],"t":331,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[100.257,146.834,0],"t":332,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[100.295,147.049,0],"t":333,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[100.339,147.263,0],"t":334,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[100.39,147.474,0],"t":335,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[100.446,147.683,0],"t":336,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[100.51,147.89,0],"t":337,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[100.58,148.094,0],"t":338,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[100.656,148.296,0],"t":339,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[100.74,148.494,0],"t":340,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[100.829,148.69,0],"t":341,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[100.926,148.883,0],"t":342,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[101.029,149.073,0],"t":343,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[101.139,149.259,0],"t":344,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[101.256,149.442,0],"t":345,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[101.38,149.622,0],"t":346,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[101.51,149.798,0],"t":347,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[101.647,149.97,0],"t":348,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[101.791,150.139,0],"t":349,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[101.942,150.303,0],"t":350,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[102.1,150.464,0],"t":351,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[102.264,150.62,0],"t":352,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[102.435,150.773,0],"t":353,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[102.613,150.921,0],"t":354,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[102.798,151.065,0],"t":355,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[102.989,151.204,0],"t":356,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[103.188,151.338,0],"t":357,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[103.392,151.468,0],"t":358,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[103.604,151.593,0],"t":359,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[103.822,151.713,0],"t":360,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.046,151.828,0],"t":361,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.277,151.938,0],"t":362,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.514,152.043,0],"t":363,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[104.758,152.142,0],"t":364,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[105.008,152.236,0],"t":365,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[105.264,152.325,0],"t":366,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[105.527,152.408,0],"t":367,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[105.795,152.485,0],"t":368,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[106.069,152.557,0],"t":369,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[106.35,152.622,0],"t":370,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[106.636,152.682,0],"t":371,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[106.927,152.736,0],"t":372,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[107.225,152.784,0],"t":373,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[107.528,152.825,0],"t":374,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[107.836,152.86,0],"t":375,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[108.149,152.889,0],"t":376,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[108.468,152.912,0],"t":377,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[108.791,152.928,0],"t":378,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[109.119,152.938,0],"t":379,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[109.451,152.944,0],"t":380,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[109.785,152.946,0],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.122,152.945,0],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.462,152.94,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[110.804,152.931,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[111.149,152.918,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[111.495,152.901,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[111.844,152.88,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[112.194,152.855,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[112.546,152.825,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[112.899,152.792,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[113.253,152.754,0],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[113.608,152.711,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[113.964,152.664,0],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[114.321,152.612,0],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[114.678,152.556,0],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[115.035,152.494,0],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[115.392,152.428,0],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[115.749,152.356,0],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[116.106,152.279,0],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[116.461,152.198,0],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[116.816,152.11,0],"t":401,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[117.17,152.017,0],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[117.522,151.919,0],"t":403,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[117.873,151.815,0],"t":404,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[118.222,151.705,0],"t":405,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[118.57,151.589,0],"t":406,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[118.914,151.467,0],"t":407,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[119.257,151.338,0],"t":408,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[119.596,151.204,0],"t":409,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[119.934,151.063,0],"t":410,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[120.27,150.918,0],"t":411,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[120.603,150.767,0],"t":412,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[120.933,150.61,0],"t":413,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[121.261,150.449,0],"t":414,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[121.586,150.283,0],"t":415,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[121.907,150.113,0],"t":416,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[122.225,149.938,0],"t":417,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[122.539,149.759,0],"t":418,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[122.85,149.576,0],"t":419,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[123.156,149.389,0],"t":420,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[123.383,149.206,0],"t":421,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[123.391,149.047,0],"t":422,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[123.051,148.936,0],"t":423,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[122.252,148.902,0],"t":424,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[121.138,148.938,0],"t":425,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[120.024,149.003,0],"t":426,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[119.05,149.071,0],"t":427,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[118.217,149.137,0],"t":428,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[117.499,149.199,0],"t":429,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[116.872,149.259,0],"t":430,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[116.315,149.319,0],"t":431,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[115.816,149.377,0],"t":432,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[115.365,149.436,0],"t":433,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[114.954,149.494,0],"t":434,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[114.578,149.552,0],"t":435,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[114.232,149.61,0],"t":436,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[113.913,149.668,0],"t":437,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[113.618,149.725,0],"t":438,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[113.346,149.782,0],"t":439,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[113.094,149.837,0],"t":440,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[112.861,149.892,0],"t":441,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[112.645,149.946,0],"t":442,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[112.446,149.998,0],"t":443,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[112.263,150.049,0],"t":444,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[112.095,150.098,0],"t":445,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[111.941,150.145,0],"t":446,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[111.801,150.19,0],"t":447,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[111.673,150.232,0],"t":448,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[111.559,150.272,0],"t":449,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[111.456,150.31,0],"t":450,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[111.364,150.344,0],"t":451,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[111.284,150.375,0],"t":452,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[111.215,150.404,0],"t":453,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[111.107,150.45,0],"t":455,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":205.25,"s":[84,84,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":224,"s":[105,105,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":246.75,"s":[100,100,100]},{"i":{"x":[0,0,0],"y":[1,1,1]},"o":{"x":[0.2,0.2,0.2],"y":[0,0,0]},"t":420,"s":[100,100,100]},{"t":460,"s":[84,84,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.518,-1.548],[-2.131,0],[0,0],[-1.518,1.548],[0,2.174],[0,0],[1.518,1.548],[2.131,0],[0,0],[0,0],[3.778,3.854],[5.36,0],[3.778,-3.854],[0,-5.467],[0,0],[0,0],[1.518,-1.548],[0,-2.174],[0,0]],"o":[[1.518,1.548],[0,0],[2.131,0],[1.518,-1.548],[0,0],[0,-2.174],[-1.518,-1.548],[0,0],[0,0],[0,-5.467],[-3.778,-3.854],[-5.36,0],[-3.778,3.854],[0,0],[0,0],[-2.131,0],[-1.518,1.548],[0,0],[0,2.174]],"v":[[-28.723,39.178],[-23.25,41.5],[23.25,41.5],[28.723,39.178],[31,33.595],[31,-5.929],[28.723,-11.511],[23.25,-13.833],[19.375,-13.833],[19.375,-21.738],[13.708,-35.72],[0,-41.5],[-13.708,-35.72],[-19.375,-21.738],[-19.375,-13.833],[-23.25,-13.833],[-28.723,-11.511],[-31,-5.929],[-31,33.595]],"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":[[1.518,-1.548],[2.131,0],[1.518,1.548],[0,2.174],[-1.518,1.548],[-2.131,0],[-1.518,-1.548],[0,-2.174]],"o":[[-1.518,1.548],[-2.131,0],[-1.518,-1.548],[0,-2.174],[1.518,-1.548],[2.131,0],[1.518,1.548],[0,2.174]],"v":[[5.473,19.416],[0,21.738],[-5.473,19.416],[-7.75,13.833],[-5.473,8.251],[0,5.929],[5.473,8.251],[7.75,13.833]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[-2.26,2.306],[-3.229,0],[-2.26,-2.306],[0,-3.294]],"o":[[0,0],[0,0],[0,-3.294],[2.26,-2.306],[3.229,0],[2.26,2.306],[0,0]],"v":[[11.625,-13.833],[-11.625,-13.833],[-11.625,-21.738],[-8.234,-30.137],[0,-33.595],[8.234,-30.137],[11.625,-21.738]],"c":true},"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":205,"s":[0.1254902035,0.129411771894,0.141176477075,1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":215,"s":[0.823529481888,0.890196144581,0.988235354424,1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":420,"s":[0.823529481888,0.890196144581,0.988235354424,1]},{"t":433,"s":[0.1254902035,0.129411771894,0.141176477075,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":"Vector","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":482,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"mat_shape","parent":8,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":205,"s":[0]},{"t":451,"s":[108]}],"ix":10},"p":{"a":0,"k":[0.262,-0.211,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":[[4.069,-0.125],[2.817,-2.942],[0,0],[0,0],[2.467,-0.801],[2.554,0.426],[0,0],[0.301,-0.038],[3.23,-2.492],[0.551,-4.044],[0,0],[1.528,-2.104],[2.354,-1.089],[0,0],[0.213,-0.213],[1.164,-3.919],[-1.916,-3.606],[0,0],[0,-2.592],[1.29,-2.254],[0,0],[0.213,-0.213],[0.1,-2.041],[-0.689,-1.928],[-1.377,-1.515],[-1.853,-0.876],[0,0],[-1.528,-2.104],[-0.313,-2.579],[0,0],[0.025,-0.313],[-3.393,-2.279],[-4.019,0.764],[0,0],[-2.504,-0.776],[-1.791,-1.928],[0,0],[0,0],[-4.069,0.125],[-2.817,2.93],[0,0],[-2.467,0.801],[-2.554,-0.438],[0,0],[-0.313,0.025],[-3.23,2.492],[-0.563,4.032],[0,0],[-1.528,2.104],[-2.354,1.089],[0,0],[-0.213,0.213],[-1.164,3.919],[1.928,3.606],[0,0],[0,2.592],[-1.29,2.254],[0,0],[-0.213,0.213],[1.377,3.831],[3.669,1.765],[0,0],[1.528,2.091],[0.313,2.567],[0,0],[-0.038,0.301],[3.393,2.291],[4.019,-0.764],[0,0],[2.492,0.801],[1.765,1.928],[0,0],[0,0]],"o":[[-4.069,0.125],[0,0],[0,0],[-1.828,1.841],[-2.467,0.789],[0,0],[-0.263,-0.175],[-4.044,-0.488],[-3.218,2.492],[0,0],[-0.313,2.579],[-1.515,2.104],[0,0],[-0.225,0.225],[-3.581,1.978],[-1.164,3.906],[0,0],[1.277,2.254],[0,2.592],[0,0],[-0.225,0.225],[-0.889,1.853],[-0.1,2.041],[0.689,1.928],[1.377,1.515],[0,0],[2.354,1.089],[1.515,2.104],[0,0],[0.163,0.263],[0.801,4.007],[3.393,2.291],[0,0],[2.567,-0.576],[2.504,0.776],[0,0],[0,0],[2.993,2.767],[4.057,-0.125],[0,0],[1.816,-1.841],[2.467,-0.801],[0,0],[0.263,0.163],[4.044,0.476],[3.23,-2.492],[0,0],[0.313,-2.579],[1.515,-2.104],[0,0],[0.225,-0.225],[3.581,-1.978],[1.164,-3.932],[0,0],[-1.277,-2.254],[0,-2.592],[0,0],[0.225,-0.225],[1.703,-3.706],[-1.39,-3.831],[0,0],[-2.354,-1.089],[-1.528,-2.091],[0,0],[-0.175,-0.263],[-0.801,-4.007],[-3.393,-2.279],[0,0],[-2.554,0.526],[-2.492,-0.801],[0,0],[0,0],[-2.993,-2.767]],"v":[[-0.49,-79.282],[-11.233,-74.511],[-11.271,-74.511],[-15.09,-70.655],[-21.626,-66.648],[-29.264,-66.085],[-34.698,-67.174],[-35.574,-67.387],[-46.918,-64.269],[-52.816,-54.09],[-53.467,-48.431],[-56.259,-41.294],[-62.169,-36.423],[-67.203,-33.994],[-68.079,-33.556],[-75.479,-24.366],[-74.302,-12.634],[-71.685,-7.6],[-69.732,-0.188],[-71.685,7.225],[-74.302,12.233],[-74.74,13.109],[-76.243,19.007],[-75.354,25.029],[-72.224,30.25],[-67.328,33.869],[-62.294,36.26],[-56.384,41.131],[-53.592,48.268],[-52.941,53.952],[-52.728,54.829],[-46.18,64.658],[-34.61,67.049],[-29.176,65.96],[-21.438,66.26],[-14.877,70.38],[-10.933,74.511],[-10.282,75.163],[0.737,79.282],[11.48,74.511],[15.337,70.53],[21.86,66.511],[29.511,65.96],[34.794,67.049],[35.671,67.262],[47.015,64.132],[52.925,53.952],[53.576,48.268],[56.368,41.131],[62.278,36.26],[67.312,33.869],[68.188,33.431],[75.601,24.228],[74.411,12.471],[71.794,7.462],[69.841,0.05],[71.794,-7.362],[74.411,-12.183],[74.85,-13.059],[75.338,-24.816],[67.45,-33.543],[62.416,-36.123],[56.506,-40.981],[53.714,-48.093],[53.063,-53.777],[52.85,-54.653],[46.301,-64.482],[34.732,-66.861],[29.298,-65.772],[21.61,-66.185],[15.124,-70.342],[11.18,-74.511],[10.529,-75.163]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":205,"s":[0.235294118524,0.250980407,0.262745112181,1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":215,"s":[0.400000035763,0.615686297417,0.964705944061,1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":420,"s":[0.400000035763,0.615686297417,0.964705944061,1]},{"t":433,"s":[0.235294118524,0.250980407,0.262745112181,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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":482,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Vector","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.29,0],[0,0],[0,0],[0,15.54],[0,0],[15.18,0],[0,0],[0,-15.65],[0,0]],"o":[[0,0],[0,0],[15.29,0],[0,0],[0,-15.65],[0,0],[-15.29,0],[0,0],[0,15.65]],"v":[[-178.179,150],[178.189,150],[178.179,150],[206,121.63],[206,-121.52],[178.289,-150],[-178.179,-150],[-206,-121.52],[-206,121.52]],"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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":482,"st":0,"ct":1,"bm":0}],"markers":[{"tm":481,"cm":"End","dr":0}],"props":{}}
\ No newline at end of file
diff --git a/res/values/accessibility_shortcut_keys.xml b/res/values/accessibility_shortcut_keys.xml
index 0d409a8..4992a54 100644
--- a/res/values/accessibility_shortcut_keys.xml
+++ b/res/values/accessibility_shortcut_keys.xml
@@ -16,6 +16,7 @@
-->
<resources>
+ <string name="accessibility_shortcut_description_pref" translatable="false">shortcut_description</string>
<string name="accessibility_shortcut_volume_keys_pref" translatable="false">shortcut_volume_keys_pref</string>
<string name="accessibility_shortcut_gesture_pref" translatable="false">shortcut_gesture_pref</string>
<string name="accessibility_shortcut_nav_button_pref" translatable="false">shortcut_nav_button_pref</string>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 40a7c58..200253a 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -198,4 +198,9 @@
<attr name="notification_importance_button_background_color_selected" format="color" />
<attr name="notification_importance_button_border_color_selected" format="color" />
<attr name="notification_importance_button_foreground_color_selected" format="color" />
+
+ <!-- For BackgroundPreference -->
+ <declare-styleable name="BackgroundPreference">
+ <attr name="background" format="reference" />
+ </declare-styleable>
</resources>
diff --git a/res/values/config.xml b/res/values/config.xml
index afd6fdd..9e91dcc 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -808,4 +808,8 @@
<!-- Array of carrier id to allow the pSIM conversion-->
<integer-array name="config_psim_conversion_menu_enabled_carrier" translatable="false">
</integer-array>
+
+ <!-- Array of carrier id that uses reusable activation code-->
+ <integer-array name="config_carrier_use_rac" translatable="false">
+ </integer-array>
</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 164f487..cbfd3a8 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -479,4 +479,13 @@
<!-- Credential Manager settings dimensions -->
<dimen name="credman_primary_provider_pref_left_padding">80dp</dimen>
<dimen name="credman_primary_provider_pref_left_padding_compact">24dp</dimen>
+
+ <!-- Color contrast screen -->
+ <dimen name="contrast_button_total_size">90dp</dimen>
+ <dimen name="contrast_button_inner_size">82dp</dimen>
+ <dimen name="contrast_button_radius">20dp</dimen>
+ <dimen name="contrast_button_stroke_width">2dp</dimen>
+ <dimen name="contrast_button_text_size">14sp</dimen>
+ <dimen name="contrast_button_text_spacing">4dp</dimen>
+ <dimen name="contrast_button_horizontal_spacing">16dp</dimen>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 76c7106..a369d3d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -152,10 +152,10 @@
<string name="bluetooth_pair_other_ear_dialog_left_ear_positive_button">Pair left ear</string>
<!-- Title for all hearing devices related controls section. [CHAR LIMIT=60] -->
<string name="bluetooth_device_controls_general">For all available hearing devices</string>
- <!-- Connected devices settings. Title of the preference to show the entrance of the hearing device controls related page. [CHAR LIMIT=65] -->
- <string name="bluetooth_device_controls_title">Hearing device settings</string>
- <!-- Connected devices settings. Title of the preference to show the entrance of the hearing device controls related page. [CHAR LIMIT=65] -->
- <string name="bluetooth_device_controls_summary">Shortcut, hearing aid compatibility</string>
+ <!-- Connected devices settings. Title of the preference to show the entrance of the hearing device settings related page. [CHAR LIMIT=65] -->
+ <string name="bluetooth_device_controls_title">More hearing device settings</string>
+ <!-- Connected devices settings. Summary of the preference to show the item in the hearing device settings related page. [CHAR LIMIT=120] -->
+ <string name="bluetooth_device_controls_summary">Change cross-device settings like shortcut, and telecoil controls</string>
<!-- Title for this device specific controls section. [CHAR LIMIT=30] -->
<string name="bluetooth_device_controls_specific">For this device</string>
<!-- Connected devices settings. Title of the preference to show the entrance of the audio output page. It can change different types of audio are played on phone or other bluetooth devices. [CHAR LIMIT=35] -->
@@ -300,21 +300,6 @@
<!-- Name shown in the title of individual stylus preference in the connected devices page [CHAR LIMIT=60] -->
<string name="stylus_connected_devices_title">Stylus</string>
- <!-- Title for audio sharing page [CHAR LIMIT=none]-->
- <string name="audio_sharing_title">Audio sharing</string>
- <!-- Title for audio sharing primary switch [CHAR LIMIT=none]-->
- <string name="audio_sharing_switch_title">Share audio</string>
- <!-- Title for calls and alarms device on audio sharing page [CHAR LIMIT=none]-->
- <string name="calls_and_alarms_device_title">Calls and alarms</string>
-
- <!-- Title for audio streams preference category [CHAR LIMIT=none]-->
- <string name="audio_streams_category_title">Connect to a LE audio stream</string>
- <!-- Title for audio streams preference [CHAR LIMIT=none]-->
- <string name="audio_streams_pref_title">Nearby audio streams</string>
- <!-- Title for audio streams page [CHAR LIMIT=none]-->
- <string name="audio_streams_title">Audio streams</string>
- <!-- Summary for QR code scanning in audio streams page [CHAR LIMIT=none]-->
- <string name="audio_streams_qr_code_summary">Connect to an audio stream using QR code</string>
<!--Text that appears when scanning for nearby audio streams is finished and no streams were found [CHAR LIMIT=40]-->
<string name="audio_streams_empty">No nearby audio streams were found.</string>
@@ -1313,27 +1298,27 @@
<!-- Label for private space setup button to create private space [CHAR LIMIT=30] -->
<string name="private_space_setup_button_label">Set up</string>
<!-- Title for Private Space setup education screen. [CHAR LIMIT=50] -->
- <string name="private_space_setup_title">Set up a private space</string>
+ <string name="private_space_setup_title">Private space</string>
<!-- Summary for the private space setup education screen. [CHAR LIMIT=NONE] -->
- <string name="private_space_hide_apps_summary">Keep private apps in a separate space that you can hide or lock</string>
+ <string name="private_space_hide_apps_summary">Hide or lock private apps in a separate space. Use a dedicated Google Account for extra security.</string>
<!-- Text shown in private space setup screen which explains how the private space works [CHAR LIMIT=50] -->
- <string name="private_space_how_title">How it works</string>
+ <string name="private_space_setup_sub_header">Set up your private space</string>
<!-- Text shown in private space setup screen which explains private space can be accessed from bottom of all apps list. [CHAR LIMIT=NONE] -->
- <string name="private_space_access_bottom_text">You can access your private space from the bottom of your apps list</string>
- <!-- Text shown in private space setup screen which explains private space apps are protected by a lock. [CHAR LIMIT=60] -->
- <string name="private_space_protected_lock_text">Apps in your private space are protected by a lock</string>
+ <string name="private_space_separate_account_text"><b>Choose a Google Account for your space</b>\nUsing a dedicated account helps to stop synced files, photos, and emails appearing outside your space</string>
+ <!-- Text shown in private space setup screen which explains private space apps are protected by a lock. [CHAR LIMIT=NONE] -->
+ <string name="private_space_protected_lock_text"><b>Set a lock</b>\nLock your space to stop other people opening it</string>
<!-- Text shown in private space setup screen which explains notifications from private space apps will not be shown when private space is locked. [CHAR LIMIT=NONE] -->
- <string name="private_space_hidden_notifications_text">Notifications from apps in your private space are hidden when it\u2019s locked</string>
+ <string name="private_space_install_apps_text"><b>Install apps</b>\nYour private space has its own Play Store so you can install apps easily.</string>
<!-- This is info text to help explain in private space setup screen that the permissions granted to private space apps will not be shown in settings when private space is locked. [CHAR LIMIT=NONE] -->
<string name="private_space_apps_permission_text">Apps in your private space won\'t appear in permission manager, privacy dashboard, and other settings when your private space is locked.\n\nYour private space can\'t be moved to a new device. You\'ll need to set up another private space if you want to use it on another device.\n\nAnyone that connects your device to a computer or installs harmful apps on your device may be able to access your private space.</string>
<!-- Text shown at the bottom in private space auto advancing screens. [CHAR LIMIT=60] -->
<string name="private_space_setting_up_text">Setting up private space\u2026</string>
<!-- Title for private space setup in auto advancing screen informing private space notifications are hidden when locked. [CHAR LIMIT=NONE] -->
<string name="private_space_notifications_hidden_title">Notifications from private space apps are hidden when it\u2019s locked</string>
- <!-- Title for private space setup in auto advancing screen informing photos/files from private space can be shared when unlocked. [CHAR LIMIT=NONE] -->
- <string name="private_space_share_photos_title">Unlock private space to share photos or files</string>
+ <!-- Title for private space setup in auto advancing screen informing to explore private space settings for hide and auto lock. [CHAR LIMIT=NONE] -->
+ <string name="private_space_explore_settings_title">Explore private space settings to hide private space and set up automatic locking</string>
<!-- Title for private space setup in auto advancing screen informing some system apps are already installed in private space. [CHAR LIMIT=NONE] -->
- <string name="private_space_apps_installed_title">Some apps are already installed in your private space</string>
+ <string name="private_space_apps_installed_title">Required apps are already installed in your private space</string>
<!-- Title for private space creation error screen. [CHAR LIMIT=60] -->
<string name="private_space_error_screen_title">Couldn\u2019t set up a private space</string>
<!-- Label for button to retry creating private space again on creation error. [CHAR LIMIT=30] -->
@@ -1378,6 +1363,22 @@
<string name="private_space_notifications_title">Sensitive notifications on lock screen</string>
<!-- Summary description for private space sensitive notifications toggle [CHAR LIMIT=200] -->
<string name="private_space_sensitive_notifications_description">Show sensitive content when private space is unlocked</string>
+ <!-- Title for private space GAIA education screen [CHAR LIMIT=90] -->
+ <string name="private_space_gaia_education_title">Create a Google Account to help keep your data private</string>
+ <!-- Description for private space GAIA education screen [CHAR LIMIT=120] -->
+ <string name="private_space_gaia_education_description">On the next screen you can sign in to an account to use with your private space</string>
+ <!-- Sub header for private space GAIA education screen [CHAR LIMIT=120] -->
+ <string name="private_space_gaia_education_header"><b>Create a dedicated account to help stop data appearing outside private space, such as:</b></string>
+ <!-- Text for private space GAIA education screen [CHAR LIMIT=90] -->
+ <string name="private_space_gaia_education_bullet1">Synced photos, files, emails, contacts, calendar events, and other data</string>
+ <!-- Text for private space GAIA education screen [CHAR LIMIT=60] -->
+ <string name="private_space_gaia_education_bullet2">App download history and recommendations</string>
+ <!-- Text for private space GAIA education screen [CHAR LIMIT=60] -->
+ <string name="private_space_gaia_education_bullet3">Browsing history, bookmarks, and saved passwords</string>
+ <!-- Text for private space GAIA education screen [CHAR LIMIT=90] -->
+ <string name="private_space_gaia_education_bullet4">Suggested content related to your activity in private space apps</string>
+ <!-- Text for button in private space GAIA education screen to start login [CHAR LIMIT=20] -->
+ <string name="private_space_gaia_education_got_it">Got it</string>
<!-- Text shown when "Add fingerprint" button is disabled -->
<string name="fingerprint_add_max">You can add up to <xliff:g id="count" example="5">%d</xliff:g> fingerprints</string>
@@ -2059,6 +2060,10 @@
<string name="wifi_settings_wep_networks_button_allow">Allow WEP</string>
<!-- Wi-Fi settings dialog. Button text of dialog displayed when WEP network toggle is blocked. [CHAR LIMIT=NONE] -->
<string name="wifi_settings_ssid_block_button_close">Close</string>
+ <!-- Wi-Fi settings dialog. Title of dialog displayed when the user turns off “Allow WEP networks” while connected to a WEP network. [CHAR LIMIT=NONE] -->
+ <string name="wifi_settings_wep_networks_disconnect_title">Disconnect from <xliff:g id="name">%1$s</xliff:g>?</string>
+ <!-- Wi-Fi settings dialog. Summary text of dialog displayed when the user turns off “Allow WEP networks” while connected to a WEP network. [CHAR LIMIT=NONE] -->
+ <string name="wifi_settings_wep_networks_disconnect_summary">You\u0027re connected to a WEP network. If you block these networks, you\u0027ll be disconnected.</string>
<!-- Dialog for Access Points --> <skip />
<!-- Label to show/hide advanced options [CHAR LIMIT=40] -->
@@ -2125,6 +2130,14 @@
<string name="wifi_ip_settings">IP settings</string>
<!-- Label for the spinner to show Wifi MAC randomization [CHAR LIMIT=25] -->
<string name="wifi_privacy_settings">Privacy</string>
+ <!-- Category title for the spinner to show Wifi MAC randomization [CHAR LIMIT=25] -->
+ <string name="wifi_privacy_mac_settings">MAC</string>
+ <!-- Category title for Device name [CHAR LIMIT=25] -->
+ <string name="wifi_privacy_device_name_settings">Device name</string>
+ <!-- Toggle button title for allowing/disallowing sending device name to DHCP [CHAR LIMIT=50] -->
+ <string name="wifi_privacy_send_device_name_toggle_title">Send device name</string>
+ <!-- Toggle button title for allowing/disallowing sending device name to DHCP [CHAR LIMIT=50] -->
+ <string name="wifi_privacy_send_device_name_toggle_summary">Share this device\u0027s name with the network</string>
<!-- Label for the subscription preference. [CHAR LIMIT=32] -->
<string name="wifi_subscription">Subscription</string>
<!-- Summary text for the subscription preference. [CHAR LIMIT=NONE] -->
@@ -2836,6 +2849,10 @@
<string name="dark_ui_bedtime_footer_summary">Dark theme is currently following your Bedtime mode schedule</string>
<!-- Dark UI screen footer action text shown when the when Dark theme turns on/off automatically according to a user bedtime schedule. [CHAR LIMIT=NONE] -->
<string name="dark_ui_bedtime_footer_action">Bedtime mode settings</string>
+ <!-- Even Dimmer setting title. Allows device to reduce brightness even further than standard range. [CHAR LIMIT=NONE] -->
+ <string name="even_dimmer_display_title">Even Dimmer</string>
+ <!-- Even Dimmer setting summary. [CHAR LIMIT=NONE] -->
+ <string name="even_dimmer_display_summary">Allow device to go dimmer than usual</string>
<!-- Sound & display settings screen, setting option name to change screen timeout -->
@@ -4611,6 +4628,28 @@
<string name="display_category_title">Display</string>
<!-- Title for the accessibility color and motion page. [CHAR LIMIT=50] -->
<string name="accessibility_color_and_motion_title">Color and motion</string>
+ <!-- Title for the accessibility color contrast page. [CHAR LIMIT=50] -->
+ <string name="accessibility_color_contrast_title">Color contrast</string>
+ <!-- Intro for the accessibility color contrast page. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_color_contrast_intro">Higher contrast makes text, buttons, and icons stand out more. Choose the contrast that looks best to you.</string>
+ <!-- Notes in color contrast page footer for something should be aware. [CHAR LIMIT=NONE] -->
+ <string name="color_contrast_note">Some apps may not support all color and text contrast settings</string>
+ <!-- Summary for the accessibility color setting. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_color_contrast_summary">Adjust how colors and text look against your screen\'s background color</string>
+ <!-- Preview screen title on the color contrast page. [CHAR LIMIT=20] -->
+ <string name="color_contrast_preview">Preview</string>
+ <!-- Preview screen email sender's name on the color contrast page. [CHAR LIMIT=15] -->
+ <string name="color_contrast_preview_sender_name">Helen, Adam</string>
+ <!-- Preview screen email sent date on the color contrast page. [CHAR LIMIT=15] -->
+ <string name="color_contrast_preview_email_send_date">2 days ago</string>
+ <!-- Preview screen email badge on the contrast page. [CHAR LIMIT=15] -->
+ <string name="color_contrast_preview_email_badge">Follow up?</string>
+ <!-- Preview screen email title on the contrast page. [CHAR LIMIT=30] -->
+ <string name="color_contrast_preview_email_title">Business trip report</string>
+ <!-- Preview screen email body on the contrast page. [CHAR LIMIT=NONE] -->
+ <string name="color_contrast_preview_email_body">For further assistance, please reach out to \nmyself or Helen. This report will be</string>
+ <!-- Preview screen email attachment name on the contrast page. [CHAR LIMIT=30] -->
+ <string name="color_contrast_preview_email_attachment_name">Client Expenses</string>
<!-- Title for the accessibility text options page. [CHAR LIMIT=50] -->
<string name="accessibility_turn_screen_darker_title">Turn screen darker</string>
<!-- Title for the accessibility preference category of interaction control services and settings. [CHAR LIMIT=50] -->
@@ -4912,6 +4951,10 @@
<string name="accessibility_toggle_high_text_contrast_preference_title">High contrast text</string>
<!-- Summary for the accessibility preference to high contrast text. [CHAR LIMIT=NONE] -->
<string name="accessibility_toggle_high_text_contrast_preference_summary">Change text color to black or white. Maximizes contrast with the background.</string>
+ <!-- Title for the accessibility preference to high contrast text. [CHAR LIMIT=35] -->
+ <string name="accessibility_toggle_maximize_text_contrast_preference_title">Maximize text contrast</string>
+ <!-- Summary for the accessibility preference to high contrast text. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_toggle_maximize_text_contrast_preference_summary">Change text color to black or white to increase contrast with the background.</string>
<!-- Title for the accessibility preference to auto update screen magnification. [CHAR LIMIT=35] -->
<string name="accessibility_toggle_screen_magnification_auto_update_preference_title">Auto
update screen magnification</string>
@@ -5112,13 +5155,17 @@
<string name="accessibility_shortcut_type_hardware">Hold volume keys</string>
<!-- Summary for accessibility shortcut preference for magnification triple tap shortcut type. [CHAR LIMIT=NONE] -->
<string name="accessibility_shortcut_type_triple_tap">Triple tap screen</string>
+ <!-- Generic title for editing the shortcuts of multiple accessibility features. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_shortcut_edit_screen_title">Edit accessibility shortcuts</string>
+ <!-- Prompt for editing the shortcuts of multiple accessibility features. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_shortcut_edit_screen_prompt">Choose your shortcut for %1$s</string>
<!-- Button text for the accessibility dialog continue to the next screen for hearing aid. [CHAR LIMIT=32] -->
<string name="accessibility_hearingaid_instruction_continue_button">Continue</string>
<!-- Title for the accessibility preference for hearing devices. [CHAR LIMIT=35] -->
<string name="accessibility_hearingaid_title">Hearing devices</string>
- <!-- Introduction for the Hearing devices page to introduce feature. [CHAR LIMIT=NONE] -->
- <string name="accessibility_hearingaid_intro">You can use hearing aids, cochlear implants, and other amplification devices with your phone</string>
+ <!-- Introduction for the Hearing devices page to introduce feature. [CHAR LIMIT=NONE BACKUP_MESSAGE_ID=5856992709195963850] -->
+ <string name="accessibility_hearingaid_intro">Set up and manage ASHA and LE Audio hearing aids, cochlear implants, and other amplification devices</string>
<!-- Summary for the accessibility preference for hearing aid when not connected. [CHAR LIMIT=50] -->
<string name="accessibility_hearingaid_not_connected_summary">No hearing devices connected</string>
<!-- Summary for the accessibility preference for hearing aid when adding new devices. [CHAR LIMIT=50] -->
@@ -5139,8 +5186,6 @@
<string name="accessibility_hearingaid_more_device_summary"><xliff:g id="device_name" example="GN Hearing Aids">%1$s</xliff:g> +1 more</string>
<!-- Title for the hearing device pairing preference. [CHAR LIMIT=20] -->
<string name="accessibility_hearing_device_pairing_title">Pair new device</string>
- <!-- Title for accessibility pair new hearing device page footer. [CHAR LIMIT=60] -->
- <string name="accessibility_pair_hearing_device_about_title">About Pair new device</string>
<!-- Title for the preference category containing the connected hearing device group. [CHAR LIMIT=20]-->
<string name="accessibility_hearing_device_connected_title">Hearing devices</string>
<!-- Title for the preference category containing the previously connected hearing device group. [CHAR LIMIT=20]-->
@@ -5155,10 +5200,12 @@
<string name="accessibility_hac_mode_summary">Improves compatibility with telecoils and reduces unwanted noise</string>
<!-- Title for accessibility hearing device page footer. [CHAR LIMIT=40] -->
<string name="accessibility_hearing_device_about_title">About hearing devices</string>
- <!-- Description for text in accessibility hearing aids footer. [CHAR LIMIT=NONE] -->
- <string name="accessibility_hearing_device_footer_summary">Make sure your hearing device is turned on and ready to pair. Only ASHA and LE Audio hearing devices show on this page.</string>
+ <!-- Description for text in accessibility hearing aids footer. [CHAR LIMIT=NONE BACKUP_MESSAGE_ID=7451899224828040581] -->
+ <string name="accessibility_hearing_device_footer_summary">To find other hearing devices that aren’t supported by ASHA or LE Audio, tap <b>Pair new device</b> > <b>See more devices</b></string>
<!-- Title for the pair hearing device page. [CHAR LIMIT=25] -->
<string name="accessibility_hearing_device_pairing_page_title">Pair hearing device</string>
+ <!-- Subtitle for the pair hearing device page. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_hearing_device_pairing_intro">You can pair ASHA and LE Audio hearing devices on this page. Make sure your hearing device is turned on and ready to pair.</string>
<!-- Title for the preference category containing the list of the available hearing during and after bluetooth scanning devices. [CHAR LIMIT=30] -->
<string name="accessibility_found_hearing_devices">Available hearing devices</string>
<!-- Title for the preference category containing the all bluetooth devices during and after bluetooth scanning devices. Used when people can not find their hearing device in hearing device pairing list. [CHAR LIMIT=45] -->
@@ -11468,8 +11515,6 @@
<!-- Mobile network settings screen, message asking the user to check their pricing with their Carrier, when enabling Data roaming. [CHAR LIMIT=NONE] -->
<string name="roaming_check_price_warning">Check with your network provider for pricing.</string>
- <!-- Title for mobile data preference, to display the mobile data usage for each app. [CHAR LIMIT=NONE]-->
- <string name="mobile_data_usage_title">App data usage</string>
<!-- Summary to show the current network mode is invalid. [CHAR LIMIT=NONE]-->
<string name="mobile_network_mode_error">Invalid Network Mode <xliff:g id="networkModeId" example="0">%1$d</xliff:g>. Ignore.</string>
<!-- Title for _satellite_setting_preference_layout in mobile network settings [CHAR LIMIT=60] -->
@@ -11697,6 +11742,16 @@
<!-- Body text of error message indicating the device could not erase the SIM due to an error. [CHAR_LIMIT=NONE] -->
<string name="erase_sim_fail_text">Something went wrong and this eSIM wasn\'t erased.\n\nRestart your device and try again.</string>
+ <!-- Strings for to use Wi-Fi before deleting eUICC subscriptions -->
+ <!-- Title on confirmation dialog asking the user to have Wi-Fi. [CHAR_LIMIT=NONE] -->
+ <string name="wifi_warning_dialog_title">Connect to Wi\u2011Fi before erasing</string>
+ <!-- Body text in confirmation dialog indicating why having Wi-Fi is recommended. [CHAR_LIMIT=NONE] -->
+ <string name="wifi_warning_dialog_text">This makes it easier to use your eSIM again in the future without needing to contact your carrier</string>
+ <!-- Button label to continue with erasing [CHAR_LIMIT=20] -->
+ <string name="wifi_warning_continue_button">Erase anyway</string>
+ <!-- Button label to return to settings [CHAR_LIMIT=20] -->
+ <string name="wifi_warning_return_button">OK</string>
+
<!-- Title for Network connection request Dialog [CHAR LIMIT=60] -->
<string name="network_connection_request_dialog_title">Connect to device</string>
<!-- Summary for Network connection request Dialog [CHAR LIMIT=NONE] -->
@@ -12289,6 +12344,15 @@
<!-- Summary for UWB preference when UWB is unavailable due to regulatory requirements. [CHAR_LIMIT=NONE]-->
<string name="uwb_settings_summary_no_uwb_regulatory">UWB is unavailable in the current location</string>
+ <!-- Title for Thread network preference [CHAR_LIMIT=60] -->
+ <string name="thread_network_settings_title">Thread</string>
+
+ <!-- Summary for Thread network preference. [CHAR_LIMIT=NONE]-->
+ <string name="thread_network_settings_summary">Connect to compatible devices using Thread for a seamless smart home experience</string>
+
+ <!-- Summary for Thread network preference when airplane mode is enabled. [CHAR_LIMIT=NONE]-->
+ <string name="thread_network_settings_summary_airplane_mode">Turn off airplane mode to use Thread</string>
+
<!-- Label for the camera use toggle [CHAR LIMIT=40] -->
<string name="camera_toggle_title">Camera access</string>
<!-- Label for the camera use toggle [CHAR LIMIT=40] -->
@@ -12863,6 +12927,8 @@
<string name="contrast_title">Contrast</string>
<!-- 'Standard' contrast option [CHAR LIMIT=20] -->
<string name="contrast_standard">Standard</string>
+ <!-- 'Default' contrast option [CHAR LIMIT=20] -->
+ <string name="contrast_default">Default</string>
<!-- 'Medium' contrast option [CHAR LIMIT=20] -->
<string name="contrast_medium">Medium</string>
<!-- 'High' contrast option [CHAR LIMIT=20] -->
diff --git a/res/values/styles.xml b/res/values/styles.xml
index fbc6d7f..0a28b01 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -966,6 +966,7 @@
<item name="android:paddingTop">20dp</item>
<item name="android:paddingBottom">8dp</item>
<item name="android:textSize">14sp</item>
+ <item name="android:fontFamily">google-sans-medium</item>
</style>
<style name="PrivateSpaceSetupBulletPointLayoutStyle">
diff --git a/res/xml/accessibility_color_and_motion.xml b/res/xml/accessibility_color_and_motion.xml
index a500b72..3522234 100644
--- a/res/xml/accessibility_color_and_motion.xml
+++ b/res/xml/accessibility_color_and_motion.xml
@@ -22,6 +22,16 @@
android:title="@string/accessibility_color_and_motion_title">
<Preference
+ android:fragment="com.android.settings.accessibility.ColorContrastFragment"
+ android:key="color_contrast"
+ android:icon="@drawable/ic_color_contrast"
+ android:persistent="false"
+ android:title="@string/accessibility_color_contrast_title"
+ android:summary="@string/accessibility_color_contrast_summary"
+ settings:controller="com.android.settings.accessibility.ContrastPreferenceController"
+ settings:searchable="true"/>
+
+ <Preference
android:fragment="com.android.settings.accessibility.ToggleDaltonizerPreferenceFragment"
android:icon="@drawable/ic_daltonizer"
android:key="daltonizer_preference"
diff --git a/res/xml/accessibility_color_contrast.xml b/res/xml/accessibility_color_contrast.xml
new file mode 100644
index 0000000..67c939b
--- /dev/null
+++ b/res/xml/accessibility_color_contrast.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 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.
+ -->
+
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:persistent="false"
+ android:title="@string/accessibility_color_contrast_title">
+
+ <com.android.settingslib.widget.TopIntroPreference
+ android:title="@string/accessibility_color_contrast_intro" />
+
+ <com.android.settingslib.widget.LayoutPreference
+ android:key="color_contrast_selector"
+ android:selectable="false"
+ android:layout="@layout/accessibility_color_contrast_selector"
+ settings:controller="com.android.settings.accessibility.ContrastSelectorPreferenceController"/>
+
+ <SwitchPreferenceCompat
+ android:key="toggle_high_text_contrast_preference"
+ android:persistent="false"
+ android:summary="@string/accessibility_toggle_maximize_text_contrast_preference_summary"
+ android:title="@string/accessibility_toggle_maximize_text_contrast_preference_title"
+ settings:controller=
+ "com.android.settings.accessibility.HighTextContrastPreferenceController" />
+
+ <com.android.settings.accessibility.AccessibilityFooterPreference
+ android:title="@string/color_contrast_note"
+ android:selectable="false"
+ settings:searchable="false"/>
+
+</PreferenceScreen>
diff --git a/res/xml/accessibility_edit_shortcuts.xml b/res/xml/accessibility_edit_shortcuts.xml
index 06cbedd..8be0ee5 100644
--- a/res/xml/accessibility_edit_shortcuts.xml
+++ b/res/xml/accessibility_edit_shortcuts.xml
@@ -18,62 +18,66 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto">
- <com.android.settings.accessibility.shortcuts.ShortcutOptionPreference
- android:key="@string/accessibility_shortcut_fab_pref"
- android:persistent="false"
- android:selectable="true"
- settings:allowDividerAbove="false"
- settings:allowDividerBelow="false"
- settings:controller="com.android.settings.accessibility.shortcuts.FloatingButtonShortcutOptionController" />
+ <PreferenceCategory
+ android:key="@string/accessibility_shortcut_description_pref">
- <com.android.settings.accessibility.shortcuts.ShortcutOptionPreference
- android:key="@string/accessibility_shortcut_gesture_pref"
- android:persistent="false"
- android:selectable="true"
- settings:allowDividerAbove="false"
- settings:allowDividerBelow="false"
- settings:controller="com.android.settings.accessibility.shortcuts.GestureShortcutOptionController" />
+ <com.android.settings.accessibility.shortcuts.ShortcutOptionPreference
+ android:key="@string/accessibility_shortcut_fab_pref"
+ android:persistent="false"
+ android:selectable="true"
+ settings:allowDividerAbove="false"
+ settings:allowDividerBelow="false"
+ settings:controller="com.android.settings.accessibility.shortcuts.FloatingButtonShortcutOptionController" />
- <com.android.settings.accessibility.shortcuts.ShortcutOptionPreference
- android:key="@string/accessibility_shortcut_nav_button_pref"
- android:persistent="false"
- android:selectable="true"
- settings:allowDividerAbove="false"
- settings:allowDividerBelow="false"
- settings:controller="com.android.settings.accessibility.shortcuts.NavButtonShortcutOptionController" />
+ <com.android.settings.accessibility.shortcuts.ShortcutOptionPreference
+ android:key="@string/accessibility_shortcut_gesture_pref"
+ android:persistent="false"
+ android:selectable="true"
+ settings:allowDividerAbove="false"
+ settings:allowDividerBelow="false"
+ settings:controller="com.android.settings.accessibility.shortcuts.GestureShortcutOptionController" />
- <com.android.settings.accessibility.shortcuts.ShortcutOptionPreference
- android:key="@string/accessibility_shortcut_volume_keys_pref"
- android:persistent="false"
- android:selectable="true"
- settings:allowDividerAbove="false"
- settings:allowDividerBelow="false"
- settings:controller="com.android.settings.accessibility.shortcuts.VolumeKeysShortcutOptionController" />
+ <com.android.settings.accessibility.shortcuts.ShortcutOptionPreference
+ android:key="@string/accessibility_shortcut_nav_button_pref"
+ android:persistent="false"
+ android:selectable="true"
+ settings:allowDividerAbove="false"
+ settings:allowDividerBelow="false"
+ settings:controller="com.android.settings.accessibility.shortcuts.NavButtonShortcutOptionController" />
- <com.android.settings.accessibility.shortcuts.ShortcutOptionPreference
- android:key="@string/accessibility_shortcut_two_fingers_double_tap_pref"
- android:persistent="false"
- android:selectable="true"
- settings:allowDividerAbove="false"
- settings:allowDividerBelow="false"
- settings:controller="com.android.settings.accessibility.shortcuts.TwoFingersDoubleTapShortcutOptionController" />
+ <com.android.settings.accessibility.shortcuts.ShortcutOptionPreference
+ android:key="@string/accessibility_shortcut_volume_keys_pref"
+ android:persistent="false"
+ android:selectable="true"
+ settings:allowDividerAbove="false"
+ settings:allowDividerBelow="false"
+ settings:controller="com.android.settings.accessibility.shortcuts.VolumeKeysShortcutOptionController" />
- <Preference
- android:icon="@drawable/ic_keyboard_arrow_down"
- android:key="@string/accessibility_shortcuts_advanced_collapsed"
- android:persistent="false"
- android:selectable="true"
- android:title="@string/accessibility_shortcut_edit_dialog_title_advance"
- settings:allowDividerAbove="false"
- settings:allowDividerBelow="false"
- settings:controller="com.android.settings.accessibility.shortcuts.AdvancedShortcutsPreferenceController" />
+ <com.android.settings.accessibility.shortcuts.ShortcutOptionPreference
+ android:key="@string/accessibility_shortcut_two_fingers_double_tap_pref"
+ android:persistent="false"
+ android:selectable="true"
+ settings:allowDividerAbove="false"
+ settings:allowDividerBelow="false"
+ settings:controller="com.android.settings.accessibility.shortcuts.TwoFingersDoubleTapShortcutOptionController" />
- <com.android.settings.accessibility.shortcuts.ShortcutOptionPreference
- android:key="@string/accessibility_shortcut_triple_tap_pref"
- android:persistent="false"
- android:selectable="true"
- settings:allowDividerAbove="false"
- settings:allowDividerBelow="false"
- settings:controller="com.android.settings.accessibility.shortcuts.TripleTapShortcutOptionController" />
+ <Preference
+ android:icon="@drawable/ic_keyboard_arrow_down"
+ android:key="@string/accessibility_shortcuts_advanced_collapsed"
+ android:persistent="false"
+ android:selectable="true"
+ android:title="@string/accessibility_shortcut_edit_dialog_title_advance"
+ settings:allowDividerAbove="false"
+ settings:allowDividerBelow="false"
+ settings:controller="com.android.settings.accessibility.shortcuts.AdvancedShortcutsPreferenceController" />
+
+ <com.android.settings.accessibility.shortcuts.ShortcutOptionPreference
+ android:key="@string/accessibility_shortcut_triple_tap_pref"
+ android:persistent="false"
+ android:selectable="true"
+ settings:allowDividerAbove="false"
+ settings:allowDividerBelow="false"
+ settings:controller="com.android.settings.accessibility.shortcuts.TripleTapShortcutOptionController" />
+ </PreferenceCategory>
</PreferenceScreen>
\ No newline at end of file
diff --git a/res/xml/accessibility_text_reading_options.xml b/res/xml/accessibility_text_reading_options.xml
index 0711a3f..795c4ffb9 100644
--- a/res/xml/accessibility_text_reading_options.xml
+++ b/res/xml/accessibility_text_reading_options.xml
@@ -57,9 +57,7 @@
android:key="toggle_high_text_contrast_preference"
android:persistent="false"
android:summary="@string/accessibility_toggle_high_text_contrast_preference_summary"
- android:title="@string/accessibility_toggle_high_text_contrast_preference_title"
- settings:controller=
- "com.android.settings.accessibility.HighTextContrastPreferenceController" />
+ android:title="@string/accessibility_toggle_high_text_contrast_preference_title" />
<com.android.settings.accessibility.TextReadingResetPreference
android:key="reset"
diff --git a/res/xml/bluetooth_audio_sharing.xml b/res/xml/bluetooth_audio_sharing.xml
deleted file mode 100644
index 45781c0..0000000
--- a/res/xml/bluetooth_audio_sharing.xml
+++ /dev/null
@@ -1,80 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 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.
- -->
-<PreferenceScreen
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:settings="http://schemas.android.com/apk/res-auto"
- android:title="@string/audio_sharing_title">
-
- <com.android.settingslib.widget.TopIntroPreference
- android:key="audio_sharing_top_intro"
- android:title="Let others listen to your media along with you using their own compatible headphones"
- settings:searchable="false" />
-
- <PreferenceCategory
- android:key="audio_sharing_device_volume_group"
- android:title="Devices listening"
- settings:controller="com.android.settings.connecteddevice.audiosharing.AudioSharingDeviceVolumeGroupController" />
-
- <Preference
- android:icon="@drawable/ic_audio_calls_and_alarms"
- android:key="calls_and_alarms"
- android:summary=""
- android:title="@string/calls_and_alarms_device_title"
- settings:controller="com.android.settings.connecteddevice.audiosharing.CallsAndAlarmsPreferenceController" />
-
- <Preference
- android:icon="@drawable/ic_audio_play_sample"
- android:key="audio_sharing_play_sound"
- android:summary="Everyone listening should hear it"
- android:title="Play a test sound"
- settings:controller="com.android.settings.connecteddevice.audiosharing.AudioSharingPlaySoundPreferenceController" />
-
- <PreferenceCategory
- android:key="audio_sharing_stream_settings_category"
- android:title="Stream settings"
- settings:controller="com.android.settings.connecteddevice.audiosharing.StreamSettingsCategoryController">
-
- <com.android.settings.connecteddevice.audiosharing.AudioSharingNamePreference
- android:key="audio_sharing_stream_name"
- android:title="Name"
- settings:controller="com.android.settings.connecteddevice.audiosharing.AudioSharingNamePreferenceController" />
-
- <com.android.settings.widget.ValidatedEditTextPreference
- android:key="audio_sharing_stream_password"
- android:summary="********"
- android:title="Password"
- settings:controller="com.android.settings.connecteddevice.audiosharing.AudioSharingPasswordPreferenceController" />
-
- <SwitchPreferenceCompat
- android:key="audio_sharing_stream_compatibility"
- android:title="Improve compatibility"
- settings:controller="com.android.settings.connecteddevice.audiosharing.AudioSharingCompatibilityPreferenceController" />
- </PreferenceCategory>
-
- <PreferenceCategory
- android:key="audio_streams_settings_category"
- android:title="@string/audio_streams_category_title"
- settings:controller="com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsCategoryController">
-
- <Preference
- android:fragment="com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsDashboardFragment"
- android:icon="@drawable/ic_chevron_right_24dp"
- android:key="audio_streams_settings"
- android:title="@string/audio_streams_pref_title" />
-
- </PreferenceCategory>
-</PreferenceScreen>
\ No newline at end of file
diff --git a/res/xml/bluetooth_audio_streams.xml b/res/xml/bluetooth_audio_streams.xml
deleted file mode 100644
index e7e708e..0000000
--- a/res/xml/bluetooth_audio_streams.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 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.
- -->
-
-<PreferenceScreen
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:settings="http://schemas.android.com/apk/res-auto"
- android:title="Find an audio stream">
-
- <com.android.settingslib.widget.TopIntroPreference
- android:key="audio_streams_top_intro"
- android:title="Listen to a device that's sharing audio or to a nearby Auracast broadcast"
- settings:searchable="false"/>
-
- <Preference
- android:key="audio_streams_active_device"
- android:title="Your audio device"
- settings:controller="com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsActiveDeviceController" />
-
- <com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryPreference
- android:key="audio_streams_nearby_category"
- android:title="Audio streams nearby"
- settings:controller="com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController">
-
- <Preference
- android:key="audio_streams_scan_qr_code"
- android:title="Scan a QR code"
- android:icon="@drawable/ic_add_24dp"
- android:summary="Start listening by scanning a stream's QR code"
- android:order="0"
- settings:controller="com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsScanQrCodeController" />
-
- </com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryPreference>
-
-</PreferenceScreen>
\ No newline at end of file
diff --git a/res/xml/connected_devices.xml b/res/xml/connected_devices.xml
index 34a5798..40ab145 100644
--- a/res/xml/connected_devices.xml
+++ b/res/xml/connected_devices.xml
@@ -27,22 +27,8 @@
settings:controller="com.android.settings.slices.SlicePreferenceController" />
<PreferenceCategory
- android:key="audio_sharing_device_list"
- android:title="@string/audio_sharing_title"
- settings:controller="com.android.settings.connecteddevice.audiosharing.AudioSharingDevicePreferenceController">
- <Preference
- android:fragment="com.android.settings.connecteddevice.audiosharing.AudioSharingDashboardFragment"
- android:icon="@drawable/ic_bt_audio_sharing"
- android:key="connected_device_audio_sharing_settings"
- android:order="10"
- android:title="@string/audio_sharing_title"
- settings:searchable="false" />
- </PreferenceCategory>
-
- <PreferenceCategory
android:key="available_device_list"
- android:title="@string/connected_device_media_device_title"
- settings:controller="com.android.settings.connecteddevice.AvailableMediaDeviceGroupController" />
+ android:title="@string/connected_device_media_device_title"/>
<PreferenceCategory
android:key="connected_device_list"
diff --git a/res/xml/connected_devices_advanced.xml b/res/xml/connected_devices_advanced.xml
index b088791..87db619 100644
--- a/res/xml/connected_devices_advanced.xml
+++ b/res/xml/connected_devices_advanced.xml
@@ -26,15 +26,6 @@
android:order="-10"
android:title="@string/bluetooth_settings_title" />
- <Preference
- android:fragment="com.android.settings.connecteddevice.audiosharing.AudioSharingDashboardFragment"
- android:icon="@drawable/ic_bt_audio_sharing"
- android:key="audio_sharing_settings"
- android:order="-9"
- android:title="@string/audio_sharing_title"
- settings:controller="com.android.settings.connecteddevice.audiosharing.AudioSharingPreferenceController"
- settings:searchable="true" />
-
<com.android.settingslib.RestrictedPreference
android:fragment="com.android.settings.connecteddevice.NfcAndPaymentFragment"
android:icon="@drawable/ic_nfc"
@@ -72,6 +63,15 @@
settings:useAdminDisabledSummary="true"
settings:userRestriction="no_ultra_wideband_radio" />
+ <com.android.settingslib.RestrictedSwitchPreference
+ android:key="thread_network_settings"
+ android:title="@string/thread_network_settings_title"
+ android:order="110"
+ android:summary="@string/summary_placeholder"
+ settings:controller="com.android.settings.connecteddevice.threadnetwork.ThreadNetworkPreferenceController"
+ settings:userRestriction="no_thread_network"
+ settings:useAdminDisabledSummary="true"/>
+
<PreferenceCategory
android:key="dashboard_tile_placeholder"
android:order="-8" />
diff --git a/res/xml/display_settings.xml b/res/xml/display_settings.xml
index 2df360d..5b4bee8 100644
--- a/res/xml/display_settings.xml
+++ b/res/xml/display_settings.xml
@@ -36,6 +36,11 @@
android:title="@string/auto_brightness_title"
android:fragment="com.android.settings.display.AutoBrightnessSettings"
settings:controller="com.android.settings.display.AutoBrightnessPreferenceController"/>
+ <SwitchPreferenceCompat
+ android:key="even_dimmer_activated"
+ android:title="@string/even_dimmer_display_title"
+ android:summary="@string/even_dimmer_display_summary"
+ settings:controller="com.android.settings.display.EvenDimmerPreferenceController"/>
</PreferenceCategory>
<PreferenceCategory
diff --git a/res/xml/hearing_device_pairing_detail.xml b/res/xml/hearing_device_pairing_detail.xml
index 0ccd000..0526eb3 100644
--- a/res/xml/hearing_device_pairing_detail.xml
+++ b/res/xml/hearing_device_pairing_detail.xml
@@ -32,12 +32,4 @@
settings:useAdminDisabledSummary="true"
settings:controller="com.android.settings.accessibility.ViewAllBluetoothDevicesPreferenceController"/>
</PreferenceCategory>
-
- <com.android.settings.accessibility.AccessibilityFooterPreference
- android:key="hearing_device_footer"
- android:title="@string/accessibility_hearing_device_footer_summary"
- android:selectable="false"
- settings:searchable="false"
- settings:controller="com.android.settings.accessibility.PairHearingDeviceFooterPreferenceController"/>
-
</PreferenceScreen>
\ No newline at end of file
diff --git a/res/xml/hearing_device_pairing_fragment.xml b/res/xml/hearing_device_pairing_fragment.xml
index 1ccc1dd..d84f22b 100644
--- a/res/xml/hearing_device_pairing_fragment.xml
+++ b/res/xml/hearing_device_pairing_fragment.xml
@@ -19,6 +19,10 @@
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:title="@string/bluetooth_pairing_pref_title">
+ <com.android.settingslib.widget.TopIntroPreference
+ settings:searchable="false"
+ android:title="@string/accessibility_hearing_device_pairing_intro" />
+
<com.android.settings.bluetooth.BluetoothProgressCategory
android:key="available_hearing_devices"
android:title="@string/accessibility_found_hearing_devices" />
@@ -34,12 +38,4 @@
settings:useAdminDisabledSummary="true"
settings:controller="com.android.settings.accessibility.ViewAllBluetoothDevicesPreferenceController"/>
</PreferenceCategory>
-
- <com.android.settings.accessibility.AccessibilityFooterPreference
- android:key="hearing_device_footer"
- android:title="@string/accessibility_hearing_device_footer_summary"
- android:selectable="false"
- settings:searchable="false"
- settings:controller="com.android.settings.accessibility.PairHearingDeviceFooterPreferenceController"/>
-
</PreferenceScreen>
\ No newline at end of file
diff --git a/res/xml/mobile_network_settings.xml b/res/xml/mobile_network_settings.xml
index a29e123..1e43ef0 100644
--- a/res/xml/mobile_network_settings.xml
+++ b/res/xml/mobile_network_settings.xml
@@ -95,7 +95,7 @@
<Preference
android:key="data_usage_summary"
- android:title="@string/mobile_data_usage_title"
+ android:title="@string/app_cellular_data_usage"
settings:controller="com.android.settings.network.telephony.DataUsagePreferenceController"/>
<com.android.settings.datausage.BillingCyclePreference
diff --git a/res/xml/privatespace_hide_locked.xml b/res/xml/private_space_hide_locked.xml
similarity index 96%
rename from res/xml/privatespace_hide_locked.xml
rename to res/xml/private_space_hide_locked.xml
index f453d75..cd1c406 100644
--- a/res/xml/privatespace_hide_locked.xml
+++ b/res/xml/private_space_hide_locked.xml
@@ -21,7 +21,7 @@
<com.android.settingslib.widget.IllustrationPreference
android:key="privatespace_hide_video"
settings:searchable="false"
- settings:lottie_rawRes="@drawable/privatespace_placeholder_image"/>
+ settings:lottie_rawRes="@raw/private_space_hide_when_locked_illustration"/>
<com.android.settingslib.widget.MainSwitchPreference
android:key="hide_when_locked"
diff --git a/res/xml/private_space_settings.xml b/res/xml/private_space_settings.xml
index e718ca8..93c016b 100644
--- a/res/xml/private_space_settings.xml
+++ b/res/xml/private_space_settings.xml
@@ -25,7 +25,7 @@
<com.android.settingslib.widget.IllustrationPreference
android:key="private_space_settings"
settings:searchable="false"
- settings:lottie_rawRes="@drawable/private_space_illustration"/>
+ settings:lottie_rawRes="@raw/private_space_illustration"/>
<Preference
android:key="private_space_description"
diff --git a/res/xml/wifi_network_details_fragment2.xml b/res/xml/wifi_network_details_fragment2.xml
index daff20f..598f9d8 100644
--- a/res/xml/wifi_network_details_fragment2.xml
+++ b/res/xml/wifi_network_details_fragment2.xml
@@ -97,6 +97,11 @@
android:entries="@array/wifi_privacy_entries"
android:entryValues="@array/wifi_privacy_values"/>
+ <com.android.settings.spa.preference.ComposePreference
+ android:key="privacy_settings"
+ android:title="@string/wifi_privacy_settings"
+ settings:controller="com.android.settings.wifi.details2.WifiPrivacyPreferenceController"/>
+
<Preference
android:key="subscription_detail"
android:title="@string/wifi_subscription"
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index 63ce331..92e3efd 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -117,6 +117,8 @@
public static class TextReadingSettingsActivity extends SettingsActivity { /* empty */ }
/** Activity for text color and motion settings. */
public static class ColorAndMotionActivity extends SettingsActivity { /* empty */ }
+ /** Activity for color contrast settings. */
+ public static class ColorContrastActivity extends SettingsActivity { /* empty */ }
/** Activity for the security dashboard. */
public static class SecurityDashboardActivity extends SettingsActivity {
diff --git a/src/com/android/settings/accessibility/ArrowPreference.java b/src/com/android/settings/accessibility/ArrowPreference.java
index 32e2bcb..ccee50d 100644
--- a/src/com/android/settings/accessibility/ArrowPreference.java
+++ b/src/com/android/settings/accessibility/ArrowPreference.java
@@ -22,17 +22,25 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.res.TypedArrayUtils;
-import androidx.preference.Preference;
import com.android.settings.R;
/**
- * A settings preference with colored rounded rectangle background and an arrow icon on the right
+ * A settings preference with colored rounded rectangle background and an arrow icon on the right.
*/
-public class ArrowPreference extends Preference {
+public class ArrowPreference extends BackgroundPreference {
- public ArrowPreference(@NonNull Context context) {
- this(context, null);
+ public ArrowPreference(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ setBackground(
+ com.android.settingslib.widget.mainswitch.R.drawable.settingslib_switch_bar_bg_on);
+ setWidgetLayoutResource(R.layout.preference_widget_arrow);
+ }
+
+ public ArrowPreference(@NonNull Context context, @Nullable AttributeSet attrs,
+ int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
}
public ArrowPreference(@NonNull Context context, @Nullable AttributeSet attrs) {
@@ -41,18 +49,7 @@
android.R.attr.preferenceStyle));
}
- public ArrowPreference(@NonNull Context context, @Nullable AttributeSet attrs,
- int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- }
-
- public ArrowPreference(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- init();
- }
-
- private void init() {
- setLayoutResource(R.layout.arrow_preference);
+ public ArrowPreference(@NonNull Context context) {
+ this(context, null);
}
}
diff --git a/src/com/android/settings/accessibility/BackgroundPreference.java b/src/com/android/settings/accessibility/BackgroundPreference.java
new file mode 100644
index 0000000..ea56ac5
--- /dev/null
+++ b/src/com/android/settings/accessibility/BackgroundPreference.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2024 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.accessibility;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+
+import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.content.res.TypedArrayUtils;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.settings.R;
+
+/**
+ * A preference with custom background.
+ */
+public class BackgroundPreference extends Preference {
+
+ private int mBackgroundId;
+
+ public BackgroundPreference(@NonNull Context context,
+ @Nullable AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ setLayoutResource(R.layout.preference_background);
+ setIconSpaceReserved(false);
+
+ final TypedArray styledAttrs = context.obtainStyledAttributes(attrs,
+ R.styleable.BackgroundPreference);
+ mBackgroundId = styledAttrs.getResourceId(R.styleable.BackgroundPreference_background, 0);
+ styledAttrs.recycle();
+ }
+
+ public BackgroundPreference(@NonNull Context context, @Nullable AttributeSet attrs,
+ int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public BackgroundPreference(@NonNull Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, TypedArrayUtils.getAttr(context,
+ androidx.preference.R.attr.preferenceStyle,
+ com.android.internal.R.attr.preferenceStyle), 0);
+ }
+
+ public BackgroundPreference(@NonNull Context context) {
+ this(context, null, TypedArrayUtils.getAttr(context,
+ androidx.preference.R.attr.preferenceStyle,
+ com.android.internal.R.attr.preferenceStyle), 0);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+
+ final LinearLayout layout = (LinearLayout) holder.findViewById(R.id.background);
+ if (mBackgroundId != 0) {
+ final Drawable backgroundDrawable = getContext().getDrawable(mBackgroundId);
+ layout.setBackground(backgroundDrawable);
+ }
+ }
+
+ /**
+ * Sets the background to a given resource. The resource should refer to a Drawable object.
+ *
+ * @param resId The identifier of the resource.
+ */
+ public void setBackground(@DrawableRes int resId) {
+ if (mBackgroundId != resId) {
+ mBackgroundId = resId;
+ notifyChanged();
+ }
+ }
+}
diff --git a/src/com/android/settings/accessibility/ColorContrastFragment.java b/src/com/android/settings/accessibility/ColorContrastFragment.java
new file mode 100644
index 0000000..a572657
--- /dev/null
+++ b/src/com/android/settings/accessibility/ColorContrastFragment.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 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.accessibility;
+
+import com.android.settings.R;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settingslib.search.SearchIndexable;
+
+/** Accessibility settings for color contrast. */
+@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
+public class ColorContrastFragment extends DashboardFragment {
+
+ private static final String TAG = "ColorContrastFragment";
+
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.accessibility_color_contrast;
+ }
+
+ @Override
+ protected String getLogTag() {
+ return TAG;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ // TODO(b/326539398): Add metrics tracking for color contrast.
+ return 0;
+ }
+
+ public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider(R.xml.accessibility_color_contrast);
+}
diff --git a/src/com/android/settings/accessibility/ContrastPreferenceController.java b/src/com/android/settings/accessibility/ContrastPreferenceController.java
new file mode 100644
index 0000000..4e9a9ec
--- /dev/null
+++ b/src/com/android/settings/accessibility/ContrastPreferenceController.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 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.accessibility;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+
+import com.android.settings.core.BasePreferenceController;
+
+/**
+ * Controller for {@link ColorContrastFragment}.
+ */
+public class ContrastPreferenceController extends BasePreferenceController {
+
+ public ContrastPreferenceController(@NonNull Context context, @NonNull String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return Flags.enableColorContrastControl() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+ }
+}
diff --git a/src/com/android/settings/accessibility/ContrastSelectorPreferenceController.java b/src/com/android/settings/accessibility/ContrastSelectorPreferenceController.java
new file mode 100644
index 0000000..b99680f
--- /dev/null
+++ b/src/com/android/settings/accessibility/ContrastSelectorPreferenceController.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2024 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.accessibility;
+
+import static android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_HIGH;
+import static android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_MEDIUM;
+import static android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_STANDARD;
+import static android.app.UiModeManager.ContrastUtils.fromContrastLevel;
+import static android.app.UiModeManager.ContrastUtils.toContrastLevel;
+
+import android.app.UiModeManager;
+import android.content.Context;
+import android.provider.Settings;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnStart;
+import com.android.settingslib.core.lifecycle.events.OnStop;
+import com.android.settingslib.widget.LayoutPreference;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * Controller for contrast selector.
+ */
+public class ContrastSelectorPreferenceController extends BasePreferenceController
+ implements LifecycleObserver, OnStart, OnStop, UiModeManager.ContrastChangeListener {
+
+ private static final String KEY_COLOR_CONTRAST_SELECTOR = "color_contrast_selector";
+
+ private final Executor mMainExecutor;
+ private final UiModeManager mUiModeManager;
+ private Map<Integer, FrameLayout> mContrastButtons = new HashMap<>();
+
+ public ContrastSelectorPreferenceController(@NonNull Context context,
+ @NonNull String preferenceKey) {
+ super(context, preferenceKey);
+
+ mMainExecutor = mContext.getMainExecutor();
+ mUiModeManager = mContext.getSystemService(UiModeManager.class);
+ }
+
+ @Override
+ public void displayPreference(@NonNull PreferenceScreen screen) {
+ super.displayPreference(screen);
+
+ final LayoutPreference mLayoutPreference =
+ screen.findPreference(KEY_COLOR_CONTRAST_SELECTOR);
+
+ mContrastButtons = Map.ofEntries(
+ Map.entry(CONTRAST_LEVEL_STANDARD,
+ mLayoutPreference.findViewById(R.id.contrast_button_default)),
+ Map.entry(CONTRAST_LEVEL_MEDIUM,
+ mLayoutPreference.findViewById(R.id.contrast_button_medium)),
+ Map.entry(CONTRAST_LEVEL_HIGH,
+ mLayoutPreference.findViewById(R.id.contrast_button_high))
+ );
+
+ mContrastButtons.forEach((contrastLevel, contrastButton) -> {
+ contrastButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(@Nullable View v) {
+ Settings.Secure.putFloat(mContext.getContentResolver(),
+ Settings.Secure.CONTRAST_LEVEL,
+ fromContrastLevel(contrastLevel));
+ }
+ });
+ });
+
+ highlightContrast(toContrastLevel(mUiModeManager.getContrast()));
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ // The main preferences screen is feature guarded, so this always returns AVAILABLE.
+ return AVAILABLE;
+ }
+
+ @Override
+ public void onStart() {
+ mUiModeManager.addContrastChangeListener(mMainExecutor, this);
+ }
+
+ @Override
+ public void onStop() {
+ mUiModeManager.removeContrastChangeListener(this);
+ }
+
+ @Override
+ public void onContrastChanged(float contrast) {
+ highlightContrast(toContrastLevel(contrast));
+ }
+
+ private void highlightContrast(int contrast) {
+ mContrastButtons.forEach((level, button) -> {
+ button.setSelected(level == contrast);
+ });
+ }
+}
diff --git a/src/com/android/settings/accessibility/PairHearingDeviceFooterPreferenceController.java b/src/com/android/settings/accessibility/PairHearingDeviceFooterPreferenceController.java
deleted file mode 100644
index 09396ff..0000000
--- a/src/com/android/settings/accessibility/PairHearingDeviceFooterPreferenceController.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2023 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.accessibility;
-
-import android.content.Context;
-
-import com.android.settings.R;
-
-/** Preference controller for footer in pair hearing device page. */
-public class PairHearingDeviceFooterPreferenceController extends
- AccessibilityFooterPreferenceController {
- public PairHearingDeviceFooterPreferenceController(Context context,
- String key) {
- super(context, key);
- }
-
- @Override
- protected String getIntroductionTitle() {
- return mContext.getString(R.string.accessibility_pair_hearing_device_about_title);
- }
-}
diff --git a/src/com/android/settings/accessibility/TextReadingPreferenceFragment.java b/src/com/android/settings/accessibility/TextReadingPreferenceFragment.java
index a90af21..5976ef5 100644
--- a/src/com/android/settings/accessibility/TextReadingPreferenceFragment.java
+++ b/src/com/android/settings/accessibility/TextReadingPreferenceFragment.java
@@ -117,6 +117,11 @@
}
}
}
+
+ if (Flags.enableColorContrastControl()) {
+ // High text contrast toggle will be added inside Color Contrast page on V+.
+ removePreference(HIGH_TEXT_CONTRAST_KEY);
+ }
}
@Override
@@ -203,10 +208,12 @@
mFontWeightAdjustmentController.setEntryPoint(mEntryPoint);
controllers.add(mFontWeightAdjustmentController);
- final HighTextContrastPreferenceController highTextContrastController =
- new HighTextContrastPreferenceController(context, HIGH_TEXT_CONTRAST_KEY);
- highTextContrastController.setEntryPoint(mEntryPoint);
- controllers.add(highTextContrastController);
+ if (!Flags.enableColorContrastControl()) {
+ final HighTextContrastPreferenceController highTextContrastController =
+ new HighTextContrastPreferenceController(context, HIGH_TEXT_CONTRAST_KEY);
+ highTextContrastController.setEntryPoint(mEntryPoint);
+ controllers.add(highTextContrastController);
+ }
final TextReadingResetController resetController =
new TextReadingResetController(context, RESET_KEY,
diff --git a/src/com/android/settings/accessibility/ViewAllBluetoothDevicesPreferenceController.java b/src/com/android/settings/accessibility/ViewAllBluetoothDevicesPreferenceController.java
index 213cfbd..622d5d8 100644
--- a/src/com/android/settings/accessibility/ViewAllBluetoothDevicesPreferenceController.java
+++ b/src/com/android/settings/accessibility/ViewAllBluetoothDevicesPreferenceController.java
@@ -16,27 +16,19 @@
package com.android.settings.accessibility;
-import static android.app.Activity.RESULT_OK;
-
import android.content.Context;
-import android.content.Intent;
-import android.preference.PreferenceManager;
import android.text.TextUtils;
+import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
-import com.android.settings.bluetooth.BluetoothPairingDetail;
+import com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.dashboard.DashboardFragment;
-import androidx.annotation.VisibleForTesting;
-
/** Preference controller for all bluetooth device preference. */
-public class ViewAllBluetoothDevicesPreferenceController extends BasePreferenceController implements
- PreferenceManager.OnActivityResultListener {
-
- private static final int REQUEST_CODE_BONDED_DEVICE = 270;
+public class ViewAllBluetoothDevicesPreferenceController extends BasePreferenceController {
private DashboardFragment mFragment;
public ViewAllBluetoothDevicesPreferenceController(Context context, String preferenceKey) {
@@ -60,29 +52,18 @@
@Override
public boolean handlePreferenceTreeClick(Preference preference) {
if (TextUtils.equals(preference.getKey(), getPreferenceKey())) {
- launchBluetoothPairingDetail();
+ launchConnectedDevicePage();
return true;
}
return false;
}
- @Override
- public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
- // If back from BONDED device page, then no need to show scan result again.
- // Finish the fragment.
- if (requestCode == REQUEST_CODE_BONDED_DEVICE && resultCode == RESULT_OK) {
- mFragment.finish();
- }
- return false;
- }
-
@VisibleForTesting
- void launchBluetoothPairingDetail() {
+ void launchConnectedDevicePage() {
new SubSettingLauncher(mContext)
- .setDestination(BluetoothPairingDetail.class.getName())
+ .setDestination(ConnectedDeviceDashboardFragment.class.getName())
.setSourceMetricsCategory(mFragment.getMetricsCategory())
- .setResultListener(mFragment, REQUEST_CODE_BONDED_DEVICE)
.launch();
}
}
diff --git a/src/com/android/settings/accessibility/shortcuts/EditShortcutsPreferenceFragment.java b/src/com/android/settings/accessibility/shortcuts/EditShortcutsPreferenceFragment.java
index a3cbb57..5a3b13a 100644
--- a/src/com/android/settings/accessibility/shortcuts/EditShortcutsPreferenceFragment.java
+++ b/src/com/android/settings/accessibility/shortcuts/EditShortcutsPreferenceFragment.java
@@ -27,16 +27,21 @@
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
import static com.android.settings.SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE;
+import android.app.Activity;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.res.Resources;
import android.database.ContentObserver;
+import android.icu.text.ListFormatter;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.provider.Settings;
import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -48,9 +53,12 @@
import androidx.preference.Preference;
import androidx.recyclerview.widget.RecyclerView;
+import com.android.internal.accessibility.dialog.AccessibilityTarget;
+import com.android.internal.accessibility.dialog.AccessibilityTargetHelper;
import com.android.settings.R;
import com.android.settings.SetupWizardUtils;
import com.android.settings.accessibility.AccessibilitySetupWizardUtils;
+import com.android.settings.accessibility.Flags;
import com.android.settings.accessibility.PreferredShortcuts;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.dashboard.DashboardFragment;
@@ -60,7 +68,10 @@
import com.google.android.setupcompat.util.WizardManagerHelper;
import com.google.android.setupdesign.GlifPreferenceLayout;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.List;
+import java.util.Map;
import java.util.Set;
/**
@@ -171,6 +182,36 @@
registerSettingsObserver();
}
+ @Override
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+ super.onCreatePreferences(savedInstanceState, rootKey);
+
+ Activity activity = getActivity();
+
+ if (!activity.getIntent().getAction().equals(
+ Settings.ACTION_ACCESSIBILITY_SHORTCUT_SETTINGS)
+ || !Flags.editShortcutsInFullScreen()) {
+ return;
+ }
+
+ // TODO(b/325664350): Implement shortcut type for "all shortcuts"
+ List<AccessibilityTarget> accessibilityTargets =
+ AccessibilityTargetHelper.getInstalledTargets(
+ activity.getBaseContext(), AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY);
+
+ Pair<String, String> titles = getTitlesFromAccessibilityTargetList(
+ mShortcutTargets,
+ accessibilityTargets,
+ activity.getResources()
+ );
+
+ activity.setTitle(titles.first);
+
+ String categoryKey = activity.getResources().getString(
+ R.string.accessibility_shortcut_description_pref);
+ findPreference(categoryKey).setTitle(titles.second);
+ }
+
@NonNull
@Override
public RecyclerView onCreateRecyclerView(
@@ -275,7 +316,6 @@
}
mShortcutTargets = Set.of(targets);
- // TODO(318748373): use 'targets' to populate title when no title is given
}
@Override
@@ -356,4 +396,52 @@
// A11y Nav Button
refreshPreferenceController(NavButtonShortcutOptionController.class);
}
+
+ /**
+ * Generates a title & subtitle pair describing the features whose shortcuts are being edited.
+ *
+ * @param shortcutTargets string list of component names corresponding to
+ * the relevant shortcut targets.
+ * @param accessibilityTargets list of accessibility targets
+ * to try and find corresponding labels in.
+ * @return pair of strings to be used as page title and subtitle.
+ * If there is only one shortcut label, It is displayed in the title and the subtitle is null.
+ * Otherwise, the title is a generic prompt and the subtitle lists all shortcut labels.
+ */
+ @VisibleForTesting
+ static Pair<String, String> getTitlesFromAccessibilityTargetList(
+ Set<String> shortcutTargets,
+ List<AccessibilityTarget> accessibilityTargets,
+ Resources resources) {
+ ArrayList<CharSequence> featureLabels = new ArrayList<>();
+
+ Map<String, CharSequence> accessibilityTargetLabels = new ArrayMap<>();
+ accessibilityTargets.forEach((target) -> accessibilityTargetLabels.put(
+ target.getId(), target.getLabel()));
+
+ for (String target: shortcutTargets) {
+ if (accessibilityTargetLabels.containsKey(target)) {
+ featureLabels.add(accessibilityTargetLabels.get(target));
+ } else {
+ throw new IllegalStateException("Shortcut target does not have a label: " + target);
+ }
+ }
+
+ if (featureLabels.size() == 1) {
+ return new Pair<>(
+ resources.getString(
+ R.string.accessibility_shortcut_title, featureLabels.get(0)),
+ null
+ );
+ } else if (featureLabels.size() == 0) {
+ throw new IllegalStateException("Found no labels for any shortcut targets.");
+ } else {
+ return new Pair<>(
+ resources.getString(R.string.accessibility_shortcut_edit_screen_title),
+ resources.getString(
+ R.string.accessibility_shortcut_edit_screen_prompt,
+ ListFormatter.getInstance().format(featureLabels))
+ );
+ }
+ }
}
diff --git a/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java b/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java
index b0996a4..d776b9a 100644
--- a/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java
+++ b/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java
@@ -563,7 +563,7 @@
@Override
@StringRes
protected int getAgreeButtonTextRes() {
- return R.string.security_settings_fingerprint_enroll_introduction_agree;
+ return R.string.security_settings_face_enroll_introduction_agree;
}
@Override
diff --git a/src/com/android/settings/biometrics/face/FaceSettingsRemoveButtonPreferenceController.java b/src/com/android/settings/biometrics/face/FaceSettingsRemoveButtonPreferenceController.java
index 797364b..ae5b62b 100644
--- a/src/com/android/settings/biometrics/face/FaceSettingsRemoveButtonPreferenceController.java
+++ b/src/com/android/settings/biometrics/face/FaceSettingsRemoveButtonPreferenceController.java
@@ -29,7 +29,9 @@
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
+import android.window.OnBackInvokedCallback;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
@@ -57,9 +59,14 @@
private static final String TAG = "FaceSettings/Remove";
static final String KEY = "security_settings_face_delete_faces_container";
- public static class ConfirmRemoveDialog extends InstrumentedDialogFragment {
+ public static class ConfirmRemoveDialog extends InstrumentedDialogFragment
+ implements OnBackInvokedCallback {
private static final String KEY_IS_CONVENIENCE = "is_convenience";
private DialogInterface.OnClickListener mOnClickListener;
+ @Nullable
+ private AlertDialog mDialog = null;
+ @Nullable
+ private Preference mFaceUnlockPreference = null;
/** Returns the new instance of the class */
public static ConfirmRemoveDialog newInstance(boolean isConvenience) {
@@ -99,14 +106,41 @@
.setMessage(dialogMessageRes)
.setPositiveButton(R.string.delete, mOnClickListener)
.setNegativeButton(R.string.cancel, mOnClickListener);
- AlertDialog dialog = builder.create();
- dialog.setCanceledOnTouchOutside(false);
- return dialog;
+ mDialog = builder.create();
+ mDialog.setCanceledOnTouchOutside(false);
+ mDialog.getOnBackInvokedDispatcher().registerOnBackInvokedCallback(0, this);
+ return mDialog;
}
public void setOnClickListener(DialogInterface.OnClickListener listener) {
mOnClickListener = listener;
}
+
+ public void setPreference(@Nullable Preference preference) {
+ mFaceUnlockPreference = preference;
+ }
+
+ public void unregisterOnBackInvokedCallback() {
+ if (mDialog != null) {
+ mDialog.getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(this);
+ }
+ }
+
+ @Override
+ public void onBackInvoked() {
+ if (mDialog != null) {
+ mDialog.cancel();
+ }
+ unregisterOnBackInvokedCallback();
+
+ if (mFaceUnlockPreference != null) {
+ final Button removeButton = ((LayoutPreference) mFaceUnlockPreference)
+ .findViewById(R.id.security_settings_face_settings_remove_button);
+ if (removeButton != null) {
+ removeButton.setEnabled(true);
+ }
+ }
+ }
}
interface Listener {
@@ -171,6 +205,13 @@
mButton.setEnabled(true);
mRemoving = false;
}
+
+ final ConfirmRemoveDialog removeDialog =
+ (ConfirmRemoveDialog) mActivity.getSupportFragmentManager()
+ .findFragmentByTag(ConfirmRemoveDialog.class.getName());
+ if (removeDialog != null) {
+ removeDialog.unregisterOnBackInvokedCallback();
+ }
}
};
@@ -210,6 +251,7 @@
(ConfirmRemoveDialog) mActivity.getSupportFragmentManager()
.findFragmentByTag(ConfirmRemoveDialog.class.getName());
if (removeDialog != null) {
+ removeDialog.setPreference(mPreference);
mRemoving = true;
removeDialog.setOnClickListener(mOnConfirmDialogClickListener);
}
diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControlsController.java b/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControlsController.java
index 3d85ca2..162abc7 100644
--- a/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControlsController.java
+++ b/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControlsController.java
@@ -26,6 +26,7 @@
import com.android.settings.R;
import com.android.settings.accessibility.AccessibilityHearingAidsFragment;
+import com.android.settings.accessibility.ArrowPreference;
import com.android.settings.core.SubSettingLauncher;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.core.lifecycle.Lifecycle;
@@ -83,7 +84,7 @@
}
private Preference createHearingDeviceControlsPreference(Context context) {
- final Preference preference = new Preference(context);
+ final ArrowPreference preference = new ArrowPreference(context);
preference.setKey(KEY_HEARING_DEVICE_CONTROLS);
preference.setTitle(context.getString(R.string.bluetooth_device_controls_title));
preference.setSummary(context.getString(R.string.bluetooth_device_controls_summary));
diff --git a/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBase.java b/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBase.java
index 67c32ed..d71328e 100644
--- a/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBase.java
+++ b/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBase.java
@@ -16,7 +16,6 @@
package com.android.settings.bluetooth;
-import static android.app.Activity.RESULT_OK;
import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
import android.bluetooth.BluetoothAdapter;
@@ -94,7 +93,6 @@
public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
if (bondState == BluetoothDevice.BOND_BONDED) {
// If one device is connected(bonded), then close this fragment.
- setResult(RESULT_OK);
finish();
return;
} else if (bondState == BluetoothDevice.BOND_BONDING) {
@@ -126,7 +124,6 @@
if (cachedDevice != null && cachedDevice.isConnected()) {
final BluetoothDevice device = cachedDevice.getDevice();
if (device != null && mSelectedList.contains(device)) {
- setResult(RESULT_OK);
finish();
} else {
onDeviceDeleted(cachedDevice);
diff --git a/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdater.java b/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdater.java
index 012220c..d15696b 100644
--- a/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdater.java
+++ b/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdater.java
@@ -24,9 +24,9 @@
import androidx.preference.Preference;
import com.android.settings.connecteddevice.DevicePreferenceCallback;
-import com.android.settings.flags.Flags;
import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.flags.Flags;
/**
* Controller to maintain connected bluetooth devices
diff --git a/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.kt b/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.kt
index 3224f94..095bed9 100644
--- a/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.kt
+++ b/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.kt
@@ -34,13 +34,13 @@
import androidx.preference.PreferenceGroup
import com.android.settings.R
import com.android.settings.dashboard.RestrictedDashboardFragment
-import com.android.settings.flags.Flags
import com.android.settingslib.bluetooth.BluetoothCallback
import com.android.settingslib.bluetooth.BluetoothDeviceFilter
import com.android.settingslib.bluetooth.BluetoothUtils
import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager
import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.settingslib.flags.Flags
import java.util.concurrent.ConcurrentHashMap
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
diff --git a/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdater.java b/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdater.java
index 1db90fa..ed1be7a 100644
--- a/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdater.java
+++ b/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdater.java
@@ -25,10 +25,10 @@
import androidx.preference.Preference;
import com.android.settings.connecteddevice.DevicePreferenceCallback;
-import com.android.settings.flags.Flags;
import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
+import com.android.settingslib.flags.Flags;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupController.java b/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupController.java
index fc3493c..0535d15 100644
--- a/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupController.java
+++ b/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupController.java
@@ -17,18 +17,17 @@
import static com.android.settingslib.Utils.isAudioModeOngoingCall;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothLeBroadcastAssistant;
-import android.bluetooth.BluetoothLeBroadcastMetadata;
-import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.pm.PackageManager;
import android.util.Log;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.FragmentManager;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
import androidx.preference.Preference;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;
@@ -38,138 +37,66 @@
import com.android.settings.bluetooth.AvailableMediaBluetoothDeviceUpdater;
import com.android.settings.bluetooth.BluetoothDeviceUpdater;
import com.android.settings.bluetooth.Utils;
-import com.android.settings.connecteddevice.audiosharing.AudioSharingUtils;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
-import com.android.settingslib.core.lifecycle.LifecycleObserver;
-import com.android.settingslib.core.lifecycle.events.OnStart;
-import com.android.settingslib.core.lifecycle.events.OnStop;
-
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
+import com.android.settingslib.core.lifecycle.Lifecycle;
/**
* Controller to maintain the {@link androidx.preference.PreferenceGroup} for all available media
* devices. It uses {@link DevicePreferenceCallback} to add/remove {@link Preference}
*/
public class AvailableMediaDeviceGroupController extends BasePreferenceController
- implements LifecycleObserver, OnStart, OnStop, DevicePreferenceCallback, BluetoothCallback {
+ implements DefaultLifecycleObserver, DevicePreferenceCallback, BluetoothCallback {
private static final boolean DEBUG = BluetoothUtils.D;
private static final String TAG = "AvailableMediaDeviceGroupController";
private static final String KEY = "available_device_list";
- @VisibleForTesting PreferenceGroup mPreferenceGroup;
+ @VisibleForTesting @Nullable PreferenceGroup mPreferenceGroup;
@VisibleForTesting LocalBluetoothManager mLocalBluetoothManager;
- private final Executor mExecutor;
- private BluetoothDeviceUpdater mBluetoothDeviceUpdater;
- private FragmentManager mFragmentManager;
- private BluetoothLeBroadcastAssistant.Callback mAssistantCallback =
- new BluetoothLeBroadcastAssistant.Callback() {
- @Override
- public void onSearchStarted(int reason) {}
+ @Nullable private BluetoothDeviceUpdater mBluetoothDeviceUpdater;
+ @Nullable private FragmentManager mFragmentManager;
- @Override
- public void onSearchStartFailed(int reason) {}
-
- @Override
- public void onSearchStopped(int reason) {}
-
- @Override
- public void onSearchStopFailed(int reason) {}
-
- @Override
- public void onSourceFound(@NonNull BluetoothLeBroadcastMetadata source) {}
-
- @Override
- public void onSourceAdded(@NonNull BluetoothDevice sink, int sourceId, int reason) {
- mBluetoothDeviceUpdater.forceUpdate();
- }
-
- @Override
- public void onSourceAddFailed(
- @NonNull BluetoothDevice sink,
- @NonNull BluetoothLeBroadcastMetadata source,
- int reason) {}
-
- @Override
- public void onSourceModified(
- @NonNull BluetoothDevice sink, int sourceId, int reason) {}
-
- @Override
- public void onSourceModifyFailed(
- @NonNull BluetoothDevice sink, int sourceId, int reason) {}
-
- @Override
- public void onSourceRemoved(
- @NonNull BluetoothDevice sink, int sourceId, int reason) {
- mBluetoothDeviceUpdater.forceUpdate();
- }
-
- @Override
- public void onSourceRemoveFailed(
- @NonNull BluetoothDevice sink, int sourceId, int reason) {}
-
- @Override
- public void onReceiveStateChanged(
- BluetoothDevice sink,
- int sourceId,
- BluetoothLeBroadcastReceiveState state) {}
- };
-
- public AvailableMediaDeviceGroupController(Context context) {
+ public AvailableMediaDeviceGroupController(
+ Context context,
+ @Nullable DashboardFragment fragment,
+ @Nullable Lifecycle lifecycle) {
super(context, KEY);
+ if (fragment != null) {
+ init(fragment);
+ }
+ if (lifecycle != null) {
+ lifecycle.addObserver(this);
+ }
mLocalBluetoothManager = Utils.getLocalBtManager(mContext);
- mExecutor = Executors.newSingleThreadExecutor();
}
@Override
- public void onStart() {
+ public void onStart(@NonNull LifecycleOwner owner) {
if (mLocalBluetoothManager == null) {
Log.e(TAG, "onStart() Bluetooth is not supported on this device");
return;
}
- if (AudioSharingUtils.isFeatureEnabled()) {
- LocalBluetoothLeBroadcastAssistant assistant =
- mLocalBluetoothManager
- .getProfileManager()
- .getLeAudioBroadcastAssistantProfile();
- if (assistant != null) {
- if (DEBUG) {
- Log.d(TAG, "onStart() Register callbacks for assistant.");
- }
- assistant.registerServiceCallBack(mExecutor, mAssistantCallback);
- }
- }
- mBluetoothDeviceUpdater.registerCallback();
mLocalBluetoothManager.getEventManager().registerCallback(this);
- mBluetoothDeviceUpdater.refreshPreference();
+ if (mBluetoothDeviceUpdater != null) {
+ mBluetoothDeviceUpdater.registerCallback();
+ mBluetoothDeviceUpdater.refreshPreference();
+ }
}
@Override
- public void onStop() {
+ public void onStop(@NonNull LifecycleOwner owner) {
if (mLocalBluetoothManager == null) {
Log.e(TAG, "onStop() Bluetooth is not supported on this device");
return;
}
- if (AudioSharingUtils.isFeatureEnabled()) {
- LocalBluetoothLeBroadcastAssistant assistant =
- mLocalBluetoothManager
- .getProfileManager()
- .getLeAudioBroadcastAssistantProfile();
- if (assistant != null) {
- if (DEBUG) {
- Log.d(TAG, "onStop() Register callbacks for assistant.");
- }
- assistant.unregisterServiceCallBack(mAssistantCallback);
- }
+ if (mBluetoothDeviceUpdater != null) {
+ mBluetoothDeviceUpdater.unregisterCallback();
}
- mBluetoothDeviceUpdater.unregisterCallback();
mLocalBluetoothManager.getEventManager().unregisterCallback(this);
}
@@ -178,12 +105,16 @@
super.displayPreference(screen);
mPreferenceGroup = screen.findPreference(KEY);
- mPreferenceGroup.setVisible(false);
+ if (mPreferenceGroup != null) {
+ mPreferenceGroup.setVisible(false);
+ }
if (isAvailable()) {
updateTitle();
- mBluetoothDeviceUpdater.setPrefContext(screen.getContext());
- mBluetoothDeviceUpdater.forceUpdate();
+ if (mBluetoothDeviceUpdater != null) {
+ mBluetoothDeviceUpdater.setPrefContext(screen.getContext());
+ mBluetoothDeviceUpdater.forceUpdate();
+ }
}
}
@@ -201,17 +132,21 @@
@Override
public void onDeviceAdded(Preference preference) {
- if (mPreferenceGroup.getPreferenceCount() == 0) {
- mPreferenceGroup.setVisible(true);
+ if (mPreferenceGroup != null) {
+ if (mPreferenceGroup.getPreferenceCount() == 0) {
+ mPreferenceGroup.setVisible(true);
+ }
+ mPreferenceGroup.addPreference(preference);
}
- mPreferenceGroup.addPreference(preference);
}
@Override
public void onDeviceRemoved(Preference preference) {
- mPreferenceGroup.removePreference(preference);
- if (mPreferenceGroup.getPreferenceCount() == 0) {
- mPreferenceGroup.setVisible(false);
+ if (mPreferenceGroup != null) {
+ mPreferenceGroup.removePreference(preference);
+ if (mPreferenceGroup.getPreferenceCount() == 0) {
+ mPreferenceGroup.setVisible(false);
+ }
}
}
@@ -253,14 +188,16 @@
}
private void updateTitle() {
- if (isAudioModeOngoingCall(mContext)) {
- // in phone call
- mPreferenceGroup.setTitle(
- mContext.getString(R.string.connected_device_call_device_title));
- } else {
- // without phone call
- mPreferenceGroup.setTitle(
- mContext.getString(R.string.connected_device_media_device_title));
+ if (mPreferenceGroup != null) {
+ if (isAudioModeOngoingCall(mContext)) {
+ // in phone call
+ mPreferenceGroup.setTitle(
+ mContext.getString(R.string.connected_device_call_device_title));
+ } else {
+ // without phone call
+ mPreferenceGroup.setTitle(
+ mContext.getString(R.string.connected_device_media_device_title));
+ }
}
}
}
diff --git a/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java b/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java
index 2798be4..04ba5d2 100644
--- a/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java
+++ b/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java
@@ -24,12 +24,10 @@
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
-import androidx.lifecycle.Lifecycle;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.Utils;
-import com.android.settings.connecteddevice.audiosharing.AudioSharingUtils;
import com.android.settings.core.SettingsUIDeviceConfig;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.overlay.FeatureFactory;
@@ -38,6 +36,7 @@
import com.android.settings.slices.SlicePreferenceController;
import com.android.settingslib.bluetooth.HearingAidStatsLogUtils;
import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.search.SearchIndexable;
import java.util.ArrayList;
@@ -92,7 +91,6 @@
+ ", action : "
+ action);
}
- use(AvailableMediaDeviceGroupController.class).init(this);
use(ConnectedDeviceGroupController.class).init(this);
use(PreviouslyConnectedDevicePreferenceController.class).init(this);
use(SlicePreferenceController.class)
@@ -124,15 +122,17 @@
@Nullable ConnectedDeviceDashboardFragment fragment,
@Nullable Lifecycle lifecycle) {
final List<AbstractPreferenceController> controllers = new ArrayList<>();
- if (AudioSharingUtils.isFeatureEnabled()) {
- AbstractPreferenceController audioSharingController =
- FeatureFactory.getFeatureFactory()
- .getAudioSharingFeatureProvider()
- .createAudioSharingDevicePreferenceController(
- context, fragment, lifecycle);
- if (audioSharingController != null) {
- controllers.add(audioSharingController);
- }
+ AbstractPreferenceController availableMediaController =
+ FeatureFactory.getFeatureFactory()
+ .getAudioSharingFeatureProvider()
+ .createAvailableMediaDeviceGroupController(context, fragment, lifecycle);
+ controllers.add(availableMediaController);
+ AbstractPreferenceController audioSharingController =
+ FeatureFactory.getFeatureFactory()
+ .getAudioSharingFeatureProvider()
+ .createAudioSharingDevicePreferenceController(context, fragment, lifecycle);
+ if (audioSharingController != null) {
+ controllers.add(audioSharingController);
}
return controllers;
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingBasePreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingBasePreferenceController.java
deleted file mode 100644
index 8497c9d..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingBasePreferenceController.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing;
-
-import android.bluetooth.BluetoothAdapter;
-import android.content.Context;
-
-import androidx.annotation.NonNull;
-import androidx.lifecycle.DefaultLifecycleObserver;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settings.bluetooth.Utils;
-import com.android.settings.core.BasePreferenceController;
-import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
-import com.android.settingslib.utils.ThreadUtils;
-
-public abstract class AudioSharingBasePreferenceController extends BasePreferenceController
- implements DefaultLifecycleObserver {
- private final BluetoothAdapter mBluetoothAdapter;
- private final LocalBluetoothManager mBtManager;
- protected final LocalBluetoothLeBroadcast mBroadcast;
- protected Preference mPreference;
-
- public AudioSharingBasePreferenceController(Context context, String preferenceKey) {
- super(context, preferenceKey);
- mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
- mBtManager = Utils.getLocalBtManager(context);
- mBroadcast =
- mBtManager == null
- ? null
- : mBtManager.getProfileManager().getLeAudioBroadcastProfile();
- }
-
- @Override
- public int getAvailabilityStatus() {
- return AudioSharingUtils.isFeatureEnabled() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
- }
-
- @Override
- public void displayPreference(PreferenceScreen screen) {
- super.displayPreference(screen);
- mPreference = screen.findPreference(getPreferenceKey());
- }
-
- @Override
- public void onStart(@NonNull LifecycleOwner owner) {
- if (isAvailable()) {
- updateVisibility();
- }
- }
-
- /** Update the visibility of the preference. */
- protected void updateVisibility() {
- if (mPreference != null) {
- var unused =
- ThreadUtils.postOnBackgroundThread(
- () -> {
- boolean isVisible = isBroadcasting() && isBluetoothStateOn();
- ThreadUtils.postOnMainThread(
- () -> mPreference.setVisible(isVisible));
- });
- }
- }
-
- protected boolean isBroadcasting() {
- return mBroadcast != null && mBroadcast.isEnabled(null);
- }
-
- protected boolean isBluetoothStateOn() {
- return mBluetoothAdapter != null && mBluetoothAdapter.isEnabled();
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingBluetoothDeviceUpdater.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingBluetoothDeviceUpdater.java
deleted file mode 100644
index 59393ad..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingBluetoothDeviceUpdater.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing;
-
-import android.content.Context;
-import android.util.Log;
-
-import androidx.preference.Preference;
-
-import com.android.settings.bluetooth.BluetoothDevicePreference;
-import com.android.settings.bluetooth.BluetoothDeviceUpdater;
-import com.android.settings.bluetooth.Utils;
-import com.android.settings.connecteddevice.DevicePreferenceCallback;
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
-
-public class AudioSharingBluetoothDeviceUpdater extends BluetoothDeviceUpdater
- implements Preference.OnPreferenceClickListener {
-
- private static final String TAG = "AudioSharingBluetoothDeviceUpdater";
-
- private static final String PREF_KEY = "audio_sharing_bt";
-
- private LocalBluetoothManager mLocalBluetoothManager;
-
- public AudioSharingBluetoothDeviceUpdater(
- Context context,
- DevicePreferenceCallback devicePreferenceCallback,
- int metricsCategory) {
- super(context, devicePreferenceCallback, metricsCategory);
- mLocalBluetoothManager = Utils.getLocalBluetoothManager(context);
- }
-
- @Override
- public boolean isFilterMatched(CachedBluetoothDevice cachedDevice) {
- boolean isFilterMatched = false;
- if (isDeviceConnected(cachedDevice) && isDeviceInCachedDevicesList(cachedDevice)) {
- // If device is LE audio device and has a broadcast source,
- // it would show in audio sharing devices group.
- if (cachedDevice.isConnectedLeAudioDevice()
- && AudioSharingUtils.hasBroadcastSource(cachedDevice, mLocalBluetoothManager)) {
- isFilterMatched = true;
- }
- }
- Log.d(
- TAG,
- "isFilterMatched() device : "
- + cachedDevice.getName()
- + ", isFilterMatched : "
- + isFilterMatched);
- return isFilterMatched;
- }
-
- @Override
- public boolean onPreferenceClick(Preference preference) {
- mMetricsFeatureProvider.logClickedPreference(preference, mMetricsCategory);
- final CachedBluetoothDevice device =
- ((BluetoothDevicePreference) preference).getBluetoothDevice();
- return device.setActive();
- }
-
- @Override
- protected String getPreferenceKey() {
- return PREF_KEY;
- }
-
- @Override
- protected String getLogTag() {
- return TAG;
- }
-
- @Override
- protected void update(CachedBluetoothDevice cachedBluetoothDevice) {
- super.update(cachedBluetoothDevice);
- Log.d(TAG, "Map : " + mPreferenceMap);
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCompatibilityPreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCompatibilityPreferenceController.java
deleted file mode 100644
index c39257d..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCompatibilityPreferenceController.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Copyright (C) 2024 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.connecteddevice.audiosharing;
-
-import android.bluetooth.BluetoothLeBroadcast;
-import android.bluetooth.BluetoothLeBroadcastMetadata;
-import android.content.Context;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.lifecycle.DefaultLifecycleObserver;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.preference.PreferenceScreen;
-import androidx.preference.TwoStatePreference;
-
-import com.android.settings.bluetooth.Utils;
-import com.android.settings.core.TogglePreferenceController;
-import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
-
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-
-public class AudioSharingCompatibilityPreferenceController extends TogglePreferenceController
- implements DefaultLifecycleObserver {
-
- private static final String TAG = "AudioSharingCompatibilityPrefController";
-
- private static final String PREF_KEY = "audio_sharing_stream_compatibility";
- private static final String SHARING_OFF_SUMMARY =
- "Helps some devices like hearing aids connect by reducing audio quality";
- private static final String SHARING_ON_SUMMARY =
- "Turns off the audio sharing to config the compatibility";
-
- private final LocalBluetoothManager mBtManager;
- private final Executor mExecutor;
- private final LocalBluetoothLeBroadcast mBroadcast;
- @Nullable private TwoStatePreference mPreference;
-
- private final BluetoothLeBroadcast.Callback mBroadcastCallback =
- new BluetoothLeBroadcast.Callback() {
- @Override
- public void onBroadcastStarted(int reason, int broadcastId) {
- Log.d(
- TAG,
- "onBroadcastStarted(), reason = "
- + reason
- + ", broadcastId = "
- + broadcastId);
- updateEnabled();
- }
-
- @Override
- public void onBroadcastStartFailed(int reason) {}
-
- @Override
- public void onBroadcastMetadataChanged(
- int broadcastId, @NonNull BluetoothLeBroadcastMetadata metadata) {}
-
- @Override
- public void onBroadcastStopped(int reason, int broadcastId) {
- Log.d(
- TAG,
- "onBroadcastStopped(), reason = "
- + reason
- + ", broadcastId = "
- + broadcastId);
- updateEnabled();
- }
-
- @Override
- public void onBroadcastStopFailed(int reason) {}
-
- @Override
- public void onBroadcastUpdated(int reason, int broadcastId) {}
-
- @Override
- public void onBroadcastUpdateFailed(int reason, int broadcastId) {}
-
- @Override
- public void onPlaybackStarted(int reason, int broadcastId) {}
-
- @Override
- public void onPlaybackStopped(int reason, int broadcastId) {}
- };
-
- public AudioSharingCompatibilityPreferenceController(Context context, String preferenceKey) {
- super(context, preferenceKey);
- mBtManager = Utils.getLocalBtManager(context);
- mBroadcast = mBtManager.getProfileManager().getLeAudioBroadcastProfile();
- mExecutor = Executors.newSingleThreadExecutor();
- }
-
- @Override
- public void onStart(@NonNull LifecycleOwner owner) {
- if (mBroadcast != null) {
- mBroadcast.registerServiceCallBack(mExecutor, mBroadcastCallback);
- }
- }
-
- @Override
- public void onStop(@NonNull LifecycleOwner owner) {
- if (mBroadcast != null) {
- mBroadcast.unregisterServiceCallBack(mBroadcastCallback);
- }
- }
-
- @Override
- public int getAvailabilityStatus() {
- return AudioSharingUtils.isFeatureEnabled() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
- }
-
- @Override
- public void displayPreference(PreferenceScreen screen) {
- super.displayPreference(screen);
- mPreference = screen.findPreference(getPreferenceKey());
- updateEnabled();
- }
-
- @Override
- public String getPreferenceKey() {
- return PREF_KEY;
- }
-
- @Override
- public boolean isChecked() {
- return mBroadcast != null && mBroadcast.getImproveCompatibility();
- }
-
- @Override
- public boolean setChecked(boolean isChecked) {
- if (mBroadcast == null || mBroadcast.getImproveCompatibility() == isChecked) {
- Log.d(
- TAG,
- "Skip setting improveCompatibility, unchanged = "
- + (mBroadcast.getImproveCompatibility() == isChecked));
- return false;
- }
- mBroadcast.setImproveCompatibility(isChecked);
- // TODO: call updateBroadcast once framework change ready.
- return true;
- }
-
- @Override
- public int getSliceHighlightMenuRes() {
- return 0;
- }
-
- private void updateEnabled() {
- mContext.getMainExecutor()
- .execute(
- () -> {
- if (mPreference != null) {
- boolean isBroadcasting =
- AudioSharingUtils.isBroadcasting(mBtManager);
- mPreference.setEnabled(!isBroadcasting);
- mPreference.setSummary(
- isBroadcasting ? SHARING_ON_SUMMARY : SHARING_OFF_SUMMARY);
- }
- });
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragment.java
deleted file mode 100644
index 7a7f337..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragment.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing;
-
-import android.app.settings.SettingsEnums;
-import android.content.Context;
-import android.os.Bundle;
-
-import com.android.settings.R;
-import com.android.settings.SettingsActivity;
-import com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsCategoryController;
-import com.android.settings.dashboard.DashboardFragment;
-import com.android.settings.widget.SettingsMainSwitchBar;
-
-public class AudioSharingDashboardFragment extends DashboardFragment
- implements AudioSharingSwitchBarController.OnSwitchBarChangedListener {
- private static final String TAG = "AudioSharingDashboardFrag";
-
- SettingsMainSwitchBar mMainSwitchBar;
- private AudioSharingSwitchBarController mSwitchBarController;
- private AudioSharingDeviceVolumeGroupController mAudioSharingDeviceVolumeGroupController;
- private CallsAndAlarmsPreferenceController mCallsAndAlarmsPreferenceController;
- private AudioSharingPlaySoundPreferenceController mAudioSharingPlaySoundPreferenceController;
- private AudioStreamsCategoryController mAudioStreamsCategoryController;
-
- public AudioSharingDashboardFragment() {
- super();
- }
-
- @Override
- public int getMetricsCategory() {
- return SettingsEnums.AUDIO_SHARING_SETTINGS;
- }
-
- @Override
- protected String getLogTag() {
- return TAG;
- }
-
- @Override
- public int getHelpResource() {
- return R.string.help_url_audio_sharing;
- }
-
- @Override
- protected int getPreferenceScreenResId() {
- return R.xml.bluetooth_audio_sharing;
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- }
-
- @Override
- public void onAttach(Context context) {
- super.onAttach(context);
- mAudioSharingDeviceVolumeGroupController =
- use(AudioSharingDeviceVolumeGroupController.class);
- mAudioSharingDeviceVolumeGroupController.init(this);
- mCallsAndAlarmsPreferenceController = use(CallsAndAlarmsPreferenceController.class);
- mCallsAndAlarmsPreferenceController.init(this);
- mAudioSharingPlaySoundPreferenceController =
- use(AudioSharingPlaySoundPreferenceController.class);
- mAudioStreamsCategoryController = use(AudioStreamsCategoryController.class);
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
- // Assume we are in a SettingsActivity. This is only safe because we currently use
- // SettingsActivity as base for all preference fragments.
- final SettingsActivity activity = (SettingsActivity) getActivity();
- mMainSwitchBar = activity.getSwitchBar();
- mMainSwitchBar.setTitle(getText(R.string.audio_sharing_switch_title));
- mSwitchBarController = new AudioSharingSwitchBarController(activity, mMainSwitchBar, this);
- mSwitchBarController.init(this);
- getSettingsLifecycle().addObserver(mSwitchBarController);
- mMainSwitchBar.show();
- }
-
- @Override
- public void onSwitchBarChanged() {
- updateVisibilityForAttachedPreferences();
- }
-
- private void updateVisibilityForAttachedPreferences() {
- mAudioSharingDeviceVolumeGroupController.updateVisibility();
- mCallsAndAlarmsPreferenceController.updateVisibility();
- mAudioSharingPlaySoundPreferenceController.updateVisibility();
- mAudioStreamsCategoryController.updateVisibility();
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceAdapter.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceAdapter.java
deleted file mode 100644
index a5f5adb..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceAdapter.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing;
-
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Button;
-
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.android.settings.R;
-
-import java.util.ArrayList;
-
-public class AudioSharingDeviceAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
-
- private static final String TAG = "AudioSharingDeviceAdapter";
- private final ArrayList<AudioSharingDeviceItem> mDevices;
- private final OnClickListener mOnClickListener;
- private final String mPrefix;
-
- public AudioSharingDeviceAdapter(
- ArrayList<AudioSharingDeviceItem> devices, OnClickListener listener, String prefix) {
- mDevices = devices;
- mOnClickListener = listener;
- mPrefix = prefix;
- }
-
- private class AudioSharingDeviceViewHolder extends RecyclerView.ViewHolder {
- private final Button mButtonView;
-
- AudioSharingDeviceViewHolder(View view) {
- super(view);
- mButtonView = view.findViewById(R.id.device_button);
- }
-
- public void bindView(int position) {
- if (mButtonView != null) {
- mButtonView.setText(mPrefix + mDevices.get(position).getName());
- mButtonView.setOnClickListener(
- v -> mOnClickListener.onClick(mDevices.get(position)));
- } else {
- Log.w(TAG, "bind view skipped due to button view is null");
- }
- }
- }
-
- @Override
- public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- View view =
- LayoutInflater.from(parent.getContext())
- .inflate(R.layout.audio_sharing_device_item, parent, false);
- return new AudioSharingDeviceViewHolder(view);
- }
-
- @Override
- public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
- ((AudioSharingDeviceViewHolder) holder).bindView(position);
- }
-
- @Override
- public int getItemCount() {
- return mDevices.size();
- }
-
- public interface OnClickListener {
- /** Called when an item has been clicked. */
- void onClick(AudioSharingDeviceItem item);
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceItem.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceItem.java
deleted file mode 100644
index 5998e30..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceItem.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-public final class AudioSharingDeviceItem implements Parcelable {
- private final String mName;
- private final int mGroupId;
- private final boolean mIsActive;
-
- public AudioSharingDeviceItem(String name, int groupId, boolean isActive) {
- mName = name;
- mGroupId = groupId;
- mIsActive = isActive;
- }
-
- public String getName() {
- return mName;
- }
-
- public int getGroupId() {
- return mGroupId;
- }
-
- public boolean isActive() {
- return mIsActive;
- }
-
- public AudioSharingDeviceItem(Parcel in) {
- mName = in.readString();
- mGroupId = in.readInt();
- mIsActive = in.readBoolean();
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(mName);
- dest.writeInt(mGroupId);
- dest.writeBoolean(mIsActive);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- public static final Creator<AudioSharingDeviceItem> CREATOR =
- new Creator<AudioSharingDeviceItem>() {
- @Override
- public AudioSharingDeviceItem createFromParcel(Parcel in) {
- return new AudioSharingDeviceItem(in);
- }
-
- @Override
- public AudioSharingDeviceItem[] newArray(int size) {
- return new AudioSharingDeviceItem[size];
- }
- };
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java
deleted file mode 100644
index ef0f226..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java
+++ /dev/null
@@ -1,677 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothCsipSetCoordinator;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothLeBroadcast;
-import android.bluetooth.BluetoothLeBroadcastAssistant;
-import android.bluetooth.BluetoothLeBroadcastMetadata;
-import android.bluetooth.BluetoothLeBroadcastReceiveState;
-import android.bluetooth.BluetoothProfile;
-import android.content.Context;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.DialogFragment;
-import androidx.fragment.app.Fragment;
-import androidx.lifecycle.DefaultLifecycleObserver;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceGroup;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settings.bluetooth.BluetoothDeviceUpdater;
-import com.android.settings.bluetooth.Utils;
-import com.android.settings.connecteddevice.DevicePreferenceCallback;
-import com.android.settings.core.BasePreferenceController;
-import com.android.settings.dashboard.DashboardFragment;
-import com.android.settingslib.bluetooth.BluetoothCallback;
-import com.android.settingslib.bluetooth.BluetoothUtils;
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
-import com.android.settingslib.bluetooth.LeAudioProfile;
-import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
-import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
-import com.android.settingslib.utils.ThreadUtils;
-
-import com.google.common.collect.ImmutableList;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-
-public class AudioSharingDevicePreferenceController extends BasePreferenceController
- implements DefaultLifecycleObserver, DevicePreferenceCallback, BluetoothCallback {
- private static final boolean DEBUG = BluetoothUtils.D;
-
- private static final String TAG = "AudioSharingDevicePrefController";
- private static final String KEY = "audio_sharing_device_list";
- private static final String KEY_AUDIO_SHARING_SETTINGS =
- "connected_device_audio_sharing_settings";
-
- private final LocalBluetoothManager mLocalBtManager;
- private final Executor mExecutor;
- private CachedBluetoothDeviceManager mDeviceManager;
- private LocalBluetoothLeBroadcast mBroadcast;
- private LocalBluetoothLeBroadcastAssistant mAssistant;
- private PreferenceGroup mPreferenceGroup;
- private Preference mAudioSharingSettingsPreference;
- private BluetoothDeviceUpdater mBluetoothDeviceUpdater;
- private DashboardFragment mFragment;
- private List<BluetoothDevice> mTargetSinks = new ArrayList<>();
-
- private final BluetoothLeBroadcast.Callback mBroadcastCallback =
- new BluetoothLeBroadcast.Callback() {
- @Override
- public void onBroadcastStarted(int reason, int broadcastId) {
- Log.d(
- TAG,
- "onBroadcastStarted(), reason = "
- + reason
- + ", broadcastId = "
- + broadcastId);
- }
-
- @Override
- public void onBroadcastStartFailed(int reason) {
- Log.d(TAG, "onBroadcastStartFailed(), reason = " + reason);
- // TODO: handle broadcast start fail
- }
-
- @Override
- public void onBroadcastMetadataChanged(
- int broadcastId, @NonNull BluetoothLeBroadcastMetadata metadata) {
- Log.d(
- TAG,
- "onBroadcastMetadataChanged(), broadcastId = "
- + broadcastId
- + ", metadata = "
- + metadata);
- addSourceToTargetDevices(mTargetSinks);
- mTargetSinks = new ArrayList<>();
- }
-
- @Override
- public void onBroadcastStopped(int reason, int broadcastId) {
- Log.d(
- TAG,
- "onBroadcastStopped(), reason = "
- + reason
- + ", broadcastId = "
- + broadcastId);
- }
-
- @Override
- public void onBroadcastStopFailed(int reason) {
- Log.d(TAG, "onBroadcastStopFailed(), reason = " + reason);
- // TODO: handle broadcast stop fail
- }
-
- @Override
- public void onBroadcastUpdated(int reason, int broadcastId) {}
-
- @Override
- public void onBroadcastUpdateFailed(int reason, int broadcastId) {}
-
- @Override
- public void onPlaybackStarted(int reason, int broadcastId) {}
-
- @Override
- public void onPlaybackStopped(int reason, int broadcastId) {}
- };
-
- private BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
- new BluetoothLeBroadcastAssistant.Callback() {
- @Override
- public void onSearchStarted(int reason) {}
-
- @Override
- public void onSearchStartFailed(int reason) {}
-
- @Override
- public void onSearchStopped(int reason) {}
-
- @Override
- public void onSearchStopFailed(int reason) {}
-
- @Override
- public void onSourceFound(@NonNull BluetoothLeBroadcastMetadata source) {}
-
- @Override
- public void onSourceAdded(@NonNull BluetoothDevice sink, int sourceId, int reason) {
- Log.d(
- TAG,
- "onSourceAdded(), sink = "
- + sink
- + ", sourceId = "
- + sourceId
- + ", reason = "
- + reason);
- mBluetoothDeviceUpdater.forceUpdate();
- if (mDeviceManager != null) {
- CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(sink);
- if (cachedDevice != null) {
- closeOpeningDialogsForLeaDevice(cachedDevice);
- }
- }
- }
-
- @Override
- public void onSourceAddFailed(
- @NonNull BluetoothDevice sink,
- @NonNull BluetoothLeBroadcastMetadata source,
- int reason) {
- Log.d(
- TAG,
- "onSourceAddFailed(), sink = "
- + sink
- + ", source = "
- + source
- + ", reason = "
- + reason);
- AudioSharingUtils.toastMessage(
- mContext,
- String.format(
- Locale.US,
- "Fail to add source to %s reason %d",
- sink.getAddress(),
- reason));
- }
-
- @Override
- public void onSourceModified(
- @NonNull BluetoothDevice sink, int sourceId, int reason) {}
-
- @Override
- public void onSourceModifyFailed(
- @NonNull BluetoothDevice sink, int sourceId, int reason) {}
-
- @Override
- public void onSourceRemoved(
- @NonNull BluetoothDevice sink, int sourceId, int reason) {
- Log.d(
- TAG,
- "onSourceRemoved(), sink = "
- + sink
- + ", sourceId = "
- + sourceId
- + ", reason = "
- + reason);
- mBluetoothDeviceUpdater.forceUpdate();
- }
-
- @Override
- public void onSourceRemoveFailed(
- @NonNull BluetoothDevice sink, int sourceId, int reason) {
- Log.d(
- TAG,
- "onSourceRemoveFailed(), sink = "
- + sink
- + ", sourceId = "
- + sourceId
- + ", reason = "
- + reason);
- AudioSharingUtils.toastMessage(
- mContext,
- String.format(
- Locale.US,
- "Fail to remove source from %s reason %d",
- sink.getAddress(),
- reason));
- }
-
- @Override
- public void onReceiveStateChanged(
- BluetoothDevice sink,
- int sourceId,
- BluetoothLeBroadcastReceiveState state) {}
- };
-
- public AudioSharingDevicePreferenceController(Context context) {
- super(context, KEY);
- mLocalBtManager = Utils.getLocalBtManager(mContext);
- if (mLocalBtManager != null) {
- mDeviceManager = mLocalBtManager.getCachedDeviceManager();
- mBroadcast = mLocalBtManager.getProfileManager().getLeAudioBroadcastProfile();
- mAssistant = mLocalBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
- }
- mExecutor = Executors.newSingleThreadExecutor();
- }
-
- @Override
- public void onStart(@NonNull LifecycleOwner owner) {
- if (mLocalBtManager == null) {
- Log.d(TAG, "onStart() Bluetooth is not supported on this device");
- return;
- }
- if (mBroadcast == null || mAssistant == null) {
- Log.d(TAG, "onStart() Broadcast or assistant is not supported on this device");
- return;
- }
- if (mBluetoothDeviceUpdater == null) {
- Log.d(TAG, "onStart() Bluetooth device updater is not initialized");
- return;
- }
- mLocalBtManager.getEventManager().registerCallback(this);
- if (DEBUG) {
- Log.d(TAG, "onStart() Register callbacks for broadcast and assistant.");
- }
- mBroadcast.registerServiceCallBack(mExecutor, mBroadcastCallback);
- mAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback);
- mBluetoothDeviceUpdater.registerCallback();
- mBluetoothDeviceUpdater.refreshPreference();
- }
-
- @Override
- public void onStop(@NonNull LifecycleOwner owner) {
- if (mLocalBtManager == null) {
- Log.d(TAG, "onStop() Bluetooth is not supported on this device");
- return;
- }
- if (mBroadcast == null || mAssistant == null) {
- Log.d(TAG, "onStop() Broadcast or assistant is not supported on this device");
- return;
- }
- if (mBluetoothDeviceUpdater == null) {
- Log.d(TAG, "onStop() Bluetooth device updater is not initialized");
- return;
- }
- mLocalBtManager.getEventManager().unregisterCallback(this);
- if (DEBUG) {
- Log.d(TAG, "onStop() Unregister callbacks for broadcast and assistant.");
- }
- mBroadcast.unregisterServiceCallBack(mBroadcastCallback);
- mAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback);
- mBluetoothDeviceUpdater.unregisterCallback();
- }
-
- @Override
- public void displayPreference(PreferenceScreen screen) {
- super.displayPreference(screen);
-
- mPreferenceGroup = screen.findPreference(KEY);
- mAudioSharingSettingsPreference =
- mPreferenceGroup.findPreference(KEY_AUDIO_SHARING_SETTINGS);
- mPreferenceGroup.setVisible(false);
- mAudioSharingSettingsPreference.setVisible(false);
-
- if (isAvailable()) {
- mBluetoothDeviceUpdater.setPrefContext(screen.getContext());
- mBluetoothDeviceUpdater.forceUpdate();
- }
- }
-
- @Override
- public int getAvailabilityStatus() {
- return AudioSharingUtils.isFeatureEnabled() && mBluetoothDeviceUpdater != null
- ? AVAILABLE_UNSEARCHABLE
- : UNSUPPORTED_ON_DEVICE;
- }
-
- @Override
- public String getPreferenceKey() {
- return KEY;
- }
-
- @Override
- public void onDeviceAdded(Preference preference) {
- if (mPreferenceGroup.getPreferenceCount() == 1) {
- mPreferenceGroup.setVisible(true);
- mAudioSharingSettingsPreference.setVisible(true);
- }
- mPreferenceGroup.addPreference(preference);
- }
-
- @Override
- public void onDeviceRemoved(Preference preference) {
- mPreferenceGroup.removePreference(preference);
- if (mPreferenceGroup.getPreferenceCount() == 1) {
- mPreferenceGroup.setVisible(false);
- mAudioSharingSettingsPreference.setVisible(false);
- }
- }
-
- @Override
- public void onProfileConnectionStateChanged(
- @NonNull CachedBluetoothDevice cachedDevice,
- @ConnectionState int state,
- int bluetoothProfile) {
- if (state == BluetoothAdapter.STATE_DISCONNECTED) {
- boolean isLeAudio = isLeAudioSupported(cachedDevice);
- if (isLeAudio && bluetoothProfile == BluetoothProfile.LE_AUDIO) {
- closeOpeningDialogsForLeaDevice(cachedDevice);
- return;
- }
- if (!isLeAudio && !cachedDevice.isConnected()) {
- closeOpeningDialogsForNonLeaDevice(cachedDevice);
- return;
- }
- }
- if (state != BluetoothAdapter.STATE_CONNECTED || !cachedDevice.getDevice().isConnected()) {
- Log.d(TAG, "Ignore onProfileConnectionStateChanged, not connected state");
- return;
- }
- if (mFragment == null) {
- Log.d(TAG, "Ignore onProfileConnectionStateChanged, no host fragment");
- return;
- }
- if (mAssistant == null && mBroadcast == null) {
- Log.d(
- TAG,
- "Ignore onProfileConnectionStateChanged, no broadcast or assistant supported");
- return;
- }
- var unused =
- ThreadUtils.postOnBackgroundThread(
- () -> handleOnProfileStateChanged(cachedDevice, bluetoothProfile));
- }
-
- /**
- * Initialize the controller.
- *
- * @param fragment The fragment to provide the context and metrics category for {@link
- * AudioSharingBluetoothDeviceUpdater} and provide the host for dialogs.
- */
- public void init(DashboardFragment fragment) {
- mFragment = fragment;
- mBluetoothDeviceUpdater =
- new AudioSharingBluetoothDeviceUpdater(
- fragment.getContext(),
- AudioSharingDevicePreferenceController.this,
- fragment.getMetricsCategory());
- }
-
- private void handleOnProfileStateChanged(
- @NonNull CachedBluetoothDevice cachedDevice, int bluetoothProfile) {
- boolean isLeAudioSupported = isLeAudioSupported(cachedDevice);
- // For eligible (LE audio) remote device, we only check its connected LE audio profile.
- if (isLeAudioSupported && bluetoothProfile != BluetoothProfile.LE_AUDIO) {
- Log.d(
- TAG,
- "Ignore onProfileConnectionStateChanged, not the le profile for le audio"
- + " device");
- return;
- }
- boolean isFirstConnectedProfile = isFirstConnectedProfile(cachedDevice, bluetoothProfile);
- // For ineligible (non LE audio) remote device, we only check its first connected profile.
- if (!isLeAudioSupported && !isFirstConnectedProfile) {
- Log.d(
- TAG,
- "Ignore onProfileConnectionStateChanged, not the first connected profile for"
- + " non le audio device");
- return;
- }
- if (DEBUG) {
- Log.d(
- TAG,
- "Start handling onProfileConnectionStateChanged for "
- + cachedDevice.getDevice().getAnonymizedAddress());
- }
- if (!isLeAudioSupported) {
- // Handle connected ineligible (non LE audio) remote device
- handleOnProfileStateChangedForNonLeAudioDevice(cachedDevice);
- } else {
- // Handle connected eligible (LE audio) remote device
- handleOnProfileStateChangedForLeAudioDevice(cachedDevice);
- }
- }
-
- private void handleOnProfileStateChangedForNonLeAudioDevice(
- @NonNull CachedBluetoothDevice cachedDevice) {
- if (isBroadcasting()) {
- // Show stop audio sharing dialog when an ineligible (non LE audio) remote device
- // connected during a sharing session.
- postOnMainThread(
- () -> {
- closeOpeningDialogsOtherThan(AudioSharingStopDialogFragment.tag());
- AudioSharingStopDialogFragment.show(
- mFragment,
- cachedDevice,
- () -> mBroadcast.stopBroadcast(mBroadcast.getLatestBroadcastId()));
- });
- } else {
- // Do nothing for ineligible (non LE audio) remote device when no sharing session.
- if (DEBUG) {
- Log.d(
- TAG,
- "Ignore onProfileConnectionStateChanged for non LE audio without"
- + " sharing session");
- }
- }
- }
-
- private void handleOnProfileStateChangedForLeAudioDevice(
- @NonNull CachedBluetoothDevice cachedDevice) {
- Map<Integer, List<CachedBluetoothDevice>> groupedDevices =
- AudioSharingUtils.fetchConnectedDevicesByGroupId(mLocalBtManager);
- if (isBroadcasting()) {
- int groupId = AudioSharingUtils.getGroupId(cachedDevice);
- if (groupedDevices.containsKey(groupId)
- && groupedDevices.get(groupId).stream()
- .anyMatch(
- device ->
- AudioSharingUtils.hasBroadcastSource(
- device, mLocalBtManager))) {
- Log.d(
- TAG,
- "Automatically add another device within the same group to the sharing: "
- + cachedDevice.getDevice().getAnonymizedAddress());
- addSourceToTargetDevices(ImmutableList.of(cachedDevice.getDevice()));
- return;
- }
- // Show audio sharing switch or join dialog according to device count in the sharing
- // session.
- ArrayList<AudioSharingDeviceItem> deviceItemsInSharingSession =
- AudioSharingUtils.buildOrderedConnectedLeadAudioSharingDeviceItem(
- mLocalBtManager, groupedDevices, /* filterByInSharing= */ true);
- // Show audio sharing switch dialog when the third eligible (LE audio) remote device
- // connected during a sharing session.
- if (deviceItemsInSharingSession.size() >= 2) {
- postOnMainThread(
- () -> {
- closeOpeningDialogsOtherThan(
- AudioSharingDisconnectDialogFragment.tag());
- AudioSharingDisconnectDialogFragment.show(
- mFragment,
- deviceItemsInSharingSession,
- cachedDevice,
- (AudioSharingDeviceItem item) -> {
- // Remove all sources from the device user clicked
- if (groupedDevices.containsKey(item.getGroupId())) {
- for (CachedBluetoothDevice device :
- groupedDevices.get(item.getGroupId())) {
- for (BluetoothLeBroadcastReceiveState source :
- mAssistant.getAllSources(
- device.getDevice())) {
- mAssistant.removeSource(
- device.getDevice(),
- source.getSourceId());
- }
- }
- }
- // Add current broadcast to the latest connected device
- mAssistant.addSource(
- cachedDevice.getDevice(),
- mBroadcast.getLatestBluetoothLeBroadcastMetadata(),
- /* isGroupOp= */ true);
- });
- });
- } else {
- // Show audio sharing join dialog when the first or second eligible (LE audio)
- // remote device connected during a sharing session.
- postOnMainThread(
- () -> {
- closeOpeningDialogsOtherThan(AudioSharingJoinDialogFragment.tag());
- AudioSharingJoinDialogFragment.show(
- mFragment,
- deviceItemsInSharingSession,
- cachedDevice,
- () -> {
- // Add current broadcast to the latest connected device
- mAssistant.addSource(
- cachedDevice.getDevice(),
- mBroadcast.getLatestBluetoothLeBroadcastMetadata(),
- /* isGroupOp= */ true);
- });
- });
- }
- } else {
- ArrayList<AudioSharingDeviceItem> deviceItems = new ArrayList<>();
- for (List<CachedBluetoothDevice> devices : groupedDevices.values()) {
- // Use random device in the group within the sharing session to represent the group.
- CachedBluetoothDevice device = devices.get(0);
- if (AudioSharingUtils.getGroupId(device)
- == AudioSharingUtils.getGroupId(cachedDevice)) {
- continue;
- }
- deviceItems.add(AudioSharingUtils.buildAudioSharingDeviceItem(device));
- }
- // Show audio sharing join dialog when the second eligible (LE audio) remote
- // device connect and no sharing session.
- if (deviceItems.size() == 1) {
- postOnMainThread(
- () -> {
- closeOpeningDialogsOtherThan(AudioSharingJoinDialogFragment.tag());
- AudioSharingJoinDialogFragment.show(
- mFragment,
- deviceItems,
- cachedDevice,
- () -> {
- mTargetSinks = new ArrayList<>();
- for (List<CachedBluetoothDevice> devices :
- groupedDevices.values()) {
- for (CachedBluetoothDevice device : devices) {
- mTargetSinks.add(device.getDevice());
- }
- }
- mBroadcast.startPrivateBroadcast();
- });
- });
- }
- }
- }
-
- private boolean isLeAudioSupported(CachedBluetoothDevice cachedDevice) {
- return cachedDevice.getProfiles().stream()
- .anyMatch(
- profile ->
- profile instanceof LeAudioProfile
- && profile.isEnabled(cachedDevice.getDevice()));
- }
-
- private boolean isFirstConnectedProfile(
- CachedBluetoothDevice cachedDevice, int bluetoothProfile) {
- return cachedDevice.getProfiles().stream()
- .noneMatch(
- profile ->
- profile.getProfileId() != bluetoothProfile
- && profile.getConnectionStatus(cachedDevice.getDevice())
- == BluetoothProfile.STATE_CONNECTED);
- }
-
- private boolean isBroadcasting() {
- return mBroadcast != null && mBroadcast.isEnabled(null);
- }
-
- private void addSourceToTargetDevices(List<BluetoothDevice> sinks) {
- if (sinks.isEmpty() || mBroadcast == null || mAssistant == null) {
- Log.d(TAG, "Skip adding source to target.");
- return;
- }
- BluetoothLeBroadcastMetadata broadcastMetadata =
- mBroadcast.getLatestBluetoothLeBroadcastMetadata();
- if (broadcastMetadata == null) {
- Log.e(TAG, "Error: There is no broadcastMetadata.");
- return;
- }
- for (BluetoothDevice sink : sinks) {
- Log.d(
- TAG,
- "Add broadcast with broadcastId: "
- + broadcastMetadata.getBroadcastId()
- + "to the device: "
- + sink.getAnonymizedAddress());
- mAssistant.addSource(sink, broadcastMetadata, /* isGroupOp= */ false);
- }
- }
-
- private void closeOpeningDialogsOtherThan(String tag) {
- if (mFragment == null) return;
- List<Fragment> fragments = mFragment.getChildFragmentManager().getFragments();
- for (Fragment fragment : fragments) {
- if (fragment instanceof DialogFragment && !fragment.getTag().equals(tag)) {
- Log.d(TAG, "Remove staled opening dialog " + fragment.getTag());
- ((DialogFragment) fragment).dismiss();
- }
- }
- }
-
- private void closeOpeningDialogsForLeaDevice(@NonNull CachedBluetoothDevice cachedDevice) {
- if (mFragment == null) return;
- int groupId = AudioSharingUtils.getGroupId(cachedDevice);
- List<Fragment> fragments = mFragment.getChildFragmentManager().getFragments();
- for (Fragment fragment : fragments) {
- CachedBluetoothDevice device = getCachedBluetoothDeviceFromDialog(fragment);
- if (device != null
- && groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID
- && AudioSharingUtils.getGroupId(device) == groupId) {
- Log.d(TAG, "Remove staled opening dialog for group " + groupId);
- ((DialogFragment) fragment).dismiss();
- }
- }
- }
-
- private void closeOpeningDialogsForNonLeaDevice(@NonNull CachedBluetoothDevice cachedDevice) {
- if (mFragment == null) return;
- String address = cachedDevice.getAddress();
- List<Fragment> fragments = mFragment.getChildFragmentManager().getFragments();
- for (Fragment fragment : fragments) {
- CachedBluetoothDevice device = getCachedBluetoothDeviceFromDialog(fragment);
- if (device != null && address != null && address.equals(device.getAddress())) {
- Log.d(
- TAG,
- "Remove staled opening dialog for device "
- + cachedDevice.getDevice().getAnonymizedAddress());
- ((DialogFragment) fragment).dismiss();
- }
- }
- }
-
- @Nullable
- private CachedBluetoothDevice getCachedBluetoothDeviceFromDialog(Fragment fragment) {
- CachedBluetoothDevice device = null;
- if (fragment instanceof AudioSharingJoinDialogFragment) {
- device = ((AudioSharingJoinDialogFragment) fragment).getDevice();
- } else if (fragment instanceof AudioSharingStopDialogFragment) {
- device = ((AudioSharingStopDialogFragment) fragment).getDevice();
- } else if (fragment instanceof AudioSharingDisconnectDialogFragment) {
- device = ((AudioSharingDisconnectDialogFragment) fragment).getDevice();
- }
- return device;
- }
-
- private void postOnMainThread(@NonNull Runnable runnable) {
- mContext.getMainExecutor().execute(runnable);
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeControlUpdater.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeControlUpdater.java
deleted file mode 100644
index 5c0a90a..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeControlUpdater.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing;
-
-import android.bluetooth.BluetoothDevice;
-import android.content.Context;
-import android.util.Log;
-import android.widget.SeekBar;
-
-import androidx.preference.Preference;
-
-import com.android.settings.bluetooth.BluetoothDevicePreference;
-import com.android.settings.bluetooth.BluetoothDeviceUpdater;
-import com.android.settings.bluetooth.Utils;
-import com.android.settings.connecteddevice.DevicePreferenceCallback;
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
-
-public class AudioSharingDeviceVolumeControlUpdater extends BluetoothDeviceUpdater
- implements Preference.OnPreferenceClickListener {
-
- private static final String TAG = "AudioSharingDeviceVolumeControlUpdater";
-
- private static final String PREF_KEY = "audio_sharing_volume_control";
-
- private final LocalBluetoothManager mLocalBtManager;
-
- public AudioSharingDeviceVolumeControlUpdater(
- Context context,
- DevicePreferenceCallback devicePreferenceCallback,
- int metricsCategory) {
- super(context, devicePreferenceCallback, metricsCategory);
- mLocalBtManager = Utils.getLocalBluetoothManager(context);
- }
-
- @Override
- public boolean isFilterMatched(CachedBluetoothDevice cachedDevice) {
- boolean isFilterMatched = false;
- if (isDeviceConnected(cachedDevice) && isDeviceInCachedDevicesList(cachedDevice)) {
- // If device is LE audio device and in a sharing session on current sharing device,
- // it would show in volume control group.
- if (cachedDevice.isConnectedLeAudioDevice()
- && AudioSharingUtils.isBroadcasting(mLocalBtManager)
- && AudioSharingUtils.hasBroadcastSource(cachedDevice, mLocalBtManager)) {
- isFilterMatched = true;
- }
- }
- Log.d(
- TAG,
- "isFilterMatched() device : "
- + cachedDevice.getName()
- + ", isFilterMatched : "
- + isFilterMatched);
- return isFilterMatched;
- }
-
- @Override
- public boolean onPreferenceClick(Preference preference) {
- return true;
- }
-
- @Override
- protected void addPreference(CachedBluetoothDevice cachedDevice) {
- if (cachedDevice == null) return;
- final BluetoothDevice device = cachedDevice.getDevice();
- if (!mPreferenceMap.containsKey(device)) {
- SeekBar.OnSeekBarChangeListener listener =
- new SeekBar.OnSeekBarChangeListener() {
- @Override
- public void onProgressChanged(
- SeekBar seekBar, int progress, boolean fromUser) {}
-
- @Override
- public void onStartTrackingTouch(SeekBar seekBar) {}
-
- @Override
- public void onStopTrackingTouch(SeekBar seekBar) {
- if (mLocalBtManager != null
- && mLocalBtManager.getProfileManager().getVolumeControlProfile()
- != null) {
- mLocalBtManager
- .getProfileManager()
- .getVolumeControlProfile()
- .setDeviceVolume(
- cachedDevice.getDevice(),
- seekBar.getProgress(),
- /* isGroupOp= */ true);
- }
- }
- };
- AudioSharingDeviceVolumePreference vPreference =
- new AudioSharingDeviceVolumePreference(mPrefContext, cachedDevice);
- vPreference.initialize();
- vPreference.setOnSeekBarChangeListener(listener);
- vPreference.setKey(getPreferenceKey());
- vPreference.setIcon(com.android.settingslib.R.drawable.ic_bt_untethered_earbuds);
- vPreference.setTitle(cachedDevice.getName());
- mPreferenceMap.put(device, vPreference);
- mDevicePreferenceCallback.onDeviceAdded(vPreference);
- }
- }
-
- @Override
- protected String getPreferenceKey() {
- return PREF_KEY;
- }
-
- @Override
- protected String getLogTag() {
- return TAG;
- }
-
- @Override
- protected void update(CachedBluetoothDevice cachedBluetoothDevice) {
- super.update(cachedBluetoothDevice);
- Log.d(TAG, "Map : " + mPreferenceMap);
- }
-
- @Override
- protected void addPreference(
- CachedBluetoothDevice cachedDevice, @BluetoothDevicePreference.SortType int type) {}
-
- @Override
- protected void launchDeviceDetails(Preference preference) {}
-
- @Override
- public void refreshPreference() {}
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeGroupController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeGroupController.java
deleted file mode 100644
index edd1caf..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeGroupController.java
+++ /dev/null
@@ -1,345 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing;
-
-import android.annotation.IntRange;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothLeBroadcastAssistant;
-import android.bluetooth.BluetoothLeBroadcastMetadata;
-import android.bluetooth.BluetoothLeBroadcastReceiveState;
-import android.bluetooth.BluetoothVolumeControl;
-import android.content.Context;
-import android.media.AudioManager;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.fragment.app.FragmentManager;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceGroup;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settings.bluetooth.BluetoothDeviceUpdater;
-import com.android.settings.bluetooth.Utils;
-import com.android.settings.connecteddevice.DevicePreferenceCallback;
-import com.android.settings.dashboard.DashboardFragment;
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
-import com.android.settingslib.bluetooth.VolumeControlProfile;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-
-public class AudioSharingDeviceVolumeGroupController extends AudioSharingBasePreferenceController
- implements DevicePreferenceCallback {
- private static final String TAG = "AudioSharingDeviceVolumeGroupController";
- private static final String KEY = "audio_sharing_device_volume_group";
-
- private final LocalBluetoothManager mLocalBtManager;
- private final LocalBluetoothLeBroadcastAssistant mAssistant;
- private final Executor mExecutor;
- private VolumeControlProfile mVolumeControl;
- private BluetoothDeviceUpdater mBluetoothDeviceUpdater;
- private FragmentManager mFragmentManager;
- private PreferenceGroup mPreferenceGroup;
- private List<AudioSharingDeviceVolumePreference> mVolumePreferences = new ArrayList<>();
- private Map<Integer, Integer> mValueMap = new HashMap<Integer, Integer>();
-
- private BluetoothVolumeControl.Callback mVolumeControlCallback =
- new BluetoothVolumeControl.Callback() {
- @Override
- public void onVolumeOffsetChanged(
- @NonNull BluetoothDevice device, int volumeOffset) {}
-
- @Override
- public void onDeviceVolumeChanged(
- @NonNull BluetoothDevice device,
- @IntRange(from = -255, to = 255) int volume) {
- CachedBluetoothDevice cachedDevice =
- mLocalBtManager.getCachedDeviceManager().findDevice(device);
- if (cachedDevice == null) return;
- int groupId = AudioSharingUtils.getGroupId(cachedDevice);
- mValueMap.put(groupId, volume);
- for (AudioSharingDeviceVolumePreference preference : mVolumePreferences) {
- if (preference.getCachedDevice() != null
- && AudioSharingUtils.getGroupId(preference.getCachedDevice())
- == groupId) {
- // If the callback return invalid volume, try to
- // get the volume from AudioManager.STREAM_MUSIC
- int finalVolume = getAudioVolumeIfNeeded(volume);
- Log.d(
- TAG,
- "onDeviceVolumeChanged: set volume to "
- + finalVolume
- + " for "
- + device.getAnonymizedAddress());
- mContext.getMainExecutor()
- .execute(() -> preference.setProgress(finalVolume));
- break;
- }
- }
- }
- };
-
- private BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
- new BluetoothLeBroadcastAssistant.Callback() {
- @Override
- public void onSearchStarted(int reason) {}
-
- @Override
- public void onSearchStartFailed(int reason) {}
-
- @Override
- public void onSearchStopped(int reason) {}
-
- @Override
- public void onSearchStopFailed(int reason) {}
-
- @Override
- public void onSourceFound(@NonNull BluetoothLeBroadcastMetadata source) {}
-
- @Override
- public void onSourceAdded(@NonNull BluetoothDevice sink, int sourceId, int reason) {
- Log.d(
- TAG,
- "onSourceAdded(), sink = "
- + sink
- + ", sourceId = "
- + sourceId
- + ", reason = "
- + reason);
- mBluetoothDeviceUpdater.forceUpdate();
- }
-
- @Override
- public void onSourceAddFailed(
- @NonNull BluetoothDevice sink,
- @NonNull BluetoothLeBroadcastMetadata source,
- int reason) {
- Log.d(
- TAG,
- "onSourceAddFailed(), sink = "
- + sink
- + ", source = "
- + source
- + ", reason = "
- + reason);
- }
-
- @Override
- public void onSourceModified(
- @NonNull BluetoothDevice sink, int sourceId, int reason) {}
-
- @Override
- public void onSourceModifyFailed(
- @NonNull BluetoothDevice sink, int sourceId, int reason) {}
-
- @Override
- public void onSourceRemoved(
- @NonNull BluetoothDevice sink, int sourceId, int reason) {
- Log.d(
- TAG,
- "onSourceRemoved(), sink = "
- + sink
- + ", sourceId = "
- + sourceId
- + ", reason = "
- + reason);
- mBluetoothDeviceUpdater.forceUpdate();
- }
-
- @Override
- public void onSourceRemoveFailed(
- @NonNull BluetoothDevice sink, int sourceId, int reason) {
- Log.d(
- TAG,
- "onSourceRemoveFailed(), sink = "
- + sink
- + ", sourceId = "
- + sourceId
- + ", reason = "
- + reason);
- }
-
- @Override
- public void onReceiveStateChanged(
- BluetoothDevice sink,
- int sourceId,
- BluetoothLeBroadcastReceiveState state) {}
- };
-
- public AudioSharingDeviceVolumeGroupController(Context context) {
- super(context, KEY);
- mLocalBtManager = Utils.getLocalBtManager(mContext);
- mAssistant = mLocalBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
- mExecutor = Executors.newSingleThreadExecutor();
- if (mLocalBtManager != null) {
- mVolumeControl = mLocalBtManager.getProfileManager().getVolumeControlProfile();
- }
- }
-
- @Override
- public void onStart(@NonNull LifecycleOwner owner) {
- super.onStart(owner);
- if (mAssistant == null) {
- Log.d(TAG, "onStart() Broadcast or assistant is not supported on this device");
- return;
- }
- if (mBluetoothDeviceUpdater == null) {
- Log.d(TAG, "onStart() Bluetooth device updater is not initialized");
- return;
- }
- mAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback);
- mBluetoothDeviceUpdater.registerCallback();
- if (mVolumeControl != null) {
- Log.d(TAG, "onStart() Registered volume control callback");
- mVolumeControl.registerCallback(mExecutor, mVolumeControlCallback);
- }
- }
-
- @Override
- public void onStop(@NonNull LifecycleOwner owner) {
- super.onStop(owner);
- if (mAssistant == null) {
- Log.d(TAG, "onStop() Broadcast or assistant is not supported on this device");
- return;
- }
- if (mBluetoothDeviceUpdater == null) {
- Log.d(TAG, "onStop() Bluetooth device updater is not initialized");
- return;
- }
- mAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback);
- mBluetoothDeviceUpdater.unregisterCallback();
- if (mVolumeControl != null) {
- Log.d(TAG, "onStop() Unregistered volume control callback");
- mVolumeControl.unregisterCallback(mVolumeControlCallback);
- mValueMap.clear();
- }
- }
-
- @Override
- public void onDestroy(@NonNull LifecycleOwner owner) {
- mVolumePreferences.clear();
- }
-
- @Override
- public void displayPreference(PreferenceScreen screen) {
- super.displayPreference(screen);
-
- mPreferenceGroup = screen.findPreference(KEY);
- mPreferenceGroup.setVisible(false);
-
- if (isAvailable() && mBluetoothDeviceUpdater != null) {
- mBluetoothDeviceUpdater.setPrefContext(screen.getContext());
- mBluetoothDeviceUpdater.forceUpdate();
- }
- }
-
- @Override
- public String getPreferenceKey() {
- return KEY;
- }
-
- @Override
- public void onDeviceAdded(Preference preference) {
- if (mPreferenceGroup.getPreferenceCount() == 0) {
- mPreferenceGroup.setVisible(true);
- }
- mPreferenceGroup.addPreference(preference);
- if (preference instanceof AudioSharingDeviceVolumePreference) {
- var volumePref = (AudioSharingDeviceVolumePreference) preference;
- mVolumePreferences.add(volumePref);
- if (volumePref.getProgress() > 0) return;
- CachedBluetoothDevice device = volumePref.getCachedDevice();
- if (device == null) return;
- int volume = mValueMap.getOrDefault(AudioSharingUtils.getGroupId(device), -1);
- // If the volume is invalid, try to get the volume from AudioManager.STREAM_MUSIC
- int finalVolume = getAudioVolumeIfNeeded(volume);
- Log.d(
- TAG,
- "onDeviceAdded: set volume to "
- + finalVolume
- + " for "
- + device.getDevice().getAnonymizedAddress());
- mContext.getMainExecutor().execute(() -> volumePref.setProgress(finalVolume));
- }
- }
-
- @Override
- public void onDeviceRemoved(Preference preference) {
- mPreferenceGroup.removePreference(preference);
- if (mPreferenceGroup.getPreferenceCount() == 0) {
- mPreferenceGroup.setVisible(false);
- }
- if (preference instanceof AudioSharingDeviceVolumePreference) {
- var volumePref = (AudioSharingDeviceVolumePreference) preference;
- if (mVolumePreferences.contains(volumePref)) {
- mVolumePreferences.remove(volumePref);
- }
- CachedBluetoothDevice device = volumePref.getCachedDevice();
- Log.d(
- TAG,
- "onDeviceRemoved: "
- + (device == null
- ? "null"
- : device.getDevice().getAnonymizedAddress()));
- }
- }
-
- @Override
- public void updateVisibility() {
- if (mPreferenceGroup != null) {
- mPreferenceGroup.setVisible(false);
- if (mPreferenceGroup.getPreferenceCount() > 0) {
- super.updateVisibility();
- }
- }
- }
-
- /**
- * Initialize the controller.
- *
- * @param fragment The fragment to provide the context and metrics category for {@link
- * AudioSharingBluetoothDeviceUpdater} and provide the host for dialogs.
- */
- public void init(DashboardFragment fragment) {
- mBluetoothDeviceUpdater =
- new AudioSharingDeviceVolumeControlUpdater(
- fragment.getContext(),
- AudioSharingDeviceVolumeGroupController.this,
- fragment.getMetricsCategory());
- }
-
- private int getAudioVolumeIfNeeded(int volume) {
- if (volume >= 0) return volume;
- try {
- AudioManager audioManager = mContext.getSystemService(AudioManager.class);
- int max = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
- int min = audioManager.getStreamMinVolume(AudioManager.STREAM_MUSIC);
- return Math.round(
- audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) * 255f / (max - min));
- } catch (RuntimeException e) {
- Log.e(TAG, "Fail to fetch current music stream volume, error = " + e);
- return volume;
- }
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumePreference.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumePreference.java
deleted file mode 100644
index 9dd9fb0..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumePreference.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing;
-
-import android.content.Context;
-import android.widget.SeekBar;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.settings.R;
-import com.android.settings.widget.SeekBarPreference;
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-
-public class AudioSharingDeviceVolumePreference extends SeekBarPreference {
- public static final int MIN_VOLUME = 0;
- public static final int MAX_VOLUME = 255;
-
- protected SeekBar mSeekBar;
- private final CachedBluetoothDevice mCachedDevice;
-
- public AudioSharingDeviceVolumePreference(
- Context context, @NonNull CachedBluetoothDevice device) {
- super(context);
- setLayoutResource(R.layout.preference_volume_slider);
- mCachedDevice = device;
- }
-
- @Nullable
- public CachedBluetoothDevice getCachedDevice() {
- return mCachedDevice;
- }
-
- /**
- * Initialize {@link AudioSharingDeviceVolumePreference}.
- * Need to be called after creating the preference.
- */
- public void initialize() {
- setMax(MAX_VOLUME);
- setMin(MIN_VOLUME);
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java
deleted file mode 100644
index 32cd2f8..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing;
-
-import android.app.Dialog;
-import android.app.settings.SettingsEnums;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import androidx.appcompat.app.AlertDialog;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentManager;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.android.settings.R;
-import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
-
-import com.google.common.collect.Iterables;
-
-import java.util.ArrayList;
-import java.util.stream.Collectors;
-
-public class AudioSharingDialogFragment extends InstrumentedDialogFragment {
- private static final String TAG = "AudioSharingDialog";
-
- private static final String BUNDLE_KEY_DEVICE_ITEMS = "bundle_key_device_items";
-
- // The host creates an instance of this dialog fragment must implement this interface to receive
- // event callbacks.
- public interface DialogEventListener {
- /**
- * Called when users click the device item for sharing in the dialog.
- *
- * @param item The device item clicked.
- */
- void onItemClick(AudioSharingDeviceItem item);
- }
-
- private static DialogEventListener sListener;
-
- @Override
- public int getMetricsCategory() {
- return SettingsEnums.DIALOG_START_AUDIO_SHARING;
- }
-
- /**
- * Display the {@link AudioSharingDialogFragment} dialog.
- *
- * @param host The Fragment this dialog will be hosted.
- * @param deviceItems The connected device items eligible for audio sharing.
- * @param listener The callback to handle the user action on this dialog.
- */
- public static void show(
- Fragment host,
- ArrayList<AudioSharingDeviceItem> deviceItems,
- DialogEventListener listener) {
- if (!AudioSharingUtils.isFeatureEnabled()) return;
- final FragmentManager manager = host.getChildFragmentManager();
- sListener = listener;
- if (manager.findFragmentByTag(TAG) == null) {
- final Bundle bundle = new Bundle();
- bundle.putParcelableArrayList(BUNDLE_KEY_DEVICE_ITEMS, deviceItems);
- AudioSharingDialogFragment dialog = new AudioSharingDialogFragment();
- dialog.setArguments(bundle);
- dialog.show(manager, TAG);
- }
- }
-
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- Bundle arguments = requireArguments();
- ArrayList<AudioSharingDeviceItem> deviceItems =
- arguments.getParcelableArrayList(BUNDLE_KEY_DEVICE_ITEMS);
- final AlertDialog.Builder builder =
- new AlertDialog.Builder(getActivity()).setCancelable(false);
- LayoutInflater inflater = LayoutInflater.from(builder.getContext());
- View customTitle = inflater.inflate(R.layout.dialog_custom_title_audio_sharing, null);
- ImageView icon = customTitle.findViewById(R.id.title_icon);
- icon.setImageResource(R.drawable.ic_bt_audio_sharing);
- TextView title = customTitle.findViewById(R.id.title_text);
- View rootView = inflater.inflate(R.layout.dialog_audio_sharing, /* parent= */ null);
- TextView subTitle1 = rootView.findViewById(R.id.share_audio_subtitle1);
- TextView subTitle2 = rootView.findViewById(R.id.share_audio_subtitle2);
- RecyclerView recyclerView = rootView.findViewById(R.id.btn_list);
- Button shareBtn = rootView.findViewById(R.id.share_btn);
- Button cancelBtn = rootView.findViewById(R.id.cancel_btn);
- if (deviceItems.isEmpty()) {
- title.setText("Share your audio");
- subTitle2.setText(
- "To start sharing audio, "
- + "connect two pairs of headphones that support LE Audio");
- ImageView image = rootView.findViewById(R.id.share_audio_guidance);
- image.setVisibility(View.VISIBLE);
- builder.setNegativeButton("Close", null);
- } else if (deviceItems.size() == 1) {
- title.setText("Share your audio");
- subTitle1.setText(
- deviceItems.stream()
- .map(AudioSharingDeviceItem::getName)
- .collect(Collectors.joining(" and ")));
- subTitle2.setText(
- "This device's music and videos will play on both pairs of headphones");
- shareBtn.setText("Share audio");
- shareBtn.setOnClickListener(
- v -> {
- sListener.onItemClick(Iterables.getOnlyElement(deviceItems));
- dismiss();
- });
- cancelBtn.setOnClickListener(v -> dismiss());
- subTitle1.setVisibility(View.VISIBLE);
- shareBtn.setVisibility(View.VISIBLE);
- cancelBtn.setVisibility(View.VISIBLE);
- } else {
- title.setText("Share audio with another device");
- subTitle2.setText(
- "This device's music and videos will play on the headphones you connect");
- recyclerView.setAdapter(
- new AudioSharingDeviceAdapter(
- deviceItems,
- (AudioSharingDeviceItem item) -> {
- sListener.onItemClick(item);
- dismiss();
- },
- "Connect "));
- recyclerView.setLayoutManager(
- new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false));
- recyclerView.setVisibility(View.VISIBLE);
- cancelBtn.setOnClickListener(v -> dismiss());
- cancelBtn.setVisibility(View.VISIBLE);
- }
- AlertDialog dialog = builder.setCustomTitle(customTitle).setView(rootView).create();
- dialog.setCanceledOnTouchOutside(false);
- return dialog;
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDisconnectDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDisconnectDialogFragment.java
deleted file mode 100644
index 74c73aa..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDisconnectDialogFragment.java
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing;
-
-import android.app.Dialog;
-import android.app.settings.SettingsEnums;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.appcompat.app.AlertDialog;
-import androidx.fragment.app.DialogFragment;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentManager;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.android.settings.R;
-import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-
-import java.util.ArrayList;
-import java.util.Locale;
-
-public class AudioSharingDisconnectDialogFragment extends InstrumentedDialogFragment {
- private static final String TAG = "AudioSharingDisconnectDialog";
-
- private static final String BUNDLE_KEY_DEVICE_TO_DISCONNECT_ITEMS =
- "bundle_key_device_to_disconnect_items";
- private static final String BUNDLE_KEY_NEW_DEVICE_NAME = "bundle_key_new_device_name";
-
- // The host creates an instance of this dialog fragment must implement this interface to receive
- // event callbacks.
- public interface DialogEventListener {
- /**
- * Called when users click the device item to disconnect from the audio sharing in the
- * dialog.
- *
- * @param item The device item clicked.
- */
- void onItemClick(AudioSharingDeviceItem item);
- }
-
- private static DialogEventListener sListener;
- @Nullable private static CachedBluetoothDevice sNewDevice;
-
- @Override
- public int getMetricsCategory() {
- return SettingsEnums.DIALOG_AUDIO_SHARING_SWITCH_DEVICE;
- }
-
- /**
- * Display the {@link AudioSharingDisconnectDialogFragment} dialog.
- *
- * <p>If the dialog is showing for the same group, update the dialog event listener.
- *
- * @param host The Fragment this dialog will be hosted.
- * @param deviceItems The existing connected device items in audio sharing session.
- * @param newDevice The latest connected device triggered this dialog.
- * @param listener The callback to handle the user action on this dialog.
- */
- public static void show(
- Fragment host,
- ArrayList<AudioSharingDeviceItem> deviceItems,
- CachedBluetoothDevice newDevice,
- DialogEventListener listener) {
- if (!AudioSharingUtils.isFeatureEnabled()) return;
- final FragmentManager manager = host.getChildFragmentManager();
- Fragment dialog = manager.findFragmentByTag(TAG);
- if (dialog != null
- && ((DialogFragment) dialog).getDialog() != null
- && ((DialogFragment) dialog).getDialog().isShowing()) {
- int newGroupId = AudioSharingUtils.getGroupId(newDevice);
- if (sNewDevice != null && newGroupId == AudioSharingUtils.getGroupId(sNewDevice)) {
- Log.d(
- TAG,
- String.format(
- Locale.US,
- "Dialog is showing for the same device group %d, "
- + "update the content.",
- newGroupId));
- sListener = listener;
- sNewDevice = newDevice;
- return;
- } else {
- Log.d(
- TAG,
- String.format(
- Locale.US,
- "Dialog is showing for new device group %d, "
- + "dismiss current dialog.",
- newGroupId));
- ((DialogFragment) dialog).dismiss();
- }
- }
- sListener = listener;
- sNewDevice = newDevice;
- Log.d(TAG, "Show up the dialog.");
- final Bundle bundle = new Bundle();
- bundle.putParcelableArrayList(BUNDLE_KEY_DEVICE_TO_DISCONNECT_ITEMS, deviceItems);
- bundle.putString(BUNDLE_KEY_NEW_DEVICE_NAME, newDevice.getName());
- AudioSharingDisconnectDialogFragment dialogFrag =
- new AudioSharingDisconnectDialogFragment();
- dialogFrag.setArguments(bundle);
- dialogFrag.show(manager, TAG);
- }
-
- /** Return the tag of {@link AudioSharingDisconnectDialogFragment} dialog. */
- public static @NonNull String tag() {
- return TAG;
- }
-
- /** Get the latest connected device which triggers the dialog. */
- public @Nullable CachedBluetoothDevice getDevice() {
- return sNewDevice;
- }
-
- @Override
- public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
- Bundle arguments = requireArguments();
- ArrayList<AudioSharingDeviceItem> deviceItems =
- arguments.getParcelableArrayList(BUNDLE_KEY_DEVICE_TO_DISCONNECT_ITEMS);
- final AlertDialog.Builder builder =
- new AlertDialog.Builder(getActivity()).setCancelable(false);
- LayoutInflater inflater = LayoutInflater.from(builder.getContext());
- // Set custom title for the dialog.
- View customTitle = inflater.inflate(R.layout.dialog_custom_title_audio_sharing, null);
- ImageView icon = customTitle.findViewById(R.id.title_icon);
- icon.setImageResource(R.drawable.ic_bt_audio_sharing);
- TextView title = customTitle.findViewById(R.id.title_text);
- title.setText("Choose a device to disconnect");
- View rootView =
- inflater.inflate(R.layout.dialog_audio_sharing_disconnect, /* parent= */ null);
- TextView subTitle = rootView.findViewById(R.id.share_audio_disconnect_description);
- subTitle.setText("Only 2 devices can share audio at a time");
- RecyclerView recyclerView = rootView.findViewById(R.id.device_btn_list);
- recyclerView.setAdapter(
- new AudioSharingDeviceAdapter(
- deviceItems,
- (AudioSharingDeviceItem item) -> {
- sListener.onItemClick(item);
- dismiss();
- },
- "Disconnect "));
- recyclerView.setLayoutManager(
- new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false));
- Button cancelBtn = rootView.findViewById(R.id.cancel_btn);
- cancelBtn.setOnClickListener(v -> dismiss());
- AlertDialog dialog = builder.setCustomTitle(customTitle).setView(rootView).create();
- dialog.setCanceledOnTouchOutside(false);
- return dialog;
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProvider.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProvider.java
index c71a368..9fe4d50 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProvider.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProvider.java
@@ -20,12 +20,12 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.lifecycle.Lifecycle;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.core.lifecycle.Lifecycle;
/** Feature provider for the audio sharing related features, */
public interface AudioSharingFeatureProvider {
@@ -37,6 +37,12 @@
@Nullable DashboardFragment fragment,
@Nullable Lifecycle lifecycle);
+ /** Create available media device preference controller. */
+ AbstractPreferenceController createAvailableMediaDeviceGroupController(
+ @NonNull Context context,
+ @Nullable DashboardFragment fragment,
+ @Nullable Lifecycle lifecycle);
+
/**
* Check if the device match the audio sharing filter.
*
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProviderImpl.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProviderImpl.java
index 05a6a63..259ed7a 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProviderImpl.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProviderImpl.java
@@ -20,12 +20,13 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.lifecycle.Lifecycle;
+import com.android.settings.connecteddevice.AvailableMediaDeviceGroupController;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.core.lifecycle.Lifecycle;
public class AudioSharingFeatureProviderImpl implements AudioSharingFeatureProvider {
@@ -39,6 +40,14 @@
}
@Override
+ public AbstractPreferenceController createAvailableMediaDeviceGroupController(
+ @NonNull Context context,
+ @Nullable DashboardFragment fragment,
+ @Nullable Lifecycle lifecycle) {
+ return new AvailableMediaDeviceGroupController(context, fragment, lifecycle);
+ }
+
+ @Override
public boolean isAudioSharingFilterMatched(
@NonNull CachedBluetoothDevice cachedDevice, LocalBluetoothManager localBtManager) {
return false;
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingJoinDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingJoinDialogFragment.java
deleted file mode 100644
index 8791c11..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingJoinDialogFragment.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing;
-
-import android.app.Dialog;
-import android.app.settings.SettingsEnums;
-import android.graphics.Typeface;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.appcompat.app.AlertDialog;
-import androidx.fragment.app.DialogFragment;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentManager;
-
-import com.android.settings.R;
-import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-
-import java.util.ArrayList;
-import java.util.Locale;
-import java.util.stream.Collectors;
-
-public class AudioSharingJoinDialogFragment extends InstrumentedDialogFragment {
- private static final String TAG = "AudioSharingJoinDialog";
- private static final String BUNDLE_KEY_DEVICE_ITEMS = "bundle_key_device_items";
- private static final String BUNDLE_KEY_NEW_DEVICE_NAME = "bundle_key_new_device_name";
-
- // The host creates an instance of this dialog fragment must implement this interface to receive
- // event callbacks.
- public interface DialogEventListener {
- /** Called when users click the share audio button in the dialog. */
- void onShareClick();
- }
-
- private static DialogEventListener sListener;
- private static @Nullable CachedBluetoothDevice sNewDevice;
-
- @Override
- public int getMetricsCategory() {
- return SettingsEnums.DIALOG_START_AUDIO_SHARING;
- }
-
- /**
- * Display the {@link AudioSharingJoinDialogFragment} dialog.
- *
- * <p>If the dialog is showing, update the dialog message and event listener.
- *
- * @param host The Fragment this dialog will be hosted.
- * @param deviceItems The existing connected device items eligible for audio sharing.
- * @param newDevice The latest connected device triggered this dialog.
- * @param listener The callback to handle the user action on this dialog.
- */
- public static void show(
- Fragment host,
- ArrayList<AudioSharingDeviceItem> deviceItems,
- CachedBluetoothDevice newDevice,
- DialogEventListener listener) {
- if (!AudioSharingUtils.isFeatureEnabled()) return;
- final FragmentManager manager = host.getChildFragmentManager();
- sListener = listener;
- sNewDevice = newDevice;
- Fragment dialog = manager.findFragmentByTag(TAG);
- if (dialog != null
- && ((DialogFragment) dialog).getDialog() != null
- && ((DialogFragment) dialog).getDialog().isShowing()) {
- Log.d(TAG, "Dialog is showing, update the content.");
- updateDialog(
- deviceItems,
- newDevice.getName(),
- (AlertDialog) ((DialogFragment) dialog).getDialog());
- } else {
- Log.d(TAG, "Show up the dialog.");
- final Bundle bundle = new Bundle();
- bundle.putParcelableArrayList(BUNDLE_KEY_DEVICE_ITEMS, deviceItems);
- bundle.putString(BUNDLE_KEY_NEW_DEVICE_NAME, newDevice.getName());
- final AudioSharingJoinDialogFragment dialogFrag = new AudioSharingJoinDialogFragment();
- dialogFrag.setArguments(bundle);
- dialogFrag.show(manager, TAG);
- }
- }
-
- /** Return the tag of {@link AudioSharingJoinDialogFragment} dialog. */
- public static @NonNull String tag() {
- return TAG;
- }
-
- /** Get the latest connected device which triggers the dialog. */
- public @Nullable CachedBluetoothDevice getDevice() {
- return sNewDevice;
- }
-
- @Override
- public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
- Bundle arguments = requireArguments();
- ArrayList<AudioSharingDeviceItem> deviceItems =
- arguments.getParcelableArrayList(BUNDLE_KEY_DEVICE_ITEMS);
- String newDeviceName = arguments.getString(BUNDLE_KEY_NEW_DEVICE_NAME);
- final AlertDialog.Builder builder =
- new AlertDialog.Builder(getActivity()).setCancelable(false);
- LayoutInflater inflater = LayoutInflater.from(builder.getContext());
- // Set custom title for the dialog.
- View customTitle =
- inflater.inflate(R.layout.dialog_custom_title_audio_sharing, /* parent= */ null);
- ImageView icon = customTitle.findViewById(R.id.title_icon);
- icon.setImageResource(R.drawable.ic_bt_audio_sharing);
- TextView title = customTitle.findViewById(R.id.title_text);
- title.setText("Share your audio");
- View rootView = inflater.inflate(R.layout.dialog_audio_sharing_join, /* parent= */ null);
- TextView subtitle = rootView.findViewById(R.id.share_audio_subtitle);
- subtitle.setText("This device's music and videos will play on both pairs of headphones");
- Button shareBtn = rootView.findViewById(R.id.share_btn);
- Button cancelBtn = rootView.findViewById(R.id.cancel_btn);
- shareBtn.setOnClickListener(
- v -> {
- sListener.onShareClick();
- dismiss();
- });
- shareBtn.setText("Share audio");
- cancelBtn.setOnClickListener(v -> dismiss());
- AlertDialog dialog = builder.setCustomTitle(customTitle).setView(rootView).create();
- dialog.setCanceledOnTouchOutside(false);
- updateDialog(deviceItems, newDeviceName, dialog);
- dialog.show();
- TextView messageView = (TextView) dialog.findViewById(android.R.id.message);
- if (messageView != null) {
- Typeface typeface = Typeface.create(Typeface.DEFAULT_FAMILY, Typeface.NORMAL);
- messageView.setTypeface(typeface);
- messageView.setTextDirection(View.TEXT_DIRECTION_LOCALE);
- messageView.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
- } else {
- Log.w(TAG, "Fail to update message style: message view is null");
- }
- return dialog;
- }
-
- private static void updateDialog(
- ArrayList<AudioSharingDeviceItem> deviceItems,
- String newDeviceName,
- @NonNull AlertDialog dialog) {
- if (deviceItems.isEmpty()) {
- dialog.setMessage(newDeviceName);
- } else {
- dialog.setMessage(
- String.format(
- Locale.US,
- "%s and %s",
- deviceItems.stream()
- .map(AudioSharingDeviceItem::getName)
- .collect(Collectors.joining(", ")),
- newDeviceName));
- }
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreference.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreference.java
deleted file mode 100644
index 44c947d..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreference.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing;
-
-import android.app.settings.SettingsEnums;
-import android.content.Context;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.View;
-import android.widget.ImageButton;
-
-import androidx.preference.PreferenceViewHolder;
-
-import com.android.settings.R;
-import com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsQrCodeFragment;
-import com.android.settings.core.SubSettingLauncher;
-import com.android.settings.widget.ValidatedEditTextPreference;
-
-public class AudioSharingNamePreference extends ValidatedEditTextPreference {
- private static final String TAG = "AudioSharingNamePreference";
- private boolean mShowQrCodeIcon = false;
-
- public AudioSharingNamePreference(
- Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- initialize();
- }
-
- public AudioSharingNamePreference(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- initialize();
- }
-
- public AudioSharingNamePreference(Context context, AttributeSet attrs) {
- super(context, attrs);
- initialize();
- }
-
- public AudioSharingNamePreference(Context context) {
- super(context);
- initialize();
- }
-
- private void initialize() {
- setLayoutResource(
- com.android.settingslib.widget.preference.twotarget.R.layout.preference_two_target);
- setWidgetLayoutResource(R.layout.preference_widget_qrcode);
- }
-
- void setShowQrCodeIcon(boolean show) {
- mShowQrCodeIcon = show;
- notifyChanged();
- }
-
- @Override
- public void onBindViewHolder(PreferenceViewHolder holder) {
- super.onBindViewHolder(holder);
-
- ImageButton shareButton = (ImageButton) holder.findViewById(R.id.button_icon);
- View divider =
- holder.findViewById(
- com.android.settingslib.widget.preference.twotarget.R.id
- .two_target_divider);
-
- if (shareButton != null && divider != null) {
- if (mShowQrCodeIcon) {
- configureVisibleStateForQrCodeIcon(shareButton, divider);
- } else {
- configureInvisibleStateForQrCodeIcon(shareButton, divider);
- }
- } else {
- Log.w(TAG, "onBindViewHolder() : shareButton or divider is null!");
- }
- }
-
- private void configureVisibleStateForQrCodeIcon(ImageButton shareButton, View divider) {
- divider.setVisibility(View.VISIBLE);
- shareButton.setVisibility(View.VISIBLE);
- shareButton.setImageDrawable(getContext().getDrawable(R.drawable.ic_qrcode_24dp));
- shareButton.setOnClickListener(unused -> launchAudioSharingQrCodeFragment());
- }
-
- private void configureInvisibleStateForQrCodeIcon(ImageButton shareButton, View divider) {
- divider.setVisibility(View.INVISIBLE);
- shareButton.setVisibility(View.INVISIBLE);
- shareButton.setOnClickListener(null);
- }
-
- private void launchAudioSharingQrCodeFragment() {
- new SubSettingLauncher(getContext())
- .setTitleText("Audio sharing QR code")
- .setDestination(AudioStreamsQrCodeFragment.class.getName())
- .setSourceMetricsCategory(SettingsEnums.AUDIO_SHARING_SETTINGS)
- .launch();
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceController.java
deleted file mode 100644
index 644e05e..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceController.java
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing;
-
-import static com.android.settings.connecteddevice.audiosharing.AudioSharingUtils.isBroadcasting;
-
-import android.bluetooth.BluetoothLeBroadcast;
-import android.bluetooth.BluetoothLeBroadcastMetadata;
-import android.content.Context;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.lifecycle.DefaultLifecycleObserver;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settings.bluetooth.Utils;
-import com.android.settings.core.BasePreferenceController;
-import com.android.settings.widget.ValidatedEditTextPreference;
-import com.android.settingslib.bluetooth.BluetoothUtils;
-import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
-import com.android.settingslib.utils.ThreadUtils;
-
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-
-public class AudioSharingNamePreferenceController extends BasePreferenceController
- implements ValidatedEditTextPreference.Validator,
- Preference.OnPreferenceChangeListener,
- DefaultLifecycleObserver {
-
- private static final String TAG = "AudioSharingNamePreferenceController";
- private static final boolean DEBUG = BluetoothUtils.D;
- private static final String PREF_KEY = "audio_sharing_stream_name";
-
- private final BluetoothLeBroadcast.Callback mBroadcastCallback =
- new BluetoothLeBroadcast.Callback() {
- @Override
- public void onBroadcastMetadataChanged(
- int broadcastId, BluetoothLeBroadcastMetadata metadata) {
- if (DEBUG) {
- Log.d(
- TAG,
- "onBroadcastMetadataChanged() broadcastId : "
- + broadcastId
- + " metadata: "
- + metadata);
- }
- updateQrCodeIcon(true);
- }
-
- @Override
- public void onBroadcastStartFailed(int reason) {}
-
- @Override
- public void onBroadcastStarted(int reason, int broadcastId) {}
-
- @Override
- public void onBroadcastStopFailed(int reason) {}
-
- @Override
- public void onBroadcastStopped(int reason, int broadcastId) {
- if (DEBUG) {
- Log.d(
- TAG,
- "onBroadcastStopped() reason : "
- + reason
- + " broadcastId: "
- + broadcastId);
- }
- updateQrCodeIcon(false);
- }
-
- @Override
- public void onBroadcastUpdateFailed(int reason, int broadcastId) {
- Log.w(TAG, "onBroadcastUpdateFailed() reason : " + reason);
- // Do nothing if update failed.
- }
-
- @Override
- public void onBroadcastUpdated(int reason, int broadcastId) {
- if (DEBUG) {
- Log.d(TAG, "onBroadcastUpdated() reason : " + reason);
- }
- updateBroadcastName();
- }
-
- @Override
- public void onPlaybackStarted(int reason, int broadcastId) {}
-
- @Override
- public void onPlaybackStopped(int reason, int broadcastId) {}
- };
-
- @Nullable private final LocalBluetoothManager mLocalBtManager;
- @Nullable private final LocalBluetoothLeBroadcast mBroadcast;
- private final Executor mExecutor;
- private final AudioSharingNameTextValidator mAudioSharingNameTextValidator;
- @Nullable private AudioSharingNamePreference mPreference;
-
- public AudioSharingNamePreferenceController(Context context, String preferenceKey) {
- super(context, preferenceKey);
- mLocalBtManager = Utils.getLocalBluetoothManager(context);
- mBroadcast =
- (mLocalBtManager != null)
- ? mLocalBtManager.getProfileManager().getLeAudioBroadcastProfile()
- : null;
- mAudioSharingNameTextValidator = new AudioSharingNameTextValidator();
- mExecutor = Executors.newSingleThreadExecutor();
- }
-
- @Override
- public void onStart(@NonNull LifecycleOwner owner) {
- if (mBroadcast != null) {
- mBroadcast.registerServiceCallBack(mExecutor, mBroadcastCallback);
- }
- }
-
- @Override
- public void onStop(@NonNull LifecycleOwner owner) {
- if (mBroadcast != null) {
- mBroadcast.unregisterServiceCallBack(mBroadcastCallback);
- }
- }
-
- @Override
- public int getAvailabilityStatus() {
- return AudioSharingUtils.isFeatureEnabled() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
- }
-
- @Override
- public void displayPreference(PreferenceScreen screen) {
- super.displayPreference(screen);
- mPreference = screen.findPreference(getPreferenceKey());
- if (mPreference != null) {
- mPreference.setValidator(this);
- updateBroadcastName();
- updateQrCodeIcon(isBroadcasting(mLocalBtManager));
- }
- }
-
- @Override
- public String getPreferenceKey() {
- return PREF_KEY;
- }
-
- @Override
- public boolean onPreferenceChange(Preference preference, Object newValue) {
- if (mPreference != null
- && mPreference.getSummary() != null
- && ((String) newValue).contentEquals(mPreference.getSummary())) {
- return false;
- }
-
- var unused =
- ThreadUtils.postOnBackgroundThread(
- () -> {
- if (mBroadcast != null) {
- mBroadcast.setProgramInfo((String) newValue);
- if (isBroadcasting(mLocalBtManager)) {
- // Update broadcast, UI update will be handled after callback
- mBroadcast.updateBroadcast();
- } else {
- // Directly update UI if no ongoing broadcast
- updateBroadcastName();
- }
- }
- });
- return true;
- }
-
- private void updateBroadcastName() {
- if (mPreference != null) {
- var unused =
- ThreadUtils.postOnBackgroundThread(
- () -> {
- if (mBroadcast != null) {
- String name = mBroadcast.getProgramInfo();
- ThreadUtils.postOnMainThread(
- () -> {
- if (mPreference != null) {
- mPreference.setText(name);
- mPreference.setSummary(name);
- }
- });
- }
- });
- }
- }
-
- private void updateQrCodeIcon(boolean show) {
- if (mPreference != null) {
- ThreadUtils.postOnMainThread(
- () -> {
- if (mPreference != null) {
- mPreference.setShowQrCodeIcon(show);
- }
- });
- }
- }
-
- @Override
- public boolean isTextValid(String value) {
- return mAudioSharingNameTextValidator.isTextValid(value);
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNameTextValidator.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNameTextValidator.java
deleted file mode 100644
index 2022eb2..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNameTextValidator.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing;
-
-import com.android.settings.widget.ValidatedEditTextPreference;
-
-import java.nio.charset.StandardCharsets;
-
-/**
- * Validator for Audio Sharing Name, which should be a UTF-8 encoded string containing a minimum of
- * 4 characters and a maximum of 32 human-readable characters.
- */
-public class AudioSharingNameTextValidator implements ValidatedEditTextPreference.Validator {
- private static final int MIN_LENGTH = 4;
- private static final int MAX_LENGTH = 32;
-
- @Override
- public boolean isTextValid(String value) {
- if (value == null || value.length() < MIN_LENGTH || value.length() > MAX_LENGTH) {
- return false;
- }
- return isValidUTF8(value);
- }
-
- private static boolean isValidUTF8(String value) {
- byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
- String reconstructedString = new String(bytes, StandardCharsets.UTF_8);
- return value.equals(reconstructedString);
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPasswordPreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPasswordPreferenceController.java
deleted file mode 100644
index da0eb2e..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPasswordPreferenceController.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing;
-
-import android.bluetooth.BluetoothLeBroadcast;
-import android.bluetooth.BluetoothLeBroadcastMetadata;
-import android.content.Context;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.lifecycle.DefaultLifecycleObserver;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settings.bluetooth.Utils;
-import com.android.settings.core.BasePreferenceController;
-import com.android.settings.widget.ValidatedEditTextPreference;
-import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
-
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-
-public class AudioSharingPasswordPreferenceController extends BasePreferenceController
- implements ValidatedEditTextPreference.Validator,
- Preference.OnPreferenceChangeListener,
- DefaultLifecycleObserver {
- private static final String PREF_KEY = "audio_sharing_stream_password";
-
- private final BluetoothLeBroadcast.Callback mBroadcastCallback =
- new BluetoothLeBroadcast.Callback() {
- @Override
- public void onBroadcastMetadataChanged(
- int broadcastId, BluetoothLeBroadcastMetadata metadata) {}
-
- @Override
- public void onBroadcastStartFailed(int reason) {}
-
- @Override
- public void onBroadcastStarted(int reason, int broadcastId) {}
-
- @Override
- public void onBroadcastStopFailed(int reason) {}
-
- @Override
- public void onBroadcastStopped(int reason, int broadcastId) {}
-
- @Override
- public void onBroadcastUpdateFailed(int reason, int broadcastId) {}
-
- @Override
- public void onBroadcastUpdated(int reason, int broadcastId) {}
-
- @Override
- public void onPlaybackStarted(int reason, int broadcastId) {}
-
- @Override
- public void onPlaybackStopped(int reason, int broadcastId) {}
- };
- @Nullable private final LocalBluetoothLeBroadcast mBroadcast;
- private final Executor mExecutor;
- private final AudioSharingPasswordValidator mAudioSharingPasswordValidator;
- @Nullable private ValidatedEditTextPreference mPreference;
-
- public AudioSharingPasswordPreferenceController(Context context, String preferenceKey) {
- super(context, preferenceKey);
- mBroadcast =
- Utils.getLocalBtManager(context).getProfileManager().getLeAudioBroadcastProfile();
- mAudioSharingPasswordValidator = new AudioSharingPasswordValidator();
- mExecutor = Executors.newSingleThreadExecutor();
- }
-
- @Override
- public void onStart(@NonNull LifecycleOwner owner) {
- if (mBroadcast != null) {
- mBroadcast.registerServiceCallBack(mExecutor, mBroadcastCallback);
- }
- }
-
- @Override
- public void onStop(@NonNull LifecycleOwner owner) {
- if (mBroadcast != null) {
- mBroadcast.unregisterServiceCallBack(mBroadcastCallback);
- }
- }
-
- @Override
- public int getAvailabilityStatus() {
- return AudioSharingUtils.isFeatureEnabled() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
- }
-
- @Override
- public void displayPreference(PreferenceScreen screen) {
- super.displayPreference(screen);
- mPreference = screen.findPreference(getPreferenceKey());
- if (mPreference != null) {
- mPreference.setValidator(this);
- }
- }
-
- @Override
- public String getPreferenceKey() {
- return PREF_KEY;
- }
-
- @Override
- public boolean onPreferenceChange(Preference preference, Object newValue) {
- // TODO(chelseahao): implement
- return true;
- }
-
- @Override
- public boolean isTextValid(String value) {
- return mAudioSharingPasswordValidator.isTextValid(value);
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPasswordValidator.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPasswordValidator.java
deleted file mode 100644
index dbb40ec..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPasswordValidator.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing;
-
-import com.android.settings.widget.ValidatedEditTextPreference;
-
-import java.nio.charset.StandardCharsets;
-
-/**
- * Validator for Audio Sharing Password, which should be a UTF-8 string that has at least 4 octets
- * and should not exceed 16 octets.
- */
-public class AudioSharingPasswordValidator implements ValidatedEditTextPreference.Validator {
- private static final int MIN_OCTETS = 4;
- private static final int MAX_OCTETS = 16;
-
- @Override
- public boolean isTextValid(String value) {
- if (value == null
- || getOctetsCount(value) < MIN_OCTETS
- || getOctetsCount(value) > MAX_OCTETS) {
- return false;
- }
-
- return isValidUTF8(value);
- }
-
- private static int getOctetsCount(String value) {
- return value.getBytes(StandardCharsets.UTF_8).length;
- }
-
- private static boolean isValidUTF8(String value) {
- byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
- String reconstructedString = new String(bytes, StandardCharsets.UTF_8);
- return value.equals(reconstructedString);
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPlaySoundPreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPlaySoundPreferenceController.java
deleted file mode 100644
index 6722219..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPlaySoundPreferenceController.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.media.AudioAttributes;
-import android.media.AudioManager;
-import android.media.Ringtone;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settings.R;
-
-public class AudioSharingPlaySoundPreferenceController
- extends AudioSharingBasePreferenceController {
-
- private static final String TAG = "AudioSharingPlaySoundPreferenceController";
-
- private static final String PREF_KEY = "audio_sharing_play_sound";
-
- private final Ringtone mRingtone;
-
- public AudioSharingPlaySoundPreferenceController(Context context) {
- super(context, PREF_KEY);
- mRingtone = RingtoneManager.getRingtone(context, getMediaVolumeUri());
- if (mRingtone != null) {
- mRingtone.setStreamType(AudioManager.STREAM_MUSIC);
- }
- }
-
- @Override
- public void displayPreference(PreferenceScreen screen) {
- super.displayPreference(screen);
- mPreference.setVisible(mRingtone != null);
- mPreference.setOnPreferenceClickListener(
- (v) -> {
- if (mRingtone == null) {
- Log.d(TAG, "Skip onClick due to ringtone is null");
- return true;
- }
- try {
- mRingtone.setAudioAttributes(
- new AudioAttributes.Builder(mRingtone.getAudioAttributes())
- .setFlags(AudioAttributes.FLAG_BYPASS_MUTE)
- .addTag("VX_AOSP_SAMPLESOUND")
- .build());
- if (!mRingtone.isPlaying()) {
- mRingtone.play();
- }
- } catch (Throwable e) {
- Log.w(TAG, "Fail to play sample, error = " + e);
- }
- return true;
- });
- }
-
- @Override
- public void onStop(@NonNull LifecycleOwner owner) {
- super.onStop(owner);
- if (mRingtone != null && mRingtone.isPlaying()) {
- mRingtone.stop();
- }
- }
-
- @Override
- public String getPreferenceKey() {
- return PREF_KEY;
- }
-
- private Uri getMediaVolumeUri() {
- return Uri.parse(
- ContentResolver.SCHEME_ANDROID_RESOURCE
- + "://"
- + mContext.getPackageName()
- + "/"
- + R.raw.media_volume);
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPreferenceController.java
deleted file mode 100644
index 16c9888..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPreferenceController.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing;
-
-import android.bluetooth.BluetoothLeBroadcast;
-import android.bluetooth.BluetoothLeBroadcastMetadata;
-import android.content.Context;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.lifecycle.DefaultLifecycleObserver;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settings.bluetooth.Utils;
-import com.android.settings.core.BasePreferenceController;
-import com.android.settingslib.bluetooth.BluetoothCallback;
-import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
-
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-
-public class AudioSharingPreferenceController extends BasePreferenceController
- implements DefaultLifecycleObserver, BluetoothCallback {
- private static final String TAG = "AudioSharingPreferenceController";
-
- private final LocalBluetoothManager mLocalBtManager;
- private final Executor mExecutor;
- @Nullable private LocalBluetoothLeBroadcast mBroadcast = null;
- @Nullable private Preference mPreference;
-
- private final BluetoothLeBroadcast.Callback mBroadcastCallback =
- new BluetoothLeBroadcast.Callback() {
- @Override
- public void onBroadcastStarted(int reason, int broadcastId) {
- if (mPreference != null) {
- refreshSummary(mPreference);
- }
- }
-
- @Override
- public void onBroadcastStartFailed(int reason) {}
-
- @Override
- public void onBroadcastMetadataChanged(
- int broadcastId, @NonNull BluetoothLeBroadcastMetadata metadata) {}
-
- @Override
- public void onBroadcastStopped(int reason, int broadcastId) {
- if (mPreference != null) {
- refreshSummary(mPreference);
- }
- }
-
- @Override
- public void onBroadcastStopFailed(int reason) {}
-
- @Override
- public void onBroadcastUpdated(int reason, int broadcastId) {}
-
- @Override
- public void onBroadcastUpdateFailed(int reason, int broadcastId) {}
-
- @Override
- public void onPlaybackStarted(int reason, int broadcastId) {}
-
- @Override
- public void onPlaybackStopped(int reason, int broadcastId) {}
- };
-
- public AudioSharingPreferenceController(Context context, String preferenceKey) {
- super(context, preferenceKey);
- mLocalBtManager = Utils.getLocalBtManager(context);
- if (mLocalBtManager != null) {
- mBroadcast = mLocalBtManager.getProfileManager().getLeAudioBroadcastProfile();
- }
- mExecutor = Executors.newSingleThreadExecutor();
- }
-
- @Override
- public void onStart(@NonNull LifecycleOwner owner) {
- if (mLocalBtManager != null) {
- mLocalBtManager.getEventManager().registerCallback(this);
- }
- if (mBroadcast != null) {
- mBroadcast.registerServiceCallBack(mExecutor, mBroadcastCallback);
- }
- }
-
- @Override
- public void onStop(@NonNull LifecycleOwner owner) {
- if (mLocalBtManager != null) {
- mLocalBtManager.getEventManager().unregisterCallback(this);
- }
- if (mBroadcast != null) {
- mBroadcast.unregisterServiceCallBack(mBroadcastCallback);
- }
- }
-
- @Override
- public void displayPreference(@NonNull PreferenceScreen screen) {
- super.displayPreference(screen);
- mPreference = screen.findPreference(getPreferenceKey());
- }
-
- @Override
- public int getAvailabilityStatus() {
- return AudioSharingUtils.isFeatureEnabled() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
- }
-
- @Override
- public CharSequence getSummary() {
- return AudioSharingUtils.isBroadcasting(mLocalBtManager) ? "On" : "Off";
- }
-
- @Override
- public void onBluetoothStateChanged(@AdapterState int bluetoothState) {
- if (mPreference != null) {
- refreshSummary(mPreference);
- }
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingStopDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingStopDialogFragment.java
deleted file mode 100644
index 3ba41f7..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingStopDialogFragment.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing;
-
-import android.app.Dialog;
-import android.app.settings.SettingsEnums;
-import android.graphics.Typeface;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.appcompat.app.AlertDialog;
-import androidx.fragment.app.DialogFragment;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentManager;
-
-import com.android.settings.R;
-import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-
-public class AudioSharingStopDialogFragment extends InstrumentedDialogFragment {
- private static final String TAG = "AudioSharingStopDialog";
-
- private static final String BUNDLE_KEY_NEW_DEVICE_NAME = "bundle_key_new_device_name";
-
- // The host creates an instance of this dialog fragment must implement this interface to receive
- // event callbacks.
- public interface DialogEventListener {
- /** Called when users click the stop sharing button in the dialog. */
- void onStopSharingClick();
- }
-
- private static DialogEventListener sListener;
- private static @Nullable CachedBluetoothDevice sNewDevice;
-
- @Override
- public int getMetricsCategory() {
- return SettingsEnums.DIALOG_STOP_AUDIO_SHARING;
- }
-
- /**
- * Display the {@link AudioSharingStopDialogFragment} dialog.
- *
- * <p>If the dialog is showing, update the dialog message and event listener.
- *
- * @param host The Fragment this dialog will be hosted.
- * @param newDevice The latest connected device triggered this dialog.
- * @param listener The callback to handle the user action on this dialog.
- */
- public static void show(
- Fragment host, CachedBluetoothDevice newDevice, DialogEventListener listener) {
- if (!AudioSharingUtils.isFeatureEnabled()) return;
- final FragmentManager manager = host.getChildFragmentManager();
- sListener = listener;
- sNewDevice = newDevice;
- Fragment dialog = manager.findFragmentByTag(TAG);
- if (dialog != null
- && ((DialogFragment) dialog).getDialog() != null
- && ((DialogFragment) dialog).getDialog().isShowing()) {
- Log.d(TAG, "Dialog is showing, update the content.");
- updateDialog(newDevice.getName(), (AlertDialog) ((DialogFragment) dialog).getDialog());
- } else {
- Log.d(TAG, "Show up the dialog.");
- final Bundle bundle = new Bundle();
- bundle.putString(BUNDLE_KEY_NEW_DEVICE_NAME, newDevice.getName());
- AudioSharingStopDialogFragment dialogFrag = new AudioSharingStopDialogFragment();
- dialogFrag.setArguments(bundle);
- dialogFrag.show(manager, TAG);
- }
- }
-
- /** Return the tag of {@link AudioSharingStopDialogFragment} dialog. */
- public static @NonNull String tag() {
- return TAG;
- }
-
- /** Get the latest connected device which triggers the dialog. */
- public @Nullable CachedBluetoothDevice getDevice() {
- return sNewDevice;
- }
-
- @Override
- public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
- Bundle arguments = requireArguments();
- String newDeviceName = arguments.getString(BUNDLE_KEY_NEW_DEVICE_NAME);
- final AlertDialog.Builder builder =
- new AlertDialog.Builder(getActivity()).setCancelable(false);
- LayoutInflater inflater = LayoutInflater.from(builder.getContext());
- // Set custom title for the dialog.
- View customTitle =
- inflater.inflate(R.layout.dialog_custom_title_audio_sharing, /* parent= */ null);
- ImageView icon = customTitle.findViewById(R.id.title_icon);
- icon.setImageResource(R.drawable.ic_warning_24dp);
- TextView title = customTitle.findViewById(R.id.title_text);
- title.setText("Stop sharing audio?");
- builder.setPositiveButton(
- "Stop sharing", (dialog, which) -> sListener.onStopSharingClick());
- builder.setNegativeButton("Cancel", (dialog, which) -> dismiss());
- AlertDialog dialog = builder.setCustomTitle(customTitle).create();
- dialog.setCanceledOnTouchOutside(false);
- updateDialog(newDeviceName, dialog);
- dialog.show();
- TextView messageView = (TextView) dialog.findViewById(android.R.id.message);
- if (messageView != null) {
- Typeface typeface = Typeface.create(Typeface.DEFAULT_FAMILY, Typeface.NORMAL);
- messageView.setTypeface(typeface);
- messageView.setTextDirection(View.TEXT_DIRECTION_LOCALE);
- messageView.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
- } else {
- Log.w(TAG, "sssFail to update dialog: message view is null");
- }
- return dialog;
- }
-
- private static void updateDialog(String newDeviceName, @NonNull AlertDialog dialog) {
- dialog.setMessage(
- newDeviceName + " wants to connect, headphones in audio sharing will disconnect.");
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
deleted file mode 100644
index b82c94d..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
+++ /dev/null
@@ -1,395 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothLeBroadcast;
-import android.bluetooth.BluetoothLeBroadcastAssistant;
-import android.bluetooth.BluetoothLeBroadcastMetadata;
-import android.bluetooth.BluetoothLeBroadcastReceiveState;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.util.Log;
-import android.widget.CompoundButton;
-import android.widget.CompoundButton.OnCheckedChangeListener;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
-import androidx.lifecycle.DefaultLifecycleObserver;
-import androidx.lifecycle.LifecycleOwner;
-
-import com.android.settings.bluetooth.Utils;
-import com.android.settings.core.BasePreferenceController;
-import com.android.settings.dashboard.DashboardFragment;
-import com.android.settings.widget.SettingsMainSwitchBar;
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
-import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
-import com.android.settingslib.utils.ThreadUtils;
-
-import com.google.common.collect.ImmutableList;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.stream.Collectors;
-
-public class AudioSharingSwitchBarController extends BasePreferenceController
- implements DefaultLifecycleObserver, OnCheckedChangeListener {
- private static final String TAG = "AudioSharingSwitchBarCtl";
- private static final String PREF_KEY = "audio_sharing_main_switch";
-
- interface OnSwitchBarChangedListener {
- void onSwitchBarChanged();
- }
-
- private final SettingsMainSwitchBar mSwitchBar;
- private final BluetoothAdapter mBluetoothAdapter;
- private final LocalBluetoothManager mBtManager;
- private final LocalBluetoothLeBroadcast mBroadcast;
- private final LocalBluetoothLeBroadcastAssistant mAssistant;
- private final Executor mExecutor;
- private final OnSwitchBarChangedListener mListener;
- private DashboardFragment mFragment;
- private Map<Integer, List<CachedBluetoothDevice>> mGroupedConnectedDevices = new HashMap<>();
- private List<BluetoothDevice> mTargetActiveSinks = new ArrayList<>();
- private ArrayList<AudioSharingDeviceItem> mDeviceItemsForSharing = new ArrayList<>();
- @VisibleForTesting IntentFilter mIntentFilter;
-
- @VisibleForTesting
- BroadcastReceiver mReceiver =
- new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (!BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) return;
- int adapterState =
- intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothDevice.ERROR);
- mSwitchBar.setChecked(isBroadcasting());
- mSwitchBar.setEnabled(adapterState == BluetoothAdapter.STATE_ON);
- mListener.onSwitchBarChanged();
- }
- };
-
- private final BluetoothLeBroadcast.Callback mBroadcastCallback =
- new BluetoothLeBroadcast.Callback() {
- @Override
- public void onBroadcastStarted(int reason, int broadcastId) {
- Log.d(
- TAG,
- "onBroadcastStarted(), reason = "
- + reason
- + ", broadcastId = "
- + broadcastId);
- updateSwitch();
- }
-
- @Override
- public void onBroadcastStartFailed(int reason) {
- Log.d(TAG, "onBroadcastStartFailed(), reason = " + reason);
- // TODO: handle broadcast start fail
- updateSwitch();
- }
-
- @Override
- public void onBroadcastMetadataChanged(
- int broadcastId, @NonNull BluetoothLeBroadcastMetadata metadata) {
- Log.d(
- TAG,
- "onBroadcastMetadataChanged(), broadcastId = "
- + broadcastId
- + ", metadata = "
- + metadata.getBroadcastName());
- addSourceToTargetSinks(mTargetActiveSinks);
- if (mFragment == null) {
- Log.w(TAG, "Dialog fail to show due to null fragment.");
- return;
- }
- ThreadUtils.postOnMainThread(
- () -> {
- AudioSharingDialogFragment.show(
- mFragment,
- mDeviceItemsForSharing,
- item -> {
- addSourceToTargetSinks(
- mGroupedConnectedDevices
- .getOrDefault(
- item.getGroupId(),
- ImmutableList.of())
- .stream()
- .map(CachedBluetoothDevice::getDevice)
- .collect(Collectors.toList()));
- });
- });
- }
-
- @Override
- public void onBroadcastStopped(int reason, int broadcastId) {
- Log.d(
- TAG,
- "onBroadcastStopped(), reason = "
- + reason
- + ", broadcastId = "
- + broadcastId);
- updateSwitch();
- }
-
- @Override
- public void onBroadcastStopFailed(int reason) {
- Log.d(TAG, "onBroadcastStopFailed(), reason = " + reason);
- // TODO: handle broadcast stop fail
- updateSwitch();
- }
-
- @Override
- public void onBroadcastUpdated(int reason, int broadcastId) {}
-
- @Override
- public void onBroadcastUpdateFailed(int reason, int broadcastId) {}
-
- @Override
- public void onPlaybackStarted(int reason, int broadcastId) {}
-
- @Override
- public void onPlaybackStopped(int reason, int broadcastId) {}
- };
-
- private BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
- new BluetoothLeBroadcastAssistant.Callback() {
- @Override
- public void onSearchStarted(int reason) {}
-
- @Override
- public void onSearchStartFailed(int reason) {}
-
- @Override
- public void onSearchStopped(int reason) {}
-
- @Override
- public void onSearchStopFailed(int reason) {}
-
- @Override
- public void onSourceFound(@NonNull BluetoothLeBroadcastMetadata source) {}
-
- @Override
- public void onSourceAdded(@NonNull BluetoothDevice sink, int sourceId, int reason) {
- Log.d(
- TAG,
- "onSourceAdded(), sink = "
- + sink
- + ", sourceId = "
- + sourceId
- + ", reason = "
- + reason);
- }
-
- @Override
- public void onSourceAddFailed(
- @NonNull BluetoothDevice sink,
- @NonNull BluetoothLeBroadcastMetadata source,
- int reason) {
- Log.d(
- TAG,
- "onSourceAddFailed(), sink = "
- + sink
- + ", source = "
- + source
- + ", reason = "
- + reason);
- AudioSharingUtils.toastMessage(
- mContext,
- String.format(
- Locale.US,
- "Fail to add source to %s reason %d",
- sink.getAddress(),
- reason));
- }
-
- @Override
- public void onSourceModified(
- @NonNull BluetoothDevice sink, int sourceId, int reason) {}
-
- @Override
- public void onSourceModifyFailed(
- @NonNull BluetoothDevice sink, int sourceId, int reason) {}
-
- @Override
- public void onSourceRemoved(
- @NonNull BluetoothDevice sink, int sourceId, int reason) {}
-
- @Override
- public void onSourceRemoveFailed(
- @NonNull BluetoothDevice sink, int sourceId, int reason) {}
-
- @Override
- public void onReceiveStateChanged(
- BluetoothDevice sink,
- int sourceId,
- BluetoothLeBroadcastReceiveState state) {}
- };
-
- AudioSharingSwitchBarController(
- Context context, SettingsMainSwitchBar switchBar, OnSwitchBarChangedListener listener) {
- super(context, PREF_KEY);
- mSwitchBar = switchBar;
- mListener = listener;
- mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
- mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
- mBtManager = Utils.getLocalBtManager(context);
- mBroadcast = mBtManager.getProfileManager().getLeAudioBroadcastProfile();
- mAssistant = mBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
- mExecutor = Executors.newSingleThreadExecutor();
- }
-
- @Override
- public void onStart(@NonNull LifecycleOwner owner) {
- mSwitchBar.addOnSwitchChangeListener(this);
- mContext.registerReceiver(mReceiver, mIntentFilter, Context.RECEIVER_EXPORTED_UNAUDITED);
- if (mBroadcast != null) {
- mBroadcast.registerServiceCallBack(mExecutor, mBroadcastCallback);
- }
- if (mAssistant != null) {
- mAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback);
- }
- if (isAvailable()) {
- mSwitchBar.setChecked(isBroadcasting());
- mSwitchBar.setEnabled(mBluetoothAdapter != null && mBluetoothAdapter.isEnabled());
- }
- }
-
- @Override
- public void onStop(@NonNull LifecycleOwner owner) {
- mSwitchBar.removeOnSwitchChangeListener(this);
- mContext.unregisterReceiver(mReceiver);
- if (mBroadcast != null) {
- mBroadcast.unregisterServiceCallBack(mBroadcastCallback);
- }
- if (mAssistant != null) {
- mAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback);
- }
- }
-
- @Override
- public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- // Filter out unnecessary callbacks when switch is disabled.
- if (!buttonView.isEnabled()) return;
- if (isChecked) {
- startAudioSharing();
- } else {
- stopAudioSharing();
- }
- }
-
- @Override
- public int getAvailabilityStatus() {
- return AudioSharingUtils.isFeatureEnabled() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
- }
-
- /**
- * Initialize the controller.
- *
- * @param fragment The fragment to host the {@link AudioSharingSwitchBarController} dialog.
- */
- public void init(DashboardFragment fragment) {
- this.mFragment = fragment;
- }
-
- private void startAudioSharing() {
- mSwitchBar.setEnabled(false);
- if (mBroadcast == null || isBroadcasting()) {
- Log.d(TAG, "Already in broadcasting or broadcast not support, ignore!");
- mSwitchBar.setEnabled(true);
- return;
- }
- mGroupedConnectedDevices = AudioSharingUtils.fetchConnectedDevicesByGroupId(mBtManager);
- ArrayList<AudioSharingDeviceItem> deviceItems =
- AudioSharingUtils.buildOrderedConnectedLeadAudioSharingDeviceItem(
- mBtManager, mGroupedConnectedDevices, /* filterByInSharing= */ false);
- // deviceItems is ordered. The active device is the first place if exits.
- mDeviceItemsForSharing = new ArrayList<>(deviceItems);
- if (!deviceItems.isEmpty() && deviceItems.get(0).isActive()) {
- for (CachedBluetoothDevice device :
- mGroupedConnectedDevices.getOrDefault(
- deviceItems.get(0).getGroupId(), ImmutableList.of())) {
- // If active device exists for audio sharing, share to it
- // automatically once the broadcast is started.
- mTargetActiveSinks.add(device.getDevice());
- }
- mDeviceItemsForSharing.remove(0);
- }
- mBroadcast.startPrivateBroadcast();
- }
-
- private void stopAudioSharing() {
- mSwitchBar.setEnabled(false);
- if (mBroadcast == null || !isBroadcasting()) {
- Log.d(TAG, "Already not broadcasting or broadcast not support, ignore!");
- mSwitchBar.setEnabled(true);
- return;
- }
- mBroadcast.stopBroadcast(mBroadcast.getLatestBroadcastId());
- }
-
- private void updateSwitch() {
- var unused =
- ThreadUtils.postOnBackgroundThread(
- () -> {
- boolean isBroadcasting = isBroadcasting();
- ThreadUtils.postOnMainThread(
- () -> {
- if (mSwitchBar.isChecked() != isBroadcasting) {
- mSwitchBar.setChecked(isBroadcasting);
- }
- mSwitchBar.setEnabled(true);
- mListener.onSwitchBarChanged();
- });
- });
- }
-
- private boolean isBroadcasting() {
- return mBroadcast != null && mBroadcast.isEnabled(null);
- }
-
- private void addSourceToTargetSinks(List<BluetoothDevice> sinks) {
- if (sinks.isEmpty() || mBroadcast == null || mAssistant == null) {
- Log.d(TAG, "Skip adding source to target.");
- return;
- }
- BluetoothLeBroadcastMetadata broadcastMetadata =
- mBroadcast.getLatestBluetoothLeBroadcastMetadata();
- if (broadcastMetadata == null) {
- Log.e(TAG, "Error: There is no broadcastMetadata.");
- return;
- }
- for (BluetoothDevice sink : sinks) {
- Log.d(
- TAG,
- "Add broadcast with broadcastId: "
- + broadcastMetadata.getBroadcastId()
- + "to the device: "
- + sink.getAnonymizedAddress());
- mAssistant.addSource(sink, broadcastMetadata, /* isGroupOp= */ false);
- }
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java
deleted file mode 100644
index f489e9c..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java
+++ /dev/null
@@ -1,408 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothCsipSetCoordinator;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothLeBroadcastReceiveState;
-import android.bluetooth.BluetoothStatusCodes;
-import android.content.Context;
-import android.provider.Settings;
-import android.util.Log;
-import android.widget.Toast;
-
-import androidx.annotation.NonNull;
-
-import com.android.settings.flags.Flags;
-import com.android.settingslib.bluetooth.BluetoothUtils;
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
-import com.android.settingslib.bluetooth.LeAudioProfile;
-import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
-import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
-import com.android.settingslib.bluetooth.LocalBluetoothProfile;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.stream.Collectors;
-
-import javax.annotation.Nullable;
-
-public class AudioSharingUtils {
- private static final String TAG = "AudioSharingUtils";
- private static final boolean DEBUG = BluetoothUtils.D;
-
- /**
- * Fetch {@link CachedBluetoothDevice}s connected to the broadcast assistant. The devices are
- * grouped by CSIP group id.
- *
- * @param localBtManager The BT manager to provide BT functions.
- * @return A map of connected devices grouped by CSIP group id.
- */
- public static Map<Integer, List<CachedBluetoothDevice>> fetchConnectedDevicesByGroupId(
- LocalBluetoothManager localBtManager) {
- Map<Integer, List<CachedBluetoothDevice>> groupedDevices = new HashMap<>();
- if (localBtManager == null) {
- Log.d(TAG, "Skip fetchConnectedDevicesByGroupId due to bt manager is null");
- return groupedDevices;
- }
- LocalBluetoothLeBroadcastAssistant assistant =
- localBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
- if (assistant == null) {
- Log.d(TAG, "Skip fetchConnectedDevicesByGroupId due to assistant profile is null");
- return groupedDevices;
- }
- List<BluetoothDevice> connectedDevices = assistant.getConnectedDevices();
- CachedBluetoothDeviceManager cacheManager = localBtManager.getCachedDeviceManager();
- for (BluetoothDevice device : connectedDevices) {
- CachedBluetoothDevice cachedDevice = cacheManager.findDevice(device);
- if (cachedDevice == null) {
- Log.d(TAG, "Skip device due to not being cached: " + device.getAnonymizedAddress());
- continue;
- }
- int groupId = getGroupId(cachedDevice);
- if (groupId == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
- Log.d(
- TAG,
- "Skip device due to no valid group id: " + device.getAnonymizedAddress());
- continue;
- }
- if (!groupedDevices.containsKey(groupId)) {
- groupedDevices.put(groupId, new ArrayList<>());
- }
- groupedDevices.get(groupId).add(cachedDevice);
- }
- if (DEBUG) {
- Log.d(TAG, "fetchConnectedDevicesByGroupId: " + groupedDevices);
- }
- return groupedDevices;
- }
-
- /**
- * Fetch a list of ordered connected lead {@link CachedBluetoothDevice}s eligible for audio
- * sharing. The active device is placed in the first place if it exists. The devices can be
- * filtered by whether it is already in the audio sharing session.
- *
- * @param localBtManager The BT manager to provide BT functions. *
- * @param groupedConnectedDevices devices connected to broadcast assistant grouped by CSIP group
- * id.
- * @param filterByInSharing Whether to filter the device by if is already in the sharing
- * session.
- * @return A list of ordered connected devices eligible for the audio sharing. The active device
- * is placed in the first place if it exists.
- */
- public static List<CachedBluetoothDevice> buildOrderedConnectedLeadDevices(
- LocalBluetoothManager localBtManager,
- Map<Integer, List<CachedBluetoothDevice>> groupedConnectedDevices,
- boolean filterByInSharing) {
- List<CachedBluetoothDevice> orderedDevices = new ArrayList<>();
- for (List<CachedBluetoothDevice> devices : groupedConnectedDevices.values()) {
- @Nullable CachedBluetoothDevice leadDevice = getLeadDevice(devices);
- if (leadDevice == null) {
- Log.d(TAG, "Skip due to no lead device");
- continue;
- }
- if (filterByInSharing && !hasBroadcastSource(leadDevice, localBtManager)) {
- Log.d(
- TAG,
- "Filtered the device due to not in sharing session: "
- + leadDevice.getDevice().getAnonymizedAddress());
- continue;
- }
- orderedDevices.add(leadDevice);
- }
- orderedDevices.sort(
- (CachedBluetoothDevice d1, CachedBluetoothDevice d2) -> {
- // Active above not inactive
- int comparison =
- (isActiveLeAudioDevice(d2) ? 1 : 0)
- - (isActiveLeAudioDevice(d1) ? 1 : 0);
- if (comparison != 0) return comparison;
- // Bonded above not bonded
- comparison =
- (d2.getBondState() == BluetoothDevice.BOND_BONDED ? 1 : 0)
- - (d1.getBondState() == BluetoothDevice.BOND_BONDED ? 1 : 0);
- if (comparison != 0) return comparison;
- // Bond timestamp available above unavailable
- comparison =
- (d2.getBondTimestamp() != null ? 1 : 0)
- - (d1.getBondTimestamp() != null ? 1 : 0);
- if (comparison != 0) return comparison;
- // Order by bond timestamp if it is available
- // Otherwise order by device name
- return d1.getBondTimestamp() != null
- ? d1.getBondTimestamp().compareTo(d2.getBondTimestamp())
- : d1.getName().compareTo(d2.getName());
- });
- return orderedDevices;
- }
-
- /**
- * Get the lead device from a list of devices with same group id.
- *
- * @param devices A list of devices with same group id.
- * @return The lead device
- */
- @Nullable
- public static CachedBluetoothDevice getLeadDevice(
- @NonNull List<CachedBluetoothDevice> devices) {
- if (devices.isEmpty()) return null;
- for (CachedBluetoothDevice device : devices) {
- if (!device.getMemberDevice().isEmpty()) {
- return device;
- }
- }
- CachedBluetoothDevice leadDevice = devices.get(0);
- Log.d(
- TAG,
- "No lead device in the group, pick arbitrary device as the lead: "
- + leadDevice.getDevice().getAnonymizedAddress());
- return leadDevice;
- }
-
- /**
- * Fetch a list of ordered connected lead {@link AudioSharingDeviceItem}s eligible for audio
- * sharing. The active device is placed in the first place if it exists. The devices can be
- * filtered by whether it is already in the audio sharing session.
- *
- * @param localBtManager The BT manager to provide BT functions. *
- * @param groupedConnectedDevices devices connected to broadcast assistant grouped by CSIP group
- * id.
- * @param filterByInSharing Whether to filter the device by if is already in the sharing
- * session.
- * @return A list of ordered connected devices eligible for the audio sharing. The active device
- * is placed in the first place if it exists.
- */
- public static ArrayList<AudioSharingDeviceItem> buildOrderedConnectedLeadAudioSharingDeviceItem(
- LocalBluetoothManager localBtManager,
- Map<Integer, List<CachedBluetoothDevice>> groupedConnectedDevices,
- boolean filterByInSharing) {
- return buildOrderedConnectedLeadDevices(
- localBtManager, groupedConnectedDevices, filterByInSharing)
- .stream()
- .map(device -> buildAudioSharingDeviceItem(device))
- .collect(Collectors.toCollection(ArrayList::new));
- }
-
- /** Build {@link AudioSharingDeviceItem} from {@link CachedBluetoothDevice}. */
- public static AudioSharingDeviceItem buildAudioSharingDeviceItem(
- CachedBluetoothDevice cachedDevice) {
- return new AudioSharingDeviceItem(
- cachedDevice.getName(),
- getGroupId(cachedDevice),
- isActiveLeAudioDevice(cachedDevice));
- }
-
- /**
- * Check if {@link CachedBluetoothDevice} is in an audio sharing session.
- *
- * @param cachedDevice The cached bluetooth device to check.
- * @param localBtManager The BT manager to provide BT functions.
- * @return Whether the device is in an audio sharing session.
- */
- public static boolean hasBroadcastSource(
- CachedBluetoothDevice cachedDevice, LocalBluetoothManager localBtManager) {
- if (localBtManager == null) {
- Log.d(TAG, "Skip check hasBroadcastSource due to bt manager is null");
- return false;
- }
- LocalBluetoothLeBroadcastAssistant assistant =
- localBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
- if (assistant == null) {
- Log.d(TAG, "Skip check hasBroadcastSource due to assistant profile is null");
- return false;
- }
- List<BluetoothLeBroadcastReceiveState> sourceList =
- assistant.getAllSources(cachedDevice.getDevice());
- if (!sourceList.isEmpty()) {
- Log.d(
- TAG,
- "Lead device has broadcast source, device = "
- + cachedDevice.getDevice().getAnonymizedAddress());
- return true;
- }
- // Return true if member device is in broadcast.
- for (CachedBluetoothDevice device : cachedDevice.getMemberDevice()) {
- List<BluetoothLeBroadcastReceiveState> list =
- assistant.getAllSources(device.getDevice());
- if (!list.isEmpty()) {
- Log.d(
- TAG,
- "Member device has broadcast source, device = "
- + device.getDevice().getAnonymizedAddress());
- return true;
- }
- }
- return false;
- }
-
- /**
- * Check if {@link CachedBluetoothDevice} is an active le audio device.
- *
- * @param cachedDevice The cached bluetooth device to check.
- * @return Whether the device is an active le audio device.
- */
- public static boolean isActiveLeAudioDevice(CachedBluetoothDevice cachedDevice) {
- return BluetoothUtils.isActiveLeAudioDevice(cachedDevice);
- }
-
- /**
- * Retrieves the one and only active Bluetooth LE Audio sink device, regardless if the device is
- * currently in an audio sharing session.
- *
- * @param manager The LocalBluetoothManager instance used to fetch connected devices.
- * @return An Optional containing the active LE Audio device, or an empty Optional if not found.
- */
- public static Optional<CachedBluetoothDevice> getActiveSinkOnAssistant(
- @Nullable LocalBluetoothManager manager) {
- if (manager == null) {
- Log.w(TAG, "getActiveSinksOnAssistant(): LocalBluetoothManager is null!");
- return Optional.empty();
- }
- var groupedDevices = fetchConnectedDevicesByGroupId(manager);
- var leadDevices = buildOrderedConnectedLeadDevices(manager, groupedDevices, false);
-
- if (!leadDevices.isEmpty() && isActiveLeAudioDevice(leadDevices.get(0))) {
- return Optional.of(leadDevices.get(0));
- } else {
- Log.w(TAG, "getActiveSinksOnAssistant(): No active lead device!");
- }
- return Optional.empty();
- }
-
- /** Toast message on main thread. */
- public static void toastMessage(Context context, String message) {
- context.getMainExecutor()
- .execute(() -> Toast.makeText(context, message, Toast.LENGTH_LONG).show());
- }
-
- /** Returns if the le audio sharing is enabled. */
- public static boolean isFeatureEnabled() {
- BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
- return Flags.enableLeAudioSharing()
- && adapter.isLeAudioBroadcastSourceSupported()
- == BluetoothStatusCodes.FEATURE_SUPPORTED
- && adapter.isLeAudioBroadcastAssistantSupported()
- == BluetoothStatusCodes.FEATURE_SUPPORTED;
- }
-
- /** Automatically update active device if needed. */
- public static void updateActiveDeviceIfNeeded(LocalBluetoothManager localBtManager) {
- if (localBtManager == null) {
- Log.d(TAG, "Skip updateActiveDeviceIfNeeded due to bt manager is null");
- return;
- }
- Map<Integer, List<CachedBluetoothDevice>> groupedConnectedDevices =
- fetchConnectedDevicesByGroupId(localBtManager);
- List<CachedBluetoothDevice> devicesInSharing =
- buildOrderedConnectedLeadDevices(
- localBtManager, groupedConnectedDevices, /* filterByInSharing= */ true);
- if (devicesInSharing.isEmpty()) return;
- List<BluetoothDevice> devices =
- BluetoothAdapter.getDefaultAdapter().getMostRecentlyConnectedDevices();
- CachedBluetoothDevice targetDevice = null;
- // Find the earliest connected device in sharing session.
- int targetDeviceIdx = -1;
- for (CachedBluetoothDevice device : devicesInSharing) {
- if (devices.contains(device.getDevice())) {
- int idx = devices.indexOf(device.getDevice());
- if (idx > targetDeviceIdx) {
- targetDeviceIdx = idx;
- targetDevice = device;
- }
- }
- }
- if (targetDevice != null && !isActiveLeAudioDevice(targetDevice)) {
- Log.d(
- TAG,
- "updateActiveDeviceIfNeeded, set active device: "
- + targetDevice.getDevice().getAnonymizedAddress());
- targetDevice.setActive();
- } else {
- Log.d(
- TAG,
- "updateActiveDeviceIfNeeded, skip set active device: "
- + (targetDevice == null
- ? "null"
- : (targetDevice.getDevice().getAnonymizedAddress()
- + " is already active")));
- }
- }
-
- /** Returns if the broadcast is on-going. */
- public static boolean isBroadcasting(@Nullable LocalBluetoothManager manager) {
- if (manager == null) return false;
- LocalBluetoothLeBroadcast broadcast =
- manager.getProfileManager().getLeAudioBroadcastProfile();
- return broadcast != null && broadcast.isEnabled(null);
- }
-
- /** Stops the latest broadcast. */
- public static void stopBroadcasting(LocalBluetoothManager manager) {
- if (manager == null) {
- Log.d(TAG, "Skip stop broadcasting due to bt manager is null");
- return;
- }
- LocalBluetoothLeBroadcast broadcast =
- manager.getProfileManager().getLeAudioBroadcastProfile();
- if (broadcast == null) {
- Log.d(TAG, "Skip stop broadcasting due to broadcast profile is null");
- }
- broadcast.stopBroadcast(broadcast.getLatestBroadcastId());
- }
-
- /**
- * Get CSIP group id for {@link CachedBluetoothDevice}.
- *
- * <p>If CachedBluetoothDevice#getGroupId is invalid, fetch group id from
- * LeAudioProfile#getGroupId.
- */
- public static int getGroupId(CachedBluetoothDevice cachedDevice) {
- int groupId = cachedDevice.getGroupId();
- String anonymizedAddress = cachedDevice.getDevice().getAnonymizedAddress();
- if (groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
- Log.d(TAG, "getGroupId by CSIP profile for device: " + anonymizedAddress);
- return groupId;
- }
- for (LocalBluetoothProfile profile : cachedDevice.getProfiles()) {
- if (profile instanceof LeAudioProfile) {
- Log.d(TAG, "getGroupId by LEA profile for device: " + anonymizedAddress);
- return ((LeAudioProfile) profile).getGroupId(cachedDevice.getDevice());
- }
- }
- Log.d(TAG, "getGroupId return invalid id for device: " + anonymizedAddress);
- return BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
- }
-
- /** Get the fallback active group id from SettingsProvider. */
- public static int getFallbackActiveGroupId(@NonNull Context context) {
- return Settings.Secure.getInt(
- context.getContentResolver(),
- "bluetooth_le_broadcast_fallback_active_group_id",
- BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
- }
-
- /** Post the runnable to main thread. */
- public static void postOnMainThread(@NonNull Context context, @NonNull Runnable runnable) {
- context.getMainExecutor().execute(runnable);
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsDialogFragment.java
deleted file mode 100644
index 9d346d3..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsDialogFragment.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing;
-
-import android.app.Dialog;
-import android.app.settings.SettingsEnums;
-import android.os.Bundle;
-
-import androidx.appcompat.app.AlertDialog;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentManager;
-
-import com.android.settings.R;
-import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
-
-import java.util.ArrayList;
-
-/** Provides a dialog to choose the active device for calls and alarms. */
-public class CallsAndAlarmsDialogFragment extends InstrumentedDialogFragment {
- private static final String TAG = "CallsAndAlarmsDialog";
- private static final String BUNDLE_KEY_DEVICE_ITEMS = "bundle_key_device_items";
-
- // The host creates an instance of this dialog fragment must implement this interface to receive
- // event callbacks.
- public interface DialogEventListener {
- /**
- * Called when users click the device item to set active for calls and alarms in the dialog.
- *
- * @param item The device item clicked.
- */
- void onItemClick(AudioSharingDeviceItem item);
- }
-
- private static DialogEventListener sListener;
-
- @Override
- public int getMetricsCategory() {
- return SettingsEnums.DIALOG_AUDIO_SHARING_SWITCH_ACTIVE;
- }
-
- /**
- * Display the {@link CallsAndAlarmsDialogFragment} dialog.
- *
- * @param host The Fragment this dialog will be hosted.
- * @param deviceItems The connected device items in audio sharing session.
- * @param listener The callback to handle the user action on this dialog.
- */
- public static void show(
- Fragment host,
- ArrayList<AudioSharingDeviceItem> deviceItems,
- DialogEventListener listener) {
- if (!AudioSharingUtils.isFeatureEnabled()) return;
- final FragmentManager manager = host.getChildFragmentManager();
- sListener = listener;
- if (manager.findFragmentByTag(TAG) == null) {
- final Bundle bundle = new Bundle();
- bundle.putParcelableArrayList(BUNDLE_KEY_DEVICE_ITEMS, deviceItems);
- final CallsAndAlarmsDialogFragment dialog = new CallsAndAlarmsDialogFragment();
- dialog.setArguments(bundle);
- dialog.show(manager, TAG);
- }
- }
-
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- Bundle arguments = requireArguments();
- ArrayList<AudioSharingDeviceItem> deviceItems =
- arguments.getParcelableArrayList(BUNDLE_KEY_DEVICE_ITEMS);
- int checkedItem = -1;
- for (AudioSharingDeviceItem item : deviceItems) {
- int fallbackActiveGroupId = AudioSharingUtils.getFallbackActiveGroupId(getContext());
- if (item.getGroupId() == fallbackActiveGroupId) {
- checkedItem = deviceItems.indexOf(item);
- }
- }
- String[] choices =
- deviceItems.stream().map(AudioSharingDeviceItem::getName).toArray(String[]::new);
- AlertDialog.Builder builder =
- new AlertDialog.Builder(getActivity())
- .setTitle(R.string.calls_and_alarms_device_title)
- .setSingleChoiceItems(
- choices,
- checkedItem,
- (dialog, which) -> {
- sListener.onItemClick(deviceItems.get(which));
- });
- return builder.create();
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsPreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsPreferenceController.java
deleted file mode 100644
index 2a538d5..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsPreferenceController.java
+++ /dev/null
@@ -1,261 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothCsipSetCoordinator;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothLeBroadcastAssistant;
-import android.bluetooth.BluetoothLeBroadcastMetadata;
-import android.bluetooth.BluetoothLeBroadcastReceiveState;
-import android.bluetooth.BluetoothProfile;
-import android.content.Context;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settings.bluetooth.Utils;
-import com.android.settings.dashboard.DashboardFragment;
-import com.android.settingslib.bluetooth.BluetoothCallback;
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
-import com.android.settingslib.utils.ThreadUtils;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-
-/** PreferenceController to control the dialog to choose the active device for calls and alarms */
-public class CallsAndAlarmsPreferenceController extends AudioSharingBasePreferenceController
- implements BluetoothCallback {
-
- private static final String TAG = "CallsAndAlarmsPreferenceController";
- private static final String PREF_KEY = "calls_and_alarms";
-
- private final LocalBluetoothManager mLocalBtManager;
- private final Executor mExecutor;
- @Nullable private LocalBluetoothLeBroadcastAssistant mAssistant = null;
- private DashboardFragment mFragment;
- Map<Integer, List<CachedBluetoothDevice>> mGroupedConnectedDevices = new HashMap<>();
- private ArrayList<AudioSharingDeviceItem> mDeviceItemsInSharingSession = new ArrayList<>();
-
- private BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
- new BluetoothLeBroadcastAssistant.Callback() {
- @Override
- public void onSearchStarted(int reason) {}
-
- @Override
- public void onSearchStartFailed(int reason) {}
-
- @Override
- public void onSearchStopped(int reason) {}
-
- @Override
- public void onSearchStopFailed(int reason) {}
-
- @Override
- public void onSourceFound(@NonNull BluetoothLeBroadcastMetadata source) {}
-
- @Override
- public void onSourceAdded(@NonNull BluetoothDevice sink, int sourceId, int reason) {
- Log.d(TAG, "onSourceAdded");
- updatePreference();
- }
-
- @Override
- public void onSourceAddFailed(
- @NonNull BluetoothDevice sink,
- @NonNull BluetoothLeBroadcastMetadata source,
- int reason) {}
-
- @Override
- public void onSourceModified(
- @NonNull BluetoothDevice sink, int sourceId, int reason) {}
-
- @Override
- public void onSourceModifyFailed(
- @NonNull BluetoothDevice sink, int sourceId, int reason) {}
-
- @Override
- public void onSourceRemoved(
- @NonNull BluetoothDevice sink, int sourceId, int reason) {
- Log.d(TAG, "onSourceRemoved");
- updatePreference();
- }
-
- @Override
- public void onSourceRemoveFailed(
- @NonNull BluetoothDevice sink, int sourceId, int reason) {}
-
- @Override
- public void onReceiveStateChanged(
- BluetoothDevice sink,
- int sourceId,
- BluetoothLeBroadcastReceiveState state) {}
- };
-
- public CallsAndAlarmsPreferenceController(Context context) {
- super(context, PREF_KEY);
- mLocalBtManager = Utils.getLocalBtManager(mContext);
- if (mLocalBtManager != null) {
- mAssistant = mLocalBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
- }
- mExecutor = Executors.newSingleThreadExecutor();
- }
-
- @Override
- public String getPreferenceKey() {
- return PREF_KEY;
- }
-
- @Override
- public void displayPreference(@NonNull PreferenceScreen screen) {
- super.displayPreference(screen);
- mPreference.setOnPreferenceClickListener(
- preference -> {
- if (mFragment == null) {
- Log.w(TAG, "Dialog fail to show due to null host.");
- return true;
- }
- updateDeviceItemsInSharingSession();
- if (mDeviceItemsInSharingSession.size() >= 1) {
- CallsAndAlarmsDialogFragment.show(
- mFragment,
- mDeviceItemsInSharingSession,
- (AudioSharingDeviceItem item) -> {
- if (!mGroupedConnectedDevices.containsKey(item.getGroupId())) {
- return;
- }
- List<CachedBluetoothDevice> devices =
- mGroupedConnectedDevices.get(item.getGroupId());
- @Nullable
- CachedBluetoothDevice lead =
- AudioSharingUtils.getLeadDevice(devices);
- if (lead != null) {
- Log.d(
- TAG,
- "Set fallback active device: "
- + lead.getDevice().getAnonymizedAddress());
- lead.setActive();
- updatePreference();
- } else {
- Log.w(
- TAG,
- "Fail to set fallback active device: no lead"
- + " device");
- }
- });
- }
- return true;
- });
- }
-
- @Override
- public void onStart(@NonNull LifecycleOwner owner) {
- super.onStart(owner);
- if (mLocalBtManager != null) {
- mLocalBtManager.getEventManager().registerCallback(this);
- }
- if (mAssistant != null) {
- mAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback);
- }
- }
-
- @Override
- public void onStop(@NonNull LifecycleOwner owner) {
- super.onStop(owner);
- if (mLocalBtManager != null) {
- mLocalBtManager.getEventManager().unregisterCallback(this);
- }
- if (mAssistant != null) {
- mAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback);
- }
- }
-
- @Override
- public void updateVisibility() {
- if (mPreference == null) return;
- var unused = ThreadUtils.postOnBackgroundThread(() -> updatePreference());
- }
-
- private void updatePreference() {
- boolean isVisible = isBroadcasting() && isBluetoothStateOn();
- if (!isVisible) {
- AudioSharingUtils.postOnMainThread(mContext, () -> mPreference.setVisible(false));
- return;
- }
- updateDeviceItemsInSharingSession();
- int fallbackActiveGroupId = AudioSharingUtils.getFallbackActiveGroupId(mContext);
- Log.d(TAG, "updatePreference: get fallback active group " + fallbackActiveGroupId);
- if (fallbackActiveGroupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
- for (AudioSharingDeviceItem item : mDeviceItemsInSharingSession) {
- if (item.getGroupId() == fallbackActiveGroupId) {
- AudioSharingUtils.postOnMainThread(
- mContext,
- () -> {
- mPreference.setSummary(item.getName());
- mPreference.setVisible(true);
- });
- return;
- }
- }
- }
- AudioSharingUtils.postOnMainThread(
- mContext,
- () -> {
- mPreference.setSummary("No active device in sharing");
- mPreference.setVisible(true);
- });
- }
-
- @Override
- public void onProfileConnectionStateChanged(
- @NonNull CachedBluetoothDevice cachedDevice,
- @ConnectionState int state,
- int bluetoothProfile) {
- if (state == BluetoothAdapter.STATE_DISCONNECTED
- && bluetoothProfile == BluetoothProfile.LE_AUDIO) {
- // The fallback active device could be updated if the previous fallback device is
- // disconnected.
- updatePreference();
- }
- }
-
- /**
- * Initialize the controller.
- *
- * @param fragment The fragment to host the {@link CallsAndAlarmsDialogFragment} dialog.
- */
- public void init(DashboardFragment fragment) {
- this.mFragment = fragment;
- }
-
- private void updateDeviceItemsInSharingSession() {
- mGroupedConnectedDevices =
- AudioSharingUtils.fetchConnectedDevicesByGroupId(mLocalBtManager);
- mDeviceItemsInSharingSession =
- AudioSharingUtils.buildOrderedConnectedLeadAudioSharingDeviceItem(
- mLocalBtManager, mGroupedConnectedDevices, /* filterByInSharing= */ true);
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/StreamSettingsCategoryController.java b/src/com/android/settings/connecteddevice/audiosharing/StreamSettingsCategoryController.java
deleted file mode 100644
index f62183d..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/StreamSettingsCategoryController.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2024 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.connecteddevice.audiosharing;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.lifecycle.DefaultLifecycleObserver;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settings.core.BasePreferenceController;
-
-public class StreamSettingsCategoryController extends BasePreferenceController
- implements DefaultLifecycleObserver {
- private static final String TAG = "StreamSettingsCategoryController";
- private final BluetoothAdapter mBluetoothAdapter;
- private final IntentFilter mIntentFilter;
- private @Nullable Preference mPreference;
- private BroadcastReceiver mReceiver =
- new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (!BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) return;
- int adapterState =
- intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothDevice.ERROR);
- mContext.getMainExecutor()
- .execute(
- () -> {
- if (mPreference == null) {
- Log.w(
- TAG,
- "Skip BT state change due to mPreference "
- + "is null");
- } else {
- mPreference.setVisible(
- adapterState == BluetoothAdapter.STATE_ON);
- }
- });
- }
- };
-
- public StreamSettingsCategoryController(Context context, String key) {
- super(context, key);
- mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
- mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
- }
-
- @Override
- public void onStart(@NonNull LifecycleOwner owner) {
- mContext.registerReceiver(mReceiver, mIntentFilter, Context.RECEIVER_EXPORTED_UNAUDITED);
- }
-
- @Override
- public void onStop(@NonNull LifecycleOwner owner) {
- mContext.unregisterReceiver(mReceiver);
- }
-
- @Override
- public void displayPreference(PreferenceScreen screen) {
- super.displayPreference(screen);
- mPreference = screen.findPreference(getPreferenceKey());
- if (mPreference != null) {
- mPreference.setVisible(isBluetoothStateOn());
- }
- }
-
- @Override
- public int getAvailabilityStatus() {
- return AudioSharingUtils.isFeatureEnabled() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
- }
-
- private boolean isBluetoothStateOn() {
- return mBluetoothAdapter != null && mBluetoothAdapter.isEnabled();
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonController.java
deleted file mode 100644
index 47597cf..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonController.java
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing.audiostreams;
-
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothLeBroadcastAssistant;
-import android.bluetooth.BluetoothLeBroadcastMetadata;
-import android.bluetooth.BluetoothLeBroadcastReceiveState;
-import android.content.Context;
-import android.util.Log;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.lifecycle.DefaultLifecycleObserver;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settings.R;
-import com.android.settings.bluetooth.Utils;
-import com.android.settings.core.BasePreferenceController;
-import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
-import com.android.settingslib.utils.ThreadUtils;
-import com.android.settingslib.widget.ActionButtonsPreference;
-
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-
-public class AudioStreamButtonController extends BasePreferenceController
- implements DefaultLifecycleObserver {
- private static final String TAG = "AudioStreamButtonController";
- private static final String KEY = "audio_stream_button";
- private final BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
- new AudioStreamsBroadcastAssistantCallback() {
- @Override
- public void onSourceRemoved(BluetoothDevice sink, int sourceId, int reason) {
- super.onSourceRemoved(sink, sourceId, reason);
- updateButton();
- }
-
- @Override
- public void onSourceRemoveFailed(BluetoothDevice sink, int sourceId, int reason) {
- super.onSourceRemoveFailed(sink, sourceId, reason);
- updateButton();
- }
-
- @Override
- public void onReceiveStateChanged(
- BluetoothDevice sink,
- int sourceId,
- BluetoothLeBroadcastReceiveState state) {
- super.onReceiveStateChanged(sink, sourceId, state);
- if (mAudioStreamsHelper.isConnected(state)) {
- updateButton();
- }
- }
-
- @Override
- public void onSourceAddFailed(
- BluetoothDevice sink, BluetoothLeBroadcastMetadata source, int reason) {
- super.onSourceAddFailed(sink, source, reason);
- updateButton();
- }
-
- @Override
- public void onSourceLost(int broadcastId) {
- super.onSourceLost(broadcastId);
- updateButton();
- }
- };
-
- private final AudioStreamsRepository mAudioStreamsRepository =
- AudioStreamsRepository.getInstance();
- private final Executor mExecutor;
- private final AudioStreamsHelper mAudioStreamsHelper;
- private final @Nullable LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant;
- private @Nullable ActionButtonsPreference mPreference;
- private int mBroadcastId = -1;
-
- public AudioStreamButtonController(Context context, String preferenceKey) {
- super(context, preferenceKey);
- mExecutor = Executors.newSingleThreadExecutor();
- mAudioStreamsHelper = new AudioStreamsHelper(Utils.getLocalBtManager(context));
- mLeBroadcastAssistant = mAudioStreamsHelper.getLeBroadcastAssistant();
- }
-
- @Override
- public void onStart(@NonNull LifecycleOwner owner) {
- if (mLeBroadcastAssistant == null) {
- Log.w(TAG, "onStart(): LeBroadcastAssistant is null!");
- return;
- }
- mLeBroadcastAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback);
- }
-
- @Override
- public void onStop(@NonNull LifecycleOwner owner) {
- if (mLeBroadcastAssistant == null) {
- Log.w(TAG, "onStop(): LeBroadcastAssistant is null!");
- return;
- }
- mLeBroadcastAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback);
- }
-
- @Override
- public final void displayPreference(PreferenceScreen screen) {
- mPreference = screen.findPreference(getPreferenceKey());
- updateButton();
- super.displayPreference(screen);
- }
-
- private void updateButton() {
- if (mPreference != null) {
- if (mAudioStreamsHelper.getAllConnectedSources().stream()
- .map(BluetoothLeBroadcastReceiveState::getBroadcastId)
- .anyMatch(connectedBroadcastId -> connectedBroadcastId == mBroadcastId)) {
- ThreadUtils.postOnMainThread(
- () -> {
- if (mPreference != null) {
- mPreference.setButton1Enabled(true);
- mPreference
- .setButton1Text(
- R.string.bluetooth_device_context_disconnect)
- .setButton1Icon(R.drawable.ic_settings_close)
- .setButton1OnClickListener(
- unused -> {
- if (mPreference != null) {
- mPreference.setButton1Enabled(false);
- }
- mAudioStreamsHelper.removeSource(mBroadcastId);
- });
- }
- });
- } else {
- View.OnClickListener clickToRejoin =
- unused ->
- ThreadUtils.postOnBackgroundThread(
- () -> {
- var metadata =
- mAudioStreamsRepository.getSavedMetadata(
- mContext, mBroadcastId);
- if (metadata != null) {
- mAudioStreamsHelper.addSource(metadata);
- ThreadUtils.postOnMainThread(
- () -> {
- if (mPreference != null) {
- mPreference.setButton1Enabled(
- false);
- }
- });
- }
- });
- ThreadUtils.postOnMainThread(
- () -> {
- if (mPreference != null) {
- mPreference.setButton1Enabled(true);
- mPreference
- .setButton1Text(R.string.bluetooth_device_context_connect)
- .setButton1Icon(R.drawable.ic_add_24dp)
- .setButton1OnClickListener(clickToRejoin);
- }
- });
- }
- } else {
- Log.w(TAG, "updateButton(): preference is null!");
- }
- }
-
- @Override
- public int getAvailabilityStatus() {
- return AVAILABLE;
- }
-
- @Override
- public String getPreferenceKey() {
- return KEY;
- }
-
- /** Initialize with broadcast id */
- void init(int broadcastId) {
- mBroadcastId = broadcastId;
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamConfirmDialog.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamConfirmDialog.java
deleted file mode 100644
index 131c8f6..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamConfirmDialog.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2024 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.connecteddevice.audiosharing.audiostreams;
-
-import android.app.Activity;
-import android.app.Dialog;
-import android.app.settings.SettingsEnums;
-import android.bluetooth.BluetoothLeBroadcastMetadata;
-import android.os.Bundle;
-import android.util.Log;
-
-import com.android.settings.R;
-import com.android.settings.bluetooth.Utils;
-import com.android.settings.connecteddevice.audiosharing.audiostreams.qrcode.QrCodeScanModeFragment;
-import com.android.settings.core.SubSettingLauncher;
-import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
-import com.android.settingslib.bluetooth.BluetoothLeBroadcastMetadataExt;
-
-import com.google.common.base.Strings;
-
-public class AudioStreamConfirmDialog extends InstrumentedDialogFragment {
- public static final String KEY_BROADCAST_METADATA = "key_broadcast_metadata";
- private static final String TAG = "AudioStreamConfirmDialog";
- private Activity mActivity;
- private String mBroadcastMetadataStr;
- private BluetoothLeBroadcastMetadata mBroadcastMetadata;
- private boolean mIsRequestValid = false;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setShowsDialog(true);
- mActivity = getActivity();
- if (mActivity == null) {
- Log.w(TAG, "onCreate() mActivity is null!");
- return;
- }
- mBroadcastMetadataStr =
- mActivity.getIntent().getStringExtra(QrCodeScanModeFragment.KEY_BROADCAST_METADATA);
- if (Strings.isNullOrEmpty(mBroadcastMetadataStr)) {
- Log.w(TAG, "onCreate() mBroadcastMetadataStr is null or empty!");
- return;
- }
- mBroadcastMetadata =
- BluetoothLeBroadcastMetadataExt.INSTANCE.convertToBroadcastMetadata(
- mBroadcastMetadataStr);
- if (mBroadcastMetadata == null) {
- Log.w(TAG, "onCreate() mBroadcastMetadata is null!");
- } else {
- // Warm up LE_AUDIO_BROADCAST_ASSISTANT service
- Utils.getLocalBluetoothManager(mActivity);
- mIsRequestValid = true;
- }
- }
-
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- return mIsRequestValid ? getConfirmDialog() : getErrorDialog();
- }
-
- @Override
- public int getMetricsCategory() {
- // TODO(chelseahao): update metrics id
- return 0;
- }
-
- private Dialog getConfirmDialog() {
- return new AudioStreamsDialogFragment.DialogBuilder(mActivity)
- .setTitle("Listen to audio stream")
- .setSubTitle1(mBroadcastMetadata.getBroadcastName())
- .setSubTitle2(
- "The audio stream will play on the active LE audio device. Use this device"
- + " to control the volume.")
- .setLeftButtonText("Cancel")
- .setLeftButtonOnClickListener(
- unused -> {
- dismiss();
- mActivity.finish();
- })
- .setRightButtonText("Listen")
- .setRightButtonOnClickListener(
- unused -> {
- launchAudioStreamsActivity();
- dismiss();
- mActivity.finish();
- })
- .build();
- }
-
- private Dialog getErrorDialog() {
- return new AudioStreamsDialogFragment.DialogBuilder(mActivity)
- .setTitle("Can't listen to audio stream")
- .setSubTitle2("Can't play this audio stream. Learn more")
- .setRightButtonText("Close")
- .setRightButtonOnClickListener(
- unused -> {
- dismiss();
- mActivity.finish();
- })
- .build();
- }
-
- private void launchAudioStreamsActivity() {
- Bundle bundle = new Bundle();
- bundle.putString(KEY_BROADCAST_METADATA, mBroadcastMetadataStr);
-
- new SubSettingLauncher(mActivity)
- .setTitleRes(R.string.bluetooth_find_broadcast_title)
- .setDestination(AudioStreamsDashboardFragment.class.getName())
- .setArguments(bundle)
- .setSourceMetricsCategory(SettingsEnums.PAGE_UNKNOWN)
- .launch();
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamDetailsFragment.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamDetailsFragment.java
deleted file mode 100644
index e1dc228..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamDetailsFragment.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing.audiostreams;
-
-import android.content.Context;
-import android.os.Bundle;
-
-import com.android.settings.R;
-import com.android.settings.dashboard.DashboardFragment;
-
-public class AudioStreamDetailsFragment extends DashboardFragment {
- static final String BROADCAST_NAME_ARG = "broadcast_name";
- static final String BROADCAST_ID_ARG = "broadcast_id";
- private static final String TAG = "AudioStreamDetailsFragment";
-
- @Override
- public void onAttach(Context context) {
- super.onAttach(context);
- Bundle arguments = getArguments();
- if (arguments != null) {
- use(AudioStreamHeaderController.class)
- .init(
- this,
- arguments.getString(BROADCAST_NAME_ARG),
- arguments.getInt(BROADCAST_ID_ARG));
- use(AudioStreamButtonController.class).init(arguments.getInt(BROADCAST_ID_ARG));
- }
- }
-
- @Override
- public int getMetricsCategory() {
- // TODO(chelseahao): update metrics id
- return 0;
- }
-
- @Override
- protected int getPreferenceScreenResId() {
- return R.xml.audio_stream_details_fragment;
- }
-
- @Override
- protected String getLogTag() {
- return TAG;
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java
deleted file mode 100644
index 3524543..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing.audiostreams;
-
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothLeBroadcastAssistant;
-import android.bluetooth.BluetoothLeBroadcastReceiveState;
-import android.content.Context;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.lifecycle.DefaultLifecycleObserver;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settings.R;
-import com.android.settings.bluetooth.Utils;
-import com.android.settings.core.BasePreferenceController;
-import com.android.settings.dashboard.DashboardFragment;
-import com.android.settings.widget.EntityHeaderController;
-import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
-import com.android.settingslib.utils.ThreadUtils;
-import com.android.settingslib.widget.LayoutPreference;
-
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-
-import javax.annotation.Nullable;
-
-public class AudioStreamHeaderController extends BasePreferenceController
- implements DefaultLifecycleObserver {
- private static final String TAG = "AudioStreamHeaderController";
- private static final String KEY = "audio_stream_header";
- private final Executor mExecutor;
- private final AudioStreamsHelper mAudioStreamsHelper;
- @Nullable private final LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant;
- private final BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
- new AudioStreamsBroadcastAssistantCallback() {
- @Override
- public void onSourceRemoved(BluetoothDevice sink, int sourceId, int reason) {
- super.onSourceRemoved(sink, sourceId, reason);
- updateSummary();
- }
-
- @Override
- public void onSourceLost(int broadcastId) {
- super.onSourceLost(broadcastId);
- updateSummary();
- }
-
- @Override
- public void onReceiveStateChanged(
- BluetoothDevice sink,
- int sourceId,
- BluetoothLeBroadcastReceiveState state) {
- super.onReceiveStateChanged(sink, sourceId, state);
- if (mAudioStreamsHelper.isConnected(state)) {
- updateSummary();
- }
- }
- };
-
- private @Nullable EntityHeaderController mHeaderController;
- private @Nullable DashboardFragment mFragment;
- private String mBroadcastName = "";
- private int mBroadcastId = -1;
-
- public AudioStreamHeaderController(Context context, String preferenceKey) {
- super(context, preferenceKey);
- mExecutor = Executors.newSingleThreadExecutor();
- mAudioStreamsHelper = new AudioStreamsHelper(Utils.getLocalBtManager(context));
- mLeBroadcastAssistant = mAudioStreamsHelper.getLeBroadcastAssistant();
- }
-
- @Override
- public void onStart(@NonNull LifecycleOwner owner) {
- if (mLeBroadcastAssistant == null) {
- Log.w(TAG, "onStart(): LeBroadcastAssistant is null!");
- return;
- }
- mLeBroadcastAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback);
- }
-
- @Override
- public void onStop(@NonNull LifecycleOwner owner) {
- if (mLeBroadcastAssistant == null) {
- Log.w(TAG, "onStop(): LeBroadcastAssistant is null!");
- return;
- }
- mLeBroadcastAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback);
- }
-
- @Override
- public final void displayPreference(PreferenceScreen screen) {
- LayoutPreference headerPreference = screen.findPreference(KEY);
- if (headerPreference != null && mFragment != null) {
- mHeaderController =
- EntityHeaderController.newInstance(
- mFragment.getActivity(),
- mFragment,
- headerPreference.findViewById(R.id.entity_header));
- if (mBroadcastName != null) {
- mHeaderController.setLabel(mBroadcastName);
- }
- mHeaderController.setIcon(
- screen.getContext().getDrawable(R.drawable.ic_bt_audio_sharing));
- screen.addPreference(headerPreference);
- updateSummary();
- }
- super.displayPreference(screen);
- }
-
- private void updateSummary() {
- var unused =
- ThreadUtils.postOnBackgroundThread(
- () -> {
- var latestSummary =
- mAudioStreamsHelper.getAllConnectedSources().stream()
- .map(
- BluetoothLeBroadcastReceiveState
- ::getBroadcastId)
- .anyMatch(
- connectedBroadcastId ->
- connectedBroadcastId
- == mBroadcastId)
- ? "Listening now"
- : "";
- ThreadUtils.postOnMainThread(
- () -> {
- if (mHeaderController != null) {
- mHeaderController.setSummary(latestSummary);
- mHeaderController.done(true);
- }
- });
- });
- }
-
- @Override
- public int getAvailabilityStatus() {
- return AVAILABLE;
- }
-
- @Override
- public String getPreferenceKey() {
- return KEY;
- }
-
- /** Initialize with {@link AudioStreamDetailsFragment} and broadcast name and id */
- void init(
- AudioStreamDetailsFragment audioStreamDetailsFragment,
- String broadcastName,
- int broadcastId) {
- mFragment = audioStreamDetailsFragment;
- mBroadcastName = broadcastName;
- mBroadcastId = broadcastId;
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamPreference.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamPreference.java
deleted file mode 100644
index c2e1178..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamPreference.java
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing.audiostreams;
-
-import android.bluetooth.BluetoothLeAudioContentMetadata;
-import android.bluetooth.BluetoothLeBroadcastMetadata;
-import android.bluetooth.BluetoothLeBroadcastReceiveState;
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-
-import androidx.annotation.Nullable;
-import androidx.preference.PreferenceViewHolder;
-
-import com.android.settings.R;
-import com.android.settingslib.widget.TwoTargetPreference;
-
-import com.google.common.base.Strings;
-
-/**
- * Custom preference class for managing audio stream preferences with an optional lock icon. Extends
- * {@link TwoTargetPreference}.
- */
-class AudioStreamPreference extends TwoTargetPreference {
- private boolean mIsConnected = false;
- private AudioStream mAudioStream;
-
- /**
- * Update preference UI based on connection status
- *
- * @param isConnected Is this stream connected
- * @param summary Summary text
- * @param onPreferenceClickListener Click listener for the preference
- */
- void setIsConnected(
- boolean isConnected,
- String summary,
- @Nullable OnPreferenceClickListener onPreferenceClickListener) {
- if (mIsConnected == isConnected
- && getSummary() == summary
- && getOnPreferenceClickListener() == onPreferenceClickListener) {
- // Nothing to update.
- return;
- }
- mIsConnected = isConnected;
- setSummary(summary);
- setOnPreferenceClickListener(onPreferenceClickListener);
- notifyChanged();
- }
-
- AudioStreamPreference(Context context, @Nullable AttributeSet attrs) {
- super(context, attrs);
- setIcon(R.drawable.ic_bt_audio_sharing);
- }
-
- void setAudioStreamState(AudioStreamsProgressCategoryController.AudioStreamState state) {
- mAudioStream.setState(state);
- }
-
- void setAudioStreamMetadata(BluetoothLeBroadcastMetadata metadata) {
- mAudioStream.setMetadata(metadata);
- }
-
- int getAudioStreamBroadcastId() {
- return mAudioStream.getBroadcastId();
- }
-
- int getAudioStreamRssi() {
- return mAudioStream.getRssi();
- }
-
- @Nullable
- BluetoothLeBroadcastMetadata getAudioStreamMetadata() {
- return mAudioStream.getMetadata();
- }
-
- AudioStreamsProgressCategoryController.AudioStreamState getAudioStreamState() {
- return mAudioStream.getState();
- }
-
- @Override
- protected boolean shouldHideSecondTarget() {
- return mIsConnected;
- }
-
- @Override
- protected int getSecondTargetResId() {
- return R.layout.preference_widget_lock;
- }
-
- @Override
- public void onBindViewHolder(PreferenceViewHolder holder) {
- super.onBindViewHolder(holder);
- View divider =
- holder.findViewById(
- com.android.settingslib.widget.preference.twotarget.R.id
- .two_target_divider);
- if (divider != null) {
- divider.setVisibility(View.GONE);
- }
- }
-
- static AudioStreamPreference fromMetadata(
- Context context, BluetoothLeBroadcastMetadata source) {
- AudioStreamPreference preference = new AudioStreamPreference(context, /* attrs= */ null);
- preference.setTitle(getBroadcastName(source));
- preference.setAudioStream(new AudioStream(source));
- return preference;
- }
-
- static AudioStreamPreference fromReceiveState(
- Context context, BluetoothLeBroadcastReceiveState receiveState) {
- AudioStreamPreference preference = new AudioStreamPreference(context, /* attrs= */ null);
- preference.setTitle(getBroadcastName(receiveState));
- preference.setAudioStream(new AudioStream(receiveState));
- return preference;
- }
-
- private void setAudioStream(AudioStream audioStream) {
- mAudioStream = audioStream;
- }
-
- private static String getBroadcastName(BluetoothLeBroadcastMetadata source) {
- return source.getSubgroups().stream()
- .map(s -> s.getContentMetadata().getProgramInfo())
- .filter(i -> !Strings.isNullOrEmpty(i))
- .findFirst()
- .orElse("Broadcast Id: " + source.getBroadcastId());
- }
-
- private static String getBroadcastName(BluetoothLeBroadcastReceiveState state) {
- return state.getSubgroupMetadata().stream()
- .map(BluetoothLeAudioContentMetadata::getProgramInfo)
- .filter(i -> !Strings.isNullOrEmpty(i))
- .findFirst()
- .orElse("Broadcast Id: " + state.getBroadcastId());
- }
-
- private static final class AudioStream {
- private static final int UNAVAILABLE = -1;
- @Nullable private BluetoothLeBroadcastMetadata mMetadata;
- @Nullable private BluetoothLeBroadcastReceiveState mReceiveState;
- private AudioStreamsProgressCategoryController.AudioStreamState mState =
- AudioStreamsProgressCategoryController.AudioStreamState.UNKNOWN;
-
- private AudioStream(BluetoothLeBroadcastMetadata metadata) {
- mMetadata = metadata;
- }
-
- private AudioStream(BluetoothLeBroadcastReceiveState receiveState) {
- mReceiveState = receiveState;
- }
-
- private int getBroadcastId() {
- return mMetadata != null
- ? mMetadata.getBroadcastId()
- : mReceiveState != null ? mReceiveState.getBroadcastId() : UNAVAILABLE;
- }
-
- private int getRssi() {
- return mMetadata != null ? mMetadata.getRssi() : Integer.MAX_VALUE;
- }
-
- private AudioStreamsProgressCategoryController.AudioStreamState getState() {
- return mState;
- }
-
- @Nullable
- private BluetoothLeBroadcastMetadata getMetadata() {
- return mMetadata;
- }
-
- private void setState(AudioStreamsProgressCategoryController.AudioStreamState state) {
- mState = state;
- }
-
- private void setMetadata(BluetoothLeBroadcastMetadata metadata) {
- mMetadata = metadata;
- }
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsActiveDeviceController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsActiveDeviceController.java
deleted file mode 100644
index d001409..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsActiveDeviceController.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing.audiostreams;
-
-import android.content.Context;
-
-import androidx.annotation.NonNull;
-import androidx.lifecycle.DefaultLifecycleObserver;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settings.core.BasePreferenceController;
-
-public class AudioStreamsActiveDeviceController extends BasePreferenceController
- implements AudioStreamsActiveDeviceSummaryUpdater.OnSummaryChangeListener,
- DefaultLifecycleObserver {
-
- public static final String KEY = "audio_streams_active_device";
- private final AudioStreamsActiveDeviceSummaryUpdater mSummaryHelper;
- private Preference mPreference;
-
- public AudioStreamsActiveDeviceController(Context context, String preferenceKey) {
- super(context, preferenceKey);
- mSummaryHelper = new AudioStreamsActiveDeviceSummaryUpdater(mContext, this);
- }
-
- @Override
- public void displayPreference(PreferenceScreen screen) {
- super.displayPreference(screen);
- mPreference = screen.findPreference(KEY);
- }
-
- @Override
- public int getAvailabilityStatus() {
- return AVAILABLE;
- }
-
- @Override
- public void onSummaryChanged(String summary) {
- mPreference.setSummary(summary);
- }
-
- @Override
- public void onResume(@NonNull LifecycleOwner owner) {
- mSummaryHelper.register(true);
- }
-
- @Override
- public void onStop(@NonNull LifecycleOwner owner) {
- mSummaryHelper.register(false);
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsActiveDeviceSummaryUpdater.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsActiveDeviceSummaryUpdater.java
deleted file mode 100644
index b2e6fb2..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsActiveDeviceSummaryUpdater.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing.audiostreams;
-
-import android.bluetooth.BluetoothProfile;
-import android.content.Context;
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-
-import com.android.settings.bluetooth.Utils;
-import com.android.settings.connecteddevice.audiosharing.AudioSharingUtils;
-import com.android.settingslib.bluetooth.BluetoothCallback;
-import com.android.settingslib.bluetooth.BluetoothUtils;
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
-import com.android.settingslib.utils.ThreadUtils;
-
-public class AudioStreamsActiveDeviceSummaryUpdater implements BluetoothCallback {
- private static final String TAG = "AudioStreamsActiveDeviceSummaryUpdater";
- private static final boolean DEBUG = BluetoothUtils.D;
- private final LocalBluetoothManager mBluetoothManager;
- private String mSummary;
- private OnSummaryChangeListener mListener;
-
- public AudioStreamsActiveDeviceSummaryUpdater(
- Context context, OnSummaryChangeListener listener) {
- mBluetoothManager = Utils.getLocalBluetoothManager(context);
- mListener = listener;
- }
-
- @Override
- public void onActiveDeviceChanged(
- @Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) {
- if (DEBUG) {
- Log.d(
- TAG,
- "onActiveDeviceChanged() with activeDevice : "
- + (activeDevice == null ? "null" : activeDevice.getAddress())
- + " on profile : "
- + bluetoothProfile);
- }
- if (bluetoothProfile == BluetoothProfile.LE_AUDIO) {
- notifyChangeIfNeeded();
- }
- }
-
- void register(boolean register) {
- if (register) {
- notifyChangeIfNeeded();
- mBluetoothManager.getEventManager().registerCallback(this);
- } else {
- mBluetoothManager.getEventManager().unregisterCallback(this);
- }
- }
-
- private void notifyChangeIfNeeded() {
- ThreadUtils.postOnBackgroundThread(
- () -> {
- String summary = getSummary();
- if (!TextUtils.equals(mSummary, summary)) {
- mSummary = summary;
- ThreadUtils.postOnMainThread(() -> mListener.onSummaryChanged(summary));
- }
- });
- }
-
- private String getSummary() {
- var activeSink = AudioSharingUtils.getActiveSinkOnAssistant(mBluetoothManager);
- if (activeSink.isEmpty()) {
- return "No active LE Audio device";
- }
- return activeSink.get().getName();
- }
-
- /** Interface definition for a callback to be invoked when the summary has been changed. */
- interface OnSummaryChangeListener {
- /**
- * Called when summary has changed.
- *
- * @param summary The new summary.
- */
- void onSummaryChanged(String summary);
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsBroadcastAssistantCallback.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsBroadcastAssistantCallback.java
deleted file mode 100644
index 9fb5b21..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsBroadcastAssistantCallback.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing.audiostreams;
-
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothLeBroadcastAssistant;
-import android.bluetooth.BluetoothLeBroadcastMetadata;
-import android.bluetooth.BluetoothLeBroadcastReceiveState;
-import android.util.Log;
-
-import com.android.settingslib.bluetooth.BluetoothUtils;
-
-public class AudioStreamsBroadcastAssistantCallback
- implements BluetoothLeBroadcastAssistant.Callback {
-
- private static final String TAG = "AudioStreamsBroadcastAssistantCallback";
- private static final boolean DEBUG = BluetoothUtils.D;
-
- @Override
- public void onReceiveStateChanged(
- BluetoothDevice sink, int sourceId, BluetoothLeBroadcastReceiveState state) {
- if (DEBUG) {
- Log.d(
- TAG,
- "onReceiveStateChanged() sink : "
- + sink.getAddress()
- + " sourceId: "
- + sourceId
- + " state: "
- + state);
- }
- }
-
- @Override
- public void onSearchStartFailed(int reason) {
- Log.w(TAG, "onSearchStartFailed() reason : " + reason);
- }
-
- @Override
- public void onSearchStarted(int reason) {
- if (DEBUG) {
- Log.d(TAG, "onSearchStarted() reason : " + reason);
- }
- }
-
- @Override
- public void onSearchStopFailed(int reason) {
- Log.w(TAG, "onSearchStopFailed() reason : " + reason);
- }
-
- @Override
- public void onSearchStopped(int reason) {
- if (DEBUG) {
- Log.d(TAG, "onSearchStopped() reason : " + reason);
- }
- }
-
- @Override
- public void onSourceAddFailed(
- BluetoothDevice sink, BluetoothLeBroadcastMetadata source, int reason) {
- if (DEBUG) {
- Log.d(
- TAG,
- "onSourceAddFailed() sink : "
- + sink.getAddress()
- + " source: "
- + source
- + " reason: "
- + reason);
- }
- }
-
- @Override
- public void onSourceAdded(BluetoothDevice sink, int sourceId, int reason) {
- if (DEBUG) {
- Log.d(
- TAG,
- "onSourceAdded() sink : "
- + sink.getAddress()
- + " sourceId: "
- + sourceId
- + " reason: "
- + reason);
- }
- }
-
- @Override
- public void onSourceFound(BluetoothLeBroadcastMetadata source) {
- if (DEBUG) {
- Log.d(TAG, "onSourceFound() broadcastId : " + source.getBroadcastId());
- }
- }
-
- @Override
- public void onSourceLost(int broadcastId) {
- if (DEBUG) {
- Log.d(TAG, "onSourceLost() broadcastId : " + broadcastId);
- }
- }
-
- @Override
- public void onSourceModified(BluetoothDevice sink, int sourceId, int reason) {}
-
- @Override
- public void onSourceModifyFailed(BluetoothDevice sink, int sourceId, int reason) {}
-
- @Override
- public void onSourceRemoveFailed(BluetoothDevice sink, int sourceId, int reason) {
- Log.w(TAG, "onSourceRemoveFailed() sourceId : " + sourceId + " reason : " + reason);
- }
-
- @Override
- public void onSourceRemoved(BluetoothDevice sink, int sourceId, int reason) {
- if (DEBUG) {
- Log.d(TAG, "onSourceRemoved() sourceId : " + sourceId + " reason : " + reason);
- }
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsCategoryController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsCategoryController.java
deleted file mode 100644
index 56d0fa7..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsCategoryController.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing.audiostreams;
-
-import android.bluetooth.BluetoothProfile;
-import android.content.Context;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.lifecycle.LifecycleOwner;
-
-import com.android.settings.bluetooth.Utils;
-import com.android.settings.connecteddevice.audiosharing.AudioSharingBasePreferenceController;
-import com.android.settings.connecteddevice.audiosharing.AudioSharingUtils;
-import com.android.settings.flags.Flags;
-import com.android.settingslib.bluetooth.BluetoothCallback;
-import com.android.settingslib.bluetooth.BluetoothUtils;
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
-import com.android.settingslib.utils.ThreadUtils;
-
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-
-public class AudioStreamsCategoryController extends AudioSharingBasePreferenceController {
- private static final String TAG = "AudioStreamsCategoryController";
- private static final boolean DEBUG = BluetoothUtils.D;
- private final LocalBluetoothManager mLocalBtManager;
- private final Executor mExecutor;
- private final BluetoothCallback mBluetoothCallback =
- new BluetoothCallback() {
- @Override
- public void onActiveDeviceChanged(
- @Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) {
- if (bluetoothProfile == BluetoothProfile.LE_AUDIO) {
- updateVisibility();
- }
- }
- };
-
- public AudioStreamsCategoryController(Context context, String key) {
- super(context, key);
- mLocalBtManager = Utils.getLocalBtManager(mContext);
- mExecutor = Executors.newSingleThreadExecutor();
- }
-
- @Override
- public void onStart(@NonNull LifecycleOwner owner) {
- super.onStart(owner);
- if (mLocalBtManager != null) {
- mLocalBtManager.getEventManager().registerCallback(mBluetoothCallback);
- }
- }
-
- @Override
- public void onStop(@NonNull LifecycleOwner owner) {
- super.onStop(owner);
- if (mLocalBtManager != null) {
- mLocalBtManager.getEventManager().unregisterCallback(mBluetoothCallback);
- }
- }
-
- @Override
- public int getAvailabilityStatus() {
- return Flags.enableLeAudioQrCodePrivateBroadcastSharing()
- ? AVAILABLE
- : UNSUPPORTED_ON_DEVICE;
- }
-
- @Override
- public void updateVisibility() {
- if (mPreference == null) return;
- mExecutor.execute(
- () -> {
- boolean hasActiveLe =
- AudioSharingUtils.getActiveSinkOnAssistant(mLocalBtManager).isPresent();
- boolean isBroadcasting = isBroadcasting();
- boolean isBluetoothOn = isBluetoothStateOn();
- if (DEBUG) {
- Log.d(
- TAG,
- "updateVisibility() isBroadcasting : "
- + isBroadcasting
- + " hasActiveLe : "
- + hasActiveLe
- + " is BT on : "
- + isBluetoothOn);
- }
- ThreadUtils.postOnMainThread(
- () ->
- mPreference.setVisible(
- isBluetoothOn && hasActiveLe && !isBroadcasting));
- });
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsDashboardFragment.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsDashboardFragment.java
deleted file mode 100644
index bddbb61..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsDashboardFragment.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing.audiostreams;
-
-import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsScanQrCodeController.REQUEST_SCAN_BT_BROADCAST_QR_CODE;
-
-import android.app.Activity;
-import android.bluetooth.BluetoothLeBroadcastMetadata;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.util.Log;
-
-import com.android.settings.R;
-import com.android.settings.connecteddevice.audiosharing.audiostreams.qrcode.QrCodeScanModeFragment;
-import com.android.settings.dashboard.DashboardFragment;
-import com.android.settingslib.bluetooth.BluetoothLeBroadcastMetadataExt;
-import com.android.settingslib.bluetooth.BluetoothUtils;
-
-import com.google.common.base.Strings;
-
-public class AudioStreamsDashboardFragment extends DashboardFragment {
- private static final String TAG = "AudioStreamsDashboardFrag";
- private static final boolean DEBUG = BluetoothUtils.D;
- private AudioStreamsProgressCategoryController mAudioStreamsProgressCategoryController;
-
- public AudioStreamsDashboardFragment() {
- super();
- }
-
- @Override
- public int getMetricsCategory() {
- // TODO: update category id.
- return 0;
- }
-
- @Override
- protected String getLogTag() {
- return TAG;
- }
-
- @Override
- public int getHelpResource() {
- return R.string.help_url_audio_sharing;
- }
-
- @Override
- protected int getPreferenceScreenResId() {
- return R.xml.bluetooth_audio_streams;
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- }
-
- @Override
- public void onAttach(Context context) {
- super.onAttach(context);
- use(AudioStreamsScanQrCodeController.class).setFragment(this);
- mAudioStreamsProgressCategoryController = use(AudioStreamsProgressCategoryController.class);
- mAudioStreamsProgressCategoryController.setFragment(this);
-
- if (getArguments() != null) {
- String broadcastMetadataStr =
- getArguments().getString(AudioStreamConfirmDialog.KEY_BROADCAST_METADATA);
- if (!Strings.isNullOrEmpty(broadcastMetadataStr)) {
- BluetoothLeBroadcastMetadata broadcastMetadata =
- BluetoothLeBroadcastMetadataExt.INSTANCE.convertToBroadcastMetadata(
- broadcastMetadataStr);
- if (broadcastMetadata == null) {
- Log.w(TAG, "onAttach() broadcastMetadata is null!");
- } else {
- mAudioStreamsProgressCategoryController.setSourceFromQrCode(broadcastMetadata);
- }
- }
- }
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
- }
-
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
- if (DEBUG) {
- Log.d(
- TAG,
- "onActivityResult() requestCode : "
- + requestCode
- + " resultCode : "
- + resultCode);
- }
- if (requestCode == REQUEST_SCAN_BT_BROADCAST_QR_CODE) {
- if (resultCode == Activity.RESULT_OK) {
- String broadcastMetadata =
- data.getStringExtra(QrCodeScanModeFragment.KEY_BROADCAST_METADATA);
- BluetoothLeBroadcastMetadata source =
- BluetoothLeBroadcastMetadataExt.INSTANCE.convertToBroadcastMetadata(
- broadcastMetadata);
- if (source == null) {
- Log.w(TAG, "onActivityResult() source is null!");
- return;
- }
- if (DEBUG) {
- Log.d(TAG, "onActivityResult() broadcastId : " + source.getBroadcastId());
- }
- if (mAudioStreamsProgressCategoryController == null) {
- Log.w(
- TAG,
- "onActivityResult() AudioStreamsProgressCategoryController is null!");
- return;
- }
- mAudioStreamsProgressCategoryController.setSourceFromQrCode(source);
- }
- }
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsDialogFragment.java
deleted file mode 100644
index c7d7f16..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsDialogFragment.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2024 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.connecteddevice.audiosharing.audiostreams;
-
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.content.Context;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.Button;
-import android.widget.TextView;
-
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentManager;
-
-import com.android.settings.R;
-import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
-
-import com.google.common.base.Strings;
-
-import java.util.function.Consumer;
-
-public class AudioStreamsDialogFragment extends InstrumentedDialogFragment {
- private static final String TAG = "AudioStreamsDialogFragment";
- private final DialogBuilder mDialogBuilder;
-
- AudioStreamsDialogFragment(DialogBuilder dialogBuilder) {
- mDialogBuilder = dialogBuilder;
- }
-
- @Override
- public int getMetricsCategory() {
- // TODO(chelseahao): update metrics id
- return 0;
- }
-
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- return mDialogBuilder.build();
- }
-
- static void show(Fragment host, DialogBuilder dialogBuilder) {
- FragmentManager manager = host.getChildFragmentManager();
- (new AudioStreamsDialogFragment(dialogBuilder)).show(manager, TAG);
- }
-
- static class DialogBuilder {
- private final Context mContext;
- private final AlertDialog.Builder mBuilder;
- private String mTitle;
- private String mSubTitle1;
- private String mSubTitle2;
- private String mLeftButtonText;
- private String mRightButtonText;
- private Consumer<AlertDialog> mLeftButtonOnClickListener;
- private Consumer<AlertDialog> mRightButtonOnClickListener;
-
- DialogBuilder(Context context) {
- mContext = context;
- mBuilder = new AlertDialog.Builder(context);
- }
-
- DialogBuilder setTitle(String title) {
- mTitle = title;
- return this;
- }
-
- DialogBuilder setSubTitle1(String subTitle1) {
- mSubTitle1 = subTitle1;
- return this;
- }
-
- DialogBuilder setSubTitle2(String subTitle2) {
- mSubTitle2 = subTitle2;
- return this;
- }
-
- DialogBuilder setLeftButtonText(String text) {
- mLeftButtonText = text;
- return this;
- }
-
- DialogBuilder setLeftButtonOnClickListener(Consumer<AlertDialog> listener) {
- mLeftButtonOnClickListener = listener;
- return this;
- }
-
- DialogBuilder setRightButtonText(String text) {
- mRightButtonText = text;
- return this;
- }
-
- DialogBuilder setRightButtonOnClickListener(Consumer<AlertDialog> listener) {
- mRightButtonOnClickListener = listener;
- return this;
- }
-
- AlertDialog build() {
- View rootView =
- LayoutInflater.from(mContext)
- .inflate(R.xml.bluetooth_audio_streams_dialog, /* parent= */ null);
-
- AlertDialog dialog = mBuilder.setView(rootView).setCancelable(false).create();
- dialog.setCanceledOnTouchOutside(false);
-
- TextView title = rootView.requireViewById(R.id.dialog_title);
- title.setText(mTitle);
-
- if (!Strings.isNullOrEmpty(mSubTitle1)) {
- TextView subTitle1 = rootView.requireViewById(R.id.dialog_subtitle);
- subTitle1.setText(mSubTitle1);
- subTitle1.setVisibility(View.VISIBLE);
- }
- if (!Strings.isNullOrEmpty(mSubTitle2)) {
- TextView subTitle2 = rootView.requireViewById(R.id.dialog_subtitle_2);
- subTitle2.setText(mSubTitle2);
- subTitle2.setVisibility(View.VISIBLE);
- }
- if (!Strings.isNullOrEmpty(mLeftButtonText)) {
- Button leftButton = rootView.requireViewById(R.id.left_button);
- leftButton.setText(mLeftButtonText);
- leftButton.setVisibility(View.VISIBLE);
- leftButton.setOnClickListener(unused -> mLeftButtonOnClickListener.accept(dialog));
- }
- if (!Strings.isNullOrEmpty(mRightButtonText)) {
- Button rightButton = rootView.requireViewById(R.id.right_button);
- rightButton.setText(mRightButtonText);
- rightButton.setVisibility(View.VISIBLE);
- rightButton.setOnClickListener(
- unused -> mRightButtonOnClickListener.accept(dialog));
- }
-
- return dialog;
- }
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java
deleted file mode 100644
index 2c6eedb..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing.audiostreams;
-
-import static java.util.Collections.emptyList;
-
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothLeBroadcastMetadata;
-import android.bluetooth.BluetoothLeBroadcastReceiveState;
-import android.util.Log;
-
-import com.android.settings.connecteddevice.audiosharing.AudioSharingUtils;
-import com.android.settingslib.bluetooth.BluetoothUtils;
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
-import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
-import com.android.settingslib.utils.ThreadUtils;
-
-import java.util.List;
-import java.util.stream.Stream;
-
-import javax.annotation.Nullable;
-
-/**
- * A helper class that adds, removes and retrieves LE broadcast sources for all active sink devices.
- */
-class AudioStreamsHelper {
-
- private static final String TAG = "AudioStreamsHelper";
- private static final boolean DEBUG = BluetoothUtils.D;
-
- private final @Nullable LocalBluetoothManager mBluetoothManager;
- private final @Nullable LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant;
-
- AudioStreamsHelper(@Nullable LocalBluetoothManager bluetoothManager) {
- mBluetoothManager = bluetoothManager;
- mLeBroadcastAssistant = getLeBroadcastAssistant(mBluetoothManager);
- }
-
- /**
- * Adds the specified LE broadcast source to all active sinks.
- *
- * @param source The LE broadcast metadata representing the audio source.
- */
- void addSource(BluetoothLeBroadcastMetadata source) {
- if (mLeBroadcastAssistant == null) {
- Log.w(TAG, "addSource(): LeBroadcastAssistant is null!");
- return;
- }
- var unused =
- ThreadUtils.postOnBackgroundThread(
- () -> {
- for (var sink : getActiveSinksOnAssistant(mBluetoothManager)) {
- if (DEBUG) {
- Log.d(
- TAG,
- "addSource(): join broadcast broadcastId"
- + " : "
- + source.getBroadcastId()
- + " sink : "
- + sink.getAddress());
- }
- mLeBroadcastAssistant.addSource(sink, source, false);
- }
- });
- }
-
- /** Removes sources from LE broadcasts associated for all active sinks based on broadcast Id. */
- void removeSource(int broadcastId) {
- if (mLeBroadcastAssistant == null) {
- Log.w(TAG, "removeSource(): LeBroadcastAssistant is null!");
- return;
- }
- var unused =
- ThreadUtils.postOnBackgroundThread(
- () -> {
- for (var sink : getActiveSinksOnAssistant(mBluetoothManager)) {
- if (DEBUG) {
- Log.d(
- TAG,
- "removeSource(): remove all sources with broadcast id :"
- + broadcastId
- + " from sink : "
- + sink.getAddress());
- }
- mLeBroadcastAssistant.getAllSources(sink).stream()
- .filter(state -> state.getBroadcastId() == broadcastId)
- .forEach(
- state ->
- mLeBroadcastAssistant.removeSource(
- sink, state.getSourceId()));
- }
- });
- }
-
- /** Retrieves a list of all LE broadcast receive states from active sinks. */
- List<BluetoothLeBroadcastReceiveState> getAllConnectedSources() {
- if (mLeBroadcastAssistant == null) {
- Log.w(TAG, "getAllSources(): LeBroadcastAssistant is null!");
- return emptyList();
- }
- return getActiveSinksOnAssistant(mBluetoothManager).stream()
- .flatMap(sink -> mLeBroadcastAssistant.getAllSources(sink).stream())
- .filter(this::isConnected)
- .toList();
- }
-
- @Nullable
- LocalBluetoothLeBroadcastAssistant getLeBroadcastAssistant() {
- return mLeBroadcastAssistant;
- }
-
- boolean isConnected(BluetoothLeBroadcastReceiveState state) {
- return state.getPaSyncState() == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED
- && state.getBigEncryptionState()
- == BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING;
- }
-
- private static List<BluetoothDevice> getActiveSinksOnAssistant(
- @Nullable LocalBluetoothManager manager) {
- if (manager == null) {
- Log.w(TAG, "getActiveSinksOnAssistant(): LocalBluetoothManager is null!");
- return emptyList();
- }
- return AudioSharingUtils.getActiveSinkOnAssistant(manager)
- .map(
- cachedBluetoothDevice ->
- Stream.concat(
- Stream.of(cachedBluetoothDevice.getDevice()),
- cachedBluetoothDevice.getMemberDevice().stream()
- .map(CachedBluetoothDevice::getDevice))
- .toList())
- .orElse(emptyList());
- }
-
- private static @Nullable LocalBluetoothLeBroadcastAssistant getLeBroadcastAssistant(
- @Nullable LocalBluetoothManager manager) {
- if (manager == null) {
- Log.w(TAG, "getLeBroadcastAssistant(): LocalBluetoothManager is null!");
- return null;
- }
-
- LocalBluetoothProfileManager profileManager = manager.getProfileManager();
- if (profileManager == null) {
- Log.w(TAG, "getLeBroadcastAssistant(): LocalBluetoothProfileManager is null!");
- return null;
- }
-
- return profileManager.getLeAudioBroadcastAssistantProfile();
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallback.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallback.java
deleted file mode 100644
index 34ffc91..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallback.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2024 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.connecteddevice.audiosharing.audiostreams;
-
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothLeBroadcastMetadata;
-import android.bluetooth.BluetoothLeBroadcastReceiveState;
-import android.util.Log;
-
-import java.util.Locale;
-
-public class AudioStreamsProgressCategoryCallback extends AudioStreamsBroadcastAssistantCallback {
- private static final String TAG = "AudioStreamsProgressCategoryCallback";
-
- private final AudioStreamsProgressCategoryController mCategoryController;
-
- public AudioStreamsProgressCategoryCallback(
- AudioStreamsProgressCategoryController audioStreamsProgressCategoryController) {
- mCategoryController = audioStreamsProgressCategoryController;
- }
-
- @Override
- public void onReceiveStateChanged(
- BluetoothDevice sink, int sourceId, BluetoothLeBroadcastReceiveState state) {
- super.onReceiveStateChanged(sink, sourceId, state);
- mCategoryController.handleSourceConnected(state);
- }
-
- @Override
- public void onSearchStartFailed(int reason) {
- super.onSearchStartFailed(reason);
- mCategoryController.showToast(
- String.format(Locale.US, "Failed to start scanning, reason %d", reason));
- }
-
- @Override
- public void onSearchStarted(int reason) {
- super.onSearchStarted(reason);
- if (mCategoryController == null) {
- Log.w(TAG, "onSearchStarted() : mCategoryController is null!");
- return;
- }
- mCategoryController.setScanning(true);
- }
-
- @Override
- public void onSearchStopFailed(int reason) {
- super.onSearchStopFailed(reason);
- mCategoryController.showToast(
- String.format(Locale.US, "Failed to stop scanning, reason %d", reason));
- }
-
- @Override
- public void onSearchStopped(int reason) {
- super.onSearchStopped(reason);
- if (mCategoryController == null) {
- Log.w(TAG, "onSearchStopped() : mCategoryController is null!");
- return;
- }
- mCategoryController.setScanning(false);
- }
-
- @Override
- public void onSourceAddFailed(
- BluetoothDevice sink, BluetoothLeBroadcastMetadata source, int reason) {
- super.onSourceAddFailed(sink, source, reason);
- mCategoryController.showToast(
- String.format(Locale.US, "Failed to join broadcast, reason %d", reason));
- }
-
- @Override
- public void onSourceFound(BluetoothLeBroadcastMetadata source) {
- super.onSourceFound(source);
- if (mCategoryController == null) {
- Log.w(TAG, "onSourceFound() : mCategoryController is null!");
- return;
- }
- mCategoryController.handleSourceFound(source);
- }
-
- @Override
- public void onSourceLost(int broadcastId) {
- super.onSourceLost(broadcastId);
- mCategoryController.handleSourceLost(broadcastId);
- }
-
- @Override
- public void onSourceRemoveFailed(BluetoothDevice sink, int sourceId, int reason) {
- super.onSourceRemoveFailed(sink, sourceId, reason);
- mCategoryController.showToast(
- String.format(
- Locale.US,
- "Failed to remove source %d for sink %s",
- sourceId,
- sink.getAddress()));
- }
-
- @Override
- public void onSourceRemoved(BluetoothDevice sink, int sourceId, int reason) {
- super.onSourceRemoved(sink, sourceId, reason);
- mCategoryController.handleSourceRemoved();
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java
deleted file mode 100644
index c6f342a..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java
+++ /dev/null
@@ -1,641 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing.audiostreams;
-
-import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsScanQrCodeController.REQUEST_SCAN_BT_BROADCAST_QR_CODE;
-
-import static java.util.Collections.emptyList;
-
-import android.app.AlertDialog;
-import android.app.settings.SettingsEnums;
-import android.bluetooth.BluetoothLeBroadcastMetadata;
-import android.bluetooth.BluetoothLeBroadcastReceiveState;
-import android.bluetooth.BluetoothProfile;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.CountDownTimer;
-import android.provider.Settings;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.EditText;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.lifecycle.DefaultLifecycleObserver;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settings.R;
-import com.android.settings.bluetooth.Utils;
-import com.android.settings.connecteddevice.audiosharing.AudioSharingUtils;
-import com.android.settings.connecteddevice.audiosharing.audiostreams.qrcode.QrCodeScanModeActivity;
-import com.android.settings.core.BasePreferenceController;
-import com.android.settings.core.SubSettingLauncher;
-import com.android.settingslib.bluetooth.BluetoothBroadcastUtils;
-import com.android.settingslib.bluetooth.BluetoothCallback;
-import com.android.settingslib.bluetooth.BluetoothUtils;
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
-import com.android.settingslib.utils.ThreadUtils;
-
-import java.nio.charset.StandardCharsets;
-import java.util.Comparator;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-
-import javax.annotation.Nullable;
-
-public class AudioStreamsProgressCategoryController extends BasePreferenceController
- implements DefaultLifecycleObserver {
- private static final String TAG = "AudioStreamsProgressCategoryController";
- private static final boolean DEBUG = BluetoothUtils.D;
- private final BluetoothCallback mBluetoothCallback =
- new BluetoothCallback() {
- @Override
- public void onActiveDeviceChanged(
- @Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) {
- if (bluetoothProfile == BluetoothProfile.LE_AUDIO) {
- mExecutor.execute(() -> init(activeDevice != null));
- }
- }
- };
-
- private final Preference.OnPreferenceClickListener mAddSourceOrShowDialog =
- preference -> {
- var p = (AudioStreamPreference) preference;
- if (DEBUG) {
- Log.d(
- TAG,
- "preferenceClicked(): attempt to join broadcast id : "
- + p.getAudioStreamBroadcastId());
- }
- var source = p.getAudioStreamMetadata();
- if (source != null) {
- if (source.isEncrypted()) {
- ThreadUtils.postOnMainThread(() -> launchPasswordDialog(source, p));
- } else {
- moveToState(p, AudioStreamState.ADD_SOURCE_WAIT_FOR_RESPONSE);
- }
- }
- return true;
- };
-
- private final Preference.OnPreferenceClickListener mLaunchDetailFragment =
- preference -> {
- var p = (AudioStreamPreference) preference;
- Bundle broadcast = new Bundle();
- broadcast.putString(
- AudioStreamDetailsFragment.BROADCAST_NAME_ARG, (String) p.getTitle());
- broadcast.putInt(
- AudioStreamDetailsFragment.BROADCAST_ID_ARG, p.getAudioStreamBroadcastId());
-
- new SubSettingLauncher(mContext)
- .setTitleText("Audio stream details")
- .setDestination(AudioStreamDetailsFragment.class.getName())
- // TODO(chelseahao): Add logging enum
- .setSourceMetricsCategory(SettingsEnums.PAGE_UNKNOWN)
- .setArguments(broadcast)
- .launch();
- return true;
- };
-
- private final AudioStreamsRepository mAudioStreamsRepository =
- AudioStreamsRepository.getInstance();
-
- enum AudioStreamState {
- UNKNOWN,
- // When mTimedSourceFromQrCode is present and this source has not been synced.
- WAIT_FOR_SYNC,
- // When source has been synced but not added to any sink.
- SYNCED,
- // When addSource is called for this source and waiting for response.
- ADD_SOURCE_WAIT_FOR_RESPONSE,
- // Source is added to active sink.
- SOURCE_ADDED,
- }
-
- private final Comparator<AudioStreamPreference> mComparator =
- Comparator.<AudioStreamPreference, Boolean>comparing(
- p ->
- p.getAudioStreamState()
- == AudioStreamsProgressCategoryController
- .AudioStreamState.SOURCE_ADDED)
- .thenComparingInt(AudioStreamPreference::getAudioStreamRssi)
- .reversed();
-
- private final Executor mExecutor;
- private final AudioStreamsProgressCategoryCallback mBroadcastAssistantCallback;
- private final AudioStreamsHelper mAudioStreamsHelper;
- private final @Nullable LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant;
- private final @Nullable LocalBluetoothManager mBluetoothManager;
- private final ConcurrentHashMap<Integer, AudioStreamPreference> mBroadcastIdToPreferenceMap =
- new ConcurrentHashMap<>();
- private @Nullable TimedSourceFromQrCode mTimedSourceFromQrCode;
- private AudioStreamsProgressCategoryPreference mCategoryPreference;
- private AudioStreamsDashboardFragment mFragment;
-
- public AudioStreamsProgressCategoryController(Context context, String preferenceKey) {
- super(context, preferenceKey);
- mExecutor = Executors.newSingleThreadExecutor();
- mBluetoothManager = Utils.getLocalBtManager(mContext);
- mAudioStreamsHelper = new AudioStreamsHelper(mBluetoothManager);
- mLeBroadcastAssistant = mAudioStreamsHelper.getLeBroadcastAssistant();
- mBroadcastAssistantCallback = new AudioStreamsProgressCategoryCallback(this);
- }
-
- @Override
- public int getAvailabilityStatus() {
- return AVAILABLE;
- }
-
- @Override
- public void displayPreference(PreferenceScreen screen) {
- super.displayPreference(screen);
- mCategoryPreference = screen.findPreference(getPreferenceKey());
- }
-
- @Override
- public void onStart(@NonNull LifecycleOwner owner) {
- if (mBluetoothManager != null) {
- mBluetoothManager.getEventManager().registerCallback(mBluetoothCallback);
- }
- mExecutor.execute(
- () -> {
- boolean hasActive =
- AudioSharingUtils.getActiveSinkOnAssistant(mBluetoothManager)
- .isPresent();
- init(hasActive);
- });
- }
-
- @Override
- public void onStop(@NonNull LifecycleOwner owner) {
- if (mBluetoothManager != null) {
- mBluetoothManager.getEventManager().unregisterCallback(mBluetoothCallback);
- }
- mExecutor.execute(this::stopScanning);
- }
-
- void setFragment(AudioStreamsDashboardFragment fragment) {
- mFragment = fragment;
- }
-
- void setSourceFromQrCode(BluetoothLeBroadcastMetadata source) {
- mTimedSourceFromQrCode =
- new TimedSourceFromQrCode(source, () -> handleSourceLost(source.getBroadcastId()));
- }
-
- void setScanning(boolean isScanning) {
- ThreadUtils.postOnMainThread(
- () -> {
- if (mCategoryPreference != null) mCategoryPreference.setProgress(isScanning);
- });
- }
-
- void handleSourceFound(BluetoothLeBroadcastMetadata source) {
- var broadcastIdFound = source.getBroadcastId();
- mBroadcastIdToPreferenceMap.compute(
- broadcastIdFound,
- (k, v) -> {
- if (v == null) {
- // No existing preference for this source founded, add one and set initial
- // state to SYNCED.
- return addNewPreference(source, AudioStreamState.SYNCED);
- }
- var fromState = v.getAudioStreamState();
- if (fromState == AudioStreamState.WAIT_FOR_SYNC
- && mTimedSourceFromQrCode != null) {
- var pendingSource = mTimedSourceFromQrCode.get();
- if (pendingSource == null) {
- Log.w(
- TAG,
- "handleSourceFound(): unexpected state with null pendingSource:"
- + fromState
- + " for broadcastId : "
- + broadcastIdFound);
- v.setAudioStreamMetadata(source);
- moveToState(v, AudioStreamState.SYNCED);
- return v;
- }
- // A preference with source founded is existed from a QR code scan. As the
- // source is now synced, we update the preference with pendingSource from QR
- // code scan and add source with it (since it has the password).
- v.setAudioStreamMetadata(pendingSource);
- moveToState(v, AudioStreamState.ADD_SOURCE_WAIT_FOR_RESPONSE);
- } else {
- // A preference with source founded existed either because it's already
- // connected (SOURCE_ADDED), or other unexpected reason. We update the
- // preference with this source and won't change it's state.
- v.setAudioStreamMetadata(source);
- if (fromState != AudioStreamState.SOURCE_ADDED) {
- Log.w(
- TAG,
- "handleSourceFound(): unexpected state : "
- + fromState
- + " for broadcastId : "
- + broadcastIdFound);
- }
- }
- return v;
- });
- }
-
- private void handleSourceFromQrCodeIfExists() {
- if (mTimedSourceFromQrCode == null || mTimedSourceFromQrCode.get() == null) {
- return;
- }
- var metadataFromQrCode = mTimedSourceFromQrCode.get();
- mBroadcastIdToPreferenceMap.compute(
- metadataFromQrCode.getBroadcastId(),
- (k, v) -> {
- if (v == null) {
- // No existing preference for this source from the QR code scan, add one and
- // set initial state to WAIT_FOR_SYNC.
- return addNewPreference(metadataFromQrCode, AudioStreamState.WAIT_FOR_SYNC);
- }
- var fromState = v.getAudioStreamState();
- if (fromState == AudioStreamState.SYNCED) {
- // A preference with source from the QR code is existed because it has been
- // founded during scanning, now we have the password, we can add source.
- v.setAudioStreamMetadata(metadataFromQrCode);
- moveToState(v, AudioStreamState.ADD_SOURCE_WAIT_FOR_RESPONSE);
- } else {
- v.setAudioStreamMetadata(metadataFromQrCode);
- Log.w(
- TAG,
- "handleSourceFromQrCode(): unexpected state : "
- + fromState
- + " for broadcastId : "
- + metadataFromQrCode.getBroadcastId());
- }
- return v;
- });
- }
-
- void handleSourceLost(int broadcastId) {
- var toRemove = mBroadcastIdToPreferenceMap.remove(broadcastId);
- if (toRemove != null) {
- ThreadUtils.postOnMainThread(
- () -> {
- if (mCategoryPreference != null) {
- mCategoryPreference.removePreference(toRemove);
- }
- });
- }
- mAudioStreamsHelper.removeSource(broadcastId);
- }
-
- void handleSourceRemoved() {
- for (var entry : mBroadcastIdToPreferenceMap.entrySet()) {
- var preference = entry.getValue();
-
- // Look for preference has SOURCE_ADDED state, re-check if they are still connected. If
- // not, means the source is removed from the sink, we move back the preference to SYNCED
- // state.
- if (preference.getAudioStreamState() == AudioStreamState.SOURCE_ADDED
- && mAudioStreamsHelper.getAllConnectedSources().stream()
- .noneMatch(
- connected ->
- connected.getBroadcastId()
- == preference.getAudioStreamBroadcastId())) {
-
- ThreadUtils.postOnMainThread(
- () -> {
- var metadata = preference.getAudioStreamMetadata();
-
- if (metadata != null) {
- moveToState(preference, AudioStreamState.SYNCED);
- } else {
- handleSourceLost(preference.getAudioStreamBroadcastId());
- }
- });
-
- return;
- }
- }
- }
-
- void handleSourceConnected(BluetoothLeBroadcastReceiveState receiveState) {
- if (!mAudioStreamsHelper.isConnected(receiveState)) {
- return;
- }
- var broadcastIdConnected = receiveState.getBroadcastId();
- mBroadcastIdToPreferenceMap.compute(
- broadcastIdConnected,
- (k, v) -> {
- if (v == null) {
- // No existing preference for this source even if it's already connected,
- // add one and set initial state to SOURCE_ADDED. This could happen because
- // we retrieves the connected source during onStart() from
- // AudioStreamsHelper#getAllConnectedSources() even before the source is
- // founded by scanning.
- return addNewPreference(receiveState, AudioStreamState.SOURCE_ADDED);
- }
- var fromState = v.getAudioStreamState();
- if (fromState == AudioStreamState.ADD_SOURCE_WAIT_FOR_RESPONSE
- || fromState == AudioStreamState.SYNCED
- || fromState == AudioStreamState.WAIT_FOR_SYNC
- || fromState == AudioStreamState.SOURCE_ADDED) {
- // Expected state, do nothing
- } else {
- Log.w(
- TAG,
- "handleSourceConnected(): unexpected state : "
- + fromState
- + " for broadcastId : "
- + broadcastIdConnected);
- }
- moveToState(v, AudioStreamState.SOURCE_ADDED);
- return v;
- });
- }
-
- void showToast(String msg) {
- AudioSharingUtils.toastMessage(mContext, msg);
- }
-
- private void init(boolean hasActive) {
- mBroadcastIdToPreferenceMap.clear();
- ThreadUtils.postOnMainThread(
- () -> {
- if (mCategoryPreference != null) {
- mCategoryPreference.removeAudioStreamPreferences();
- mCategoryPreference.setVisible(hasActive);
- }
- });
- if (hasActive) {
- startScanning();
- } else {
- stopScanning();
- ThreadUtils.postOnMainThread(
- () -> AudioStreamsDialogFragment.show(mFragment, getNoLeDeviceDialog()));
- }
- }
-
- private void startScanning() {
- if (mLeBroadcastAssistant == null) {
- Log.w(TAG, "startScanning(): LeBroadcastAssistant is null!");
- return;
- }
- if (mLeBroadcastAssistant.isSearchInProgress()) {
- showToast("Failed to start scanning, please try again.");
- return;
- }
- if (DEBUG) {
- Log.d(TAG, "startScanning()");
- }
- mLeBroadcastAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback);
-
- // Handle QR code scan and display currently connected streams
- var unused =
- ThreadUtils.postOnBackgroundThread(
- () -> {
- handleSourceFromQrCodeIfExists();
- mAudioStreamsHelper
- .getAllConnectedSources()
- .forEach(this::handleSourceConnected);
- mLeBroadcastAssistant.startSearchingForSources(emptyList());
- });
- }
-
- private void stopScanning() {
- if (mLeBroadcastAssistant == null) {
- Log.w(TAG, "stopScanning(): LeBroadcastAssistant is null!");
- return;
- }
- if (mLeBroadcastAssistant.isSearchInProgress()) {
- if (DEBUG) {
- Log.d(TAG, "stopScanning()");
- }
- mLeBroadcastAssistant.stopSearchingForSources();
- }
- mLeBroadcastAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback);
- if (mTimedSourceFromQrCode != null) {
- mTimedSourceFromQrCode.cleanup();
- mTimedSourceFromQrCode = null;
- }
- }
-
- private AudioStreamPreference addNewPreference(
- BluetoothLeBroadcastReceiveState receiveState, AudioStreamState state) {
- var preference = AudioStreamPreference.fromReceiveState(mContext, receiveState);
- moveToState(preference, state);
- return preference;
- }
-
- private AudioStreamPreference addNewPreference(
- BluetoothLeBroadcastMetadata metadata, AudioStreamState state) {
- var preference = AudioStreamPreference.fromMetadata(mContext, metadata);
- moveToState(preference, state);
- return preference;
- }
-
- private void moveToState(AudioStreamPreference preference, AudioStreamState state) {
- if (preference.getAudioStreamState() == state) {
- return;
- }
- preference.setAudioStreamState(state);
-
- // Perform action according to the new state
- if (state == AudioStreamState.ADD_SOURCE_WAIT_FOR_RESPONSE) {
- if (mTimedSourceFromQrCode != null) {
- mTimedSourceFromQrCode.consumed(preference.getAudioStreamBroadcastId());
- }
- var metadata = preference.getAudioStreamMetadata();
- if (metadata != null) {
- mAudioStreamsHelper.addSource(metadata);
- // Cache the metadata that used for add source, if source is added successfully, we
- // will save it persistently.
- mAudioStreamsRepository.cacheMetadata(metadata);
- }
- } else if (state == AudioStreamState.SOURCE_ADDED) {
- if (mTimedSourceFromQrCode != null) {
- mTimedSourceFromQrCode.consumed(preference.getAudioStreamBroadcastId());
- }
- // Saved connected metadata for user to re-join this broadcast later.
- var cached =
- mAudioStreamsRepository.getCachedMetadata(
- preference.getAudioStreamBroadcastId());
- if (cached != null) {
- mAudioStreamsRepository.saveMetadata(mContext, cached);
- }
- } else if (state == AudioStreamState.WAIT_FOR_SYNC) {
- if (mTimedSourceFromQrCode != null) {
- mTimedSourceFromQrCode.waitForConsume();
- }
- }
-
- // Get preference click listener according to the new state
- Preference.OnPreferenceClickListener listener;
- if (state == AudioStreamState.SYNCED) {
- listener = mAddSourceOrShowDialog;
- } else if (state == AudioStreamState.SOURCE_ADDED) {
- listener = mLaunchDetailFragment;
- } else {
- listener = null;
- }
-
- // Get preference summary according to the new state
- String summary;
- if (state == AudioStreamState.WAIT_FOR_SYNC) {
- summary = "Scanning...";
- } else if (state == AudioStreamState.ADD_SOURCE_WAIT_FOR_RESPONSE) {
- summary = "Connecting...";
- } else if (state == AudioStreamState.SOURCE_ADDED) {
- summary = "Listening now";
- } else {
- summary = "";
- }
-
- // Update UI
- ThreadUtils.postOnMainThread(
- () -> {
- preference.setIsConnected(
- state == AudioStreamState.SOURCE_ADDED, summary, listener);
- if (mCategoryPreference != null) {
- mCategoryPreference.addAudioStreamPreference(preference, mComparator);
- }
- });
- }
-
- private void launchPasswordDialog(
- BluetoothLeBroadcastMetadata source, AudioStreamPreference preference) {
- View layout =
- LayoutInflater.from(mContext)
- .inflate(R.layout.bluetooth_find_broadcast_password_dialog, null);
- ((TextView) layout.requireViewById(R.id.broadcast_name_text))
- .setText(preference.getTitle());
- AlertDialog alertDialog =
- new AlertDialog.Builder(mContext)
- .setTitle(R.string.find_broadcast_password_dialog_title)
- .setView(layout)
- .setNeutralButton(android.R.string.cancel, null)
- .setPositiveButton(
- R.string.bluetooth_connect_access_dialog_positive,
- (dialog, which) -> {
- var code =
- ((EditText)
- layout.requireViewById(
- R.id.broadcast_edit_text))
- .getText()
- .toString();
- var metadata =
- new BluetoothLeBroadcastMetadata.Builder(source)
- .setBroadcastCode(
- code.getBytes(StandardCharsets.UTF_8))
- .build();
- // Update the metadata after user entered the password
- preference.setAudioStreamMetadata(metadata);
- moveToState(
- preference,
- AudioStreamState.ADD_SOURCE_WAIT_FOR_RESPONSE);
- })
- .create();
- alertDialog.show();
- }
-
- private AudioStreamsDialogFragment.DialogBuilder getNoLeDeviceDialog() {
- return new AudioStreamsDialogFragment.DialogBuilder(mContext)
- .setTitle("Connect compatible headphones")
- .setSubTitle2(
- "To listen to an audio stream, first connect headphones that support LE"
- + " Audio to this device. Learn more")
- .setLeftButtonText("Close")
- .setLeftButtonOnClickListener(AlertDialog::dismiss)
- .setRightButtonText("Connect a device")
- .setRightButtonOnClickListener(
- dialog -> {
- mContext.startActivity(new Intent(Settings.ACTION_BLUETOOTH_SETTINGS));
- dialog.dismiss();
- });
- }
-
- private AudioStreamsDialogFragment.DialogBuilder getBroadcastUnavailableDialog(
- String broadcastName) {
- return new AudioStreamsDialogFragment.DialogBuilder(mContext)
- .setTitle("Audio stream isn't available")
- .setSubTitle1(broadcastName)
- .setSubTitle2("This audio stream isn't playing anything right now")
- .setLeftButtonText("Close")
- .setLeftButtonOnClickListener(AlertDialog::dismiss)
- .setRightButtonText("Retry")
- .setRightButtonOnClickListener(
- dialog -> {
- if (mFragment != null) {
- Intent intent = new Intent(mContext, QrCodeScanModeActivity.class);
- intent.setAction(
- BluetoothBroadcastUtils
- .ACTION_BLUETOOTH_LE_AUDIO_QR_CODE_SCANNER);
- mFragment.startActivityForResult(
- intent, REQUEST_SCAN_BT_BROADCAST_QR_CODE);
- dialog.dismiss();
- }
- });
- }
-
- private class TimedSourceFromQrCode {
- private static final int WAIT_FOR_SYNC_TIMEOUT_MILLIS = 15000;
- private final CountDownTimer mTimer;
- private BluetoothLeBroadcastMetadata mSourceFromQrCode;
-
- private TimedSourceFromQrCode(
- BluetoothLeBroadcastMetadata sourceFromQrCode, Runnable timeoutAction) {
- mSourceFromQrCode = sourceFromQrCode;
- mTimer =
- new CountDownTimer(WAIT_FOR_SYNC_TIMEOUT_MILLIS, 1000) {
- @Override
- public void onTick(long millisUntilFinished) {}
-
- @Override
- public void onFinish() {
- timeoutAction.run();
- ThreadUtils.postOnMainThread(
- () ->
- AudioStreamsDialogFragment.show(
- mFragment,
- getBroadcastUnavailableDialog(
- sourceFromQrCode.getBroadcastName())));
- }
- };
- }
-
- private void waitForConsume() {
- mTimer.start();
- }
-
- private void cleanup() {
- mTimer.cancel();
- mSourceFromQrCode = null;
- }
-
- private void consumed(int broadcastId) {
- if (mSourceFromQrCode == null || broadcastId != mSourceFromQrCode.getBroadcastId()) {
- return;
- }
- cleanup();
- }
-
- private BluetoothLeBroadcastMetadata get() {
- return mSourceFromQrCode;
- }
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryPreference.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryPreference.java
deleted file mode 100644
index 33adc31..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryPreference.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing.audiostreams;
-
-import android.content.Context;
-import android.util.AttributeSet;
-
-import androidx.annotation.NonNull;
-
-import com.android.settings.ProgressCategory;
-import com.android.settings.R;
-
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.List;
-
-public class AudioStreamsProgressCategoryPreference extends ProgressCategory {
-
- public AudioStreamsProgressCategoryPreference(Context context) {
- super(context);
- init();
- }
-
- public AudioStreamsProgressCategoryPreference(Context context, AttributeSet attrs) {
- super(context, attrs);
- init();
- }
-
- public AudioStreamsProgressCategoryPreference(
- Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- init();
- }
-
- public AudioStreamsProgressCategoryPreference(
- Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- init();
- }
-
- void addAudioStreamPreference(
- @NonNull AudioStreamPreference preference,
- Comparator<AudioStreamPreference> comparator) {
- super.addPreference(preference);
-
- List<AudioStreamPreference> preferences = getAllAudioStreamPreferences();
- preferences.sort(comparator);
- for (int i = 0; i < preferences.size(); i++) {
- // setOrder to i + 1, since the order 0 preference should always be the
- // "audio_streams_scan_qr_code"
- preferences.get(i).setOrder(i + 1);
- }
- }
-
- void removeAudioStreamPreferences() {
- List<AudioStreamPreference> streams = getAllAudioStreamPreferences();
- for (var toRemove : streams) {
- removePreference(toRemove);
- }
- }
-
- private List<AudioStreamPreference> getAllAudioStreamPreferences() {
- List<AudioStreamPreference> streams = new ArrayList<>();
- for (int i = 0; i < getPreferenceCount(); i++) {
- if (getPreference(i) instanceof AudioStreamPreference) {
- streams.add((AudioStreamPreference) getPreference(i));
- }
- }
- return streams;
- }
-
- private void init() {
- setEmptyTextRes(R.string.audio_streams_empty);
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsQrCodeFragment.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsQrCodeFragment.java
deleted file mode 100644
index 2366e70..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsQrCodeFragment.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing.audiostreams;
-
-import android.bluetooth.BluetoothLeBroadcastMetadata;
-import android.graphics.Bitmap;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import androidx.annotation.Nullable;
-
-import com.android.settings.R;
-import com.android.settings.bluetooth.Utils;
-import com.android.settings.core.InstrumentedFragment;
-import com.android.settingslib.bluetooth.BluetoothLeBroadcastMetadataExt;
-import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
-import com.android.settingslib.qrcode.QrCodeGenerator;
-
-import com.google.zxing.WriterException;
-
-import java.nio.charset.StandardCharsets;
-import java.util.Optional;
-
-public class AudioStreamsQrCodeFragment extends InstrumentedFragment {
- private static final String TAG = "AudioStreamsQrCodeFragment";
-
- @Override
- public int getMetricsCategory() {
- // TODO(chelseahao): update metrics id
- return 0;
- }
-
- @Override
- public final View onCreateView(
- LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- View view = inflater.inflate(R.xml.bluetooth_audio_streams_qr_code, container, false);
-
- BluetoothLeBroadcastMetadata broadcastMetadata = getBroadcastMetadata();
-
- if (broadcastMetadata != null) {
- getQrCodeBitmap(broadcastMetadata)
- .ifPresent(
- bm -> {
- ((ImageView) view.requireViewById(R.id.qrcode_view))
- .setImageBitmap(bm);
- ((TextView) view.requireViewById(R.id.password))
- .setText(
- "Password: "
- + new String(
- broadcastMetadata
- .getBroadcastCode(),
- StandardCharsets.UTF_8));
- });
- }
- return view;
- }
-
- private Optional<Bitmap> getQrCodeBitmap(@Nullable BluetoothLeBroadcastMetadata metadata) {
- if (metadata == null) {
- Log.d(TAG, "onCreateView: broadcastMetadata is empty!");
- return Optional.empty();
- }
- String metadataStr = BluetoothLeBroadcastMetadataExt.INSTANCE.toQrCodeString(metadata);
- if (metadataStr.isEmpty()) {
- Log.d(TAG, "onCreateView: metadataStr is empty!");
- return Optional.empty();
- }
- Log.d("chelsea", metadataStr);
- try {
- int qrcodeSize = getContext().getResources().getDimensionPixelSize(R.dimen.qrcode_size);
- Bitmap bitmap = QrCodeGenerator.encodeQrCode(metadataStr, qrcodeSize);
- return Optional.of(bitmap);
- } catch (WriterException e) {
- Log.d(
- TAG,
- "onCreateView: broadcastMetadata "
- + metadata
- + " qrCode generation exception "
- + e);
- }
-
- return Optional.empty();
- }
-
- @Nullable
- private BluetoothLeBroadcastMetadata getBroadcastMetadata() {
- LocalBluetoothLeBroadcast localBluetoothLeBroadcast =
- Utils.getLocalBtManager(getActivity())
- .getProfileManager()
- .getLeAudioBroadcastProfile();
- if (localBluetoothLeBroadcast == null) {
- Log.d(TAG, "getBroadcastMetadataQrCode: localBluetoothLeBroadcast is null!");
- return null;
- }
-
- BluetoothLeBroadcastMetadata metadata =
- localBluetoothLeBroadcast.getLatestBluetoothLeBroadcastMetadata();
- if (metadata == null) {
- Log.d(TAG, "getBroadcastMetadataQrCode: metadata is null!");
- return null;
- }
-
- return metadata;
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsRepository.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsRepository.java
deleted file mode 100644
index 65245ac..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsRepository.java
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * Copyright (C) 2024 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.connecteddevice.audiosharing.audiostreams;
-
-import android.bluetooth.BluetoothLeBroadcastMetadata;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.util.Log;
-
-import com.android.settingslib.bluetooth.BluetoothLeBroadcastMetadataExt;
-import com.android.settingslib.bluetooth.BluetoothUtils;
-import com.android.settingslib.utils.ThreadUtils;
-
-import java.util.concurrent.ConcurrentHashMap;
-
-import javax.annotation.Nullable;
-
-/** Manages the caching and storage of Bluetooth audio stream metadata. */
-public class AudioStreamsRepository {
-
- private static final String TAG = "AudioStreamsRepository";
- private static final boolean DEBUG = BluetoothUtils.D;
-
- private static final String PREF_KEY = "bluetooth_audio_stream_pref";
- private static final String METADATA_KEY = "bluetooth_audio_stream_metadata";
-
- @Nullable
- private static AudioStreamsRepository sInstance = null;
-
- private AudioStreamsRepository() {}
-
- /**
- * Gets the single instance of AudioStreamsRepository.
- *
- * @return The AudioStreamsRepository instance.
- */
- public static synchronized AudioStreamsRepository getInstance() {
- if (sInstance == null) {
- sInstance = new AudioStreamsRepository();
- }
- return sInstance;
- }
-
- private final ConcurrentHashMap<Integer, BluetoothLeBroadcastMetadata>
- mBroadcastIdToMetadataCacheMap = new ConcurrentHashMap<>();
-
- /**
- * Caches BluetoothLeBroadcastMetadata in a local cache.
- *
- * @param metadata The BluetoothLeBroadcastMetadata to be cached.
- */
- void cacheMetadata(BluetoothLeBroadcastMetadata metadata) {
- if (DEBUG) {
- Log.d(
- TAG,
- "cacheMetadata(): broadcastId "
- + metadata.getBroadcastId()
- + " saved in local cache.");
- }
- mBroadcastIdToMetadataCacheMap.put(metadata.getBroadcastId(), metadata);
- }
-
- /**
- * Gets cached BluetoothLeBroadcastMetadata by broadcastId.
- *
- * @param broadcastId The broadcastId to look up in the cache.
- * @return The cached BluetoothLeBroadcastMetadata or null if not found.
- */
- @Nullable
- BluetoothLeBroadcastMetadata getCachedMetadata(int broadcastId) {
- var metadata = mBroadcastIdToMetadataCacheMap.get(broadcastId);
- if (metadata == null) {
- Log.w(
- TAG,
- "getCachedMetadata(): broadcastId not found in"
- + " mBroadcastIdToMetadataCacheMap.");
- return null;
- }
- return metadata;
- }
-
- /**
- * Saves metadata to SharedPreferences asynchronously.
- *
- * @param context The context.
- * @param metadata The BluetoothLeBroadcastMetadata to be saved.
- */
- void saveMetadata(Context context, BluetoothLeBroadcastMetadata metadata) {
- var unused =
- ThreadUtils.postOnBackgroundThread(
- () -> {
- SharedPreferences sharedPref =
- context.getSharedPreferences(PREF_KEY, Context.MODE_PRIVATE);
- if (sharedPref != null) {
- SharedPreferences.Editor editor = sharedPref.edit();
- editor.putString(
- METADATA_KEY,
- BluetoothLeBroadcastMetadataExt.INSTANCE.toQrCodeString(
- metadata));
- editor.apply();
- if (DEBUG) {
- Log.d(
- TAG,
- "saveMetadata(): broadcastId "
- + metadata.getBroadcastId()
- + " metadata saved in storage.");
- }
- }
- });
- }
-
- /**
- * Gets saved metadata from SharedPreferences.
- *
- * @param context The context.
- * @param broadcastId The broadcastId to retrieve metadata for.
- * @return The saved BluetoothLeBroadcastMetadata or null if not found.
- */
- @Nullable
- BluetoothLeBroadcastMetadata getSavedMetadata(Context context, int broadcastId) {
- SharedPreferences sharedPref = context.getSharedPreferences(PREF_KEY, Context.MODE_PRIVATE);
- if (sharedPref != null) {
- String savedMetadataStr = sharedPref.getString(METADATA_KEY, null);
- if (savedMetadataStr == null) {
- Log.w(TAG, "getSavedMetadata(): savedMetadataStr is null");
- return null;
- }
- var savedMetadata =
- BluetoothLeBroadcastMetadataExt.INSTANCE.convertToBroadcastMetadata(
- savedMetadataStr);
- if (savedMetadata == null || savedMetadata.getBroadcastId() != broadcastId) {
- Log.w(TAG, "getSavedMetadata(): savedMetadata doesn't match broadcast Id.");
- return null;
- }
- if (DEBUG) {
- Log.d(
- TAG,
- "getSavedMetadata(): broadcastId "
- + savedMetadata.getBroadcastId()
- + " metadata found in storage.");
- }
- return savedMetadata;
- }
- return null;
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsScanQrCodeController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsScanQrCodeController.java
deleted file mode 100644
index 24e1ca3..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsScanQrCodeController.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing.audiostreams;
-
-import android.bluetooth.BluetoothProfile;
-import android.content.Context;
-import android.content.Intent;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.lifecycle.DefaultLifecycleObserver;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settings.bluetooth.Utils;
-import com.android.settings.connecteddevice.audiosharing.AudioSharingUtils;
-import com.android.settings.connecteddevice.audiosharing.audiostreams.qrcode.QrCodeScanModeActivity;
-import com.android.settings.core.BasePreferenceController;
-import com.android.settingslib.bluetooth.BluetoothBroadcastUtils;
-import com.android.settingslib.bluetooth.BluetoothCallback;
-import com.android.settingslib.bluetooth.BluetoothUtils;
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
-import com.android.settingslib.utils.ThreadUtils;
-
-public class AudioStreamsScanQrCodeController extends BasePreferenceController
- implements DefaultLifecycleObserver {
- static final int REQUEST_SCAN_BT_BROADCAST_QR_CODE = 0;
- private static final String TAG = "AudioStreamsProgressCategoryController";
- private static final boolean DEBUG = BluetoothUtils.D;
- private static final String KEY = "audio_streams_scan_qr_code";
- private final BluetoothCallback mBluetoothCallback =
- new BluetoothCallback() {
- @Override
- public void onActiveDeviceChanged(
- @Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) {
- if (bluetoothProfile == BluetoothProfile.LE_AUDIO) {
- updateVisibility();
- }
- }
- };
-
- private final LocalBluetoothManager mLocalBtManager;
- private AudioStreamsDashboardFragment mFragment;
- private Preference mPreference;
-
- public AudioStreamsScanQrCodeController(Context context, String preferenceKey) {
- super(context, preferenceKey);
- mLocalBtManager = Utils.getLocalBtManager(mContext);
- }
-
- public void setFragment(AudioStreamsDashboardFragment fragment) {
- mFragment = fragment;
- }
-
- @Override
- public void onStart(@NonNull LifecycleOwner owner) {
- if (mLocalBtManager != null) {
- mLocalBtManager.getEventManager().registerCallback(mBluetoothCallback);
- }
- }
-
- @Override
- public void onStop(@NonNull LifecycleOwner owner) {
- if (mLocalBtManager != null) {
- mLocalBtManager.getEventManager().unregisterCallback(mBluetoothCallback);
- }
- }
-
- @Override
- public int getAvailabilityStatus() {
- return AVAILABLE;
- }
-
- @Override
- public String getPreferenceKey() {
- return KEY;
- }
-
- @Override
- public void displayPreference(PreferenceScreen screen) {
- super.displayPreference(screen);
- mPreference = screen.findPreference(getPreferenceKey());
- if (mPreference == null) {
- Log.w(TAG, "displayPreference() mPreference is null!");
- return;
- }
- mPreference.setOnPreferenceClickListener(
- preference -> {
- if (mFragment == null) {
- Log.w(TAG, "displayPreference() mFragment is null!");
- return false;
- }
- if (preference.getKey().equals(KEY)) {
- Intent intent = new Intent(mContext, QrCodeScanModeActivity.class);
- intent.setAction(
- BluetoothBroadcastUtils.ACTION_BLUETOOTH_LE_AUDIO_QR_CODE_SCANNER);
- mFragment.startActivityForResult(intent, REQUEST_SCAN_BT_BROADCAST_QR_CODE);
- if (DEBUG) {
- Log.w(TAG, "displayPreference() sent intent : " + intent);
- }
- return true;
- }
- return false;
- });
- }
-
- private void updateVisibility() {
- ThreadUtils.postOnBackgroundThread(
- () -> {
- boolean hasActiveLe =
- AudioSharingUtils.getActiveSinkOnAssistant(mLocalBtManager).isPresent();
- ThreadUtils.postOnMainThread(() -> mPreference.setVisible(hasActiveLe));
- });
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/qrcode/QrCodeScanModeActivity.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/qrcode/QrCodeScanModeActivity.java
deleted file mode 100644
index 091ebcb..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/qrcode/QrCodeScanModeActivity.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing.audiostreams.qrcode;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.util.Log;
-
-import androidx.fragment.app.FragmentTransaction;
-
-import com.android.settings.R;
-import com.android.settingslib.bluetooth.BluetoothBroadcastUtils;
-import com.android.settingslib.bluetooth.BluetoothUtils;
-
-/**
- * Finding a broadcast through QR code.
- *
- * <p>To use intent action {@link
- * BluetoothBroadcastUtils#ACTION_BLUETOOTH_LE_AUDIO_QR_CODE_SCANNER}, specify the bluetooth device
- * sink of the broadcast to be provisioned in {@link
- * BluetoothBroadcastUtils#EXTRA_BLUETOOTH_DEVICE_SINK} and check the operation for all coordinated
- * set members throughout one session or not by {@link
- * BluetoothBroadcastUtils#EXTRA_BLUETOOTH_SINK_IS_GROUP}.
- */
-public class QrCodeScanModeActivity extends QrCodeScanModeBaseActivity {
- private static final boolean DEBUG = BluetoothUtils.D;
- private static final String TAG = "QrCodeScanModeActivity";
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- }
-
- @Override
- protected void handleIntent(Intent intent) {
- String action = intent != null ? intent.getAction() : null;
- if (DEBUG) {
- Log.d(TAG, "handleIntent(), action = " + action);
- }
-
- if (action == null) {
- finish();
- return;
- }
-
- switch (action) {
- case BluetoothBroadcastUtils.ACTION_BLUETOOTH_LE_AUDIO_QR_CODE_SCANNER:
- showQrCodeScannerFragment(intent);
- break;
- default:
- if (DEBUG) {
- Log.e(TAG, "Launch with an invalid action");
- }
- finish();
- }
- }
-
- protected void showQrCodeScannerFragment(Intent intent) {
- if (intent == null) {
- if (DEBUG) {
- Log.d(TAG, "intent is null, can not get bluetooth information from intent.");
- }
- return;
- }
-
- if (DEBUG) {
- Log.d(TAG, "showQrCodeScannerFragment");
- }
-
- if (DEBUG) {
- Log.d(TAG, "get extra from intent");
- }
-
- QrCodeScanModeFragment fragment =
- (QrCodeScanModeFragment)
- mFragmentManager.findFragmentByTag(
- BluetoothBroadcastUtils.TAG_FRAGMENT_QR_CODE_SCANNER);
-
- if (fragment == null) {
- fragment = new QrCodeScanModeFragment();
- } else {
- if (fragment.isVisible()) {
- return;
- }
-
- // When the fragment in back stack but not on top of the stack, we can simply pop
- // stack because current fragment transactions are arranged in an order
- mFragmentManager.popBackStackImmediate();
- return;
- }
- final FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
-
- fragmentTransaction.replace(
- R.id.fragment_container,
- fragment,
- BluetoothBroadcastUtils.TAG_FRAGMENT_QR_CODE_SCANNER);
- fragmentTransaction.commit();
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/qrcode/QrCodeScanModeBaseActivity.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/qrcode/QrCodeScanModeBaseActivity.java
deleted file mode 100644
index 637014a..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/qrcode/QrCodeScanModeBaseActivity.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing.audiostreams.qrcode;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.SystemProperties;
-
-import androidx.fragment.app.FragmentManager;
-
-import com.android.settings.R;
-import com.android.settingslib.core.lifecycle.ObservableActivity;
-
-import com.google.android.setupdesign.util.ThemeHelper;
-import com.google.android.setupdesign.util.ThemeResolver;
-
-public abstract class QrCodeScanModeBaseActivity extends ObservableActivity {
-
- private static final String THEME_KEY = "setupwizard.theme";
- private static final String THEME_DEFAULT_VALUE = "SudThemeGlifV3_DayNight";
- protected FragmentManager mFragmentManager;
-
- protected abstract void handleIntent(Intent intent);
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- int defaultTheme =
- ThemeHelper.isSetupWizardDayNightEnabled(this)
- ? com.google.android.setupdesign.R.style.SudThemeGlifV3_DayNight
- : com.google.android.setupdesign.R.style.SudThemeGlifV3_Light;
- ThemeResolver themeResolver =
- new ThemeResolver.Builder(ThemeResolver.getDefault())
- .setDefaultTheme(defaultTheme)
- .setUseDayNight(true)
- .build();
- setTheme(
- themeResolver.resolve(
- SystemProperties.get(THEME_KEY, THEME_DEFAULT_VALUE),
- /* suppressDayNight= */ !ThemeHelper.isSetupWizardDayNightEnabled(this)));
-
- setContentView(R.layout.qrcode_scan_mode_activity);
- mFragmentManager = getSupportFragmentManager();
-
- if (savedInstanceState == null) {
- handleIntent(getIntent());
- }
- }
-}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/qrcode/QrCodeScanModeFragment.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/qrcode/QrCodeScanModeFragment.java
deleted file mode 100644
index 378128d..0000000
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/qrcode/QrCodeScanModeFragment.java
+++ /dev/null
@@ -1,269 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing.audiostreams.qrcode;
-
-import android.app.Activity;
-import android.app.settings.SettingsEnums;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Matrix;
-import android.graphics.Outline;
-import android.graphics.Rect;
-import android.graphics.SurfaceTexture;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
-import android.util.Log;
-import android.util.Size;
-import android.view.LayoutInflater;
-import android.view.TextureView;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewOutlineProvider;
-import android.view.accessibility.AccessibilityEvent;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.StringRes;
-
-import com.android.settings.R;
-import com.android.settings.core.InstrumentedFragment;
-import com.android.settingslib.bluetooth.BluetoothBroadcastUtils;
-import com.android.settingslib.bluetooth.BluetoothUtils;
-import com.android.settingslib.qrcode.QrCamera;
-
-import java.time.Duration;
-
-public class QrCodeScanModeFragment extends InstrumentedFragment
- implements TextureView.SurfaceTextureListener, QrCamera.ScannerCallback {
- private static final boolean DEBUG = BluetoothUtils.D;
- private static final String TAG = "QrCodeScanModeFragment";
-
- /** Message sent to hide error message */
- private static final int MESSAGE_HIDE_ERROR_MESSAGE = 1;
-
- /** Message sent to show error message */
- private static final int MESSAGE_SHOW_ERROR_MESSAGE = 2;
-
- /** Message sent to broadcast QR code */
- private static final int MESSAGE_SCAN_BROADCAST_SUCCESS = 3;
-
- private static final long SHOW_ERROR_MESSAGE_INTERVAL = 10000;
- private static final long SHOW_SUCCESS_SQUARE_INTERVAL = 1000;
-
- private static final Duration VIBRATE_DURATION_QR_CODE_RECOGNITION = Duration.ofMillis(3);
-
- public static final String KEY_BROADCAST_METADATA = "key_broadcast_metadata";
-
- private int mCornerRadius;
- private String mBroadcastMetadata;
- private Context mContext;
- private QrCamera mCamera;
- private TextureView mTextureView;
- private TextView mSummary;
- private TextView mErrorMessage;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mContext = getContext();
- }
-
- @Override
- public final View onCreateView(
- LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- return inflater.inflate(
- R.layout.qrcode_scanner_fragment, container, /* attachToRoot */ false);
- }
-
- @Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- mTextureView = view.findViewById(R.id.preview_view);
- mCornerRadius =
- mContext.getResources().getDimensionPixelSize(R.dimen.qrcode_preview_radius);
- mTextureView.setSurfaceTextureListener(this);
- mTextureView.setOutlineProvider(
- new ViewOutlineProvider() {
- @Override
- public void getOutline(View view, Outline outline) {
- outline.setRoundRect(
- 0, 0, view.getWidth(), view.getHeight(), mCornerRadius);
- }
- });
- mTextureView.setClipToOutline(true);
- mErrorMessage = view.findViewById(R.id.error_message);
- }
-
- private void initCamera(SurfaceTexture surface) {
- // Check if the camera has already created.
- if (mCamera == null) {
- mCamera = new QrCamera(mContext, this);
- mCamera.start(surface);
- }
- }
-
- private void destroyCamera() {
- if (mCamera != null) {
- mCamera.stop();
- mCamera = null;
- }
- }
-
- @Override
- public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) {
- initCamera(surface);
- }
-
- @Override
- public void onSurfaceTextureSizeChanged(
- @NonNull SurfaceTexture surface, int width, int height) {}
-
- @Override
- public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
- destroyCamera();
- return true;
- }
-
- @Override
- public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {}
-
- @Override
- public void handleSuccessfulResult(String qrCode) {
- if (DEBUG) {
- Log.d(TAG, "handleSuccessfulResult(), get the qr code string.");
- }
- mBroadcastMetadata = qrCode;
- handleBtLeAudioScanner();
- }
-
- @Override
- public void handleCameraFailure() {
- destroyCamera();
- }
-
- @Override
- public Size getViewSize() {
- return new Size(mTextureView.getWidth(), mTextureView.getHeight());
- }
-
- @Override
- public Rect getFramePosition(Size previewSize, int cameraOrientation) {
- return new Rect(0, 0, previewSize.getHeight(), previewSize.getHeight());
- }
-
- @Override
- public void setTransform(Matrix transform) {
- mTextureView.setTransform(transform);
- }
-
- @Override
- public boolean isValid(String qrCode) {
- if (qrCode.startsWith(BluetoothBroadcastUtils.SCHEME_BT_BROADCAST_METADATA)) {
- return true;
- } else {
- showErrorMessage(R.string.bt_le_audio_qr_code_is_not_valid_format);
- return false;
- }
- }
-
- protected boolean isDecodeTaskAlive() {
- return mCamera != null && mCamera.isDecodeTaskAlive();
- }
-
- private final Handler mHandler =
- new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MESSAGE_HIDE_ERROR_MESSAGE:
- mErrorMessage.setVisibility(View.INVISIBLE);
- break;
-
- case MESSAGE_SHOW_ERROR_MESSAGE:
- final String errorMessage = (String) msg.obj;
-
- mErrorMessage.setVisibility(View.VISIBLE);
- mErrorMessage.setText(errorMessage);
- mErrorMessage.sendAccessibilityEvent(
- AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
-
- // Cancel any pending messages to hide error view and requeue the
- // message so
- // user has time to see error
- removeMessages(MESSAGE_HIDE_ERROR_MESSAGE);
- sendEmptyMessageDelayed(
- MESSAGE_HIDE_ERROR_MESSAGE, SHOW_ERROR_MESSAGE_INTERVAL);
- break;
-
- case MESSAGE_SCAN_BROADCAST_SUCCESS:
- Log.d(TAG, "scan success");
- final Intent resultIntent = new Intent();
- resultIntent.putExtra(KEY_BROADCAST_METADATA, mBroadcastMetadata);
- getActivity().setResult(Activity.RESULT_OK, resultIntent);
- notifyUserForQrCodeRecognition();
- break;
- default:
- }
- }
- };
-
- private void notifyUserForQrCodeRecognition() {
- if (mCamera != null) {
- mCamera.stop();
- }
-
- mErrorMessage.setVisibility(View.INVISIBLE);
- mTextureView.setVisibility(View.INVISIBLE);
-
- triggerVibrationForQrCodeRecognition(getContext());
-
- getActivity().finish();
- }
-
- private static void triggerVibrationForQrCodeRecognition(Context context) {
- Vibrator vibrator = context.getSystemService(Vibrator.class);
- if (vibrator == null) {
- return;
- }
- vibrator.vibrate(
- VibrationEffect.createOneShot(
- VIBRATE_DURATION_QR_CODE_RECOGNITION.toMillis(),
- VibrationEffect.DEFAULT_AMPLITUDE));
- }
-
- private void showErrorMessage(@StringRes int messageResId) {
- final Message message =
- mHandler.obtainMessage(MESSAGE_SHOW_ERROR_MESSAGE, getString(messageResId));
- message.sendToTarget();
- }
-
- private void handleBtLeAudioScanner() {
- Message message = mHandler.obtainMessage(MESSAGE_SCAN_BROADCAST_SUCCESS);
- mHandler.sendMessageDelayed(message, SHOW_SUCCESS_SQUARE_INTERVAL);
- }
-
- private void updateSummary() {
- mSummary.setText(getString(R.string.bt_le_audio_scan_qr_code_scanner));
- }
-
- @Override
- public int getMetricsCategory() {
- return SettingsEnums.LE_AUDIO_BROADCAST_SCAN_QR_CODE;
- }
-}
diff --git a/src/com/android/settings/connecteddevice/threadnetwork/OWNERS b/src/com/android/settings/connecteddevice/threadnetwork/OWNERS
new file mode 100644
index 0000000..4a35359
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/threadnetwork/OWNERS
@@ -0,0 +1 @@
+include platform/packages/modules/Connectivity:/thread/OWNERS
diff --git a/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkPreferenceController.kt b/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkPreferenceController.kt
new file mode 100644
index 0000000..10e3f84
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/threadnetwork/ThreadNetworkPreferenceController.kt
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2024 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.connecteddevice.threadnetwork
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.PackageManager
+import android.net.thread.ThreadNetworkController
+import android.net.thread.ThreadNetworkController.StateCallback
+import android.net.thread.ThreadNetworkException
+import android.net.thread.ThreadNetworkManager
+import android.os.OutcomeReceiver
+import android.provider.Settings
+import android.util.Log
+import androidx.annotation.VisibleForTesting
+import androidx.core.content.ContextCompat
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
+import androidx.lifecycle.LifecycleOwner
+import androidx.preference.Preference
+import androidx.preference.PreferenceScreen
+import com.android.net.thread.platform.flags.Flags
+import com.android.settings.R
+import com.android.settings.core.TogglePreferenceController
+import java.util.concurrent.Executor
+
+/** Controller for the "Thread" toggle in "Connected devices > Connection preferences". */
+class ThreadNetworkPreferenceController @VisibleForTesting constructor(
+ context: Context,
+ key: String,
+ private val executor: Executor,
+ private val threadController: BaseThreadNetworkController?
+) : TogglePreferenceController(context, key), LifecycleEventObserver {
+ private val stateCallback: StateCallback
+ private val airplaneModeReceiver: BroadcastReceiver
+ private var threadEnabled = false
+ private var airplaneModeOn = false
+ private var preference: Preference? = null
+
+ /**
+ * A testable interface for [ThreadNetworkController] which is `final`.
+ *
+ * We are in a awkward situation that Android API guideline suggest `final` for API classes
+ * while Robolectric test is being deprecated for platform testing (See
+ * tests/robotests/new_tests_hook.sh). This force us to use "mockito-target-extended" but it's
+ * conflicting with the default "mockito-target" which is somehow indirectly depended by the
+ * `SettingsUnitTests` target.
+ */
+ @VisibleForTesting
+ interface BaseThreadNetworkController {
+ fun setEnabled(
+ enabled: Boolean,
+ executor: Executor,
+ receiver: OutcomeReceiver<Void?, ThreadNetworkException>
+ )
+
+ fun registerStateCallback(executor: Executor, callback: StateCallback)
+
+ fun unregisterStateCallback(callback: StateCallback)
+ }
+
+ constructor(context: Context, key: String) : this(
+ context,
+ key,
+ ContextCompat.getMainExecutor(context),
+ getThreadNetworkController(context)
+ )
+
+ init {
+ stateCallback = newStateCallback()
+ airplaneModeReceiver = newAirPlaneModeReceiver()
+ }
+
+ val isThreadSupportedOnDevice: Boolean
+ get() = threadController != null
+
+ private fun newStateCallback(): StateCallback {
+ return object : StateCallback {
+ override fun onThreadEnableStateChanged(enabledState: Int) {
+ threadEnabled = enabledState == ThreadNetworkController.STATE_ENABLED
+ }
+
+ override fun onDeviceRoleChanged(role: Int) {}
+ }
+ }
+
+ private fun newAirPlaneModeReceiver(): BroadcastReceiver {
+ return object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ airplaneModeOn = isAirplaneModeOn(context)
+ Log.i(TAG, "Airplane mode is " + if (airplaneModeOn) "ON" else "OFF")
+ preference?.let { preference -> updateState(preference) }
+ }
+ }
+ }
+
+ override fun getAvailabilityStatus(): Int {
+ return if (!Flags.threadEnabledPlatform()) {
+ CONDITIONALLY_UNAVAILABLE
+ } else if (!isThreadSupportedOnDevice) {
+ UNSUPPORTED_ON_DEVICE
+ } else if (airplaneModeOn) {
+ DISABLED_DEPENDENT_SETTING
+ } else {
+ AVAILABLE
+ }
+ }
+
+ override fun displayPreference(screen: PreferenceScreen) {
+ super.displayPreference(screen)
+ preference = screen.findPreference(preferenceKey)
+ }
+
+ override fun isChecked(): Boolean {
+ // TODO (b/322742298):
+ // Check airplane mode here because it's planned to disable Thread state in airplane mode
+ // (code in the mainline module). But it's currently not implemented yet (b/322742298).
+ // By design, the toggle should be unchecked in airplane mode, so explicitly check the
+ // airplane mode here to acchieve the same UX.
+ return !airplaneModeOn && threadEnabled
+ }
+
+ override fun setChecked(isChecked: Boolean): Boolean {
+ if (threadController == null) {
+ return false
+ }
+ val action = if (isChecked) "enable" else "disable"
+ threadController.setEnabled(
+ isChecked,
+ executor,
+ object : OutcomeReceiver<Void?, ThreadNetworkException> {
+ override fun onError(e: ThreadNetworkException) {
+ // TODO(b/327549838): gracefully handle the failure by resetting the UI state
+ Log.e(TAG, "Failed to $action Thread", e)
+ }
+
+ override fun onResult(unused: Void?) {
+ Log.d(TAG, "Successfully $action Thread")
+ }
+ })
+ return true
+ }
+
+ override fun onStateChanged(lifecycleOwner: LifecycleOwner, event: Lifecycle.Event) {
+ if (threadController == null) {
+ return
+ }
+
+ when (event) {
+ Lifecycle.Event.ON_START -> {
+ threadController.registerStateCallback(executor, stateCallback)
+ airplaneModeOn = isAirplaneModeOn(mContext)
+ mContext.registerReceiver(
+ airplaneModeReceiver,
+ IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED)
+ )
+ preference?.let { preference -> updateState(preference) }
+ }
+ Lifecycle.Event.ON_STOP -> {
+ threadController.unregisterStateCallback(stateCallback)
+ mContext.unregisterReceiver(airplaneModeReceiver)
+ }
+ else -> {}
+ }
+ }
+
+ override fun updateState(preference: Preference) {
+ super.updateState(preference)
+ preference.isEnabled = !airplaneModeOn
+ refreshSummary(preference)
+ }
+
+ override fun getSummary(): CharSequence {
+ val resId: Int = if (airplaneModeOn) {
+ R.string.thread_network_settings_summary_airplane_mode
+ } else {
+ R.string.thread_network_settings_summary
+ }
+ return mContext.getResources().getString(resId)
+ }
+
+ override fun getSliceHighlightMenuRes(): Int {
+ return R.string.menu_key_connected_devices
+ }
+
+ companion object {
+ private const val TAG = "ThreadNetworkSettings"
+ private fun getThreadNetworkController(context: Context): BaseThreadNetworkController? {
+ if (!context.packageManager.hasSystemFeature(PackageManager.FEATURE_THREAD_NETWORK)) {
+ return null
+ }
+ val manager = context.getSystemService(ThreadNetworkManager::class.java) ?: return null
+ val controller = manager.allThreadNetworkControllers[0]
+ return object : BaseThreadNetworkController {
+ override fun setEnabled(
+ enabled: Boolean,
+ executor: Executor,
+ receiver: OutcomeReceiver<Void?, ThreadNetworkException>
+ ) {
+ controller.setEnabled(enabled, executor, receiver)
+ }
+
+ override fun registerStateCallback(executor: Executor, callback: StateCallback) {
+ controller.registerStateCallback(executor, callback)
+ }
+
+ override fun unregisterStateCallback(callback: StateCallback) {
+ controller.unregisterStateCallback(callback)
+ }
+ }
+ }
+
+ private fun isAirplaneModeOn(context: Context): Boolean {
+ return Settings.Global.getInt(
+ context.contentResolver,
+ Settings.Global.AIRPLANE_MODE_ON,
+ 0
+ ) == 1
+ }
+ }
+}
diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java
index e3131f7..e08e856 100644
--- a/src/com/android/settings/core/gateway/SettingsGateway.java
+++ b/src/com/android/settings/core/gateway/SettingsGateway.java
@@ -29,6 +29,7 @@
import com.android.settings.accessibility.AccessibilitySettingsForSetupWizard;
import com.android.settings.accessibility.CaptioningPropertiesFragment;
import com.android.settings.accessibility.ColorAndMotionFragment;
+import com.android.settings.accessibility.ColorContrastFragment;
import com.android.settings.accessibility.TextReadingPreferenceFragment;
import com.android.settings.accessibility.TextReadingPreferenceFragmentForSetupWizard;
import com.android.settings.accessibility.ToggleColorInversionPreferenceFragment;
@@ -86,7 +87,6 @@
import com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment;
import com.android.settings.connecteddevice.NfcAndPaymentFragment;
import com.android.settings.connecteddevice.PreviouslyConnectedDeviceDashboardFragment;
-import com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamConfirmDialog;
import com.android.settings.connecteddevice.stylus.StylusUsiDetailsFragment;
import com.android.settings.connecteddevice.usb.UsbDetailsFragment;
import com.android.settings.datausage.DataSaverSummary;
@@ -359,7 +359,6 @@
DataUsageList.class.getName(),
ToggleBackupSettingFragment.class.getName(),
PreviouslyConnectedDeviceDashboardFragment.class.getName(),
- AudioStreamConfirmDialog.class.getName(),
BatterySaverScheduleSettings.class.getName(),
MobileNetworkListFragment.class.getName(),
PowerMenuSettings.class.getName(),
@@ -381,6 +380,7 @@
TurnScreenOnDetails.class.getName(),
NfcAndPaymentFragment.class.getName(),
ColorAndMotionFragment.class.getName(),
+ ColorContrastFragment.class.getName(),
LongBackgroundTasksDetails.class.getName(),
RegionalPreferencesEntriesFragment.class.getName(),
BatteryInfoFragment.class.getName(),
diff --git a/src/com/android/settings/datausage/lib/AppDataUsageRepository.kt b/src/com/android/settings/datausage/lib/AppDataUsageRepository.kt
index 1844a7a..5abde31 100644
--- a/src/com/android/settings/datausage/lib/AppDataUsageRepository.kt
+++ b/src/com/android/settings/datausage/lib/AppDataUsageRepository.kt
@@ -18,10 +18,12 @@
import android.app.usage.NetworkStats
import android.content.Context
+import android.content.pm.UserProperties
import android.net.NetworkPolicyManager
import android.net.NetworkTemplate
import android.os.Process
import android.os.UserHandle
+import android.os.UserManager
import android.util.SparseArray
import android.util.SparseBooleanArray
import androidx.annotation.VisibleForTesting
@@ -50,7 +52,11 @@
val items = ArrayList<AppItem>()
val knownItems = SparseArray<AppItem>()
val profiles = context.userManager.userProfiles
- bindStats(buckets, profiles, knownItems, items)
+ val userManager : UserManager = context.getSystemService(Context.USER_SERVICE) as UserManager
+ val userIdToIsHiddenMap = profiles.associate { profile ->
+ profile.identifier to shouldSkipProfile(userManager, profile)
+ }
+ bindStats(buckets, userIdToIsHiddenMap, knownItems, items)
val restrictedUids = context.getSystemService(NetworkPolicyManager::class.java)!!
.getUidsWithPolicy(NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND)
for (uid in restrictedUids) {
@@ -98,7 +104,7 @@
private fun bindStats(
buckets: List<Bucket>,
- profiles: List<UserHandle>,
+ userIdToIsHiddenMap: Map<Int, Boolean>,
knownItems: SparseArray<AppItem>,
items: ArrayList<AppItem>,
) {
@@ -108,8 +114,11 @@
val collapseKey: Int
val category: Int
val userId = UserHandle.getUserId(uid)
+ if(userIdToIsHiddenMap[userId] == true) {
+ continue
+ }
if (UserHandle.isApp(uid) || Process.isSdkSandboxUid(uid)) {
- if (profiles.contains(UserHandle(userId))) {
+ if (userIdToIsHiddenMap.keys.contains(userId)) {
if (userId != currentUserId) {
// Add to a managed user item.
accumulate(
@@ -153,6 +162,16 @@
}
}
+ private fun shouldSkipProfile(userManager : UserManager, userHandle: UserHandle): Boolean {
+ if (android.os.Flags.allowPrivateProfile()
+ && android.multiuser.Flags.handleInterleavedSettingsForPrivateSpace()) {
+ return (userManager.isQuietModeEnabled(userHandle)
+ && userManager.getUserProperties(userHandle).showInQuietMode
+ == UserProperties.SHOW_IN_QUIET_MODE_HIDDEN)
+ }
+ return false
+ }
+
/**
* Accumulate data usage of a network stats entry for the item mapped by the collapse key.
* Creates the item if needed.
diff --git a/src/com/android/settings/development/ForcePeakRefreshRatePreferenceController.java b/src/com/android/settings/development/ForcePeakRefreshRatePreferenceController.java
index abeb949..455f74f 100644
--- a/src/com/android/settings/development/ForcePeakRefreshRatePreferenceController.java
+++ b/src/com/android/settings/development/ForcePeakRefreshRatePreferenceController.java
@@ -17,6 +17,7 @@
package com.android.settings.development;
import static com.android.internal.display.RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE;
+import static com.android.internal.display.RefreshRateSettingsUtils.findHighestRefreshRateAmongAllDisplays;
import static com.android.internal.display.RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay;
import android.content.Context;
@@ -47,7 +48,9 @@
public ForcePeakRefreshRatePreferenceController(Context context) {
super(context);
- mPeakRefreshRate = findHighestRefreshRateForDefaultDisplay(context);
+ mPeakRefreshRate = Flags.backUpSmoothDisplayAndForcePeakRefreshRate()
+ ? findHighestRefreshRateAmongAllDisplays(context)
+ : findHighestRefreshRateForDefaultDisplay(context);
Log.d(TAG, "DEFAULT_REFRESH_RATE : " + DEFAULT_REFRESH_RATE
+ " mPeakRefreshRate : " + mPeakRefreshRate);
}
diff --git a/src/com/android/settings/development/SensitiveContentProtectionPreferenceController.kt b/src/com/android/settings/development/SensitiveContentProtectionPreferenceController.kt
index e627f81..8acd700 100644
--- a/src/com/android/settings/development/SensitiveContentProtectionPreferenceController.kt
+++ b/src/com/android/settings/development/SensitiveContentProtectionPreferenceController.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.permission.flags.Flags.sensitiveNotificationAppProtection
import android.provider.Settings
+import android.view.flags.Flags.sensitiveContentAppProtection
import androidx.annotation.VisibleForTesting
import androidx.preference.Preference
import androidx.preference.TwoStatePreference
@@ -64,6 +65,7 @@
override fun isAvailable(): Boolean {
return sensitiveNotificationAppProtection() || screenshareNotificationHiding()
+ || sensitiveContentAppProtection()
}
companion object {
diff --git a/src/com/android/settings/deviceinfo/firmwareversion/SecurityPatchLevelPreferenceController.java b/src/com/android/settings/deviceinfo/firmwareversion/SecurityPatchLevelPreferenceController.java
index 1df78a8..dcb5a37 100644
--- a/src/com/android/settings/deviceinfo/firmwareversion/SecurityPatchLevelPreferenceController.java
+++ b/src/com/android/settings/deviceinfo/firmwareversion/SecurityPatchLevelPreferenceController.java
@@ -32,7 +32,7 @@
private static final String TAG = "SecurityPatchCtrl";
private static final Uri INTENT_URI_DATA = Uri.parse(
- "https://source.android.com/security/bulletin/");
+ "https://source.android.com/docs/security/bulletin/");
private final PackageManager mPackageManager;
private final String mCurrentPatch;
diff --git a/src/com/android/settings/display/EvenDimmerPreferenceController.java b/src/com/android/settings/display/EvenDimmerPreferenceController.java
new file mode 100644
index 0000000..b86c845
--- /dev/null
+++ b/src/com/android/settings/display/EvenDimmerPreferenceController.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 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.display;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.provider.Settings;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.server.display.feature.flags.Flags;
+import com.android.settings.R;
+import com.android.settings.core.TogglePreferenceController;
+
+/**
+ * Controller for the settings toggle which allows screen brightness to go even dimmer than usual.
+ *
+ */
+public class EvenDimmerPreferenceController extends TogglePreferenceController {
+
+ private static final String TAG = "EvenDimmerPreferenceController";
+
+ private final Resources mResources;
+
+ public EvenDimmerPreferenceController(@NonNull Context context, @NonNull String key) {
+ super(context, key);
+ mResources = context.getResources();
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ // enable based on flag and config.xml
+ final boolean enabledInConfig = mResources.getBoolean(
+ com.android.internal.R.bool.config_evenDimmerEnabled);
+ return (Flags.evenDimmer() && enabledInConfig) ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+ }
+
+ @Override
+ public boolean isChecked() {
+ return getEvenDimmerActivated();
+ }
+
+ @Override
+ public boolean setChecked(boolean isChecked) {
+ final float enabled = getAvailabilityStatus() == AVAILABLE && isChecked ? 1 : 0;
+ Log.i(TAG, "setChecked to : " + enabled);
+
+ return Settings.Secure.putFloat(
+ mContext.getContentResolver(), Settings.Secure.EVEN_DIMMER_ACTIVATED, enabled);
+ }
+
+ @Override
+ public int getSliceHighlightMenuRes() {
+ return R.string.menu_key_display;
+ }
+
+ private boolean getEvenDimmerActivated() {
+ return Settings.Secure.getFloat(mContext.getContentResolver(),
+ Settings.Secure.EVEN_DIMMER_ACTIVATED, 0) == 1;
+ }
+}
diff --git a/src/com/android/settings/display/PeakRefreshRatePreferenceController.java b/src/com/android/settings/display/PeakRefreshRatePreferenceController.java
index 17d763a..261eaf1 100644
--- a/src/com/android/settings/display/PeakRefreshRatePreferenceController.java
+++ b/src/com/android/settings/display/PeakRefreshRatePreferenceController.java
@@ -17,6 +17,7 @@
package com.android.settings.display;
import static com.android.internal.display.RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE;
+import static com.android.internal.display.RefreshRateSettingsUtils.findHighestRefreshRateAmongAllDisplays;
import static com.android.internal.display.RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay;
import android.content.Context;
@@ -66,7 +67,9 @@
updateState(mPreference);
}
};
- mPeakRefreshRate = Math.round(findHighestRefreshRateForDefaultDisplay(context));
+ mPeakRefreshRate = Math.round(Flags.backUpSmoothDisplayAndForcePeakRefreshRate()
+ ? findHighestRefreshRateAmongAllDisplays(context)
+ : findHighestRefreshRateForDefaultDisplay(context));
Log.d(
TAG,
"DEFAULT_REFRESH_RATE : "
diff --git a/src/com/android/settings/homepage/SettingsHomepageActivity.java b/src/com/android/settings/homepage/SettingsHomepageActivity.java
index 5510301..6688831 100644
--- a/src/com/android/settings/homepage/SettingsHomepageActivity.java
+++ b/src/com/android/settings/homepage/SettingsHomepageActivity.java
@@ -187,12 +187,6 @@
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- if (!isTaskRoot() && !isSingleTask()) {
- Log.i(TAG, "Not task root nor single task, finish");
- finish();
- return;
- }
-
mIsEmbeddingActivityEnabled = ActivityEmbeddingUtils.isEmbeddingActivityEnabled(this);
if (mIsEmbeddingActivityEnabled) {
final UserManager um = getSystemService(UserManager.class);
@@ -319,12 +313,6 @@
updateHomepageUI();
}
- private boolean isSingleTask() {
- ActivityInfo info = getIntent().resolveActivityInfo(getPackageManager(),
- PackageManager.MATCH_DEFAULT_ONLY);
- return info.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK;
- }
-
private void updateSplitLayout() {
if (!mIsEmbeddingActivityEnabled) {
return;
diff --git a/src/com/android/settings/network/SimOnboardingActivity.kt b/src/com/android/settings/network/SimOnboardingActivity.kt
index abeeb6c..98bb5d7 100644
--- a/src/com/android/settings/network/SimOnboardingActivity.kt
+++ b/src/com/android/settings/network/SimOnboardingActivity.kt
@@ -16,7 +16,6 @@
package com.android.settings.network
-import android.app.ProgressDialog
import android.content.Context
import android.content.Intent
import android.os.Bundle
@@ -46,12 +45,12 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
@@ -59,12 +58,16 @@
import com.android.settings.R
import com.android.settings.SidecarFragment
import com.android.settings.network.telephony.SubscriptionActionDialogActivity
+import com.android.settings.network.telephony.ToggleSubscriptionDialogActivity
import com.android.settings.spa.SpaActivity.Companion.startSpaActivity
import com.android.settings.spa.network.SimOnboardingPageProvider.getRoute
import com.android.settingslib.spa.SpaBaseDialogActivity
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
+import com.android.settingslib.spa.widget.dialog.AlertDialogButton
import com.android.settingslib.spa.widget.dialog.getDialogWidth
+import com.android.settingslib.spa.widget.dialog.rememberAlertDialogPresenter
+import com.android.settingslib.spa.widget.editor.SettingsOutlinedTextField
import com.android.settingslib.spa.widget.ui.SettingsTitle
import com.android.settingslib.spaprivileged.framework.common.userManager
import kotlinx.coroutines.CoroutineScope
@@ -78,8 +81,8 @@
class SimOnboardingActivity : SpaBaseDialogActivity() {
lateinit var scope: CoroutineScope
lateinit var showBottomSheet: MutableState<Boolean>
- lateinit var showError: MutableState<Boolean>
- lateinit var showDialog: MutableState<Boolean>
+ lateinit var showError: MutableState<ErrorType>
+ lateinit var showProgressDialog: MutableState<Boolean>
private var switchToEuiccSubscriptionSidecar: SwitchToEuiccSubscriptionSidecar? = null
private var switchToRemovableSlotSidecar: SwitchToRemovableSlotSidecar? = null
@@ -101,13 +104,19 @@
return
}
+ if (onboardingService.activeSubInfoList.isEmpty()) {
+ // TODO: refactor and replace the ToggleSubscriptionDialogActivity
+ Log.d(TAG, "onboardingService.activeSubInfoList is empty" +
+ ", start ToggleSubscriptionDialogActivity")
+ this.startActivity(ToggleSubscriptionDialogActivity
+ .getIntent(this.applicationContext, targetSubId, true))
+ finish()
+ return
+ }
+
switchToEuiccSubscriptionSidecar = SwitchToEuiccSubscriptionSidecar.get(fragmentManager)
switchToRemovableSlotSidecar = SwitchToRemovableSlotSidecar.get(fragmentManager)
enableMultiSimSidecar = EnableMultiSimSidecar.get(fragmentManager)
-
- setContent {
- Content()
- }
}
override fun finish() {
@@ -116,15 +125,14 @@
super.finish()
}
- var callbackListener: (Int) -> Unit = {
+ var callbackListener: (CallbackType) -> Unit = {
Log.d(TAG, "Receive the CALLBACK: $it")
when (it) {
- CALLBACK_ERROR -> {
+ CallbackType.CALLBACK_ERROR -> {
setProgressDialog(false)
- showError.value = true
}
- CALLBACK_ONBOARDING_COMPLETE -> {
+ CallbackType.CALLBACK_ONBOARDING_COMPLETE -> {
showBottomSheet.value = false
setProgressDialog(true)
scope.launch {
@@ -134,26 +142,29 @@
}
}
- CALLBACK_SETUP_NAME -> {
+ CallbackType.CALLBACK_SETUP_NAME -> {
scope.launch {
onboardingService.startSetupName()
}
}
- CALLBACK_SETUP_PRIMARY_SIM -> {
+ CallbackType.CALLBACK_SETUP_PRIMARY_SIM -> {
scope.launch {
onboardingService.startSetupPrimarySim(this@SimOnboardingActivity)
}
}
- CALLBACK_FINISH -> {
+ CallbackType.CALLBACK_FINISH -> {
finish()
}
}
}
fun setProgressDialog(enable: Boolean) {
- showDialog.value = enable
+ if (!this::showProgressDialog.isInitialized) {
+ return
+ }
+ showProgressDialog.value = enable
val progressState = if (enable) {
SubscriptionActionDialogActivity.PROGRESS_IS_SHOWING
} else {
@@ -165,16 +176,19 @@
@OptIn(ExperimentalMaterial3Api::class)
@Composable
override fun Content() {
- showBottomSheet = remember { mutableStateOf(true) }
- showError = remember { mutableStateOf(false) }
- showDialog = remember { mutableStateOf(false) }
+ showBottomSheet = remember { mutableStateOf(false) }
+ showError = remember { mutableStateOf(ErrorType.ERROR_NONE) }
+ showProgressDialog = remember { mutableStateOf(false) }
scope = rememberCoroutineScope()
registerSidecarReceiverFlow()
- if(showError.value){
- // show error
- return
+ ErrorDialogImpl()
+
+ LaunchedEffect(Unit) {
+ if (onboardingService.activeSubInfoList.isNotEmpty()) {
+ showBottomSheet.value = true
+ }
}
if (showBottomSheet.value) {
@@ -195,7 +209,9 @@
},
cancelAction = { finish() },
)
- } else {
+ }
+
+ if(showProgressDialog.value) {
ProgressDialogImpl()
}
}
@@ -203,34 +219,32 @@
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ProgressDialogImpl() {
- if(showDialog.value) {
- // TODO: Create the SPA's ProgressDialog and using SPA's widget
- BasicAlertDialog(
- onDismissRequest = {},
- modifier = Modifier.width(
- getDialogWidth()
- ),
+ // TODO: Create the SPA's ProgressDialog and using SPA's widget
+ BasicAlertDialog(
+ onDismissRequest = {},
+ modifier = Modifier.width(
+ getDialogWidth()
+ ),
+ ) {
+ Surface(
+ color = AlertDialogDefaults.containerColor,
+ shape = AlertDialogDefaults.shape
) {
- Surface(
- color = AlertDialogDefaults.containerColor,
- shape = AlertDialogDefaults.shape
- ) {
- Row(
- modifier = Modifier
+ Row(
+ modifier = Modifier
.fillMaxWidth()
.padding(SettingsDimension.itemPaddingStart),
- verticalAlignment = Alignment.CenterVertically
- ) {
- CircularProgressIndicator()
- Column(modifier = Modifier
- .padding(start = SettingsDimension.itemPaddingStart)) {
- SettingsTitle(
- stringResource(
- R.string.sim_onboarding_progressbar_turning_sim_on,
- onboardingService.targetSubInfo?.displayName ?: ""
- )
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ CircularProgressIndicator()
+ Column(modifier = Modifier
+ .padding(start = SettingsDimension.itemPaddingStart)) {
+ SettingsTitle(
+ stringResource(
+ R.string.sim_onboarding_progressbar_turning_sim_on,
+ onboardingService.targetSubInfo?.displayName ?: ""
)
- }
+ )
}
}
}
@@ -238,6 +252,56 @@
}
@Composable
+ fun ErrorDialogImpl(){
+ // EuiccSlotSidecar showErrorDialog
+ val errorDialogPresenterForEuiccSlotSidecar = rememberAlertDialogPresenter(
+ confirmButton = AlertDialogButton(
+ stringResource(android.R.string.ok)
+ ) {
+ finish()
+ },
+ title = stringResource(R.string.privileged_action_disable_fail_title),
+ text = {
+ Text(stringResource(R.string.privileged_action_disable_fail_text))
+ },
+ )
+
+ // RemovableSlotSidecar showErrorDialog
+ val errorDialogPresenterForRemovableSlotSidecar = rememberAlertDialogPresenter(
+ confirmButton = AlertDialogButton(
+ stringResource(android.R.string.ok)
+ ) {
+ finish()
+ },
+ title = stringResource(R.string.sim_action_enable_sim_fail_title),
+ text = {
+ Text(stringResource(R.string.sim_action_enable_sim_fail_text))
+ },
+ )
+
+ // enableDSDS showErrorDialog
+ val errorDialogPresenterForMultiSimSidecar = rememberAlertDialogPresenter(
+ confirmButton = AlertDialogButton(
+ stringResource(android.R.string.ok)
+ ) {
+ finish()
+ },
+ title = stringResource(R.string.dsds_activation_failure_title),
+ text = {
+ Text(stringResource(R.string.dsds_activation_failure_body_msg2))
+ },
+ )
+
+ // show error
+ when (showError.value) {
+ ErrorType.ERROR_EUICC_SLOT -> errorDialogPresenterForEuiccSlotSidecar.open()
+ ErrorType.ERROR_REMOVABLE_SLOT -> errorDialogPresenterForRemovableSlotSidecar.open()
+ ErrorType.ERROR_ENABLE_DSDS -> errorDialogPresenterForMultiSimSidecar.open()
+ else -> {}
+ }
+ }
+
+ @Composable
fun registerSidecarReceiverFlow(){
switchToEuiccSubscriptionSidecar?.sidecarReceiverFlow()
?.collectLatestWithLifecycle(LocalLifecycleOwner.current) {
@@ -304,13 +368,14 @@
SidecarFragment.State.SUCCESS -> {
Log.i(TAG, "Successfully enable the eSIM profile.")
switchToEuiccSubscriptionSidecar!!.reset()
- callbackListener(CALLBACK_SETUP_NAME)
+ callbackListener(CallbackType.CALLBACK_SETUP_NAME)
}
SidecarFragment.State.ERROR -> {
Log.i(TAG, "Failed to enable the eSIM profile.")
switchToEuiccSubscriptionSidecar!!.reset()
- callbackListener(CALLBACK_ERROR)
+ showError.value = ErrorType.ERROR_EUICC_SLOT
+ callbackListener(CallbackType.CALLBACK_ERROR)
// TODO: showErrorDialog and using privileged_action_disable_fail_title and
// privileged_action_disable_fail_text
}
@@ -323,13 +388,14 @@
Log.i(TAG, "Successfully switched to removable slot.")
switchToRemovableSlotSidecar!!.reset()
onboardingService.handleTogglePsimAction()
- callbackListener(CALLBACK_SETUP_NAME)
+ callbackListener(CallbackType.CALLBACK_SETUP_NAME)
}
SidecarFragment.State.ERROR -> {
Log.e(TAG, "Failed switching to removable slot.")
switchToRemovableSlotSidecar!!.reset()
- callbackListener(CALLBACK_ERROR)
+ showError.value = ErrorType.ERROR_REMOVABLE_SLOT
+ callbackListener(CallbackType.CALLBACK_ERROR)
// TODO: showErrorDialog and using sim_action_enable_sim_fail_title and
// sim_action_enable_sim_fail_text
}
@@ -347,7 +413,8 @@
SidecarFragment.State.ERROR -> {
enableMultiSimSidecar!!.reset()
Log.i(TAG, "Failed to switch to DSDS without rebooting.")
- callbackListener(CALLBACK_ERROR)
+ showError.value = ErrorType.ERROR_ENABLE_DSDS
+ callbackListener(CallbackType.CALLBACK_ERROR)
// TODO: showErrorDialog and using dsds_activation_failure_title and
// dsds_activation_failure_body_msg2
}
@@ -370,7 +437,7 @@
}
Log.i(TAG, "DSDS enabled, start to enable pSIM profile.")
onboardingService.handleTogglePsimAction()
- callbackListener(CALLBACK_FINISH)
+ callbackListener(CallbackType.CALLBACK_FINISH)
}
@Composable
@@ -426,7 +493,7 @@
Log.i(TAG, "setProgressState:$state")
}
- fun initServiceData(context: Context,targetSubId: Int, callback:(Int)->Unit) {
+ fun initServiceData(context: Context,targetSubId: Int, callback:(CallbackType)->Unit) {
onboardingService.initData(targetSubId, context,callback)
}
@@ -445,10 +512,20 @@
var onboardingService:SimOnboardingService = SimOnboardingService()
const val TAG = "SimOnboardingActivity"
const val SUB_ID = "sub_id"
- const val CALLBACK_ERROR = -1
- const val CALLBACK_ONBOARDING_COMPLETE = 1
- const val CALLBACK_SETUP_NAME = 2
- const val CALLBACK_SETUP_PRIMARY_SIM = 3
- const val CALLBACK_FINISH = 4
+
+ enum class ErrorType(val value:Int){
+ ERROR_NONE(-1),
+ ERROR_EUICC_SLOT(1),
+ ERROR_REMOVABLE_SLOT(2),
+ ERROR_ENABLE_DSDS(3)
+ }
+
+ enum class CallbackType(val value:Int){
+ CALLBACK_ERROR(-1),
+ CALLBACK_ONBOARDING_COMPLETE(1),
+ CALLBACK_SETUP_NAME(2),
+ CALLBACK_SETUP_PRIMARY_SIM(3),
+ CALLBACK_FINISH(4)
+ }
}
}
\ No newline at end of file
diff --git a/src/com/android/settings/network/SimOnboardingService.kt b/src/com/android/settings/network/SimOnboardingService.kt
index f33abf6..962741f 100644
--- a/src/com/android/settings/network/SimOnboardingService.kt
+++ b/src/com/android/settings/network/SimOnboardingService.kt
@@ -23,6 +23,7 @@
import android.telephony.UiccCardInfo
import android.telephony.UiccSlotInfo
import android.util.Log
+import com.android.settings.network.SimOnboardingActivity.Companion.CallbackType
import com.android.settings.spa.network.setAutomaticData
import com.android.settings.spa.network.setDefaultData
import com.android.settings.spa.network.setDefaultSms
@@ -31,7 +32,6 @@
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
-
private const val TAG = "SimOnboardingService"
private const val INVALID = SubscriptionManager.INVALID_SUBSCRIPTION_ID
@@ -60,7 +60,7 @@
.map { it.subscriptionId }
.firstOrNull() ?: SubscriptionManager.INVALID_SUBSCRIPTION_ID
}
- var callback: (Int) -> Unit = {}
+ var callback: (CallbackType) -> Unit = {}
var isMultipleEnabledProfilesSupported: Boolean = false
get() {
@@ -135,24 +135,24 @@
userSelectedSubInfoList.clear()
}
- fun initData(inputTargetSubId:Int,context: Context, callback: (Int) -> Unit) {
+ fun initData(inputTargetSubId: Int,
+ context: Context,
+ callback: (CallbackType) -> Unit) {
this.callback = callback
targetSubId = inputTargetSubId
subscriptionManager = context.getSystemService(SubscriptionManager::class.java)
telephonyManager = context.getSystemService(TelephonyManager::class.java)
Log.d(
- TAG, "startInit: targetSubId:$targetSubId"
+ TAG, "startInit: targetSubId:$targetSubId, activeSubInfoList: $activeSubInfoList"
)
+ activeSubInfoList = SubscriptionUtil.getActiveSubscriptions(subscriptionManager)
+
ThreadUtils.postOnBackgroundThread {
- activeSubInfoList = SubscriptionUtil.getActiveSubscriptions(subscriptionManager)
availableSubInfoList = SubscriptionUtil.getAvailableSubscriptions(context)
targetSubInfo =
availableSubInfoList.find { subInfo -> subInfo.subscriptionId == targetSubId }
targetSubInfo?.let { userSelectedSubInfoList.add(it) }
- Log.d(
- TAG, "targetSubId: $targetSubId" + ", targetSubInfo: $targetSubInfo" +
- ". activeSubInfoList: $activeSubInfoList"
- )
+ Log.d(TAG, "targetSubId: $targetSubId , targetSubInfo: $targetSubInfo")
slotInfoList = telephonyManager?.uiccSlotsInfo?.toList() ?: listOf()
Log.d(TAG, "slotInfoList: $slotInfoList.")
uiccCardInfoList = telephonyManager?.uiccCardsInfo!!
@@ -196,6 +196,16 @@
return userSelectedSubInfoList.toList()
}
+ fun getSelectedSubscriptionInfoListWithRenaming(): List<SubscriptionInfo> {
+ if (userSelectedSubInfoList.isEmpty()){
+ Log.d(TAG, "userSelectedSubInfoList is empty")
+ return activeSubInfoList
+ }
+ return userSelectedSubInfoList.map {
+ SubscriptionInfo.Builder(it).setDisplayName(getSubscriptionInfoDisplayName(it)).build()
+ }.toList()
+ }
+
fun addItemForRenaming(subInfo: SubscriptionInfo, newName: String) {
if (subInfo.displayName == newName) {
return
@@ -211,8 +221,12 @@
return renameMutableMap[subInfo.subscriptionId] ?: subInfo.displayName.toString()
}
- fun addCurrentItemForSelectedSim(){
- userSelectedSubInfoList.addAll(activeSubInfoList)
+ fun addCurrentItemForSelectedSim() {
+ if (userSelectedSubInfoList.size < getActiveModemCount) {
+ userSelectedSubInfoList.addAll(activeSubInfoList)
+ Log.d(TAG, "addCurrentItemForSelectedSim: userSelectedSubInfoList:" +
+ ", $userSelectedSubInfoList")
+ }
}
fun addItemForSelectedSim(selectedSubInfo: SubscriptionInfo) {
@@ -249,7 +263,7 @@
fun startActivatingSim(){
// TODO: start to activate sim
- callback(SimOnboardingActivity.CALLBACK_FINISH)
+ callback(CallbackType.CALLBACK_FINISH)
}
suspend fun startSetupName() {
@@ -261,7 +275,7 @@
)
}
// next action is SETUP_PRIMARY_SIM
- callback(SimOnboardingActivity.CALLBACK_SETUP_PRIMARY_SIM)
+ callback(CallbackType.CALLBACK_SETUP_PRIMARY_SIM)
}
}
@@ -290,7 +304,7 @@
}
// no next action, send finish
- callback(SimOnboardingActivity.CALLBACK_FINISH)
+ callback(CallbackType.CALLBACK_FINISH)
}
}
}
\ No newline at end of file
diff --git a/src/com/android/settings/network/SubscriptionUtil.java b/src/com/android/settings/network/SubscriptionUtil.java
index 84e4e75..83f6c38 100644
--- a/src/com/android/settings/network/SubscriptionUtil.java
+++ b/src/com/android/settings/network/SubscriptionUtil.java
@@ -17,13 +17,15 @@
package com.android.settings.network;
import static android.telephony.SubscriptionManager.INVALID_SIM_SLOT_INDEX;
-import static android.telephony.UiccSlotInfo.CARD_STATE_INFO_PRESENT;
import static android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING;
+import static android.telephony.UiccSlotInfo.CARD_STATE_INFO_PRESENT;
import static com.android.internal.util.CollectionUtils.emptyIfNull;
import android.content.Context;
import android.content.SharedPreferences;
+import android.net.ConnectivityManager;
+import android.net.NetworkCapabilities;
import android.os.ParcelUuid;
import android.provider.Settings;
import android.telephony.PhoneNumberUtils;
@@ -47,9 +49,11 @@
import com.android.settings.network.helper.SelectableSubscriptions;
import com.android.settings.network.helper.SubscriptionAnnotation;
import com.android.settings.network.telephony.DeleteEuiccSubscriptionDialogActivity;
+import com.android.settings.network.telephony.EuiccRacConnectivityDialogActivity;
import com.android.settings.network.telephony.ToggleSubscriptionDialogActivity;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -555,12 +559,21 @@
* @param context {@code Context}
* @param subId The id of subscription need to be deleted.
*/
- public static void startDeleteEuiccSubscriptionDialogActivity(Context context, int subId) {
+ public static void startDeleteEuiccSubscriptionDialogActivity(Context context, int subId,
+ int carrierId) {
if (!SubscriptionManager.isUsableSubscriptionId(subId)) {
Log.i(TAG, "Unable to delete subscription due to invalid subscription ID.");
return;
}
- context.startActivity(DeleteEuiccSubscriptionDialogActivity.getIntent(context, subId));
+ final int[] carriersThatUseRAC = context.getResources().getIntArray(
+ R.array.config_carrier_use_rac);
+ boolean isCarrierRac = Arrays.stream(carriersThatUseRAC).anyMatch(cid -> cid == carrierId);
+
+ if (isCarrierRac && !isConnectedToWifiOrDifferentSubId(context, subId)) {
+ context.startActivity(EuiccRacConnectivityDialogActivity.getIntent(context, subId));
+ } else {
+ context.startActivity(DeleteEuiccSubscriptionDialogActivity.getIntent(context, subId));
+ }
}
/**
@@ -832,4 +845,29 @@
}
return true;
}
+
+ /**
+ * Returns {@code true} if device is connected to Wi-Fi or mobile data provided by a different
+ * subId.
+ *
+ * @param context context
+ * @param targetSubId subscription that is going to be deleted
+ */
+ @VisibleForTesting
+ static boolean isConnectedToWifiOrDifferentSubId(@NonNull Context context, int targetSubId) {
+ ConnectivityManager connectivityManager =
+ context.getSystemService(ConnectivityManager.class);
+ NetworkCapabilities capabilities =
+ connectivityManager.getNetworkCapabilities(connectivityManager.getActiveNetwork());
+
+ if (capabilities != null) {
+ if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
+ // Connected to WiFi
+ return true;
+ } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
+ return targetSubId != SubscriptionManager.getActiveDataSubscriptionId();
+ }
+ }
+ return false;
+ }
}
diff --git a/src/com/android/settings/network/ims/WifiCallingQueryImsState.java b/src/com/android/settings/network/ims/WifiCallingQueryImsState.java
index efa93e5..00d162b 100644
--- a/src/com/android/settings/network/ims/WifiCallingQueryImsState.java
+++ b/src/com/android/settings/network/ims/WifiCallingQueryImsState.java
@@ -27,6 +27,8 @@
import androidx.annotation.VisibleForTesting;
+import com.android.settings.network.telephony.wificalling.WifiCallingRepository;
+
/**
* Controller class for querying Wifi calling status
*/
@@ -92,7 +94,9 @@
* Check whether Wifi Calling can be perform or not on this subscription
*
* @return true when Wifi Calling can be performed, otherwise false
+ * @deprecated Use {@link WifiCallingRepository#wifiCallingReadyFlow()} instead.
*/
+ @Deprecated
public boolean isReadyToWifiCalling() {
if (!SubscriptionManager.isValidSubscriptionId(mSubId)) {
return false;
diff --git a/src/com/android/settings/network/telephony/DataUsagePreferenceController.kt b/src/com/android/settings/network/telephony/DataUsagePreferenceController.kt
index 6ee6909..240843d 100644
--- a/src/com/android/settings/network/telephony/DataUsagePreferenceController.kt
+++ b/src/com/android/settings/network/telephony/DataUsagePreferenceController.kt
@@ -81,16 +81,12 @@
}
private suspend fun update() {
- val summary = withContext(Dispatchers.Default) {
+ val (summary, enabled) = withContext(Dispatchers.Default) {
networkTemplate = getNetworkTemplate()
- getDataUsageSummary()
+ getDataUsageSummaryAndEnabled()
}
- if (summary == null) {
- preference.isEnabled = false
- } else {
- preference.isEnabled = true
- preference.summary = summary
- }
+ preference.isEnabled = enabled
+ preference.summary = summary
}
private fun getNetworkTemplate(): NetworkTemplate? = when {
@@ -105,17 +101,19 @@
fun createNetworkCycleDataRepository(): NetworkCycleDataRepository? =
networkTemplate?.let { NetworkCycleDataRepository(mContext, it) }
- private fun getDataUsageSummary(): String? {
- val repository = createNetworkCycleDataRepository() ?: return null
+ private fun getDataUsageSummaryAndEnabled(): Pair<String?, Boolean> {
+ val repository = createNetworkCycleDataRepository() ?: return null to false
+
repository.loadFirstCycle()?.let { usageData ->
return mContext.getString(
R.string.data_usage_template,
usageData.formatUsage(mContext),
usageData.formatDateRange(mContext),
- )
+ ) to (usageData.usage > 0 || repository.queryUsage(AllTimeRange).usage > 0)
}
- return repository.queryUsage(AllTimeRange).takeIf { it.usage > 0 }
- ?.getDataUsedString(mContext)
+ val allTimeUsage = repository.queryUsage(AllTimeRange)
+ if (allTimeUsage.usage > 0) return allTimeUsage.getDataUsedString(mContext) to true
+ return null to false
}
}
diff --git a/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceController.kt b/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceController.kt
index 64f9730..b7ee41d 100644
--- a/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceController.kt
+++ b/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceController.kt
@@ -33,6 +33,7 @@
class DeleteSimProfilePreferenceController(context: Context, preferenceKey: String) :
BasePreferenceController(context, preferenceKey) {
private var subscriptionId: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID
+ private var carrierId: Int = TelephonyManager.UNKNOWN_CARRIER_ID
private var subscriptionInfo: SubscriptionInfo? = null
private lateinit var preference: Preference
@@ -40,6 +41,9 @@
this.subscriptionId = subscriptionId
subscriptionInfo = SubscriptionUtil.getAvailableSubscriptions(mContext)
.find { it.subscriptionId == subscriptionId && it.isEmbedded }
+ subscriptionInfo?.let {
+ carrierId = it.carrierId
+ }
}
override fun getAvailabilityStatus() = when (subscriptionInfo) {
@@ -67,7 +71,8 @@
}
private fun deleteSim() {
- SubscriptionUtil.startDeleteEuiccSubscriptionDialogActivity(mContext, subscriptionId)
+ SubscriptionUtil.startDeleteEuiccSubscriptionDialogActivity(mContext, subscriptionId,
+ carrierId)
// result handled in MobileNetworkSettings
}
}
diff --git a/src/com/android/settings/network/telephony/EnabledNetworkModePreferenceController.java b/src/com/android/settings/network/telephony/EnabledNetworkModePreferenceController.java
index 56fbcde..56ce9e7 100644
--- a/src/com/android/settings/network/telephony/EnabledNetworkModePreferenceController.java
+++ b/src/com/android/settings/network/telephony/EnabledNetworkModePreferenceController.java
@@ -19,6 +19,8 @@
import static androidx.lifecycle.Lifecycle.Event.ON_START;
import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
+import static com.android.settings.network.telephony.EnabledNetworkModePreferenceControllerHelperKt.setAllowedNetworkTypes;
+
import android.content.Context;
import android.os.PersistableBundle;
import android.telephony.CarrierConfigManager;
@@ -28,10 +30,12 @@
import android.telephony.TelephonyManager;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.OnLifecycleEvent;
import androidx.preference.ListPreference;
import androidx.preference.ListPreferenceDialogFragmentCompat;
@@ -72,6 +76,7 @@
private int mCallState = TelephonyManager.CALL_STATE_IDLE;
private PhoneCallStateTelephonyCallback mTelephonyCallback;
private FragmentManager mFragmentManager;
+ private LifecycleOwner mViewLifecycleOwner;
public EnabledNetworkModePreferenceController(Context context, String key) {
super(context, key);
@@ -169,18 +174,15 @@
}
@Override
- public boolean onPreferenceChange(Preference preference, Object object) {
+ public boolean onPreferenceChange(@NonNull Preference preference, Object object) {
final int newPreferredNetworkMode = Integer.parseInt((String) object);
final ListPreference listPreference = (ListPreference) preference;
+ mBuilder.setPreferenceValueAndSummary(newPreferredNetworkMode);
+ listPreference.setValue(Integer.toString(mBuilder.getSelectedEntryValue()));
+ listPreference.setSummary(mBuilder.getSummary());
- if (mTelephonyManager.setPreferredNetworkTypeBitmask(
- MobileNetworkUtils.getRafFromNetworkType(newPreferredNetworkMode))) {
- mBuilder.setPreferenceValueAndSummary(newPreferredNetworkMode);
- listPreference.setValue(Integer.toString(mBuilder.getSelectedEntryValue()));
- listPreference.setSummary(mBuilder.getSummary());
- return true;
- }
- return false;
+ setAllowedNetworkTypes(mTelephonyManager, mViewLifecycleOwner, newPreferredNetworkMode);
+ return true;
}
void init(int subId, FragmentManager fragmentManager) {
@@ -201,6 +203,11 @@
}
}
+ @Override
+ public void onViewCreated(@NonNull LifecycleOwner viewLifecycleOwner) {
+ mViewLifecycleOwner = viewLifecycleOwner;
+ }
+
private void updatePreference() {
if (mPreferenceScreen != null) {
displayPreference(mPreferenceScreen);
diff --git a/src/com/android/settings/network/telephony/EnabledNetworkModePreferenceControllerHelper.kt b/src/com/android/settings/network/telephony/EnabledNetworkModePreferenceControllerHelper.kt
new file mode 100644
index 0000000..eab5d74
--- /dev/null
+++ b/src/com/android/settings/network/telephony/EnabledNetworkModePreferenceControllerHelper.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 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.network.telephony
+
+import android.telephony.TelephonyManager
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+
+fun TelephonyManager.setAllowedNetworkTypes(
+ viewLifecycleOwner: LifecycleOwner,
+ newPreferredNetworkMode: Int,
+) {
+ viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Default) {
+ setAllowedNetworkTypesForReason(
+ TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER,
+ MobileNetworkUtils.getRafFromNetworkType(newPreferredNetworkMode),
+ )
+ }
+}
diff --git a/src/com/android/settings/network/telephony/EuiccRacConnectivityDialogActivity.java b/src/com/android/settings/network/telephony/EuiccRacConnectivityDialogActivity.java
new file mode 100644
index 0000000..cb4ab18
--- /dev/null
+++ b/src/com/android/settings/network/telephony/EuiccRacConnectivityDialogActivity.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 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.network.telephony;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.telephony.SubscriptionManager;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.settings.R;
+
+/** This dialog activity advise the user to have connectivity if the eSIM uses a RAC. */
+public class EuiccRacConnectivityDialogActivity extends SubscriptionActionDialogActivity
+ implements WarningDialogFragment.OnConfirmListener {
+
+ private static final String TAG = "EuiccRacConnectivityDialogActivity";
+ // Dialog tags
+ private static final int DIALOG_TAG_ERASE_ANYWAY_CONFIRMATION = 1;
+
+ private int mSubId;
+
+ /**
+ * Returns an intent of EuiccRacConnectivityDialogActivity.
+ *
+ * @param context The context used to start the EuiccRacConnectivityDialogActivity.
+ * @param subId The subscription ID of the subscription needs to be deleted. If the subscription
+ * belongs to a group of subscriptions, all subscriptions from the group will be deleted.
+ */
+ @NonNull
+ public static Intent getIntent(@NonNull Context context, int subId) {
+ Intent intent = new Intent(context, EuiccRacConnectivityDialogActivity.class);
+ intent.putExtra(ARG_SUB_ID, subId);
+ return intent;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Intent intent = getIntent();
+ mSubId = intent.getIntExtra(ARG_SUB_ID, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+
+ if (savedInstanceState == null) {
+ showConnectivityWarningDialog();
+ }
+ }
+
+ @Override
+ public void onConfirm(int tag, boolean confirmed) {
+ if (!confirmed) {
+ finish();
+ return;
+ }
+
+ switch (tag) {
+ case DIALOG_TAG_ERASE_ANYWAY_CONFIRMATION:
+ finish();
+ Log.i(TAG, "Show dialogue activity that handles deleting eSIM profiles");
+ startActivity(DeleteEuiccSubscriptionDialogActivity.getIntent(this, mSubId));
+ break;
+ default:
+ Log.e(TAG, "Unrecognized confirmation dialog tag: " + tag);
+ break;
+ }
+ }
+
+ /* Displays warning to have connectivity because subscription is RAC dialog. */
+ private void showConnectivityWarningDialog() {
+ WarningDialogFragment.show(
+ this,
+ WarningDialogFragment.OnConfirmListener.class,
+ DIALOG_TAG_ERASE_ANYWAY_CONFIRMATION,
+ getString(R.string.wifi_warning_dialog_title),
+ getString(R.string.wifi_warning_dialog_text),
+ getString(R.string.wifi_warning_continue_button),
+ getString(R.string.wifi_warning_return_button));
+ }
+}
diff --git a/src/com/android/settings/network/telephony/MobileNetworkUtils.java b/src/com/android/settings/network/telephony/MobileNetworkUtils.java
index 47515d8..8a63505 100644
--- a/src/com/android/settings/network/telephony/MobileNetworkUtils.java
+++ b/src/com/android/settings/network/telephony/MobileNetworkUtils.java
@@ -80,6 +80,7 @@
import com.android.settings.network.SubscriptionUtil;
import com.android.settings.network.ims.WifiCallingQueryImsState;
import com.android.settings.network.telephony.TelephonyConstants.TelephonyManagerConstants;
+import com.android.settings.network.telephony.wificalling.WifiCallingRepository;
import com.android.settingslib.core.instrumentation.Instrumentable;
import com.android.settingslib.development.DevelopmentSettingsEnabler;
import com.android.settingslib.graph.SignalDrawable;
@@ -928,7 +929,10 @@
/**
* Copied from WifiCallingPreferenceController#isWifiCallingEnabled()
+ *
+ * @deprecated Use {@link WifiCallingRepository#wifiCallingReadyFlow()} instead.
*/
+ @Deprecated
public static boolean isWifiCallingEnabled(Context context, int subId,
@Nullable WifiCallingQueryImsState queryImsState) {
if (queryImsState == null) {
diff --git a/src/com/android/settings/network/telephony/SatelliteSettingPreferenceController.java b/src/com/android/settings/network/telephony/SatelliteSettingPreferenceController.java
index 94940b3..5e8a3c3 100644
--- a/src/com/android/settings/network/telephony/SatelliteSettingPreferenceController.java
+++ b/src/com/android/settings/network/telephony/SatelliteSettingPreferenceController.java
@@ -29,6 +29,7 @@
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
+import com.android.internal.telephony.flags.Flags;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.network.CarrierConfigCache;
@@ -58,6 +59,11 @@
@Override
public int getAvailabilityStatus(int subId) {
+ if (!Flags.carrierEnabledSatelliteFlag()) {
+ logd("getAvailabilityStatus() : carrierEnabledSatelliteFlag is disabled");
+ return UNSUPPORTED_ON_DEVICE;
+ }
+
final PersistableBundle carrierConfig = mCarrierConfigCache.getConfigForSubId(subId);
final boolean isSatelliteAttachSupported = carrierConfig.getBoolean(
CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL);
diff --git a/src/com/android/settings/network/telephony/SubscriptionRepository.kt b/src/com/android/settings/network/telephony/SubscriptionRepository.kt
index 7a14d6b..ee4ac1e 100644
--- a/src/com/android/settings/network/telephony/SubscriptionRepository.kt
+++ b/src/com/android/settings/network/telephony/SubscriptionRepository.kt
@@ -18,12 +18,16 @@
import android.content.Context
import android.telephony.SubscriptionManager
+import android.util.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.onEach
+
+private const val TAG = "SubscriptionRepository"
fun Context.subscriptionsChangedFlow() = callbackFlow {
val subscriptionManager = getSystemService(SubscriptionManager::class.java)!!
@@ -40,4 +44,4 @@
)
awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(listener) }
-}.conflate().flowOn(Dispatchers.Default)
+}.conflate().onEach { Log.d(TAG, "subscriptions changed") }.flowOn(Dispatchers.Default)
diff --git a/src/com/android/settings/network/telephony/WarningDialogFragment.java b/src/com/android/settings/network/telephony/WarningDialogFragment.java
new file mode 100644
index 0000000..58bc1da
--- /dev/null
+++ b/src/com/android/settings/network/telephony/WarningDialogFragment.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2024 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.network.telephony;
+
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.FragmentActivity;
+
+import com.android.settings.R;
+
+/** Fragment to show a warning dialog. The caller should implement onConfirmListener. */
+public class WarningDialogFragment extends BaseDialogFragment
+ implements DialogInterface.OnClickListener {
+ private static final String TAG = "WarningDialogFragment";
+ private static final String ARG_TITLE = "title";
+ private static final String ARG_MSG = "msg";
+ private static final String ARG_POS_BUTTON_STRING = "pos_button_string";
+ private static final String ARG_NEG_BUTTON_STRING = "neg_button_string";
+
+ /**
+ * Interface defining the method that will be invoked when the user has done with the dialog.
+ */
+ public interface OnConfirmListener {
+ /**
+ * @param tag The tag in the caller.
+ * @param confirmed True if the user has clicked the positive button. False if the user has
+ * clicked the negative button or cancel the dialog.
+ */
+ void onConfirm(int tag, boolean confirmed);
+ }
+
+ /** Displays a confirmation dialog which has confirm and cancel buttons. */
+ static <T> void show(
+ FragmentActivity activity,
+ Class<T> callbackInterfaceClass,
+ int tagInCaller,
+ String title,
+ String msg,
+ String posButtonString,
+ String negButtonString) {
+ WarningDialogFragment fragment = new WarningDialogFragment();
+ Bundle arguments = new Bundle();
+ arguments.putString(ARG_TITLE, title);
+ arguments.putCharSequence(ARG_MSG, msg);
+ arguments.putString(ARG_POS_BUTTON_STRING, posButtonString);
+ arguments.putString(ARG_NEG_BUTTON_STRING, negButtonString);
+ setListener(activity, null, callbackInterfaceClass, tagInCaller, arguments);
+ fragment.setArguments(arguments);
+ fragment.show(activity.getSupportFragmentManager(), TAG);
+ }
+
+ @Override
+ @NonNull
+ public final Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
+ String title = getArguments().getString(ARG_TITLE);
+ String message = getArguments().getString(ARG_MSG);
+ String leftButton = getArguments().getString(ARG_POS_BUTTON_STRING);
+ String rightButton = getArguments().getString(ARG_NEG_BUTTON_STRING);
+
+ Log.i(TAG, "Showing dialog with title =" + title);
+ AlertDialog.Builder builder =
+ new AlertDialog.Builder(getContext())
+ .setPositiveButton(rightButton, this)
+ .setNegativeButton(leftButton, this);
+
+ View content =
+ LayoutInflater.from(getContext())
+ .inflate(R.layout.sim_warning_dialog_wifi_connectivity, null);
+
+ if (content != null) {
+ TextView dialogTitle = content.findViewById(R.id.title);
+ if (!TextUtils.isEmpty(title) && dialogTitle != null) {
+ dialogTitle.setText(title);
+ dialogTitle.setVisibility(View.VISIBLE);
+ }
+ TextView dialogMessage = content.findViewById(R.id.msg);
+ if (!TextUtils.isEmpty(message) && dialogMessage != null) {
+ dialogMessage.setText(message);
+ dialogMessage.setVisibility(View.VISIBLE);
+ }
+
+ builder.setView(content);
+ } else {
+ if (!TextUtils.isEmpty(title)) {
+ builder.setTitle(title);
+ }
+ if (!TextUtils.isEmpty(message)) {
+ builder.setMessage(message);
+ }
+ }
+
+ AlertDialog dialog = builder.create();
+ dialog.setCanceledOnTouchOutside(false);
+ return dialog;
+ }
+
+ @Override
+ public void onClick(@NonNull DialogInterface dialog, int which) {
+ Log.i(TAG, "dialog onClick =" + which);
+
+ // Positions of the buttons have been switch:
+ // negative button = left button = the button to continue
+ informCaller(which == DialogInterface.BUTTON_NEGATIVE);
+ }
+
+ @Override
+ public void onCancel(@NonNull DialogInterface dialog) {
+ informCaller(false);
+ }
+
+ private void informCaller(boolean confirmed) {
+ OnConfirmListener listener;
+ try {
+ listener = getListener(OnConfirmListener.class);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Do nothing and return.", e);
+ return;
+ }
+ if (listener == null) {
+ return;
+ }
+ listener.onConfirm(getTagInCaller(), confirmed);
+ }
+}
diff --git a/src/com/android/settings/network/telephony/WifiCallingPreferenceController.kt b/src/com/android/settings/network/telephony/WifiCallingPreferenceController.kt
index e7b8318..b0ea6a6 100644
--- a/src/com/android/settings/network/telephony/WifiCallingPreferenceController.kt
+++ b/src/com/android/settings/network/telephony/WifiCallingPreferenceController.kt
@@ -29,8 +29,7 @@
import androidx.preference.Preference
import androidx.preference.PreferenceScreen
import com.android.settings.R
-import com.android.settings.network.telephony.ims.ImsMmTelRepository
-import com.android.settings.network.telephony.ims.ImsMmTelRepositoryImpl
+import com.android.settings.network.telephony.wificalling.WifiCallingRepository
import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
@@ -46,8 +45,8 @@
context: Context,
key: String,
private val callStateFlowFactory: (subId: Int) -> Flow<Int> = context::callStateFlow,
- private val imsMmTelRepositoryFactory: (subId: Int) -> ImsMmTelRepository = { subId ->
- ImsMmTelRepositoryImpl(context, subId)
+ private val wifiCallingRepositoryFactory: (subId: Int) -> WifiCallingRepository = { subId ->
+ WifiCallingRepository(context, subId)
},
) : TelephonyBasePreferenceController(context, key) {
@@ -81,15 +80,11 @@
}
override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) {
- viewLifecycleOwner.lifecycleScope.launch {
- viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
- val isVisible = withContext(Dispatchers.Default) {
- MobileNetworkUtils.isWifiCallingEnabled(mContext, mSubId, null)
- }
- preference.isVisible = isVisible
- callingPreferenceCategoryController.updateChildVisible(preferenceKey, isVisible)
+ wifiCallingRepositoryFactory(mSubId).wifiCallingReadyFlow()
+ .collectLatestWithLifecycle(viewLifecycleOwner) {
+ preference.isVisible = it
+ callingPreferenceCategoryController.updateChildVisible(preferenceKey, it)
}
- }
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
@@ -123,7 +118,7 @@
}
private fun getSummaryForWfcMode(): String {
- val resId = when (imsMmTelRepositoryFactory(mSubId).getWiFiCallingMode()) {
+ val resId = when (wifiCallingRepositoryFactory(mSubId).getWiFiCallingMode()) {
ImsMmTelManager.WIFI_MODE_WIFI_ONLY ->
com.android.internal.R.string.wfc_mode_wifi_only_summary
diff --git a/src/com/android/settings/network/telephony/gsm/AutoSelectPreferenceController.kt b/src/com/android/settings/network/telephony/gsm/AutoSelectPreferenceController.kt
index 1ed9d9a..d709574 100644
--- a/src/com/android/settings/network/telephony/gsm/AutoSelectPreferenceController.kt
+++ b/src/com/android/settings/network/telephony/gsm/AutoSelectPreferenceController.kt
@@ -80,8 +80,6 @@
@VisibleForTesting
var progressDialog: ProgressDialog? = null
- private lateinit var preference: Preference
-
private var subId by notNull<Int>()
/**
@@ -99,11 +97,6 @@
if (MobileNetworkUtils.shouldDisplayNetworkSelectOptions(mContext, subId)) AVAILABLE
else CONDITIONALLY_UNAVAILABLE
- override fun displayPreference(screen: PreferenceScreen) {
- super.displayPreference(screen)
- preference = screen.findPreference(preferenceKey)!!
- }
-
@Composable
override fun Content() {
val coroutineScope = rememberCoroutineScope()
diff --git a/src/com/android/settings/network/telephony/ims/ImsFeatureProvisionedFlow.kt b/src/com/android/settings/network/telephony/ims/ImsFeatureProvisionedFlow.kt
new file mode 100644
index 0000000..6769498
--- /dev/null
+++ b/src/com/android/settings/network/telephony/ims/ImsFeatureProvisionedFlow.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 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.network.telephony.ims
+
+import android.telephony.ims.ProvisioningManager
+import android.telephony.ims.ProvisioningManager.FeatureProvisioningCallback
+import android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability
+import android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationTech
+import android.util.Log
+import androidx.annotation.VisibleForTesting
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.onEach
+
+private const val TAG = "ImsFeatureProvisioned"
+
+fun imsFeatureProvisionedFlow(
+ subId: Int,
+ @MmTelCapability capability: Int,
+ @ImsRegistrationTech tech: Int,
+): Flow<Boolean> = imsFeatureProvisionedFlow(
+ subId = subId,
+ capability = capability,
+ tech = tech,
+ provisioningManager = ProvisioningManager.createForSubscriptionId(subId),
+)
+
+@VisibleForTesting
+fun imsFeatureProvisionedFlow(
+ subId: Int,
+ @MmTelCapability capability: Int,
+ @ImsRegistrationTech tech: Int,
+ provisioningManager : ProvisioningManager,
+): Flow<Boolean> = callbackFlow {
+ val callback = object : FeatureProvisioningCallback() {
+ override fun onFeatureProvisioningChanged(
+ receivedCapability: Int,
+ receivedTech: Int,
+ isProvisioned: Boolean,
+ ) {
+ if (capability == receivedCapability && tech == receivedTech) trySend(isProvisioned)
+ }
+
+ override fun onRcsFeatureProvisioningChanged(
+ capability: Int,
+ tech: Int,
+ isProvisioned: Boolean,
+ ) {
+ }
+ }
+
+ provisioningManager.registerFeatureProvisioningChangedCallback(
+ Dispatchers.Default.asExecutor(),
+ callback,
+ )
+ trySend(provisioningManager.getProvisioningStatusForCapability(capability, tech))
+
+ awaitClose { provisioningManager.unregisterFeatureProvisioningChangedCallback(callback) }
+}.catch { e ->
+ Log.w(TAG, "[$subId] error while imsFeatureProvisionedFlow", e)
+}.conflate().onEach {
+ Log.d(TAG, "[$subId] changed: capability=$capability tech=$tech isProvisioned=$it")
+}.flowOn(Dispatchers.Default)
diff --git a/src/com/android/settings/network/telephony/ims/ImsMmTelRepository.kt b/src/com/android/settings/network/telephony/ims/ImsMmTelRepository.kt
index 3408eb7..822c20a 100644
--- a/src/com/android/settings/network/telephony/ims/ImsMmTelRepository.kt
+++ b/src/com/android/settings/network/telephony/ims/ImsMmTelRepository.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 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.
@@ -17,17 +17,33 @@
package com.android.settings.network.telephony.ims
import android.content.Context
-import android.telephony.CarrierConfigManager
-import android.telephony.CarrierConfigManager.KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL
-import android.telephony.TelephonyManager
+import android.telephony.AccessNetworkConstants
import android.telephony.ims.ImsManager
import android.telephony.ims.ImsMmTelManager
import android.telephony.ims.ImsMmTelManager.WiFiCallingMode
+import android.telephony.ims.ImsStateCallback
+import android.telephony.ims.feature.MmTelFeature
import android.util.Log
+import kotlin.coroutines.resume
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.withContext
interface ImsMmTelRepository {
@WiFiCallingMode
- fun getWiFiCallingMode(): Int
+ fun getWiFiCallingMode(useRoamingMode: Boolean): Int
+ fun imsReadyFlow(): Flow<Boolean>
+ suspend fun isSupported(
+ @MmTelFeature.MmTelCapabilities.MmTelCapability capability: Int,
+ @AccessNetworkConstants.TransportType transportType: Int,
+ ): Boolean
}
class ImsMmTelRepositoryImpl(
@@ -36,30 +52,61 @@
private val imsMmTelManager: ImsMmTelManager = ImsManager(context).getImsMmTelManager(subId),
) : ImsMmTelRepository {
- private val telephonyManager = context.getSystemService(TelephonyManager::class.java)!!
- .createForSubscriptionId(subId)
-
- private val carrierConfigManager = context.getSystemService(CarrierConfigManager::class.java)!!
-
@WiFiCallingMode
- override fun getWiFiCallingMode(): Int = try {
+ override fun getWiFiCallingMode(useRoamingMode: Boolean): Int = try {
when {
!imsMmTelManager.isVoWiFiSettingEnabled -> ImsMmTelManager.WIFI_MODE_UNKNOWN
-
- telephonyManager.isNetworkRoaming && !useWfcHomeModeForRoaming() ->
- imsMmTelManager.getVoWiFiRoamingModeSetting()
-
+ useRoamingMode -> imsMmTelManager.getVoWiFiRoamingModeSetting()
else -> imsMmTelManager.getVoWiFiModeSetting()
}
} catch (e: IllegalArgumentException) {
- Log.w(TAG, "getWiFiCallingMode failed subId=$subId", e)
+ Log.w(TAG, "[$subId] getWiFiCallingMode failed useRoamingMode=$useRoamingMode", e)
ImsMmTelManager.WIFI_MODE_UNKNOWN
}
- private fun useWfcHomeModeForRoaming(): Boolean =
- carrierConfigManager
- .getConfigForSubId(subId, KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL)
- .getBoolean(KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL)
+ override fun imsReadyFlow(): Flow<Boolean> = callbackFlow {
+ val callback = object : ImsStateCallback() {
+ override fun onAvailable() {
+ Log.d(TAG, "[$subId] IMS onAvailable")
+ trySend(true)
+ }
+
+ override fun onError() {
+ Log.d(TAG, "[$subId] IMS onError")
+ trySend(false)
+ }
+
+ override fun onUnavailable(reason: Int) {
+ Log.d(TAG, "[$subId] IMS onUnavailable")
+ trySend(false)
+ }
+ }
+
+ imsMmTelManager.registerImsStateCallback(Dispatchers.Default.asExecutor(), callback)
+
+ awaitClose { imsMmTelManager.unregisterImsStateCallback(callback) }
+ }.catch { e ->
+ Log.w(TAG, "[$subId] error while imsReadyFlow", e)
+ }.conflate().flowOn(Dispatchers.Default)
+
+ override suspend fun isSupported(
+ @MmTelFeature.MmTelCapabilities.MmTelCapability capability: Int,
+ @AccessNetworkConstants.TransportType transportType: Int,
+ ): Boolean = withContext(Dispatchers.Default) {
+ suspendCancellableCoroutine { continuation ->
+ try {
+ imsMmTelManager.isSupported(
+ capability,
+ transportType,
+ Dispatchers.Default.asExecutor(),
+ continuation::resume,
+ )
+ } catch (e: Exception) {
+ continuation.resume(false)
+ Log.w(TAG, "[$subId] isSupported failed", e)
+ }
+ }.also { Log.d(TAG, "[$subId] isSupported = $it") }
+ }
private companion object {
private const val TAG = "ImsMmTelRepository"
diff --git a/src/com/android/settings/network/telephony/wificalling/WifiCallingRepository.kt b/src/com/android/settings/network/telephony/wificalling/WifiCallingRepository.kt
new file mode 100644
index 0000000..ac95404
--- /dev/null
+++ b/src/com/android/settings/network/telephony/wificalling/WifiCallingRepository.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2024 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.network.telephony.wificalling
+
+import android.content.Context
+import android.telephony.AccessNetworkConstants
+import android.telephony.CarrierConfigManager
+import android.telephony.CarrierConfigManager.KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL
+import android.telephony.SubscriptionManager
+import android.telephony.TelephonyManager
+import android.telephony.ims.ImsMmTelManager.WiFiCallingMode
+import android.telephony.ims.feature.MmTelFeature
+import android.telephony.ims.stub.ImsRegistrationImplBase
+import com.android.settings.network.telephony.ims.ImsMmTelRepository
+import com.android.settings.network.telephony.ims.ImsMmTelRepositoryImpl
+import com.android.settings.network.telephony.ims.imsFeatureProvisionedFlow
+import com.android.settings.network.telephony.subscriptionsChangedFlow
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+class WifiCallingRepository(
+ private val context: Context,
+ private val subId: Int,
+ private val imsMmTelRepository : ImsMmTelRepository = ImsMmTelRepositoryImpl(context, subId)
+) {
+ private val telephonyManager = context.getSystemService(TelephonyManager::class.java)!!
+ .createForSubscriptionId(subId)
+
+ private val carrierConfigManager = context.getSystemService(CarrierConfigManager::class.java)!!
+
+ @WiFiCallingMode
+ fun getWiFiCallingMode(): Int {
+ val useRoamingMode = telephonyManager.isNetworkRoaming && !useWfcHomeModeForRoaming()
+ return imsMmTelRepository.getWiFiCallingMode(useRoamingMode)
+ }
+
+ private fun useWfcHomeModeForRoaming(): Boolean =
+ carrierConfigManager
+ .getConfigForSubId(subId, KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL)
+ .getBoolean(KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL)
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ fun wifiCallingReadyFlow(): Flow<Boolean> {
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) return flowOf(false)
+ return context.subscriptionsChangedFlow().flatMapLatest {
+ combine(
+ imsFeatureProvisionedFlow(
+ subId = subId,
+ capability = MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
+ tech = ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN,
+ ),
+ isWifiCallingSupportedFlow(),
+ ) { imsFeatureProvisioned, isWifiCallingSupported ->
+ imsFeatureProvisioned && isWifiCallingSupported
+ }
+ }
+ }
+
+ private fun isWifiCallingSupportedFlow(): Flow<Boolean> {
+ return imsMmTelRepository.imsReadyFlow().map { imsReady ->
+ imsReady && imsMmTelRepository.isSupported(
+ capability = MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
+ transportType = AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
+ )
+ }
+ }
+}
diff --git a/src/com/android/settings/notification/MediaVolumePreferenceController.java b/src/com/android/settings/notification/MediaVolumePreferenceController.java
index 79df55a..e70cf95 100644
--- a/src/com/android/settings/notification/MediaVolumePreferenceController.java
+++ b/src/com/android/settings/notification/MediaVolumePreferenceController.java
@@ -37,6 +37,7 @@
import com.android.settings.slices.CustomSliceRegistry;
import com.android.settings.slices.SliceBackgroundWorker;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.flags.Flags;
import com.android.settingslib.media.BluetoothMediaDevice;
import com.android.settingslib.media.MediaDevice;
import com.android.settingslib.media.MediaOutputConstants;
@@ -94,7 +95,9 @@
@VisibleForTesting
boolean isSupportEndItem() {
- return getWorker() != null && getWorker().isBroadcastSupported()
+ return Flags.legacyLeAudioSharing()
+ && getWorker() != null
+ && getWorker().isBroadcastSupported()
&& (getWorker().isDeviceBroadcasting() || isConnectedBLEDevice());
}
@@ -114,8 +117,9 @@
if (mPreference != null) {
if (mPreference.isMuted()) {
mPreference.updateContentDescription(
- mContext.getString(R.string.volume_content_description_silent_mode,
- mPreference.getTitle()));
+ mContext.getString(
+ R.string.volume_content_description_silent_mode,
+ mPreference.getTitle()));
} else {
mPreference.updateContentDescription(mPreference.getTitle());
}
@@ -134,11 +138,16 @@
if (getWorker().isDeviceBroadcasting()) {
intent.setPackage(MediaOutputConstants.SYSTEMUI_PACKAGE_NAME);
intent.setAction(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
- intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME,
+ intent.putExtra(
+ MediaOutputConstants.EXTRA_PACKAGE_NAME,
getWorker().getActiveLocalMediaController().getPackageName());
- pi = PendingIntent.getBroadcast(context, 0 /* requestCode */, intent,
- PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ pi =
+ PendingIntent.getBroadcast(
+ context,
+ 0 /* requestCode */,
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
} else {
final CachedBluetoothDevice bluetoothDevice =
((BluetoothMediaDevice) mMediaDevice).getCachedDevice();
@@ -147,15 +156,21 @@
return null;
}
intent.setAction(ACTION_LAUNCH_BROADCAST_DIALOG);
- intent.putExtra(BluetoothBroadcastDialog.KEY_APP_LABEL,
+ intent.putExtra(
+ BluetoothBroadcastDialog.KEY_APP_LABEL,
Utils.getApplicationLabel(mContext, getWorker().getPackageName()));
- intent.putExtra(BluetoothBroadcastDialog.KEY_DEVICE_ADDRESS,
- bluetoothDevice.getAddress());
- intent.putExtra(BluetoothBroadcastDialog.KEY_MEDIA_STREAMING, getWorker() != null
- && getWorker().getActiveLocalMediaController() != null);
+ intent.putExtra(
+ BluetoothBroadcastDialog.KEY_DEVICE_ADDRESS, bluetoothDevice.getAddress());
+ intent.putExtra(
+ BluetoothBroadcastDialog.KEY_MEDIA_STREAMING,
+ getWorker() != null && getWorker().getActiveLocalMediaController() != null);
- pi = PendingIntent.getActivity(context, 0 /* requestCode */, intent,
- PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ pi =
+ PendingIntent.getActivity(
+ context,
+ 0 /* requestCode */,
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
}
final IconCompat icon = getBroadcastIcon(context);
@@ -164,8 +179,8 @@
}
private IconCompat getBroadcastIcon(Context context) {
- final Drawable drawable = context.getDrawable(
- com.android.settingslib.R.drawable.settings_input_antenna);
+ final Drawable drawable =
+ context.getDrawable(com.android.settingslib.R.drawable.settings_input_antenna);
if (drawable != null) {
drawable.setTint(Utils.getColorAccentDefaultColor(context));
return Utils.createIconWithDrawable(drawable);
diff --git a/src/com/android/settings/privatespace/AutoAdvanceSetupFragment.java b/src/com/android/settings/privatespace/AutoAdvanceSetupFragment.java
index 13c0681..1f0f7bb 100644
--- a/src/com/android/settings/privatespace/AutoAdvanceSetupFragment.java
+++ b/src/com/android/settings/privatespace/AutoAdvanceSetupFragment.java
@@ -16,12 +16,17 @@
package com.android.settings.privatespace;
+import static android.text.Layout.BREAK_STRATEGY_SIMPLE;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -30,7 +35,6 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.ImageView;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.Nullable;
@@ -39,6 +43,7 @@
import com.android.settings.R;
import com.android.settings.core.InstrumentedFragment;
+import com.airbnb.lottie.LottieAnimationView;
import com.google.android.setupdesign.GlifLayout;
import com.google.common.collect.ImmutableList;
@@ -56,21 +61,21 @@
private GlifLayout mRootView;
private Handler mHandler;
private int mScreenTitleIndex;
- private static final List<Pair<Integer, Integer>> HEADER_IMAGE_PAIRS =
+ private static final List<Pair<Integer, Integer>> HEADER_ILLUSTRATION_PAIRS =
ImmutableList.of(
new Pair(R.string.private_space_notifications_hidden_title,
- R.drawable.private_space_setup_notification_illustration),
- new Pair(R.string.private_space_share_photos_title,
- R.drawable.private_space_setup_sharing_illustration),
+ R.raw.private_space_notifications_illustration),
new Pair(R.string.private_space_apps_installed_title,
- R.drawable.private_space_setup_preinstalled_illustration));
+ R.raw.private_space_unlock_to_share_illustration),
+ new Pair(R.string.private_space_explore_settings_title,
+ R.raw.private_space_placeholder_illustration));
private Runnable mUpdateScreenResources =
new Runnable() {
@Override
public void run() {
if (getActivity() != null) {
- if (++mScreenTitleIndex < HEADER_IMAGE_PAIRS.size()) {
+ if (++mScreenTitleIndex < HEADER_ILLUSTRATION_PAIRS.size()) {
startFadeOutAnimation();
mHandler.postDelayed(mUpdateScreenResources, DELAY_BETWEEN_SCREENS);
} else if (PrivateSpaceMaintainer.getInstance(getActivity())
@@ -79,8 +84,13 @@
getContext(),
SettingsEnums.ACTION_PRIVATE_SPACE_SETUP_SPACE_CREATED,
true);
- NavHostFragment.findNavController(AutoAdvanceSetupFragment.this)
- .navigate(R.id.action_set_lock_fragment);
+ if (isConnectedToInternet()) {
+ NavHostFragment.findNavController(AutoAdvanceSetupFragment.this)
+ .navigate(R.id.action_account_intro_fragment);
+ } else {
+ NavHostFragment.findNavController(AutoAdvanceSetupFragment.this)
+ .navigate(R.id.action_set_lock_fragment);
+ }
} else {
mMetricsFeatureProvider.action(
getContext(),
@@ -110,7 +120,7 @@
}
} else {
mScreenTitleIndex = savedInstanceState.getInt(TITLE_INDEX);
- if (mScreenTitleIndex >= HEADER_IMAGE_PAIRS.size()) {
+ if (mScreenTitleIndex >= HEADER_ILLUSTRATION_PAIRS.size()) {
return super.onCreateView(inflater, container, savedInstanceState);
}
}
@@ -118,7 +128,8 @@
(GlifLayout)
inflater.inflate(R.layout.private_space_advancing_screen, container, false);
mRootView.getHeaderTextView().setMaxLines(HEADER_TEXT_MAX_LINES);
- updateHeaderAndImage();
+ mRootView.getHeaderTextView().setBreakStrategy(BREAK_STRATEGY_SIMPLE);
+ updateHeaderAndIllustration();
mHandler = new Handler(Looper.getMainLooper());
mHandler.postDelayed(mUpdateScreenResources, DELAY_BETWEEN_SCREENS);
OnBackPressedCallback callback =
@@ -141,7 +152,9 @@
@Override
public void onDestroy() {
- mHandler.removeCallbacks(mUpdateScreenResources);
+ if (mHandler != null) {
+ mHandler.removeCallbacks(mUpdateScreenResources);
+ }
super.onDestroy();
}
@@ -155,20 +168,21 @@
.navigate(R.id.action_advance_profile_error);
}
- private void updateHeaderAndImage() {
- mRootView.setHeaderText(HEADER_IMAGE_PAIRS.get(mScreenTitleIndex).first);
- ((ImageView) mRootView.findViewById(R.id.setup_advance_image))
- .setImageResource(HEADER_IMAGE_PAIRS.get(mScreenTitleIndex).second);
+ private void updateHeaderAndIllustration() {
+ mRootView.setHeaderText(HEADER_ILLUSTRATION_PAIRS.get(mScreenTitleIndex).first);
+ LottieAnimationView animationView = mRootView.findViewById(R.id.lottie_animation);
+ animationView.setAnimation(HEADER_ILLUSTRATION_PAIRS.get(mScreenTitleIndex).second);
+ animationView.playAnimation();
startFadeInAnimation();
}
private void startFadeInAnimation() {
ValueAnimator textView = ObjectAnimator.ofFloat(
mRootView.getHeaderTextView(), View.ALPHA, 0f, 1f);
- ValueAnimator imageView = ObjectAnimator.ofFloat(
- mRootView.findViewById(R.id.setup_advance_image), View.ALPHA, 0, 1f);
+ ValueAnimator lottieView = ObjectAnimator.ofFloat(
+ mRootView.findViewById(R.id.lottie_animation), View.ALPHA, 0, 1f);
AnimatorSet fadeIn = new AnimatorSet();
- fadeIn.playTogether(textView, imageView);
+ fadeIn.playTogether(textView, lottieView);
fadeIn.setDuration(ANIMATION_DURATION_MILLIS).start();
}
@@ -176,15 +190,24 @@
AnimatorSet fadeOut = new AnimatorSet();
ValueAnimator textView = ObjectAnimator.ofFloat(
mRootView.getHeaderTextView(), View.ALPHA, 1f, 0f);
- ValueAnimator imageView = ObjectAnimator.ofFloat(
- mRootView.findViewById(R.id.setup_advance_image), View.ALPHA, 1f, 0f);
- fadeOut.playTogether(textView, imageView);
+ ValueAnimator lottieView = ObjectAnimator.ofFloat(
+ mRootView.findViewById(R.id.lottie_animation), View.ALPHA, 1f, 0f);
+ fadeOut.playTogether(textView, lottieView);
fadeOut.setDuration(ANIMATION_DURATION_MILLIS).start();
fadeOut.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- updateHeaderAndImage();
+ updateHeaderAndIllustration();
}
});
}
+
+ /** Returns true if device has an active internet connection, false otherwise. */
+ private boolean isConnectedToInternet() {
+ ConnectivityManager cm =
+ (ConnectivityManager)
+ getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
+ return activeNetwork != null && activeNetwork.isConnectedOrConnecting();
+ }
}
diff --git a/src/com/android/settings/privatespace/HidePrivateSpaceSettings.java b/src/com/android/settings/privatespace/HidePrivateSpaceSettings.java
index 09a1855..124978a 100644
--- a/src/com/android/settings/privatespace/HidePrivateSpaceSettings.java
+++ b/src/com/android/settings/privatespace/HidePrivateSpaceSettings.java
@@ -47,7 +47,7 @@
@Override
protected int getPreferenceScreenResId() {
- return R.xml.privatespace_hide_locked;
+ return R.xml.private_space_hide_locked;
}
@Override
diff --git a/src/com/android/settings/privatespace/PrivateProfileCreationError.java b/src/com/android/settings/privatespace/PrivateProfileCreationError.java
index bcaa1d3..fa890d5 100644
--- a/src/com/android/settings/privatespace/PrivateProfileCreationError.java
+++ b/src/com/android/settings/privatespace/PrivateProfileCreationError.java
@@ -60,9 +60,7 @@
.setText(R.string.private_space_cancel_label)
.setListener(onCancel())
.setButtonType(FooterButton.ButtonType.CANCEL)
- .setTheme(
- androidx.appcompat.R.style
- .Base_TextAppearance_AppCompat_Widget_Button)
+ .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Secondary)
.build());
OnBackPressedCallback callback =
new OnBackPressedCallback(true /* enabled by default */) {
diff --git a/src/com/android/settings/privatespace/PrivateSpaceAccountLoginError.java b/src/com/android/settings/privatespace/PrivateSpaceAccountLoginError.java
index fcb93b1..48a5a7f 100644
--- a/src/com/android/settings/privatespace/PrivateSpaceAccountLoginError.java
+++ b/src/com/android/settings/privatespace/PrivateSpaceAccountLoginError.java
@@ -16,15 +16,9 @@
package com.android.settings.privatespace;
-import static com.android.settings.privatespace.PrivateSpaceSetupActivity.ACCOUNT_LOGIN_ACTION;
-import static com.android.settings.privatespace.PrivateSpaceSetupActivity.EXTRA_ACTION_TYPE;
-
import android.annotation.SuppressLint;
import android.app.settings.SettingsEnums;
-import android.content.Intent;
import android.os.Bundle;
-import android.os.UserHandle;
-import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -66,9 +60,7 @@
.setText(R.string.private_space_skip_login_label)
.setListener(onSkip())
.setButtonType(FooterButton.ButtonType.CANCEL)
- .setTheme(
- androidx.appcompat.R.style
- .Base_TextAppearance_AppCompat_Widget_Button)
+ .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Secondary)
.build());
OnBackPressedCallback callback =
new OnBackPressedCallback(true /* enabled by default */) {
@@ -93,17 +85,8 @@
mMetricsFeatureProvider.action(
getContext(),
SettingsEnums.ACTION_PRIVATE_SPACE_SETUP_TRY_CREATE_ACCOUNT_AGAIN);
- PrivateSpaceMaintainer privateSpaceMaintainer =
- PrivateSpaceMaintainer.getInstance(getActivity());
- UserHandle userHandle;
- if (privateSpaceMaintainer.doesPrivateSpaceExist()
- && (userHandle = privateSpaceMaintainer.getPrivateProfileHandle()) != null) {
- Intent intent = new Intent(getContext(), PrivateProfileContextHelperActivity.class);
- intent.putExtra(EXTRA_ACTION_TYPE, ACCOUNT_LOGIN_ACTION);
- Log.d(TAG, "Start private space activity for account login");
- getActivity()
- .startActivityForResultAsUser(intent, ACCOUNT_LOGIN_ACTION, userHandle);
- }
+ NavHostFragment.findNavController(PrivateSpaceAccountLoginError.this)
+ .navigate(R.id.action_advance_login_error);
};
}
@@ -116,7 +99,7 @@
SettingsEnums.ACTION_PRIVATE_SPACE_SETUP_ACCOUNT_LOGIN_SUCCESS,
false);
NavHostFragment.findNavController(PrivateSpaceAccountLoginError.this)
- .navigate(R.id.action_success_fragment);
+ .navigate(R.id.action_skip_account_login);
};
}
}
diff --git a/src/com/android/settings/privatespace/PrivateSpaceEducation.java b/src/com/android/settings/privatespace/PrivateSpaceEducation.java
index 7b06f5b..4c99873 100644
--- a/src/com/android/settings/privatespace/PrivateSpaceEducation.java
+++ b/src/com/android/settings/privatespace/PrivateSpaceEducation.java
@@ -62,9 +62,7 @@
.setText(R.string.private_space_cancel_label)
.setListener(onCancel())
.setButtonType(FooterButton.ButtonType.CANCEL)
- .setTheme(
- androidx.appcompat.R.style
- .Base_TextAppearance_AppCompat_Widget_Button)
+ .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Secondary)
.build());
return rootView;
diff --git a/src/com/android/settings/privatespace/PrivateSpaceGaiaEducationFragment.java b/src/com/android/settings/privatespace/PrivateSpaceGaiaEducationFragment.java
new file mode 100644
index 0000000..0fa0483
--- /dev/null
+++ b/src/com/android/settings/privatespace/PrivateSpaceGaiaEducationFragment.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2024 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.privatespace;
+
+import static com.android.settings.privatespace.PrivateSpaceSetupActivity.ACCOUNT_LOGIN_ACTION;
+import static com.android.settings.privatespace.PrivateSpaceSetupActivity.EXTRA_ACTION_TYPE;
+
+import android.app.settings.SettingsEnums;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.activity.OnBackPressedCallback;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.navigation.fragment.NavHostFragment;
+
+import com.android.settings.R;
+import com.android.settings.core.InstrumentedFragment;
+
+import com.google.android.setupcompat.template.FooterBarMixin;
+import com.google.android.setupcompat.template.FooterButton;
+import com.google.android.setupdesign.GlifLayout;
+
+/** Fragment for GAIA education screen */
+public class PrivateSpaceGaiaEducationFragment extends InstrumentedFragment {
+ private static final String TAG = "PrivateSpaceGaiaEduFrag";
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ if (android.os.Flags.allowPrivateProfile()) {
+ super.onCreate(savedInstanceState);
+ }
+ }
+
+ @NonNull
+ @Override
+ public View onCreateView(
+ @NonNull LayoutInflater inflater,
+ @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ GlifLayout rootView =
+ (GlifLayout)
+ inflater.inflate(
+ R.layout.private_space_gaia_education_screen, container, false);
+ final FooterBarMixin mixin = rootView.getMixin(FooterBarMixin.class);
+ mixin.setPrimaryButton(
+ new FooterButton.Builder(getContext())
+ .setText(R.string.private_space_gaia_education_got_it)
+ .setListener(onStartLogin())
+ .setButtonType(FooterButton.ButtonType.NEXT)
+ .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
+ .build());
+ mixin.setSecondaryButton(
+ new FooterButton.Builder(getContext())
+ .setText(R.string.skip_label)
+ .setListener(onSkip())
+ .setButtonType(FooterButton.ButtonType.NEXT)
+ .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Secondary)
+ .build());
+ OnBackPressedCallback callback =
+ new OnBackPressedCallback(true /* enabled by default */) {
+ @Override
+ public void handleOnBackPressed() {
+ // Handle the back button event. We intentionally don't want to allow back
+ // button to work in this screen during the setup flow.
+ }
+ };
+ requireActivity().getOnBackPressedDispatcher().addCallback(this, callback);
+
+ return rootView;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return METRICS_CATEGORY_UNKNOWN;
+ }
+
+ private View.OnClickListener onSkip() {
+ return v -> {
+ NavHostFragment.findNavController(PrivateSpaceGaiaEducationFragment.this)
+ .navigate(R.id.action_account_lock_fragment);
+ };
+ }
+
+ private View.OnClickListener onStartLogin() {
+ return v -> {
+ startAccountLogin();
+ };
+ }
+
+ /** Start new activity in private profile to add an account to private profile */
+ private void startAccountLogin() {
+ UserHandle userHandle =
+ PrivateSpaceMaintainer.getInstance(getActivity()).getPrivateProfileHandle();
+ if (userHandle != null) {
+ Intent intent = new Intent(getContext(), PrivateProfileContextHelperActivity.class);
+ intent.putExtra(EXTRA_ACTION_TYPE, ACCOUNT_LOGIN_ACTION);
+ mMetricsFeatureProvider.action(
+ getContext(), SettingsEnums.ACTION_PRIVATE_SPACE_SETUP_ACCOUNT_LOGIN_START);
+ getActivity().startActivityForResultAsUser(intent, ACCOUNT_LOGIN_ACTION, userHandle);
+ } else {
+ Log.w(TAG, "Private profile user handle is null");
+ }
+ }
+}
diff --git a/src/com/android/settings/privatespace/PrivateSpaceSetLockFragment.java b/src/com/android/settings/privatespace/PrivateSpaceSetLockFragment.java
index 56add6d..4a1e29b 100644
--- a/src/com/android/settings/privatespace/PrivateSpaceSetLockFragment.java
+++ b/src/com/android/settings/privatespace/PrivateSpaceSetLockFragment.java
@@ -16,7 +16,6 @@
package com.android.settings.privatespace;
-import static com.android.settings.privatespace.PrivateSpaceSetupActivity.ACCOUNT_LOGIN_ACTION;
import static com.android.settings.privatespace.PrivateSpaceSetupActivity.EXTRA_ACTION_TYPE;
import static com.android.settings.privatespace.PrivateSpaceSetupActivity.SET_LOCK_ACTION;
@@ -31,6 +30,7 @@
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.Nullable;
+import androidx.navigation.fragment.NavHostFragment;
import com.android.settings.R;
import com.android.settings.core.InstrumentedFragment;
@@ -70,9 +70,7 @@
.setText(R.string.private_space_set_lock_label)
.setListener(onClickNewLock())
.setButtonType(FooterButton.ButtonType.NEXT)
- .setTheme(
- androidx.appcompat.R.style
- .Base_TextAppearance_AppCompat_Widget_Button)
+ .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Secondary)
.build());
OnBackPressedCallback callback =
new OnBackPressedCallback(true /* enabled by default */) {
@@ -97,10 +95,8 @@
mMetricsFeatureProvider.action(
getContext(), SettingsEnums.ACTION_PRIVATE_SPACE_SETUP_USE_SCREEN_LOCK);
// Simply Use default screen lock. No need to handle
- mMetricsFeatureProvider.action(
- getContext(), SettingsEnums.ACTION_PRIVATE_SPACE_SETUP_ACCOUNT_LOGIN_START);
- Log.d(TAG, "Use device lock for private profile");
- launchActivityForAction(ACCOUNT_LOGIN_ACTION);
+ NavHostFragment.findNavController(PrivateSpaceSetLockFragment.this)
+ .navigate(R.id.action_lock_success_fragment);
};
}
diff --git a/src/com/android/settings/privatespace/PrivateSpaceSetupActivity.java b/src/com/android/settings/privatespace/PrivateSpaceSetupActivity.java
index 75b69cd..78c96dc 100644
--- a/src/com/android/settings/privatespace/PrivateSpaceSetupActivity.java
+++ b/src/com/android/settings/privatespace/PrivateSpaceSetupActivity.java
@@ -19,8 +19,6 @@
import android.app.settings.SettingsEnums;
import android.content.Intent;
import android.os.Bundle;
-import android.os.UserHandle;
-import android.util.Log;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
@@ -61,23 +59,12 @@
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (requestCode == SET_LOCK_ACTION && resultCode == RESULT_OK) {
- /* Start new activity in private profile to add an account to private profile */
- UserHandle userHandle =
- PrivateSpaceMaintainer.getInstance(this).getPrivateProfileHandle();
- if (userHandle != null) {
- Intent intent = new Intent(this, PrivateProfileContextHelperActivity.class);
- intent.putExtra(EXTRA_ACTION_TYPE, ACCOUNT_LOGIN_ACTION);
- mMetricsFeatureProvider.action(
- this, SettingsEnums.ACTION_PRIVATE_SPACE_SETUP_ACCOUNT_LOGIN_START);
- startActivityForResultAsUser(intent, ACCOUNT_LOGIN_ACTION, userHandle);
- } else {
- Log.w(TAG, "Private profile user handle is null");
- }
+ mNavHostFragment.getNavController().navigate(R.id.action_success_fragment);
} else if (requestCode == ACCOUNT_LOGIN_ACTION) {
if (resultCode == RESULT_OK) {
mMetricsFeatureProvider.action(
this, SettingsEnums.ACTION_PRIVATE_SPACE_SETUP_ACCOUNT_LOGIN_SUCCESS, true);
- mNavHostFragment.getNavController().navigate(R.id.action_success_fragment);
+ mNavHostFragment.getNavController().navigate(R.id.action_account_lock_fragment);
} else {
mMetricsFeatureProvider.action(
this,
diff --git a/src/com/android/settings/sim/SimDialogActivity.java b/src/com/android/settings/sim/SimDialogActivity.java
index 4544e73..8840994 100644
--- a/src/com/android/settings/sim/SimDialogActivity.java
+++ b/src/com/android/settings/sim/SimDialogActivity.java
@@ -136,9 +136,7 @@
}
if (Flags.isDualSimOnboardingEnabled()
- && getProgressState() == SubscriptionActionDialogActivity.PROGRESS_IS_SHOWING
- && (dialogType == PREFERRED_PICK
- || dialogType == DATA_PICK
+ && (dialogType == DATA_PICK
|| dialogType == CALLS_PICK
|| dialogType == SMS_PICK)) {
Log.d(TAG, "Finish the sim dialog since the sim onboarding is shown");
diff --git a/src/com/android/settings/spa/SettingsSpaEnvironment.kt b/src/com/android/settings/spa/SettingsSpaEnvironment.kt
index 7a1d915..568188f 100644
--- a/src/com/android/settings/spa/SettingsSpaEnvironment.kt
+++ b/src/com/android/settings/spa/SettingsSpaEnvironment.kt
@@ -56,6 +56,7 @@
import com.android.settings.spa.system.AppLanguagesPageProvider
import com.android.settings.spa.system.LanguageAndInputPageProvider
import com.android.settings.spa.system.SystemMainPageProvider
+import com.android.settings.wifi.details2.WifiPrivacyPageProvider
import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
import com.android.settingslib.spa.framework.common.SpaEnvironment
import com.android.settingslib.spa.framework.common.SpaLogger
@@ -122,6 +123,7 @@
SimOnboardingPageProvider,
BatteryOptimizationModeAppListPageProvider,
NetworkCellularGroupProvider,
+ WifiPrivacyPageProvider,
)
override val logger = if (FeatureFlagUtils.isEnabled(
diff --git a/src/com/android/settings/spa/network/SimOnboardingPageProvider.kt b/src/com/android/settings/spa/network/SimOnboardingPageProvider.kt
index c60ac88..838154f 100644
--- a/src/com/android/settings/spa/network/SimOnboardingPageProvider.kt
+++ b/src/com/android/settings/spa/network/SimOnboardingPageProvider.kt
@@ -24,6 +24,7 @@
import android.util.Log
import androidx.annotation.VisibleForTesting
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.platform.LocalContext
import androidx.navigation.NavHostController
import androidx.navigation.NavType
@@ -33,6 +34,7 @@
import androidx.navigation.navArgument
import com.android.settings.R
import com.android.settings.network.SimOnboardingActivity
+import com.android.settings.network.SimOnboardingActivity.Companion.CallbackType
import com.android.settings.network.SimOnboardingService
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
@@ -92,7 +94,7 @@
val context = LocalContext.current
var finishOnboarding: () -> Unit = {
context.getActivity()?.finish()
- onboardingService.callback(SimOnboardingActivity.CALLBACK_FINISH)
+ onboardingService.callback(CallbackType.CALLBACK_FINISH)
}
NavHost(
@@ -101,10 +103,13 @@
) {
composable(route = SimOnboardingScreen.LabelSim.name) {
val nextPage =
- if (onboardingService.isMultipleEnabledProfilesSupported && onboardingService.isAllOfSlotAssigned) {
+ if (onboardingService.isMultipleEnabledProfilesSupported
+ && onboardingService.isAllOfSlotAssigned) {
SimOnboardingScreen.SelectSim.name
} else {
- onboardingService.addCurrentItemForSelectedSim()
+ LaunchedEffect(Unit) {
+ onboardingService.addCurrentItemForSelectedSim()
+ }
SimOnboardingScreen.PrimarySim.name
}
SimOnboardingLabelSimImpl(
@@ -116,7 +121,7 @@
composable(route = SimOnboardingScreen.PrimarySim.name) {
SimOnboardingPrimarySimImpl(
nextAction = {
- onboardingService.callback(SimOnboardingActivity.CALLBACK_ONBOARDING_COMPLETE)
+ onboardingService.callback(CallbackType.CALLBACK_ONBOARDING_COMPLETE)
context.getActivity()?.finish()
},
cancelAction = finishOnboarding,
diff --git a/src/com/android/settings/spa/network/SimOnboardingPrimarySim.kt b/src/com/android/settings/spa/network/SimOnboardingPrimarySim.kt
index a5f55d0..b984966 100644
--- a/src/com/android/settings/spa/network/SimOnboardingPrimarySim.kt
+++ b/src/com/android/settings/spa/network/SimOnboardingPrimarySim.kt
@@ -77,7 +77,8 @@
SettingsBody(stringResource(id = R.string.sim_onboarding_primary_sim_msg))
}
- var selectedSubscriptionInfoList = onboardingService.getSelectedSubscriptionInfoList()
+ var selectedSubscriptionInfoList =
+ onboardingService.getSelectedSubscriptionInfoListWithRenaming()
callsSelectedId.intValue = onboardingService.targetPrimarySimCalls
textsSelectedId.intValue = onboardingService.targetPrimarySimTexts
mobileDataSelectedId.intValue = onboardingService.targetPrimarySimMobileData
diff --git a/src/com/android/settings/spa/preference/ComposePreferenceController.kt b/src/com/android/settings/spa/preference/ComposePreferenceController.kt
index 9dd8282..5ba1d24 100644
--- a/src/com/android/settings/spa/preference/ComposePreferenceController.kt
+++ b/src/com/android/settings/spa/preference/ComposePreferenceController.kt
@@ -24,7 +24,7 @@
abstract class ComposePreferenceController(context: Context, preferenceKey: String) :
BasePreferenceController(context, preferenceKey) {
- private lateinit var preference: ComposePreference
+ protected lateinit var preference: ComposePreference
override fun displayPreference(screen: PreferenceScreen) {
super.displayPreference(screen)
diff --git a/src/com/android/settings/widget/CardPreference.kt b/src/com/android/settings/widget/CardPreference.kt
index 7122ac6..010d7de 100644
--- a/src/com/android/settings/widget/CardPreference.kt
+++ b/src/com/android/settings/widget/CardPreference.kt
@@ -61,6 +61,9 @@
/** The visibility of secondary button on tips card. The default value is `false`. */
var secondaryButtonVisibility: Boolean = false
+ var onClick: (() -> Unit)? = null
+
+ /** The callback for click on card preference itself. */
private var onDismiss: (() -> Unit)? = null
/** Enable the dismiss button on tips card. */
@@ -84,9 +87,10 @@
buttons = listOfNotNull(configPrimaryButton(), configSecondaryButton()),
onDismiss = onDismiss,
imageVector =
- iconResId
- ?.takeIf { it != Resources.ID_NULL }
- ?.let { ImageVector.vectorResource(it) },
+ iconResId
+ ?.takeIf { it != Resources.ID_NULL }
+ ?.let { ImageVector.vectorResource(it) },
+ onClick = onClick,
)
)
}
diff --git a/src/com/android/settings/wifi/WepNetworksPreferenceController.kt b/src/com/android/settings/wifi/WepNetworksPreferenceController.kt
index 6263bfd..2db0e02 100644
--- a/src/com/android/settings/wifi/WepNetworksPreferenceController.kt
+++ b/src/com/android/settings/wifi/WepNetworksPreferenceController.kt
@@ -18,42 +18,46 @@
import android.content.Context
import android.net.wifi.WifiManager
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.fragment.app.Fragment
import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import androidx.preference.Preference
-import androidx.preference.PreferenceScreen
import com.android.settings.R
import com.android.settings.spa.preference.ComposePreferenceController
import com.android.settingslib.spa.framework.compose.OverridableFlow
+import com.android.settingslib.spa.widget.dialog.AlertDialogButton
+import com.android.settingslib.spa.widget.dialog.SettingsAlertDialogWithIcon
import com.android.settingslib.spa.widget.preference.SwitchPreference
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
import com.android.wifi.flags.Flags
+import com.android.wifitrackerlib.WifiEntry
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.callbackFlow
-/** Controller that controls whether the Wi-Fi Wakeup feature should be enabled. */
+/** Controller that controls whether the WEP network can be connected. */
class WepNetworksPreferenceController(context: Context, preferenceKey: String) :
ComposePreferenceController(context, preferenceKey) {
- private lateinit var preference: Preference
-
var wifiManager = context.getSystemService(WifiManager::class.java)!!
- override fun displayPreference(screen: PreferenceScreen) {
- super.displayPreference(screen)
- preference = screen.findPreference(preferenceKey)!!
- }
-
override fun getAvailabilityStatus() = if (Flags.androidVWifiApi()) AVAILABLE
else UNSUPPORTED_ON_DEVICE
@Composable
override fun Content() {
val checked by wepAllowedFlow.flow.collectAsStateWithLifecycle(initialValue = null)
+ var openDialog by rememberSaveable { mutableStateOf(false) }
+ val wifiInfo = wifiManager.connectionInfo
SwitchPreference(object : SwitchPreferenceModel {
override val title = stringResource(R.string.wifi_allow_wep_networks)
override val summary = { getSummary() }
@@ -61,10 +65,40 @@
override val changeable: () -> Boolean
get() = { carrierAllowed }
override val onCheckedChange: (Boolean) -> Unit = { newChecked ->
- wifiManager.setWepAllowed(newChecked)
- wepAllowedFlow.override(newChecked)
+ if (!newChecked && wifiInfo.currentSecurityType == WifiEntry.SECURITY_WEP) {
+ openDialog = true
+ } else {
+ wifiManager.setWepAllowed(newChecked)
+ wepAllowedFlow.override(newChecked)
+ }
}
})
+ if (openDialog) {
+ SettingsAlertDialogWithIcon(
+ onDismissRequest = { openDialog = false },
+ confirmButton = AlertDialogButton(
+ stringResource(R.string.wifi_disconnect_button_text)
+ ) {
+ wifiManager.setWepAllowed(false)
+ wepAllowedFlow.override(false)
+ openDialog = false
+ },
+ dismissButton =
+ AlertDialogButton(
+ stringResource(R.string.wifi_cancel)
+ ) { openDialog = false },
+ title = String.format(
+ stringResource(R.string.wifi_settings_wep_networks_disconnect_title),
+ wifiInfo.ssid.removeSurrounding("\"")
+ ),
+ text = {
+ Text(
+ stringResource(R.string.wifi_settings_wep_networks_disconnect_summary),
+ modifier = Modifier.fillMaxWidth(),
+ textAlign = TextAlign.Center
+ )
+ })
+ }
}
override fun getSummary(): String = mContext.getString(
diff --git a/src/com/android/settings/wifi/details/WifiNetworkDetailsFragment.java b/src/com/android/settings/wifi/details/WifiNetworkDetailsFragment.java
index 0384f0d..e1774e3 100644
--- a/src/com/android/settings/wifi/details/WifiNetworkDetailsFragment.java
+++ b/src/com/android/settings/wifi/details/WifiNetworkDetailsFragment.java
@@ -58,6 +58,7 @@
import com.android.settings.wifi.details2.WifiAutoConnectPreferenceController2;
import com.android.settings.wifi.details2.WifiDetailPreferenceController2;
import com.android.settings.wifi.details2.WifiMeteredPreferenceController2;
+import com.android.settings.wifi.details2.WifiPrivacyPreferenceController;
import com.android.settings.wifi.details2.WifiPrivacyPreferenceController2;
import com.android.settings.wifi.details2.WifiSecondSummaryController2;
import com.android.settings.wifi.details2.WifiSubscriptionDetailPreferenceController2;
@@ -119,6 +120,13 @@
}
@Override
+ public void onAttach(@NonNull Context context) {
+ super.onAttach(context);
+ use(WifiPrivacyPreferenceController.class)
+ .setWifiEntryKey(getArguments().getString(KEY_CHOSEN_WIFIENTRY_KEY));
+ }
+
+ @Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setIfOnlyAvailableForAdmins(true);
diff --git a/src/com/android/settings/wifi/details2/WifiPrivacyPageProvider.kt b/src/com/android/settings/wifi/details2/WifiPrivacyPageProvider.kt
new file mode 100644
index 0000000..e41863c
--- /dev/null
+++ b/src/com/android/settings/wifi/details2/WifiPrivacyPageProvider.kt
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2024 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.wifi.details2
+
+import android.content.Context
+import android.net.wifi.WifiConfiguration
+import android.net.wifi.WifiManager
+import android.os.Bundle
+import android.os.Handler
+import android.os.HandlerThread
+import android.os.Looper
+import android.os.Process
+import android.os.SimpleClock
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.width
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.compose.ui.res.stringArrayResource
+import androidx.compose.ui.res.stringResource
+import androidx.navigation.NavType
+import androidx.navigation.navArgument
+import com.android.settings.R
+import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.widget.preference.ListPreferenceModel
+import com.android.settingslib.spa.widget.preference.ListPreferenceOption
+import com.android.settingslib.spa.widget.preference.RadioPreferences
+import com.android.settingslib.spa.widget.preference.SwitchPreference
+import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+import com.android.settingslib.spa.widget.ui.CategoryTitle
+import com.android.wifitrackerlib.WifiEntry
+import java.time.Clock
+import java.time.ZoneOffset
+
+const val WIFI_ENTRY_KEY = "wifiEntryKey"
+
+object WifiPrivacyPageProvider : SettingsPageProvider {
+ override val name = "WifiPrivacy"
+ const val TAG = "WifiPrivacyPageProvider"
+
+ override val parameter = listOf(
+ navArgument(WIFI_ENTRY_KEY) { type = NavType.StringType },
+ )
+
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ val wifiEntryKey = arguments!!.getString(WIFI_ENTRY_KEY)
+ if (wifiEntryKey != null) {
+ val context = LocalContext.current
+ val lifecycle = LocalLifecycleOwner.current.lifecycle
+ val wifiEntry = remember {
+ getWifiEntry(context, wifiEntryKey, lifecycle)
+ }
+ WifiPrivacyPage(wifiEntry)
+ }
+ }
+
+ fun getRoute(
+ wifiEntryKey: String,
+ ): String = "${name}/$wifiEntryKey"
+}
+
+@Composable
+fun WifiPrivacyPage(wifiEntry: WifiEntry) {
+ val isSelectable: Boolean = wifiEntry.canSetPrivacy()
+ RegularScaffold(
+ title = stringResource(id = R.string.wifi_privacy_settings)
+ ) {
+ Column {
+ val title = stringResource(id = R.string.wifi_privacy_mac_settings)
+ val wifiPrivacyEntries = stringArrayResource(R.array.wifi_privacy_entries)
+ val wifiPrivacyValues = stringArrayResource(R.array.wifi_privacy_values)
+ val textsSelectedId = rememberSaveable { mutableIntStateOf(wifiEntry.privacy) }
+ val dataList = remember {
+ wifiPrivacyEntries.mapIndexed { index, text ->
+ ListPreferenceOption(id = wifiPrivacyValues[index].toInt(), text = text)
+ }
+ }
+ RadioPreferences(remember {
+ object : ListPreferenceModel {
+ override val title = title
+ override val options = dataList
+ override val selectedId = textsSelectedId
+ override val onIdSelected: (Int) -> Unit = {
+ textsSelectedId.intValue = it
+ onSelectedChange(wifiEntry, it)
+ }
+ override val enabled = { isSelectable }
+ }
+ })
+ wifiEntry.wifiConfiguration?.let {
+ DeviceNameSwitchPreference(it)
+ }
+ }
+ }
+}
+
+@Composable
+fun DeviceNameSwitchPreference(wifiConfiguration: WifiConfiguration){
+ Spacer(modifier = Modifier.width(SettingsDimension.itemDividerHeight))
+ CategoryTitle(title = stringResource(R.string.wifi_privacy_device_name_settings))
+ Spacer(modifier = Modifier.width(SettingsDimension.itemDividerHeight))
+ var checked by remember {
+ mutableStateOf(wifiConfiguration.isSendDhcpHostnameEnabled)
+ }
+ val context = LocalContext.current
+ val wifiManager = context.getSystemService(WifiManager::class.java)!!
+ SwitchPreference(object : SwitchPreferenceModel {
+ override val title =
+ context.resources.getString(
+ R.string.wifi_privacy_send_device_name_toggle_title
+ )
+ override val summary =
+ {
+ context.resources.getString(
+ R.string.wifi_privacy_send_device_name_toggle_summary
+ )
+ }
+ override val checked = { checked }
+ override val onCheckedChange: (Boolean) -> Unit = { newChecked ->
+ wifiConfiguration.isSendDhcpHostnameEnabled = newChecked
+ wifiManager.save(wifiConfiguration, null /* listener */)
+ checked = newChecked
+ }
+ })
+}
+
+fun onSelectedChange(wifiEntry: WifiEntry, privacy: Int) {
+ if (wifiEntry.privacy == privacy) {
+ // Prevent disconnection + reconnection if settings not changed.
+ return
+ }
+ wifiEntry.setPrivacy(privacy)
+
+ // To activate changing, we need to reconnect network. WiFi will auto connect to
+ // current network after disconnect(). Only needed when this is connected network.
+
+ // To activate changing, we need to reconnect network. WiFi will auto connect to
+ // current network after disconnect(). Only needed when this is connected network.
+ if (wifiEntry.getConnectedState() == WifiEntry.CONNECTED_STATE_CONNECTED) {
+ wifiEntry.disconnect(null /* callback */)
+ wifiEntry.connect(null /* callback */)
+ }
+}
+
+fun getWifiEntry(
+ context: Context,
+ wifiEntryKey: String,
+ liftCycle: androidx.lifecycle.Lifecycle
+): WifiEntry {
+ // Max age of tracked WifiEntries
+ val MAX_SCAN_AGE_MILLIS: Long = 15000
+ // Interval between initiating SavedNetworkTracker scans
+ val SCAN_INTERVAL_MILLIS: Long = 10000
+ val mWorkerThread = HandlerThread(
+ WifiPrivacyPageProvider.TAG,
+ Process.THREAD_PRIORITY_BACKGROUND
+ )
+ mWorkerThread.start()
+ val elapsedRealtimeClock: Clock = object : SimpleClock(ZoneOffset.UTC) {
+ override fun millis(): Long {
+ return android.os.SystemClock.elapsedRealtime()
+ }
+ }
+ val mNetworkDetailsTracker = featureFactory
+ .wifiTrackerLibProvider
+ .createNetworkDetailsTracker(
+ liftCycle,
+ context,
+ Handler(Looper.getMainLooper()),
+ mWorkerThread.getThreadHandler(),
+ elapsedRealtimeClock,
+ MAX_SCAN_AGE_MILLIS,
+ SCAN_INTERVAL_MILLIS,
+ wifiEntryKey
+ )
+ return mNetworkDetailsTracker.wifiEntry
+}
diff --git a/src/com/android/settings/wifi/details2/WifiPrivacyPreferenceController.kt b/src/com/android/settings/wifi/details2/WifiPrivacyPreferenceController.kt
new file mode 100644
index 0000000..42741e3
--- /dev/null
+++ b/src/com/android/settings/wifi/details2/WifiPrivacyPreferenceController.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2024 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.wifi.details2
+
+import android.content.Context
+import android.net.wifi.WifiManager
+import androidx.compose.material3.Icon
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.res.vectorResource
+import com.android.settings.R
+import com.android.settings.spa.SpaActivity.Companion.startSpaActivity
+import com.android.settings.spa.preference.ComposePreferenceController
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.wifi.flags.Flags
+
+class WifiPrivacyPreferenceController(context: Context, preferenceKey: String) :
+ ComposePreferenceController(context, preferenceKey) {
+
+ private var wifiEntryKey: String? = null
+
+ var wifiManager = context.getSystemService(WifiManager::class.java)!!
+
+ fun setWifiEntryKey(key: String?) {
+ wifiEntryKey = key
+ }
+
+ override fun getAvailabilityStatus() =
+ if (Flags.androidVWifiApi() && wifiManager.isConnectedMacRandomizationSupported) AVAILABLE
+ else CONDITIONALLY_UNAVAILABLE
+
+ @Composable
+ override fun Content() {
+ Preference(object : PreferenceModel {
+ override val title = stringResource(R.string.wifi_privacy_settings)
+ override val icon = @Composable {
+ Icon(
+ ImageVector.vectorResource(R.drawable.ic_wifi_privacy_24dp),
+ contentDescription = null
+ )
+ }
+ override val onClick: () -> Unit =
+ {
+ wifiEntryKey?.let {
+ mContext.startSpaActivity(WifiPrivacyPageProvider.getRoute(it))
+ }
+ }
+ })
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/settings/wifi/details2/WifiPrivacyPreferenceController2.java b/src/com/android/settings/wifi/details2/WifiPrivacyPreferenceController2.java
index 8c78e80..5d393e5 100644
--- a/src/com/android/settings/wifi/details2/WifiPrivacyPreferenceController2.java
+++ b/src/com/android/settings/wifi/details2/WifiPrivacyPreferenceController2.java
@@ -26,6 +26,7 @@
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
+import com.android.wifi.flags.Flags;
import com.android.wifitrackerlib.WifiEntry;
/**
@@ -50,7 +51,7 @@
@Override
public int getAvailabilityStatus() {
- return mWifiManager.isConnectedMacRandomizationSupported()
+ return (!Flags.androidVWifiApi() && mWifiManager.isConnectedMacRandomizationSupported())
? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
}
diff --git a/tests/robotests/Android.bp b/tests/robotests/Android.bp
index 5648290..1c6794d 100644
--- a/tests/robotests/Android.bp
+++ b/tests/robotests/Android.bp
@@ -71,10 +71,12 @@
"Settings-testutils2",
"notification_flags_lib",
"com_android_server_accessibility_flags_lib",
+ "testables",
],
libs: [
"ims-common",
+ "android.test.mock",
],
java_resource_dirs: [
diff --git a/tests/robotests/src/com/android/settings/accessibility/ArrowPreferenceTest.java b/tests/robotests/src/com/android/settings/accessibility/ArrowPreferenceTest.java
new file mode 100644
index 0000000..ff4a748
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/accessibility/ArrowPreferenceTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 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.accessibility;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+/** Tests for {@link ArrowPreference} */
+@RunWith(RobolectricTestRunner.class)
+public class ArrowPreferenceTest {
+
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ private ArrowPreference mPreference;
+
+ @Before
+ public void setUp() {
+ mPreference = new ArrowPreference(mContext);
+ }
+
+ @Test
+ public void construct_withArrow() {
+ assertThat(mPreference.getWidgetLayoutResource()).isEqualTo(
+ R.layout.preference_widget_arrow);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/accessibility/BackgroundPreferenceTest.java b/tests/robotests/src/com/android/settings/accessibility/BackgroundPreferenceTest.java
new file mode 100644
index 0000000..007d664
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/accessibility/BackgroundPreferenceTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2024 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.accessibility;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import androidx.preference.PreferenceViewHolder;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+
+/** Tests for {@link BackgroundPreference} */
+@RunWith(RobolectricTestRunner.class)
+public class BackgroundPreferenceTest {
+
+ @Rule
+ public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+
+ private View mRootView = new View(mContext);
+ @Spy
+ private PreferenceViewHolder mViewHolder = PreferenceViewHolder.createInstanceForTests(
+ mRootView);
+ @Spy
+ private LinearLayout mLinearLayout = new LinearLayout(mContext);
+ private BackgroundPreference mPreference;
+
+ @Before
+ public void setUp() {
+ mPreference = new BackgroundPreference(mContext);
+ }
+
+ @Test
+ public void setBackground_success() {
+ doReturn(mLinearLayout).when(mViewHolder).findViewById(R.id.background);
+
+ mPreference.setBackground(android.R.drawable.screen_background_dark);
+ mPreference.onBindViewHolder(mViewHolder);
+
+ verify(mLinearLayout).setBackground(any());
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/accessibility/ColorContrastFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/ColorContrastFragmentTest.java
new file mode 100644
index 0000000..f903745
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/accessibility/ColorContrastFragmentTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 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.accessibility;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.app.UiModeManager;
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.R;
+import com.android.settings.testutils.XmlTestUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.List;
+
+/** Tests for {@link ColorContrastFragment}. */
+@RunWith(RobolectricTestRunner.class)
+public class ColorContrastFragmentTest {
+
+ private ColorContrastFragment mFragment;
+ private Context mContext;
+ @Mock
+ private UiModeManager mUiService;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mFragment = spy(new ColorContrastFragment());
+ mContext = spy(ApplicationProvider.getApplicationContext());
+ when(mFragment.getContext()).thenReturn(mContext);
+ when(mContext.getSystemService(UiModeManager.class)).thenReturn(mUiService);
+ }
+
+ @Test
+ public void getMetricsCategory_returnsCorrectCategory() {
+ assertThat(mFragment.getMetricsCategory()).isEqualTo(0);
+ }
+
+ @Test
+ public void getPreferenceScreenResId_returnsCorrectXml() {
+ assertThat(mFragment.getPreferenceScreenResId()).isEqualTo(
+ R.xml.accessibility_color_contrast);
+ }
+
+ @Test
+ public void getLogTag_returnsCorrectTag() {
+ assertThat(mFragment.getLogTag()).isEqualTo("ColorContrastFragment");
+ }
+
+ @Test
+ public void getNonIndexableKeys_existInXmlLayout() {
+ final List<String> niks =
+ ShortcutsSettingsFragment.SEARCH_INDEX_DATA_PROVIDER
+ .getNonIndexableKeys(mContext);
+ final List<String> keys =
+ XmlTestUtils.getKeysFromPreferenceXml(mContext,
+ R.xml.accessibility_color_contrast);
+ assertThat(keys).containsAtLeastElementsIn(niks);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/accessibility/ContrastPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/ContrastPreferenceControllerTest.java
new file mode 100644
index 0000000..219f3d9
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/accessibility/ContrastPreferenceControllerTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 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.accessibility;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.core.BasePreferenceController;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+/** Tests for {@link ContrastPreferenceController}. */
+@RunWith(RobolectricTestRunner.class)
+public class ContrastPreferenceControllerTest {
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ private static final String PREFERENCE_KEY = "preference_key";
+
+ private Context mContext;
+ private ContrastPreferenceController mController;
+
+ @Before
+ public void setUp() {
+ mContext = ApplicationProvider.getApplicationContext();
+ mController = new ContrastPreferenceController(mContext, PREFERENCE_KEY);
+ }
+
+ @Test
+ public void getAvailabilityStatus_flagsEnabled_shouldReturnAvailable() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_COLOR_CONTRAST_CONTROL);
+
+ assertThat(mController.getAvailabilityStatus())
+ .isEqualTo(BasePreferenceController.AVAILABLE);
+ }
+
+ @Test
+ public void getAvailabilityStatus_flagsDisabled_shouldReturnUnsupported() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_COLOR_CONTRAST_CONTROL);
+
+ assertThat(mController.getAvailabilityStatus())
+ .isEqualTo(BasePreferenceController.UNSUPPORTED_ON_DEVICE);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/accessibility/ContrastSelectorPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/ContrastSelectorPreferenceControllerTest.java
new file mode 100644
index 0000000..38d6e80
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/accessibility/ContrastSelectorPreferenceControllerTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2024 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.accessibility;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+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.app.UiModeManager;
+import android.content.Context;
+import android.widget.FrameLayout;
+
+import androidx.preference.PreferenceScreen;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.widget.LayoutPreference;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.concurrent.Executor;
+
+/** Tests for {@link ContrastSelectorPreferenceController}. */
+@RunWith(RobolectricTestRunner.class)
+public class ContrastSelectorPreferenceControllerTest {
+
+ private static final String PREFERENCE_KEY = "color_contrast_selector";
+
+ @Mock
+ private UiModeManager mUiService;
+ @Mock
+ private Executor mExecutor;
+ @Mock
+ private PreferenceScreen mScreen;
+ @Mock
+ private FrameLayout mFrameLayout;
+ @Mock
+ private LayoutPreference mLayoutPreference;
+ private Context mContext;
+ private ContrastSelectorPreferenceController mController;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = spy(ApplicationProvider.getApplicationContext());
+ when(mContext.getMainExecutor()).thenReturn(mExecutor);
+ when(mContext.getSystemService(UiModeManager.class)).thenReturn(mUiService);
+ mController = new ContrastSelectorPreferenceController(mContext, PREFERENCE_KEY);
+ when(mScreen.findPreference(PREFERENCE_KEY)).thenReturn(mLayoutPreference);
+ when(mLayoutPreference.findViewById(anyInt())).thenReturn(mFrameLayout);
+ }
+
+ @Test
+ public void getAvailabilityStatus_byDefault_shouldReturnAvailable() {
+ assertThat(mController.getAvailabilityStatus())
+ .isEqualTo(BasePreferenceController.AVAILABLE);
+ }
+
+ @Test
+ public void onStart_shouldAddContrastListener() {
+ mController.displayPreference(mScreen);
+ mController.onStart();
+
+ verify(mUiService).addContrastChangeListener(mExecutor, mController);
+ }
+
+ @Test
+ public void onStop_shouldRemoveContrastListener() {
+ mController.displayPreference(mScreen);
+ mController.onStart();
+ mController.onStop();
+
+ verify(mUiService).removeContrastChangeListener(mController);
+ }
+
+ @Test
+ public void displayPreference_shouldAddClickListener() {
+ mController.displayPreference(mScreen);
+
+ verify(mFrameLayout, times(3)).setOnClickListener(any());
+ }
+
+ @Test
+ public void onContrastChanged_buttonShouldBeSelected() {
+ mController.displayPreference(mScreen);
+ mController.onContrastChanged(1);
+
+ verify(mFrameLayout, times(2)).setSelected(true);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/accessibility/ViewAllBluetoothDevicesPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/ViewAllBluetoothDevicesPreferenceControllerTest.java
index 6c9fbfc..1f9fbeb 100644
--- a/tests/robotests/src/com/android/settings/accessibility/ViewAllBluetoothDevicesPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/ViewAllBluetoothDevicesPreferenceControllerTest.java
@@ -74,13 +74,13 @@
}
@Test
- public void handlePreferenceTreeClick_expectedPreference_launchBluetoothPairingDetail() {
- doNothing().when(mController).launchBluetoothPairingDetail();
+ public void handlePreferenceTreeClick_expectedPreference_launchConnectedDevicePage() {
+ doNothing().when(mController).launchConnectedDevicePage();
mPreference.setKey(TEST_KEY);
boolean status = mController.handlePreferenceTreeClick(mPreference);
- verify(mController).launchBluetoothPairingDetail();
+ verify(mController).launchConnectedDevicePage();
assertThat(status).isTrue();
}
}
diff --git a/tests/robotests/src/com/android/settings/accessibility/shortcuts/EditShortcutsPreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/shortcuts/EditShortcutsPreferenceFragmentTest.java
index 13f0b24..56486d2 100644
--- a/tests/robotests/src/com/android/settings/accessibility/shortcuts/EditShortcutsPreferenceFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/shortcuts/EditShortcutsPreferenceFragmentTest.java
@@ -28,6 +28,9 @@
import static com.google.android.setupcompat.util.WizardManagerHelper.EXTRA_IS_SETUP_FLOW;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf;
import android.content.ComponentName;
@@ -35,7 +38,9 @@
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
+import android.util.Pair;
import android.view.accessibility.AccessibilityManager;
import androidx.fragment.app.FragmentActivity;
@@ -46,6 +51,7 @@
import androidx.test.core.app.ApplicationProvider;
import com.android.internal.accessibility.common.ShortcutConstants;
+import com.android.internal.accessibility.dialog.AccessibilityTarget;
import com.android.internal.accessibility.util.ShortcutUtils;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
@@ -60,6 +66,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
@@ -89,6 +96,9 @@
private static final String TARGET = MAGNIFICATION_CONTROLLER_NAME;
private static final Set<String> TARGETS = Set.of(TARGET);
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private final Context mContext = ApplicationProvider.getApplicationContext();
private FragmentActivity mActivity;
private FragmentScenario<EditShortcutsPreferenceFragment> mFragmentScenario;
@@ -414,6 +424,60 @@
}
+ @Test
+ public void findTitles_withSingleTarget_hasNullSubtitle() {
+ final String fake_label = "FAKE";
+ List<AccessibilityTarget> accessibilityTargets = List.of(
+ generateAccessibilityTargetMock(TARGET_FAKE_COMPONENT, fake_label));
+
+ Pair<String, String> titles = EditShortcutsPreferenceFragment
+ .getTitlesFromAccessibilityTargetList(
+ Set.of(TARGET_FAKE_COMPONENT.flattenToString()),
+ accessibilityTargets, mActivity.getResources()
+ );
+
+ assertThat(titles.first).isNotNull();
+ assertThat(titles.first).contains(fake_label);
+ assertThat(titles.second).isNull();
+ }
+
+ @Test
+ public void findTitles_withMoreTargets_hasSubtitle() {
+ final String fake_label = "FAKE";
+ final String magnification_label = "MAGNIFICATION";
+ List<AccessibilityTarget> accessibilityTargets = List.of(
+ generateAccessibilityTargetMock(TARGET_FAKE_COMPONENT, fake_label),
+ generateAccessibilityTargetMock(MAGNIFICATION_COMPONENT_NAME, magnification_label));
+
+ Pair<String, String> titles = EditShortcutsPreferenceFragment
+ .getTitlesFromAccessibilityTargetList(
+ Set.of(TARGET_FAKE_COMPONENT.flattenToString(),
+ MAGNIFICATION_COMPONENT_NAME.flattenToString()),
+ accessibilityTargets, mActivity.getResources()
+ );
+
+ assertThat(titles.first).isNotNull();
+ assertThat(titles.second).isNotNull();
+ assertThat(titles.second).contains(fake_label);
+ assertThat(titles.second).contains(magnification_label);
+ }
+
+ @Test
+ public void findTitles_targetMissing_labelNotInTitles() {
+ final String fake_label = "FAKE";
+ List<AccessibilityTarget> accessibilityTargets = List.of(
+ generateAccessibilityTargetMock(TARGET_FAKE_COMPONENT, fake_label));
+
+ assertThrows(IllegalStateException.class,
+ () -> EditShortcutsPreferenceFragment
+ .getTitlesFromAccessibilityTargetList(
+ Set.of(MAGNIFICATION_COMPONENT_NAME.flattenToString()),
+ accessibilityTargets, mActivity.getResources()
+ ));
+ }
+
+
+
private void assertLaunchSubSettingWithCurrentTargetComponents(
String componentName, boolean isInSuw) {
Intent intent = shadowOf(mActivity.getApplication()).getNextStartedActivity();
@@ -480,4 +544,13 @@
intent.putExtra(EXTRA_IS_DEFERRED_SETUP, isInSuw);
return intent;
}
+
+ private AccessibilityTarget generateAccessibilityTargetMock(
+ ComponentName componentName, String label) {
+ AccessibilityTarget target = mock(AccessibilityTarget.class);
+ when(target.getComponentName()).thenReturn(componentName);
+ when(target.getId()).thenReturn(componentName.flattenToString());
+ when(target.getLabel()).thenReturn(label);
+ return target;
+ }
}
diff --git a/tests/robotests/src/com/android/settings/applications/InstalledAppCounterTest.java b/tests/robotests/src/com/android/settings/applications/InstalledAppCounterTest.java
index 1d76806..e2c9573 100644
--- a/tests/robotests/src/com/android/settings/applications/InstalledAppCounterTest.java
+++ b/tests/robotests/src/com/android/settings/applications/InstalledAppCounterTest.java
@@ -41,9 +41,11 @@
import android.content.pm.UserInfo;
import android.os.UserHandle;
import android.os.UserManager;
+import android.platform.test.flag.junit.SetFlagsRule;
import org.junit.Before;
import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatcher;
@@ -85,6 +87,9 @@
@Mock
private PackageManager mPackageManager;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private int mInstalledAppCount = -1;
private ApplicationInfo mApp1;
private ApplicationInfo mApp2;
@@ -218,6 +223,7 @@
eq(MAIN_USER_ID))).thenReturn(Arrays.asList(mApp2));
mFakeFeatureFlags.setFlag(Flags.FLAG_ARCHIVING, false);
+ mSetFlagsRule.disableFlags(com.android.settings.flags.Flags.FLAG_APP_ARCHIVING);
// Count the number of all apps installed, irrespective of install reason.
count(InstalledAppCounter.IGNORE_INSTALL_REASON, mFakeFeatureFlags);
assertThat(mInstalledAppCount).isEqualTo(1);
diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AppInfoDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/AppInfoDashboardFragmentTest.java
index 4fdf560..46d1cc3 100644
--- a/tests/robotests/src/com/android/settings/applications/appinfo/AppInfoDashboardFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/applications/appinfo/AppInfoDashboardFragmentTest.java
@@ -35,6 +35,7 @@
import static org.mockito.Mockito.when;
import android.app.admin.DevicePolicyManager;
+import android.app.ecm.EnhancedConfirmationManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -90,7 +91,8 @@
private DevicePolicyManager mDevicePolicyManager;
@Mock
private PackageManager mPackageManager;
-
+ @Mock
+ private EnhancedConfirmationManager mEcManager;
private AppInfoDashboardFragment mFragment;
private Context mShadowContext;
@@ -102,6 +104,7 @@
doReturn(mActivity).when(mFragment).getActivity();
doReturn(mShadowContext).when(mFragment).getContext();
doReturn(mPackageManager).when(mActivity).getPackageManager();
+ doReturn(mEcManager).when(mActivity).getSystemService(EnhancedConfirmationManager.class);
when(mUserManager.isAdminUser()).thenReturn(true);
ReflectionHelpers.setField(mFragment, "mUserManager", mUserManager);
diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AppMemoryPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/AppMemoryPreferenceControllerTest.java
index 8e1fd3d..3b48bbf 100644
--- a/tests/robotests/src/com/android/settings/applications/appinfo/AppMemoryPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/applications/appinfo/AppMemoryPreferenceControllerTest.java
@@ -18,6 +18,7 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -25,10 +26,12 @@
import android.content.Context;
import android.content.Intent;
+import android.os.UserManager;
import android.provider.Settings;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
+import androidx.test.core.app.ApplicationProvider;
import com.android.settings.SettingsActivity;
import com.android.settings.applications.ProcStatsData;
@@ -37,14 +40,14 @@
import com.android.settings.testutils.shadow.ShadowUserManager;
import org.junit.Before;
-import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.util.ReflectionHelpers;
@@ -54,6 +57,8 @@
com.android.settings.testutils.shadow.ShadowFragment.class,
})
public class AppMemoryPreferenceControllerTest {
+ @Rule
+ public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@Mock
private SettingsActivity mActivity;
@@ -69,9 +74,11 @@
@Before
public void setUp() {
- MockitoAnnotations.initMocks(this);
- mContext = RuntimeEnvironment.application;
- ShadowUserManager.getShadow().setIsAdminUser(true);
+ mContext = spy(ApplicationProvider.getApplicationContext());
+ UserManager userManager = mock(UserManager.class);
+ when(userManager.isAdminUser()).thenReturn(true);
+ doReturn(userManager).when(mContext).getSystemService(Context.USER_SERVICE);
+
mController =
spy(new AppMemoryPreferenceController(mContext, mFragment, null /* lifecycle */));
when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference);
@@ -80,7 +87,6 @@
when(mFragment.getActivity()).thenReturn(mActivity);
}
- @Ignore("b/313582035")
@Test
@Config(qualifiers = "mcc999")
public void getAvailabilityStatus_developmentSettingsEnabled_shouldReturnAvailable() {
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsControllerTestBase.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsControllerTestBase.java
index 8ba0b44..4edf117 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsControllerTestBase.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsControllerTestBase.java
@@ -43,6 +43,7 @@
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {
com.android.settings.testutils.shadow.ShadowFragment.class,
+ com.android.settings.testutils.shadow.ShadowRestrictedLockUtilsInternal.class,
})
public abstract class BluetoothDetailsControllerTestBase {
diff --git a/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java
index cd48bf1..ee00068 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java
@@ -41,12 +41,12 @@
import com.android.settings.connecteddevice.DevicePreferenceCallback;
import com.android.settings.dashboard.DashboardFragment;
-import com.android.settings.flags.Flags;
import com.android.settings.testutils.shadow.ShadowAudioManager;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
import com.android.settings.testutils.shadow.ShadowCachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.flags.Flags;
import org.junit.Before;
import org.junit.Rule;
diff --git a/tests/robotests/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdaterTest.java
index 349391d..796120d 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdaterTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdaterTest.java
@@ -40,12 +40,12 @@
import com.android.settings.connecteddevice.DevicePreferenceCallback;
import com.android.settings.dashboard.DashboardFragment;
-import com.android.settings.flags.Flags;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.flags.Flags;
import org.junit.Before;
import org.junit.Rule;
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupControllerTest.java
index e5964d0..357420a 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupControllerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupControllerTest.java
@@ -22,27 +22,23 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;
-import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothLeBroadcastAssistant;
import android.bluetooth.BluetoothProfile;
-import android.bluetooth.BluetoothStatusCodes;
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.AudioManager;
-import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.FragmentActivity;
+import androidx.lifecycle.LifecycleOwner;
import androidx.preference.Preference;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceManager;
@@ -51,19 +47,16 @@
import com.android.settings.R;
import com.android.settings.bluetooth.AvailableMediaBluetoothDeviceUpdater;
import com.android.settings.bluetooth.Utils;
-import com.android.settings.flags.Flags;
import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
import com.android.settings.testutils.shadow.ShadowAudioManager;
-import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.BluetoothEventManager;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.HearingAidInfo;
-import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
-import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.settingslib.core.lifecycle.Lifecycle;
import org.junit.Before;
import org.junit.Rule;
@@ -76,16 +69,12 @@
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
-import org.robolectric.shadow.api.Shadow;
-
-import java.util.concurrent.Executor;
/** Tests for {@link AvailableMediaDeviceGroupController}. */
@RunWith(RobolectricTestRunner.class)
@Config(
shadows = {
ShadowAudioManager.class,
- ShadowBluetoothAdapter.class,
ShadowBluetoothUtils.class,
ShadowAlertDialogCompat.class,
})
@@ -105,9 +94,7 @@
@Mock private PackageManager mPackageManager;
@Mock private BluetoothEventManager mEventManager;
@Mock private LocalBluetoothManager mLocalBluetoothManager;
- @Mock private LocalBluetoothProfileManager mLocalBtProfileManager;
@Mock private CachedBluetoothDeviceManager mCachedDeviceManager;
- @Mock private LocalBluetoothLeBroadcastAssistant mAssistant;
@Mock private CachedBluetoothDevice mCachedBluetoothDevice;
private PreferenceGroup mPreferenceGroup;
@@ -115,13 +102,16 @@
private Preference mPreference;
private AvailableMediaDeviceGroupController mAvailableMediaDeviceGroupController;
private AudioManager mAudioManager;
- private ShadowBluetoothAdapter mShadowBluetoothAdapter;
+ private LifecycleOwner mLifecycleOwner;
+ private Lifecycle mLifecycle;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = spy(RuntimeEnvironment.application);
+ mLifecycleOwner = () -> mLifecycle;
+ mLifecycle = new Lifecycle(mLifecycleOwner);
mPreference = new Preference(mContext);
mPreference.setKey(PREFERENCE_KEY_1);
mPreferenceGroup = spy(new PreferenceScreen(mContext, null));
@@ -130,24 +120,17 @@
doReturn(mPackageManager).when(mContext).getPackageManager();
doReturn(true).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);
- mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
- mShadowBluetoothAdapter.setEnabled(true);
- mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
- BluetoothStatusCodes.FEATURE_NOT_SUPPORTED);
- mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
- BluetoothStatusCodes.FEATURE_NOT_SUPPORTED);
ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager;
mLocalBluetoothManager = Utils.getLocalBtManager(mContext);
mAudioManager = mContext.getSystemService(AudioManager.class);
doReturn(mEventManager).when(mLocalBluetoothManager).getEventManager();
- when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBtProfileManager);
when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager);
when(mCachedDeviceManager.findDevice(any(BluetoothDevice.class)))
.thenReturn(mCachedBluetoothDevice);
when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
mAvailableMediaDeviceGroupController =
- spy(new AvailableMediaDeviceGroupController(mContext));
+ spy(new AvailableMediaDeviceGroupController(mContext, null, mLifecycle));
mAvailableMediaDeviceGroupController.setBluetoothDeviceUpdater(
mAvailableMediaBluetoothDeviceUpdater);
mAvailableMediaDeviceGroupController.setFragmentManager(
@@ -197,7 +180,7 @@
@Test
public void testRegister() {
// register the callback in onStart()
- mAvailableMediaDeviceGroupController.onStart();
+ mAvailableMediaDeviceGroupController.onStart(mLifecycleOwner);
verify(mAvailableMediaBluetoothDeviceUpdater).registerCallback();
verify(mLocalBluetoothManager.getEventManager())
@@ -206,36 +189,15 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
- public void testRegister_audioSharingOn() {
- setUpBroadcast();
- // register the callback in onStart()
- mAvailableMediaDeviceGroupController.onStart();
- verify(mAssistant)
- .registerServiceCallBack(
- any(Executor.class), any(BluetoothLeBroadcastAssistant.Callback.class));
- }
-
- @Test
public void testUnregister() {
// unregister the callback in onStop()
- mAvailableMediaDeviceGroupController.onStop();
+ mAvailableMediaDeviceGroupController.onStop(mLifecycleOwner);
verify(mAvailableMediaBluetoothDeviceUpdater).unregisterCallback();
verify(mLocalBluetoothManager.getEventManager())
.unregisterCallback(any(BluetoothCallback.class));
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
- public void testUnregister_audioSharingOn() {
- setUpBroadcast();
- // unregister the callback in onStop()
- mAvailableMediaDeviceGroupController.onStop();
- verify(mAssistant)
- .unregisterServiceCallBack(any(BluetoothLeBroadcastAssistant.Callback.class));
- }
-
- @Test
public void testGetAvailabilityStatus_noBluetoothFeature_returnUnSupported() {
doReturn(false).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);
@@ -274,7 +236,7 @@
mAvailableMediaDeviceGroupController.mLocalBluetoothManager = null;
// Shouldn't crash
- mAvailableMediaDeviceGroupController.onStart();
+ mAvailableMediaDeviceGroupController.onStart(mLifecycleOwner);
}
@Test
@@ -282,7 +244,7 @@
mAvailableMediaDeviceGroupController.mLocalBluetoothManager = null;
// Shouldn't crash
- mAvailableMediaDeviceGroupController.onStop();
+ mAvailableMediaDeviceGroupController.onStop(mLifecycleOwner);
}
@Test
@@ -300,19 +262,4 @@
final AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
assertThat(dialog.isShowing()).isTrue();
}
-
- private void setUpBroadcast() {
- mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
- BluetoothStatusCodes.FEATURE_SUPPORTED);
- mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
- BluetoothStatusCodes.FEATURE_SUPPORTED);
- when(mLocalBtProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(mAssistant);
- doNothing()
- .when(mAssistant)
- .registerServiceCallBack(
- any(Executor.class), any(BluetoothLeBroadcastAssistant.Callback.class));
- doNothing()
- .when(mAssistant)
- .unregisterServiceCallBack(any(BluetoothLeBroadcastAssistant.Callback.class));
- }
}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragmentTest.java
index 09f7a38..0cd464c 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragmentTest.java
@@ -20,19 +20,25 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.pm.PackageManager;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.SearchIndexableResource;
import com.android.settings.R;
+import com.android.settings.connecteddevice.fastpair.FastPairDeviceUpdater;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.PreferenceControllerListHelper;
+import com.android.settings.flags.Flags;
import com.android.settings.slices.SlicePreferenceController;
+import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
import com.android.settings.testutils.shadow.ShadowConnectivityManager;
import com.android.settings.testutils.shadow.ShadowUserManager;
@@ -60,14 +66,13 @@
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private static final String KEY_NEARBY_DEVICES = "bt_nearby_slice";
private static final String KEY_DISCOVERABLE_FOOTER = "discoverable_footer";
private static final String KEY_SAVED_DEVICE_SEE_ALL = "previously_connected_devices_see_all";
private static final String KEY_FAST_PAIR_DEVICE_SEE_ALL = "fast_pair_devices_see_all";
private static final String KEY_ADD_BT_DEVICES = "add_bt_devices";
- private static final String KEY_AUDIO_SHARING_DEVICE_LIST = "audio_sharing_device_list";
- private static final String KEY_AUDIO_SHARING_SETTINGS =
- "connected_device_audio_sharing_settings";
private static final String SETTINGS_PACKAGE_NAME = "com.android.settings";
private static final String SYSTEMUI_PACKAGE_NAME = "com.android.systemui";
private static final String SLICE_ACTION = "com.android.settings.SEARCH_RESULT_TRAMPOLINE";
@@ -75,8 +80,11 @@
private static final String TEST_ACTION = "com.testapp.settings.ACTION_START";
@Mock private PackageManager mPackageManager;
+ @Mock private FastPairDeviceUpdater mFastPairDeviceUpdater;
private Context mContext;
private ConnectedDeviceDashboardFragment mFragment;
+ private FakeFeatureFactory mFeatureFactory;
+ private AvailableMediaDeviceGroupController mMediaDeviceGroupController;
@Before
public void setUp() {
@@ -84,6 +92,22 @@
mContext = spy(RuntimeEnvironment.application);
mFragment = new ConnectedDeviceDashboardFragment();
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_SUBSEQUENT_PAIR_SETTINGS_INTEGRATION);
+ mFeatureFactory = FakeFeatureFactory.setupForTest();
+ when(mFeatureFactory
+ .getFastPairFeatureProvider()
+ .getFastPairDeviceUpdater(
+ any(Context.class), any(DevicePreferenceCallback.class)))
+ .thenReturn(mFastPairDeviceUpdater);
+ when(mFeatureFactory
+ .getAudioSharingFeatureProvider()
+ .createAudioSharingDevicePreferenceController(mContext, null, null))
+ .thenReturn(null);
+ mMediaDeviceGroupController = new AvailableMediaDeviceGroupController(mContext, null, null);
+ when(mFeatureFactory
+ .getAudioSharingFeatureProvider()
+ .createAvailableMediaDeviceGroupController(mContext, null, null))
+ .thenReturn(mMediaDeviceGroupController);
doReturn(mPackageManager).when(mContext).getPackageManager();
doReturn(true).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);
}
@@ -111,9 +135,7 @@
KEY_NEARBY_DEVICES,
KEY_DISCOVERABLE_FOOTER,
KEY_SAVED_DEVICE_SEE_ALL,
- KEY_FAST_PAIR_DEVICE_SEE_ALL,
- KEY_AUDIO_SHARING_DEVICE_LIST,
- KEY_AUDIO_SHARING_SETTINGS);
+ KEY_FAST_PAIR_DEVICE_SEE_ALL);
}
@Test
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragmentTest.java
deleted file mode 100644
index 757964b..0000000
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragmentTest.java
+++ /dev/null
@@ -1,232 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothStatusCodes;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.view.View;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import androidx.appcompat.app.AlertDialog;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentActivity;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.android.settings.R;
-import com.android.settings.flags.Flags;
-import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
-import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.annotation.Config;
-import org.robolectric.shadow.api.Shadow;
-import org.robolectric.shadows.androidx.fragment.FragmentController;
-
-import java.util.ArrayList;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-@RunWith(RobolectricTestRunner.class)
-@Config(
- shadows = {
- ShadowAlertDialogCompat.class,
- ShadowBluetoothAdapter.class,
- })
-public class AudioSharingDialogFragmentTest {
-
- @Rule public final MockitoRule mocks = MockitoJUnit.rule();
-
- @Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
- private static final String TEST_DEVICE_NAME1 = "test1";
-
- private static final String TEST_DEVICE_NAME2 = "test2";
- private static final String TEST_DEVICE_NAME3 = "test3";
- private static final AudioSharingDeviceItem TEST_DEVICE_ITEM1 =
- new AudioSharingDeviceItem(TEST_DEVICE_NAME1, /* groupId= */ 1, /* isActive= */ false);
- private static final AudioSharingDeviceItem TEST_DEVICE_ITEM2 =
- new AudioSharingDeviceItem(TEST_DEVICE_NAME2, /* groupId= */ 2, /* isActive= */ false);
- private static final AudioSharingDeviceItem TEST_DEVICE_ITEM3 =
- new AudioSharingDeviceItem(TEST_DEVICE_NAME3, /* groupId= */ 3, /* isActive= */ false);
-
- private Fragment mParent;
- private AudioSharingDialogFragment mFragment;
- private ShadowBluetoothAdapter mShadowBluetoothAdapter;
-
- @Before
- public void setUp() {
- ShadowAlertDialogCompat.reset();
- mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
- mShadowBluetoothAdapter.setEnabled(true);
- mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
- BluetoothStatusCodes.FEATURE_SUPPORTED);
- mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
- BluetoothStatusCodes.FEATURE_SUPPORTED);
- mFragment = new AudioSharingDialogFragment();
- mParent = new Fragment();
- FragmentController.setupFragment(
- mParent, FragmentActivity.class, /* containerViewId= */ 0, /* bundle= */ null);
- }
-
- @Test
- @RequiresFlagsDisabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
- public void onCreateDialog_flagOff_dialogNotExist() {
- mFragment.show(mParent, new ArrayList<>(), (item) -> {});
- shadowMainLooper().idle();
- AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- assertThat(dialog).isNull();
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
- public void onCreateDialog_flagOn_noConnectedDevice() {
- mFragment.show(mParent, new ArrayList<>(), (item) -> {});
- shadowMainLooper().idle();
- AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- ShadowAlertDialogCompat shadowDialog = ShadowAlertDialogCompat.shadowOf(dialog);
- View rootView = shadowDialog.getView();
- TextView subtitle1 = rootView.findViewById(R.id.share_audio_subtitle1);
- ImageView guidance = rootView.findViewById(R.id.share_audio_guidance);
- Button shareBtn = rootView.findViewById(R.id.share_btn);
- assertThat(dialog.isShowing()).isTrue();
- assertThat(subtitle1.getVisibility()).isEqualTo(View.GONE);
- assertThat(guidance.getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(shareBtn.getVisibility()).isEqualTo(View.GONE);
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
- public void onCreateDialog_noConnectedDevice_dialogDismiss() {
- mFragment.show(mParent, new ArrayList<>(), (item) -> {});
- shadowMainLooper().idle();
- AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- dialog.findViewById(android.R.id.button2).performClick();
- shadowMainLooper().idle();
- assertThat(dialog.isShowing()).isFalse();
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
- public void onCreateDialog_flagOn_singleConnectedDevice() {
- ArrayList<AudioSharingDeviceItem> list = new ArrayList<>();
- list.add(TEST_DEVICE_ITEM1);
- mFragment.show(mParent, list, (item) -> {});
- shadowMainLooper().idle();
- AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- ShadowAlertDialogCompat shadowDialog = ShadowAlertDialogCompat.shadowOf(dialog);
- View rootView = shadowDialog.getView();
- TextView subtitle1 = rootView.findViewById(R.id.share_audio_subtitle1);
- ImageView guidance = rootView.findViewById(R.id.share_audio_guidance);
- Button shareBtn = rootView.findViewById(R.id.share_btn);
- assertThat(dialog.isShowing()).isTrue();
- assertThat(subtitle1.getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(subtitle1.getText().toString()).isEqualTo(TEST_DEVICE_NAME1);
- assertThat(guidance.getVisibility()).isEqualTo(View.GONE);
- assertThat(shareBtn.getVisibility()).isEqualTo(View.VISIBLE);
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
- public void onCreateDialog_singleConnectedDevice_dialogDismiss() {
- ArrayList<AudioSharingDeviceItem> list = new ArrayList<>();
- list.add(TEST_DEVICE_ITEM1);
- mFragment.show(mParent, list, (item) -> {});
- shadowMainLooper().idle();
- AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- ShadowAlertDialogCompat shadowDialog = ShadowAlertDialogCompat.shadowOf(dialog);
- View rootView = shadowDialog.getView();
- rootView.findViewById(R.id.cancel_btn).performClick();
- assertThat(dialog.isShowing()).isFalse();
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
- public void onCreateDialog_singleConnectedDevice_shareClicked() {
- ArrayList<AudioSharingDeviceItem> list = new ArrayList<>();
- list.add(TEST_DEVICE_ITEM1);
- AtomicBoolean isShareBtnClicked = new AtomicBoolean(false);
- mFragment.show(
- mParent,
- list,
- (item) -> {
- isShareBtnClicked.set(true);
- });
- shadowMainLooper().idle();
- AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- ShadowAlertDialogCompat shadowDialog = ShadowAlertDialogCompat.shadowOf(dialog);
- View rootView = shadowDialog.getView();
- rootView.findViewById(R.id.share_btn).performClick();
- assertThat(dialog.isShowing()).isFalse();
- assertThat(isShareBtnClicked.get()).isTrue();
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
- public void onCreateDialog_flagOn_multipleConnectedDevice() {
- ArrayList<AudioSharingDeviceItem> list = new ArrayList<>();
- list.add(TEST_DEVICE_ITEM1);
- list.add(TEST_DEVICE_ITEM2);
- list.add(TEST_DEVICE_ITEM3);
- mFragment.show(mParent, list, (item) -> {});
- shadowMainLooper().idle();
- AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- ShadowAlertDialogCompat shadowDialog = ShadowAlertDialogCompat.shadowOf(dialog);
- View rootView = shadowDialog.getView();
- TextView subtitle1 = rootView.findViewById(R.id.share_audio_subtitle1);
- ImageView guidance = rootView.findViewById(R.id.share_audio_guidance);
- Button shareBtn = rootView.findViewById(R.id.share_btn);
- RecyclerView recyclerView = rootView.findViewById(R.id.btn_list);
- assertThat(dialog.isShowing()).isTrue();
- assertThat(subtitle1.getVisibility()).isEqualTo(View.GONE);
- assertThat(guidance.getVisibility()).isEqualTo(View.GONE);
- assertThat(shareBtn.getVisibility()).isEqualTo(View.GONE);
- assertThat(recyclerView.getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(recyclerView.getAdapter().getItemCount()).isEqualTo(3);
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
- public void onCreateDialog_multipleConnectedDevice_dialogDismiss() {
- ArrayList<AudioSharingDeviceItem> list = new ArrayList<>();
- list.add(TEST_DEVICE_ITEM1);
- list.add(TEST_DEVICE_ITEM2);
- list.add(TEST_DEVICE_ITEM3);
- mFragment.show(mParent, list, (item) -> {});
- shadowMainLooper().idle();
- AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- ShadowAlertDialogCompat shadowDialog = ShadowAlertDialogCompat.shadowOf(dialog);
- View rootView = shadowDialog.getView();
- rootView.findViewById(R.id.cancel_btn).performClick();
- assertThat(dialog.isShowing()).isFalse();
- }
-}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDisconnectDialogFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDisconnectDialogFragmentTest.java
deleted file mode 100644
index 966a695..0000000
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDisconnectDialogFragmentTest.java
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.when;
-import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothStatusCodes;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.view.View;
-import android.widget.Button;
-
-import androidx.appcompat.app.AlertDialog;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentActivity;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.android.settings.R;
-import com.android.settings.flags.Flags;
-import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
-import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.annotation.Config;
-import org.robolectric.shadow.api.Shadow;
-import org.robolectric.shadows.androidx.fragment.FragmentController;
-
-import java.util.ArrayList;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-@RunWith(RobolectricTestRunner.class)
-@Config(
- shadows = {
- ShadowAlertDialogCompat.class,
- ShadowBluetoothAdapter.class,
- })
-public class AudioSharingDisconnectDialogFragmentTest {
-
- @Rule public final MockitoRule mocks = MockitoJUnit.rule();
-
- @Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
- private static final String TEST_DEVICE_NAME1 = "test1";
- private static final String TEST_DEVICE_NAME2 = "test2";
- private static final String TEST_DEVICE_NAME3 = "test3";
- private static final int TEST_GROUP_ID1 = 1;
- private static final int TEST_GROUP_ID2 = 2;
- private static final int TEST_GROUP_ID3 = 3;
- private static final AudioSharingDeviceItem TEST_DEVICE_ITEM1 =
- new AudioSharingDeviceItem(TEST_DEVICE_NAME1, TEST_GROUP_ID1, /* isActive= */ true);
- private static final AudioSharingDeviceItem TEST_DEVICE_ITEM2 =
- new AudioSharingDeviceItem(TEST_DEVICE_NAME2, TEST_GROUP_ID2, /* isActive= */ false);
- private static final AudioSharingDeviceItem TEST_DEVICE_ITEM3 =
- new AudioSharingDeviceItem(TEST_DEVICE_NAME3, TEST_GROUP_ID3, /* isActive= */ false);
-
- @Mock private BluetoothDevice mDevice1;
- @Mock private BluetoothDevice mDevice2;
- @Mock private BluetoothDevice mDevice3;
-
- @Mock private CachedBluetoothDevice mCachedDevice1;
- @Mock private CachedBluetoothDevice mCachedDevice2;
- @Mock private CachedBluetoothDevice mCachedDevice3;
- private Fragment mParent;
- private AudioSharingDisconnectDialogFragment mFragment;
- private ShadowBluetoothAdapter mShadowBluetoothAdapter;
- private ArrayList<AudioSharingDeviceItem> mDeviceItems = new ArrayList<>();
-
- @Before
- public void setUp() {
- AlertDialog latestAlertDialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- if (latestAlertDialog != null) {
- latestAlertDialog.dismiss();
- ShadowAlertDialogCompat.reset();
- }
- mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
- mShadowBluetoothAdapter.setEnabled(true);
- mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
- BluetoothStatusCodes.FEATURE_SUPPORTED);
- mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
- BluetoothStatusCodes.FEATURE_SUPPORTED);
- when(mCachedDevice1.getName()).thenReturn(TEST_DEVICE_NAME1);
- when(mCachedDevice1.getDevice()).thenReturn(mDevice1);
- when(mCachedDevice1.getGroupId()).thenReturn(TEST_GROUP_ID1);
- when(mCachedDevice2.getName()).thenReturn(TEST_DEVICE_NAME2);
- when(mCachedDevice2.getDevice()).thenReturn(mDevice2);
- when(mCachedDevice2.getGroupId()).thenReturn(TEST_GROUP_ID2);
- when(mCachedDevice3.getName()).thenReturn(TEST_DEVICE_NAME3);
- when(mCachedDevice3.getDevice()).thenReturn(mDevice3);
- when(mCachedDevice3.getGroupId()).thenReturn(TEST_GROUP_ID3);
- mFragment = new AudioSharingDisconnectDialogFragment();
- mParent = new Fragment();
- FragmentController.setupFragment(
- mParent, FragmentActivity.class, /* containerViewId= */ 0, /* bundle= */ null);
- mDeviceItems.add(TEST_DEVICE_ITEM1);
- mDeviceItems.add(TEST_DEVICE_ITEM2);
- mFragment.show(mParent, mDeviceItems, mCachedDevice3, (item) -> {});
- shadowMainLooper().idle();
- }
-
- @Test
- @RequiresFlagsDisabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
- public void onCreateDialog_flagOff_dialogNotExist() {
- ArrayList<AudioSharingDeviceItem> list = new ArrayList<>();
- list.add(TEST_DEVICE_ITEM1);
- list.add(TEST_DEVICE_ITEM2);
- mFragment.show(mParent, list, mCachedDevice3, (item) -> {});
- shadowMainLooper().idle();
- AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- assertThat(dialog).isNull();
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
- public void onCreateDialog_flagOn_dialogShowBtnForTwoDevices() {
- AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- assertThat(dialog.isShowing()).isTrue();
- ShadowAlertDialogCompat shadowDialog = ShadowAlertDialogCompat.shadowOf(dialog);
- View rootView = shadowDialog.getView();
- RecyclerView view = rootView.findViewById(R.id.device_btn_list);
- assertThat(view.getAdapter().getItemCount()).isEqualTo(2);
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
- public void onCreateDialog_dialogIsShowingForSameGroup_updateDialog() {
- String prefix = "Disconnect ";
- AtomicBoolean isItemBtnClicked = new AtomicBoolean(false);
- AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- assertThat(dialog.isShowing()).isTrue();
- ShadowAlertDialogCompat shadowDialog = ShadowAlertDialogCompat.shadowOf(dialog);
- View rootView = shadowDialog.getView();
- RecyclerView view = rootView.findViewById(R.id.device_btn_list);
- assertThat(view.getAdapter().getItemCount()).isEqualTo(2);
- Button btn1 =
- view.findViewHolderForAdapterPosition(0).itemView.findViewById(R.id.device_button);
- assertThat(btn1.getText().toString()).isEqualTo(prefix + TEST_DEVICE_NAME1);
- Button btn2 =
- view.findViewHolderForAdapterPosition(1).itemView.findViewById(R.id.device_button);
- assertThat(btn2.getText().toString()).isEqualTo(prefix + TEST_DEVICE_NAME2);
- btn1.performClick();
- assertThat(isItemBtnClicked.get()).isFalse();
-
- // Update dialog content with same group
- mFragment.show(mParent, mDeviceItems, mCachedDevice3, (item) -> isItemBtnClicked.set(true));
- shadowMainLooper().idle();
- dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- assertThat(dialog.isShowing()).isTrue();
- btn1 = view.findViewHolderForAdapterPosition(0).itemView.findViewById(R.id.device_button);
- btn1.performClick();
- assertThat(isItemBtnClicked.get()).isTrue();
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
- public void onCreateDialog_dialogIsShowingForNewGroup_updateDialog() {
- String prefix = "Disconnect ";
- AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- assertThat(dialog.isShowing()).isTrue();
- ShadowAlertDialogCompat shadowDialog = ShadowAlertDialogCompat.shadowOf(dialog);
- View rootView = shadowDialog.getView();
- RecyclerView view = rootView.findViewById(R.id.device_btn_list);
- assertThat(view.getAdapter().getItemCount()).isEqualTo(2);
-
- // Update dialog content with new group
- ArrayList<AudioSharingDeviceItem> newDeviceItems = new ArrayList<>();
- newDeviceItems.add(TEST_DEVICE_ITEM2);
- newDeviceItems.add(TEST_DEVICE_ITEM3);
- mFragment.show(mParent, newDeviceItems, mCachedDevice1, (item) -> {});
- shadowMainLooper().idle();
- dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- assertThat(dialog.isShowing()).isTrue();
- shadowDialog = ShadowAlertDialogCompat.shadowOf(dialog);
- rootView = shadowDialog.getView();
- view = rootView.findViewById(R.id.device_btn_list);
- assertThat(view.getAdapter().getItemCount()).isEqualTo(2);
- Button btn1 =
- view.findViewHolderForAdapterPosition(0).itemView.findViewById(R.id.device_button);
- assertThat(btn1.getText().toString()).isEqualTo(prefix + TEST_DEVICE_NAME2);
- Button btn2 =
- view.findViewHolderForAdapterPosition(1).itemView.findViewById(R.id.device_button);
- assertThat(btn2.getText().toString()).isEqualTo(prefix + TEST_DEVICE_NAME3);
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
- public void onCreateDialog_clickCancel_dialogDismiss() {
- AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- assertThat(dialog.isShowing()).isTrue();
- ShadowAlertDialogCompat shadowDialog = ShadowAlertDialogCompat.shadowOf(dialog);
- View rootView = shadowDialog.getView();
- rootView.findViewById(R.id.cancel_btn).performClick();
- assertThat(dialog.isShowing()).isFalse();
- }
-}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProviderImplTest.java
index 0edbc77..1965bff 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProviderImplTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingFeatureProviderImplTest.java
@@ -22,6 +22,7 @@
import androidx.test.core.app.ApplicationProvider;
+import com.android.settings.connecteddevice.AvailableMediaDeviceGroupController;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
@@ -60,6 +61,14 @@
}
@Test
+ public void createAvailableMediaDeviceGroupController_returnsNull() {
+ assertThat(
+ mFeatureProvider.createAvailableMediaDeviceGroupController(
+ mContext, /* fragment= */ null, /* lifecycle= */ null))
+ .isInstanceOf(AvailableMediaDeviceGroupController.class);
+ }
+
+ @Test
public void isAudioSharingFilterMatched_returnsFalse() {
assertThat(mFeatureProvider.isAudioSharingFilterMatched(mCachedDevice, mLocalBtManager))
.isFalse();
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingJoinDialogFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingJoinDialogFragmentTest.java
deleted file mode 100644
index 56951c2..0000000
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingJoinDialogFragmentTest.java
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.when;
-import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothStatusCodes;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.view.View;
-
-import androidx.appcompat.app.AlertDialog;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentActivity;
-
-import com.android.settings.R;
-import com.android.settings.flags.Flags;
-import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
-import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.annotation.Config;
-import org.robolectric.shadow.api.Shadow;
-import org.robolectric.shadows.androidx.fragment.FragmentController;
-
-import java.util.ArrayList;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-@RunWith(RobolectricTestRunner.class)
-@Config(
- shadows = {
- ShadowAlertDialogCompat.class,
- ShadowBluetoothAdapter.class,
- })
-public class AudioSharingJoinDialogFragmentTest {
-
- @Rule public final MockitoRule mocks = MockitoJUnit.rule();
-
- @Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
- private static final String TEST_DEVICE_NAME1 = "test1";
- private static final String TEST_DEVICE_NAME2 = "test2";
- private static final AudioSharingDeviceItem TEST_DEVICE_ITEM1 =
- new AudioSharingDeviceItem(TEST_DEVICE_NAME1, /* groupId= */ 1, /* isActive= */ true);
- private static final AudioSharingDeviceItem TEST_DEVICE_ITEM2 =
- new AudioSharingDeviceItem(TEST_DEVICE_NAME2, /* groupId= */ 2, /* isActive= */ false);
-
- @Mock private CachedBluetoothDevice mCachedDevice1;
- @Mock private CachedBluetoothDevice mCachedDevice2;
- private Fragment mParent;
- private AudioSharingJoinDialogFragment mFragment;
- private ShadowBluetoothAdapter mShadowBluetoothAdapter;
-
- @Before
- public void setUp() {
- AlertDialog latestAlertDialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- if (latestAlertDialog != null) {
- latestAlertDialog.dismiss();
- ShadowAlertDialogCompat.reset();
- }
- mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
- mShadowBluetoothAdapter.setEnabled(true);
- mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
- BluetoothStatusCodes.FEATURE_SUPPORTED);
- mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
- BluetoothStatusCodes.FEATURE_SUPPORTED);
- when(mCachedDevice1.getName()).thenReturn(TEST_DEVICE_NAME1);
- when(mCachedDevice2.getName()).thenReturn(TEST_DEVICE_NAME2);
- mFragment = new AudioSharingJoinDialogFragment();
- mParent = new Fragment();
- FragmentController.setupFragment(
- mParent, FragmentActivity.class, /* containerViewId= */ 0, /* bundle= */ null);
- }
-
- @Test
- @RequiresFlagsDisabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
- public void onCreateDialog_flagOff_dialogNotExist() {
- mFragment.show(mParent, new ArrayList<>(), mCachedDevice2, () -> {});
- shadowMainLooper().idle();
- AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- assertThat(dialog).isNull();
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
- public void onCreateDialog_flagOn_dialogShowTextForSingleDevice() {
- mFragment.show(mParent, new ArrayList<>(), mCachedDevice2, () -> {});
- shadowMainLooper().idle();
- AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- assertThat(dialog).isNotNull();
- assertThat(dialog.isShowing()).isTrue();
- ShadowAlertDialogCompat shadowDialog = ShadowAlertDialogCompat.shadowOf(dialog);
- assertThat(shadowDialog.getMessage().toString()).isEqualTo(TEST_DEVICE_NAME2);
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
- public void onCreateDialog_flagOn_dialogShowTextForTwoDevice() {
- ArrayList<AudioSharingDeviceItem> list = new ArrayList<>();
- list.add(TEST_DEVICE_ITEM1);
- mFragment.show(mParent, list, mCachedDevice2, () -> {});
- shadowMainLooper().idle();
- AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- assertThat(dialog).isNotNull();
- assertThat(dialog.isShowing()).isTrue();
- ShadowAlertDialogCompat shadowDialog = ShadowAlertDialogCompat.shadowOf(dialog);
- assertThat(shadowDialog.getMessage().toString())
- .isEqualTo(TEST_DEVICE_NAME1 + " and " + TEST_DEVICE_NAME2);
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
- public void onCreateDialog_dialogIsShowing_updateDialog() {
- ArrayList<AudioSharingDeviceItem> list = new ArrayList<>();
- list.add(TEST_DEVICE_ITEM1);
- mFragment.show(mParent, list, mCachedDevice2, () -> {});
- shadowMainLooper().idle();
- AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- assertThat(dialog).isNotNull();
- assertThat(dialog.isShowing()).isTrue();
-
- // Update the content
- ArrayList<AudioSharingDeviceItem> list2 = new ArrayList<>();
- list2.add(TEST_DEVICE_ITEM2);
- mFragment.show(mParent, list2, mCachedDevice1, () -> {});
- shadowMainLooper().idle();
- dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- assertThat(dialog).isNotNull();
- assertThat(dialog.isShowing()).isTrue();
- ShadowAlertDialogCompat shadowDialog = ShadowAlertDialogCompat.shadowOf(dialog);
- assertThat(shadowDialog.getMessage().toString())
- .isEqualTo(TEST_DEVICE_NAME2 + " and " + TEST_DEVICE_NAME1);
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
- public void onCreateDialog_clickCancel_dialogDismiss() {
- mFragment.show(mParent, new ArrayList<>(), mCachedDevice2, () -> {});
- shadowMainLooper().idle();
- AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- ShadowAlertDialogCompat shadowDialog = ShadowAlertDialogCompat.shadowOf(dialog);
- View rootView = shadowDialog.getView();
- rootView.findViewById(R.id.cancel_btn).performClick();
- assertThat(dialog.isShowing()).isFalse();
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
- public void onCreateDialog_clickShare_callbackTriggered() {
- AtomicBoolean isShareBtnClicked = new AtomicBoolean(false);
- mFragment.show(
- mParent, new ArrayList<>(), mCachedDevice2, () -> isShareBtnClicked.set(true));
- shadowMainLooper().idle();
- AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- ShadowAlertDialogCompat shadowDialog = ShadowAlertDialogCompat.shadowOf(dialog);
- View rootView = shadowDialog.getView();
- rootView.findViewById(R.id.share_btn).performClick();
- assertThat(dialog.isShowing()).isFalse();
- assertThat(isShareBtnClicked.get()).isTrue();
- }
-}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPreferenceControllerTest.java
deleted file mode 100644
index 145c08c..0000000
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPreferenceControllerTest.java
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * Copyright (C) 2024 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.connecteddevice.audiosharing;
-
-import static android.bluetooth.BluetoothAdapter.STATE_OFF;
-import static android.bluetooth.BluetoothAdapter.STATE_ON;
-
-import static com.android.settings.core.BasePreferenceController.AVAILABLE;
-import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothLeBroadcast;
-import android.bluetooth.BluetoothStatusCodes;
-import android.content.Context;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-
-import androidx.lifecycle.LifecycleOwner;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceScreen;
-import androidx.test.core.app.ApplicationProvider;
-
-import com.android.settings.bluetooth.Utils;
-import com.android.settings.flags.Flags;
-import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
-import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
-import com.android.settingslib.bluetooth.BluetoothEventManager;
-import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
-import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
-import com.android.settingslib.core.lifecycle.Lifecycle;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.Spy;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.annotation.Config;
-import org.robolectric.shadow.api.Shadow;
-
-@RunWith(RobolectricTestRunner.class)
-@Config(shadows = {ShadowBluetoothAdapter.class, ShadowBluetoothUtils.class})
-public class AudioSharingPreferenceControllerTest {
- private static final String PREF_KEY = "audio_sharing_settings";
- private static final String SUMMARY_ON = "On";
- private static final String SUMMARY_OFF = "Off";
-
- @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
-
- @Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
- @Spy Context mContext = ApplicationProvider.getApplicationContext();
- @Mock private PreferenceScreen mScreen;
- @Mock private LocalBluetoothManager mLocalBtManager;
- @Mock private BluetoothEventManager mBtEventManager;
- @Mock private LocalBluetoothProfileManager mLocalBtProfileManager;
- @Mock private LocalBluetoothLeBroadcast mBroadcast;
- private AudioSharingPreferenceController mController;
- private ShadowBluetoothAdapter mShadowBluetoothAdapter;
- private LocalBluetoothManager mLocalBluetoothManager;
- private Lifecycle mLifecycle;
- private LifecycleOwner mLifecycleOwner;
- private Preference mPreference;
-
- @Before
- public void setUp() {
- mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
- mShadowBluetoothAdapter.setEnabled(true);
- mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
- BluetoothStatusCodes.FEATURE_SUPPORTED);
- mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
- BluetoothStatusCodes.FEATURE_SUPPORTED);
- mLifecycleOwner = () -> mLifecycle;
- mLifecycle = new Lifecycle(mLifecycleOwner);
- ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBtManager;
- mLocalBluetoothManager = Utils.getLocalBtManager(mContext);
- when(mLocalBluetoothManager.getEventManager()).thenReturn(mBtEventManager);
- when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBtProfileManager);
- when(mLocalBtProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast);
- mController = new AudioSharingPreferenceController(mContext, PREF_KEY);
- mPreference = new Preference(mContext);
- when(mScreen.findPreference(PREF_KEY)).thenReturn(mPreference);
- }
-
- @Test
- public void onStart_registerCallback() {
- mController.onStart(mLifecycleOwner);
- verify(mBtEventManager).registerCallback(mController);
- verify(mBroadcast).registerServiceCallBack(any(), any(BluetoothLeBroadcast.Callback.class));
- }
-
- @Test
- public void onStop_unregisterCallback() {
- mController.onStop(mLifecycleOwner);
- verify(mBtEventManager).unregisterCallback(mController);
- verify(mBroadcast).unregisterServiceCallBack(any(BluetoothLeBroadcast.Callback.class));
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
- public void getAvailabilityStatus_flagOn() {
- assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
- }
-
- @Test
- @RequiresFlagsDisabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
- public void getAvailabilityStatus_flagOff() {
- assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
- }
-
- @Test
- public void getSummary_broadcastOn() {
- when(mBroadcast.isEnabled(any())).thenReturn(true);
- assertThat(mController.getSummary().toString()).isEqualTo(SUMMARY_ON);
- }
-
- @Test
- public void getSummary_broadcastOff() {
- when(mBroadcast.isEnabled(any())).thenReturn(false);
- assertThat(mController.getSummary().toString()).isEqualTo(SUMMARY_OFF);
- }
-
- @Test
- public void onBluetoothStateChanged_refreshSummary() {
- mController.displayPreference(mScreen);
- when(mBroadcast.isEnabled(any())).thenReturn(true);
- mController.onBluetoothStateChanged(STATE_ON);
- assertThat(mPreference.getSummary().toString()).isEqualTo(SUMMARY_ON);
- when(mBroadcast.isEnabled(any())).thenReturn(false);
- mController.onBluetoothStateChanged(STATE_OFF);
- assertThat(mPreference.getSummary().toString()).isEqualTo(SUMMARY_OFF);
- }
-}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingStopDialogFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingStopDialogFragmentTest.java
deleted file mode 100644
index d93105d..0000000
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingStopDialogFragmentTest.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.when;
-import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothStatusCodes;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-
-import androidx.appcompat.app.AlertDialog;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentActivity;
-
-import com.android.settings.flags.Flags;
-import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
-import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.annotation.Config;
-import org.robolectric.shadow.api.Shadow;
-import org.robolectric.shadows.androidx.fragment.FragmentController;
-
-import java.util.concurrent.atomic.AtomicBoolean;
-
-@RunWith(RobolectricTestRunner.class)
-@Config(
- shadows = {
- ShadowAlertDialogCompat.class,
- ShadowBluetoothAdapter.class,
- })
-public class AudioSharingStopDialogFragmentTest {
-
- @Rule public final MockitoRule mocks = MockitoJUnit.rule();
-
- @Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
- private static final String TEST_DEVICE_NAME1 = "test1";
- private static final String TEST_DEVICE_NAME2 = "test2";
-
- @Mock private CachedBluetoothDevice mCachedDevice1;
- @Mock private CachedBluetoothDevice mCachedDevice2;
- private Fragment mParent;
- private AudioSharingStopDialogFragment mFragment;
- private ShadowBluetoothAdapter mShadowBluetoothAdapter;
-
- @Before
- public void setUp() {
- AlertDialog latestAlertDialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- if (latestAlertDialog != null) {
- latestAlertDialog.dismiss();
- ShadowAlertDialogCompat.reset();
- }
- mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
- mShadowBluetoothAdapter.setEnabled(true);
- mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
- BluetoothStatusCodes.FEATURE_SUPPORTED);
- mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
- BluetoothStatusCodes.FEATURE_SUPPORTED);
- when(mCachedDevice1.getName()).thenReturn(TEST_DEVICE_NAME1);
- when(mCachedDevice2.getName()).thenReturn(TEST_DEVICE_NAME2);
- mFragment = new AudioSharingStopDialogFragment();
- mParent = new Fragment();
- FragmentController.setupFragment(
- mParent, FragmentActivity.class, /* containerViewId= */ 0, /* bundle= */ null);
- }
-
- @Test
- @RequiresFlagsDisabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
- public void onCreateDialog_flagOff_dialogNotExist() {
- mFragment.show(mParent, mCachedDevice1, () -> {});
- shadowMainLooper().idle();
- AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- assertThat(dialog).isNull();
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
- public void onCreateDialog_dialogIsShowing_updateDialog() {
- String postMessage = " wants to connect, headphones in audio sharing will disconnect.";
- mFragment.show(mParent, mCachedDevice1, () -> {});
- shadowMainLooper().idle();
- AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- assertThat(dialog).isNotNull();
- assertThat(dialog.isShowing()).isTrue();
- ShadowAlertDialogCompat shadowDialog = ShadowAlertDialogCompat.shadowOf(dialog);
- assertThat(shadowDialog.getMessage().toString()).isEqualTo(TEST_DEVICE_NAME1 + postMessage);
-
- // Update the content
- mFragment.show(mParent, mCachedDevice2, () -> {});
- shadowMainLooper().idle();
- dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- assertThat(dialog).isNotNull();
- assertThat(dialog.isShowing()).isTrue();
- shadowDialog = ShadowAlertDialogCompat.shadowOf(dialog);
- assertThat(shadowDialog.getMessage().toString()).isEqualTo(TEST_DEVICE_NAME2 + postMessage);
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
- public void onCreateDialog_clickCancel_dialogDismiss() {
- mFragment.show(mParent, mCachedDevice1, () -> {});
- shadowMainLooper().idle();
- AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- dialog.findViewById(android.R.id.button2).performClick();
- shadowMainLooper().idle();
- assertThat(dialog.isShowing()).isFalse();
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
- public void onCreateDialog_clickShare_callbackTriggered() {
- AtomicBoolean isStopBtnClicked = new AtomicBoolean(false);
- mFragment.show(mParent, mCachedDevice1, () -> isStopBtnClicked.set(true));
- shadowMainLooper().idle();
- AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- dialog.findViewById(android.R.id.button1).performClick();
- shadowMainLooper().idle();
- assertThat(dialog.isShowing()).isFalse();
- assertThat(isStopBtnClicked.get()).isTrue();
- }
-}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java
deleted file mode 100644
index 11e8ec9..0000000
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2023 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.connecteddevice.audiosharing;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.robolectric.Shadows.shadowOf;
-
-import android.bluetooth.BluetoothAdapter;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Looper;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.widget.Switch;
-
-import androidx.test.core.app.ApplicationProvider;
-
-import com.android.settings.flags.Flags;
-import com.android.settings.widget.SettingsMainSwitchBar;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.Spy;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-import org.robolectric.RobolectricTestRunner;
-
-@RunWith(RobolectricTestRunner.class)
-public class AudioSharingSwitchBarControllerTest {
- @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
-
- @Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
- @Spy Context mContext = ApplicationProvider.getApplicationContext();
- @Mock private Switch mSwitch;
-
- private SettingsMainSwitchBar mSwitchBar;
- private AudioSharingSwitchBarController mController;
- private AudioSharingSwitchBarController.OnSwitchBarChangedListener mListener;
- private boolean mOnSwitchBarChanged;
-
- @Before
- public void setUp() {
- mSwitchBar = new SettingsMainSwitchBar(mContext);
- mOnSwitchBarChanged = false;
- mListener = () -> mOnSwitchBarChanged = true;
- mController = new AudioSharingSwitchBarController(mContext, mSwitchBar, mListener);
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
- public void bluetoothOff_switchDisabled() {
- assertThat(mSwitchBar.isEnabled()).isTrue();
- mContext.registerReceiver(
- mController.mReceiver,
- mController.mIntentFilter,
- Context.RECEIVER_EXPORTED_UNAUDITED);
- Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED);
- intent.putExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF);
- mContext.sendBroadcast(intent);
- shadowOf(Looper.getMainLooper()).idle();
- assertThat(mSwitchBar.isEnabled()).isFalse();
- assertThat(mOnSwitchBarChanged).isTrue();
- }
-}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsDialogFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsDialogFragmentTest.java
deleted file mode 100644
index 58a1272..0000000
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsDialogFragmentTest.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2024 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.connecteddevice.audiosharing;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothStatusCodes;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-
-import androidx.appcompat.app.AlertDialog;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentActivity;
-
-import com.android.settings.flags.Flags;
-import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
-import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.annotation.Config;
-import org.robolectric.shadow.api.Shadow;
-import org.robolectric.shadows.androidx.fragment.FragmentController;
-
-import java.util.ArrayList;
-
-@RunWith(RobolectricTestRunner.class)
-@Config(
- shadows = {
- ShadowAlertDialogCompat.class,
- ShadowBluetoothAdapter.class,
- })
-public class CallsAndAlarmsDialogFragmentTest {
- @Rule public final MockitoRule mocks = MockitoJUnit.rule();
-
- @Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
- private static final String TEST_DEVICE_NAME1 = "test1";
- private static final String TEST_DEVICE_NAME2 = "test2";
- private static final AudioSharingDeviceItem TEST_DEVICE_ITEM1 =
- new AudioSharingDeviceItem(TEST_DEVICE_NAME1, /* groupId= */ 1, /* isActive= */ true);
-
- private static final AudioSharingDeviceItem TEST_DEVICE_ITEM2 =
- new AudioSharingDeviceItem(TEST_DEVICE_NAME2, /* groupId= */ 1, /* isActive= */ true);
-
- private Fragment mParent;
- private CallsAndAlarmsDialogFragment mFragment;
- private ShadowBluetoothAdapter mShadowBluetoothAdapter;
-
- @Before
- public void setUp() {
- ShadowAlertDialogCompat.reset();
- mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
- mShadowBluetoothAdapter.setEnabled(true);
- mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
- BluetoothStatusCodes.FEATURE_SUPPORTED);
- mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
- BluetoothStatusCodes.FEATURE_SUPPORTED);
- mFragment = new CallsAndAlarmsDialogFragment();
- mParent = new Fragment();
- FragmentController.setupFragment(
- mParent, FragmentActivity.class, /* containerViewId= */ 0, /* bundle= */ null);
- }
-
- @Test
- @RequiresFlagsDisabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
- public void onCreateDialog_flagOff_dialogNotExist() {
- mFragment.show(mParent, new ArrayList<>(), (item) -> {});
- shadowMainLooper().idle();
- AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- assertThat(dialog).isNull();
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
- public void onCreateDialog_showCorrectItems() {
- ArrayList<AudioSharingDeviceItem> deviceItemList = new ArrayList<>();
- deviceItemList.add(TEST_DEVICE_ITEM1);
- deviceItemList.add(TEST_DEVICE_ITEM2);
- mFragment.show(mParent, deviceItemList, (item) -> {});
- shadowMainLooper().idle();
- AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
- assertThat(dialog.getListView().getCount()).isEqualTo(2);
- }
-}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsPreferenceControllerTest.java
deleted file mode 100644
index 4cdd364..0000000
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsPreferenceControllerTest.java
+++ /dev/null
@@ -1,248 +0,0 @@
-/*
- * Copyright (C) 2024 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.connecteddevice.audiosharing;
-
-import static com.android.settings.core.BasePreferenceController.AVAILABLE;
-import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.robolectric.Shadows.shadowOf;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothLeBroadcastAssistant;
-import android.bluetooth.BluetoothLeBroadcastReceiveState;
-import android.bluetooth.BluetoothProfile;
-import android.bluetooth.BluetoothStatusCodes;
-import android.content.Context;
-import android.os.Looper;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.provider.Settings;
-
-import androidx.lifecycle.LifecycleOwner;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceScreen;
-import androidx.test.core.app.ApplicationProvider;
-
-import com.android.settings.bluetooth.Utils;
-import com.android.settings.flags.Flags;
-import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
-import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
-import com.android.settingslib.bluetooth.BluetoothEventManager;
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
-import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
-import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
-import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
-import com.android.settingslib.core.lifecycle.Lifecycle;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.Spy;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.annotation.Config;
-import org.robolectric.shadow.api.Shadow;
-
-import java.util.ArrayList;
-
-@RunWith(RobolectricTestRunner.class)
-@Config(
- shadows = {
- ShadowBluetoothAdapter.class,
- ShadowBluetoothUtils.class,
- })
-public class CallsAndAlarmsPreferenceControllerTest {
- private static final String PREF_KEY = "calls_and_alarms";
- private static final String SUMMARY_EMPTY = "No active device in sharing";
- private static final String TEST_DEVICE_NAME1 = "test1";
- private static final String TEST_DEVICE_NAME2 = "test2";
- private static final String TEST_SETTINGS_KEY =
- "bluetooth_le_broadcast_fallback_active_group_id";
-
- @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
-
- @Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
- @Spy Context mContext = ApplicationProvider.getApplicationContext();
- @Mock private PreferenceScreen mScreen;
- @Mock private LocalBluetoothManager mLocalBtManager;
- @Mock private BluetoothEventManager mBtEventManager;
- @Mock private LocalBluetoothProfileManager mLocalBtProfileManager;
- @Mock private CachedBluetoothDeviceManager mCacheManager;
- @Mock private LocalBluetoothLeBroadcast mBroadcast;
- @Mock private LocalBluetoothLeBroadcastAssistant mAssistant;
- @Mock private BluetoothDevice mDevice1;
- @Mock private BluetoothDevice mDevice2;
- @Mock private BluetoothDevice mDevice3;
- @Mock private CachedBluetoothDevice mCachedDevice1;
- @Mock private CachedBluetoothDevice mCachedDevice2;
- @Mock private CachedBluetoothDevice mCachedDevice3;
- @Mock private BluetoothLeBroadcastReceiveState mState;
- private CallsAndAlarmsPreferenceController mController;
- private ShadowBluetoothAdapter mShadowBluetoothAdapter;
- private LocalBluetoothManager mLocalBluetoothManager;
- private Lifecycle mLifecycle;
- private LifecycleOwner mLifecycleOwner;
- private Preference mPreference;
-
- @Before
- public void setUp() {
- mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
- mShadowBluetoothAdapter.setEnabled(true);
- mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
- BluetoothStatusCodes.FEATURE_SUPPORTED);
- mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
- BluetoothStatusCodes.FEATURE_SUPPORTED);
- mLifecycleOwner = () -> mLifecycle;
- mLifecycle = new Lifecycle(mLifecycleOwner);
- ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBtManager;
- mLocalBluetoothManager = Utils.getLocalBtManager(mContext);
- when(mLocalBluetoothManager.getEventManager()).thenReturn(mBtEventManager);
- when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBtProfileManager);
- when(mLocalBtProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast);
- when(mLocalBtProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(mAssistant);
- mController = new CallsAndAlarmsPreferenceController(mContext);
- mPreference = new Preference(mContext);
- when(mScreen.findPreference(PREF_KEY)).thenReturn(mPreference);
- }
-
- @Test
- public void onStart_registerCallback() {
- mController.onStart(mLifecycleOwner);
- verify(mBtEventManager).registerCallback(mController);
- verify(mAssistant)
- .registerServiceCallBack(any(), any(BluetoothLeBroadcastAssistant.Callback.class));
- }
-
- @Test
- public void onStop_unregisterCallback() {
- mController.onStop(mLifecycleOwner);
- verify(mBtEventManager).unregisterCallback(mController);
- verify(mAssistant)
- .unregisterServiceCallBack(any(BluetoothLeBroadcastAssistant.Callback.class));
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
- public void getAvailabilityStatus_flagOn() {
- assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
- }
-
- @Test
- @RequiresFlagsDisabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
- public void getAvailabilityStatus_flagOff() {
- assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
- }
-
- @Test
- public void updateVisibility_broadcastOffBluetoothOff() {
- when(mBroadcast.isEnabled(any())).thenReturn(false);
- mShadowBluetoothAdapter.setEnabled(false);
- mController.displayPreference(mScreen);
- mController.updateVisibility();
- shadowOf(Looper.getMainLooper()).idle();
- assertThat(mPreference.isVisible()).isFalse();
- }
-
- @Test
- public void updateVisibility_broadcastOnBluetoothOff() {
- when(mBroadcast.isEnabled(any())).thenReturn(true);
- mShadowBluetoothAdapter.setEnabled(false);
- mController.displayPreference(mScreen);
- mController.updateVisibility();
- shadowOf(Looper.getMainLooper()).idle();
- assertThat(mPreference.isVisible()).isFalse();
- }
-
- @Test
- public void updateVisibility_broadcastOffBluetoothOn() {
- when(mBroadcast.isEnabled(any())).thenReturn(false);
- mController.displayPreference(mScreen);
- mController.updateVisibility();
- shadowOf(Looper.getMainLooper()).idle();
- assertThat(mPreference.isVisible()).isFalse();
- }
-
- @Test
- public void updateVisibility_broadcastOnBluetoothOn() {
- when(mBroadcast.isEnabled(any())).thenReturn(true);
- when(mAssistant.getConnectedDevices()).thenReturn(new ArrayList<BluetoothDevice>());
- mController.displayPreference(mScreen);
- mController.updateVisibility();
- shadowOf(Looper.getMainLooper()).idle();
- assertThat(mPreference.isVisible()).isTrue();
- assertThat(mPreference.getSummary().toString()).isEqualTo(SUMMARY_EMPTY);
- }
-
- @Test
- public void onProfileConnectionStateChanged_updatePreference() {
- when(mBroadcast.isEnabled(any())).thenReturn(true);
- when(mAssistant.getConnectedDevices()).thenReturn(new ArrayList<BluetoothDevice>());
- mController.displayPreference(mScreen);
- mController.onProfileConnectionStateChanged(
- mCachedDevice1, BluetoothAdapter.STATE_DISCONNECTED, BluetoothProfile.LE_AUDIO);
- shadowOf(Looper.getMainLooper()).idle();
- assertThat(mPreference.isVisible()).isTrue();
- assertThat(mPreference.getSummary().toString()).isEqualTo(SUMMARY_EMPTY);
- }
-
- @Test
- public void updatePreference_showCorrectSummary() {
- final int groupId1 = 1;
- final int groupId2 = 2;
- Settings.Secure.putInt(mContext.getContentResolver(), TEST_SETTINGS_KEY, groupId1);
- when(mCachedDevice1.getGroupId()).thenReturn(groupId1);
- when(mCachedDevice1.getDevice()).thenReturn(mDevice1);
- when(mCachedDevice2.getGroupId()).thenReturn(groupId1);
- when(mCachedDevice2.getDevice()).thenReturn(mDevice2);
- when(mCachedDevice1.getMemberDevice()).thenReturn(ImmutableSet.of(mCachedDevice2));
- when(mCachedDevice1.getName()).thenReturn(TEST_DEVICE_NAME1);
- when(mCachedDevice3.getGroupId()).thenReturn(groupId2);
- when(mCachedDevice3.getDevice()).thenReturn(mDevice3);
- when(mCachedDevice3.getName()).thenReturn(TEST_DEVICE_NAME2);
- when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCacheManager);
- when(mCacheManager.findDevice(mDevice1)).thenReturn(mCachedDevice1);
- when(mCacheManager.findDevice(mDevice2)).thenReturn(mCachedDevice2);
- when(mCacheManager.findDevice(mDevice3)).thenReturn(mCachedDevice3);
- when(mBroadcast.isEnabled(any())).thenReturn(true);
- ImmutableList<BluetoothDevice> deviceList = ImmutableList.of(mDevice1, mDevice2, mDevice3);
- when(mAssistant.getConnectedDevices()).thenReturn(deviceList);
- when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of(mState));
- mController.displayPreference(mScreen);
- mController.updateVisibility();
- shadowOf(Looper.getMainLooper()).idle();
- assertThat(mPreference.isVisible()).isTrue();
- assertThat(mPreference.getSummary().toString()).isEqualTo(TEST_DEVICE_NAME1);
- }
-}
diff --git a/tests/robotests/src/com/android/settings/development/ForcePeakRefreshRatePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/ForcePeakRefreshRatePreferenceControllerTest.java
index 314120a..c7c76b1 100644
--- a/tests/robotests/src/com/android/settings/development/ForcePeakRefreshRatePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/development/ForcePeakRefreshRatePreferenceControllerTest.java
@@ -16,6 +16,8 @@
package com.android.settings.development;
+import static android.hardware.display.DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED;
+
import static com.android.internal.display.RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE;
import static com.android.settings.development.ForcePeakRefreshRatePreferenceController.NO_CONFIG;
@@ -24,15 +26,18 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.content.Context;
+import android.hardware.display.DisplayManager;
import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
+import android.testing.TestableContext;
+import android.view.Display;
import androidx.preference.PreferenceScreen;
import androidx.preference.SwitchPreference;
+import androidx.test.platform.app.InstrumentationRegistry;
import com.android.server.display.feature.flags.Flags;
@@ -43,7 +48,6 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
@RunWith(RobolectricTestRunner.class)
@@ -53,17 +57,51 @@
private SwitchPreference mPreference;
@Mock
private PreferenceScreen mScreen;
+ @Mock
+ private DisplayManager mDisplayManagerMock;
+ @Mock
+ private Display mDisplayMock;
+ @Mock
+ private Display mDisplayMock2;
- private Context mContext;
private ForcePeakRefreshRatePreferenceController mController;
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+ @Rule
+ public final TestableContext mContext = new TestableContext(
+ InstrumentationRegistry.getInstrumentation().getContext());
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mContext = RuntimeEnvironment.application;
+ mContext.addMockSystemService(DisplayManager.class, mDisplayManagerMock);
+
+ Display.Mode[] modes = new Display.Mode[]{
+ new Display.Mode(/* modeId= */ 0, /* width= */ 800, /* height= */ 600,
+ /* refreshRate= */ 60),
+ new Display.Mode(/* modeId= */ 0, /* width= */ 800, /* height= */ 600,
+ /* refreshRate= */ 120),
+ new Display.Mode(/* modeId= */ 0, /* width= */ 800, /* height= */ 600,
+ /* refreshRate= */ 90)
+ };
+ when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(mDisplayMock);
+ when(mDisplayMock.getSupportedModes()).thenReturn(modes);
+
+ Display.Mode[] modes2 = new Display.Mode[]{
+ new Display.Mode(/* modeId= */ 0, /* width= */ 800, /* height= */ 600,
+ /* refreshRate= */ 70),
+ new Display.Mode(/* modeId= */ 0, /* width= */ 800, /* height= */ 600,
+ /* refreshRate= */ 130),
+ new Display.Mode(/* modeId= */ 0, /* width= */ 800, /* height= */ 600,
+ /* refreshRate= */ 80)
+ };
+ when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY + 1)).thenReturn(mDisplayMock2);
+ when(mDisplayMock2.getSupportedModes()).thenReturn(modes2);
+
+ when(mDisplayManagerMock.getDisplays(DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED))
+ .thenReturn(new Display[]{ mDisplayMock, mDisplayMock2 });
+
mController = new ForcePeakRefreshRatePreferenceController(mContext);
when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference);
when(mPreference.getKey()).thenReturn(mController.getPreferenceKey());
@@ -153,4 +191,16 @@
assertThat(mPreference.isChecked()).isFalse();
assertThat(mPreference.isEnabled()).isFalse();
}
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_BACK_UP_SMOOTH_DISPLAY_AND_FORCE_PEAK_REFRESH_RATE)
+ public void peakRefreshRate_highestOfDefaultDisplay_featureFlagOff() {
+ assertThat(mController.mPeakRefreshRate).isEqualTo(120);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_BACK_UP_SMOOTH_DISPLAY_AND_FORCE_PEAK_REFRESH_RATE)
+ public void peakRefreshRate_highestOfAllDisplays_featureFlagOn() {
+ assertThat(mController.mPeakRefreshRate).isEqualTo(130);
+ }
}
diff --git a/tests/robotests/src/com/android/settings/display/EvenDimmerPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/display/EvenDimmerPreferenceControllerTest.java
new file mode 100644
index 0000000..a3cf151
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/display/EvenDimmerPreferenceControllerTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2024 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.display;
+
+
+import static com.android.settings.core.BasePreferenceController.AVAILABLE;
+import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.provider.Settings;
+
+import com.android.server.display.feature.flags.Flags;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class EvenDimmerPreferenceControllerTest {
+
+ private EvenDimmerPreferenceController mController;
+ @Mock
+ private Context mContext;
+ @Mock
+ private Resources mResources;
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mContext.getResources()).thenReturn(mResources);
+ mController = new EvenDimmerPreferenceController(mContext, "key");
+ }
+
+ @RequiresFlagsDisabled(Flags.FLAG_EVEN_DIMMER)
+ @Test
+ public void testGetAvailabilityStatus_flagOffconfigTrue() {
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mResources.getBoolean(
+ com.android.internal.R.bool.config_evenDimmerEnabled)).thenReturn(true);
+ // setup
+ mController = new EvenDimmerPreferenceController(mContext, "key");
+
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
+ }
+
+ @RequiresFlagsDisabled(Flags.FLAG_EVEN_DIMMER)
+ @Test
+ public void testGetCheckedStatus_setTrue() throws Settings.SettingNotFoundException {
+ // setup
+ mController = new EvenDimmerPreferenceController(mContext, "key");
+ mController.setChecked(true);
+
+ assertThat(Settings.Secure.getFloat(mContext.getContentResolver(),
+ Settings.Secure.EVEN_DIMMER_ACTIVATED)).isEqualTo(0.0f); // false
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_EVEN_DIMMER)
+ @Test
+ public void testGetAvailabilityStatus_flagOnConfigTrue() {
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mResources.getBoolean(
+ com.android.internal.R.bool.config_evenDimmerEnabled)).thenReturn(true);
+ // setup
+ mController = new EvenDimmerPreferenceController(mContext, "key");
+
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_EVEN_DIMMER)
+ public void testSetChecked_enable() throws Settings.SettingNotFoundException {
+ mController.setChecked(true);
+ assertThat(Settings.Secure.getFloat(mContext.getContentResolver(),
+ Settings.Secure.EVEN_DIMMER_ACTIVATED)).isEqualTo(1.0f); // true
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_EVEN_DIMMER)
+ public void testSetChecked_disable() throws Settings.SettingNotFoundException {
+ mController.setChecked(false);
+ assertThat(Settings.Secure.getFloat(mContext.getContentResolver(),
+ Settings.Secure.EVEN_DIMMER_ACTIVATED)).isEqualTo(0.0f); // false
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/display/PeakRefreshRatePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/display/PeakRefreshRatePreferenceControllerTest.java
index cb0963b..f8e91bd 100644
--- a/tests/robotests/src/com/android/settings/display/PeakRefreshRatePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/display/PeakRefreshRatePreferenceControllerTest.java
@@ -16,6 +16,8 @@
package com.android.settings.display;
+import static android.hardware.display.DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED;
+
import static com.android.internal.display.RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE;
import static com.android.settings.core.BasePreferenceController.AVAILABLE;
import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE;
@@ -24,14 +26,17 @@
import static org.mockito.Mockito.when;
-import android.content.Context;
+import android.hardware.display.DisplayManager;
import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
+import android.testing.TestableContext;
+import android.view.Display;
import androidx.preference.SwitchPreference;
+import androidx.test.platform.app.InstrumentationRegistry;
import com.android.server.display.feature.flags.Flags;
@@ -48,21 +53,55 @@
@RunWith(RobolectricTestRunner.class)
public class PeakRefreshRatePreferenceControllerTest {
- private Context mContext;
private PeakRefreshRatePreferenceController mController;
private SwitchPreference mPreference;
@Mock
private PeakRefreshRatePreferenceController.DeviceConfigDisplaySettings
mDeviceConfigDisplaySettings;
+ @Mock
+ private DisplayManager mDisplayManagerMock;
+ @Mock
+ private Display mDisplayMock;
+ @Mock
+ private Display mDisplayMock2;
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+ @Rule
+ public final TestableContext mContext = new TestableContext(
+ InstrumentationRegistry.getInstrumentation().getContext());
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mContext = RuntimeEnvironment.application;
+ mContext.addMockSystemService(DisplayManager.class, mDisplayManagerMock);
+
+ Display.Mode[] modes = new Display.Mode[]{
+ new Display.Mode(/* modeId= */ 0, /* width= */ 800, /* height= */ 600,
+ /* refreshRate= */ 60),
+ new Display.Mode(/* modeId= */ 0, /* width= */ 800, /* height= */ 600,
+ /* refreshRate= */ 120),
+ new Display.Mode(/* modeId= */ 0, /* width= */ 800, /* height= */ 600,
+ /* refreshRate= */ 90)
+ };
+ when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(mDisplayMock);
+ when(mDisplayMock.getSupportedModes()).thenReturn(modes);
+
+ Display.Mode[] modes2 = new Display.Mode[]{
+ new Display.Mode(/* modeId= */ 0, /* width= */ 800, /* height= */ 600,
+ /* refreshRate= */ 70),
+ new Display.Mode(/* modeId= */ 0, /* width= */ 800, /* height= */ 600,
+ /* refreshRate= */ 130),
+ new Display.Mode(/* modeId= */ 0, /* width= */ 800, /* height= */ 600,
+ /* refreshRate= */ 80)
+ };
+ when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY + 1)).thenReturn(mDisplayMock2);
+ when(mDisplayMock2.getSupportedModes()).thenReturn(modes2);
+
+ when(mDisplayManagerMock.getDisplays(DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED))
+ .thenReturn(new Display[]{ mDisplayMock, mDisplayMock2 });
+
mController = new PeakRefreshRatePreferenceController(mContext, "key");
mController.injectDeviceConfigDisplaySettings(mDeviceConfigDisplaySettings);
mPreference = new SwitchPreference(RuntimeEnvironment.application);
@@ -152,4 +191,16 @@
assertThat(mController.isChecked()).isFalse();
}
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_BACK_UP_SMOOTH_DISPLAY_AND_FORCE_PEAK_REFRESH_RATE)
+ public void peakRefreshRate_highestOfDefaultDisplay_featureFlagOff() {
+ assertThat(mController.mPeakRefreshRate).isEqualTo(120);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_BACK_UP_SMOOTH_DISPLAY_AND_FORCE_PEAK_REFRESH_RATE)
+ public void peakRefreshRate_highestOfAllDisplays_featureFlagOn() {
+ assertThat(mController.mPeakRefreshRate).isEqualTo(130);
+ }
}
diff --git a/tests/robotests/src/com/android/settings/homepage/SettingsHomepageActivityTest.java b/tests/robotests/src/com/android/settings/homepage/SettingsHomepageActivityTest.java
index 6cd4f16..6431306 100644
--- a/tests/robotests/src/com/android/settings/homepage/SettingsHomepageActivityTest.java
+++ b/tests/robotests/src/com/android/settings/homepage/SettingsHomepageActivityTest.java
@@ -29,7 +29,6 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -223,28 +222,6 @@
& SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS).isEqualTo(0);
}
- @Test
- public void onCreate_notTaskRoot_shouldFinishActivity() {
- SettingsHomepageActivity activity =
- spy(Robolectric.buildActivity(SettingsHomepageActivity.class).get());
- doReturn(false).when(activity).isTaskRoot();
-
- activity.onCreate(/* savedInstanceState */ null);
-
- verify(activity).finish();
- }
-
- @Test
- public void onCreate_singleTaskActivity_shouldNotFinishActivity() {
- SettingsHomepageActivity activity =
- spy(Robolectric.buildActivity(DeepLinkHomepageActivity.class).get());
- doReturn(false).when(activity).isTaskRoot();
-
- activity.onCreate(/* savedInstanceState */ null);
-
- verify(activity, never()).finish();
- }
-
/** This test is for large screen devices Activity embedding. */
@Test
@Config(shadows = ShadowActivityEmbeddingUtils.class)
diff --git a/tests/robotests/src/com/android/settings/network/SubscriptionUtilRoboTest.java b/tests/robotests/src/com/android/settings/network/SubscriptionUtilRoboTest.java
new file mode 100644
index 0000000..2595510
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/network/SubscriptionUtilRoboTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2024 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.network;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkCapabilities;
+import android.telephony.SubscriptionManager;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.shadows.ShadowSubscriptionManager;
+
+@RunWith(RobolectricTestRunner.class)
+public class SubscriptionUtilRoboTest {
+ private static final int SUBID_1 = 1;
+ private static final int SUBID_2 = 2;
+
+ private Context mContext;
+ private NetworkCapabilities mNetworkCapabilities;
+ private ShadowSubscriptionManager mShadowSubscriptionManager;
+
+ @Mock
+ private ConnectivityManager mConnectivityManager;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = spy(ApplicationProvider.getApplicationContext());
+ mShadowSubscriptionManager = shadowOf(mContext.getSystemService(SubscriptionManager.class));
+ when(mContext.getSystemService(ConnectivityManager.class)).thenReturn(mConnectivityManager);
+ }
+
+ @Test
+ public void isConnectedToWifiOrDifferentSubId_hasDataOnSubId2_returnTrue() {
+ addNetworkTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+ mShadowSubscriptionManager.setActiveDataSubscriptionId(SUBID_2);
+
+ assertTrue(SubscriptionUtil.isConnectedToWifiOrDifferentSubId(mContext, SUBID_1));
+ }
+
+ @Test
+ public void isConnectedToWifiOrDifferentSubId_hasDataOnSubId1_returnFalse() {
+ addNetworkTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+ mShadowSubscriptionManager.setActiveDataSubscriptionId(SUBID_1);
+
+ assertFalse(SubscriptionUtil.isConnectedToWifiOrDifferentSubId(mContext, SUBID_1));
+ }
+
+ private void addNetworkTransportType(int networkType) {
+ mNetworkCapabilities =
+ new NetworkCapabilities.Builder().addTransportType(networkType).build();
+ when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(mNetworkCapabilities);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/network/telephony/AutoDataSwitchPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/network/telephony/AutoDataSwitchPreferenceControllerTest.java
index 758d6b0..29592cf 100644
--- a/tests/robotests/src/com/android/settings/network/telephony/AutoDataSwitchPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/network/telephony/AutoDataSwitchPreferenceControllerTest.java
@@ -30,12 +30,19 @@
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.telephony.TelephonyManager;
import androidx.preference.PreferenceScreen;
import androidx.preference.SwitchPreference;
+import com.android.settings.flags.Flags;
+
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -52,6 +59,9 @@
private static final int SUB_ID_1 = 111;
private static final int SUB_ID_2 = 222;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Mock
private TelephonyManager mTelephonyManager;
@Mock
@@ -79,6 +89,7 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_IS_DUAL_SIM_ONBOARDING_ENABLED)
public void getAvailabilityStatus_noInit_notAvailable() {
ShadowSubscriptionManager.setDefaultDataSubscriptionId(SUB_ID_1);
AutoDataSwitchPreferenceController controller =
@@ -90,6 +101,7 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_IS_DUAL_SIM_ONBOARDING_ENABLED)
public void displayPreference_defaultForData_notAvailable() {
ShadowSubscriptionManager.setDefaultDataSubscriptionId(SUB_ID_1);
@@ -100,6 +112,7 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_IS_DUAL_SIM_ONBOARDING_ENABLED)
public void displayPreference_notDefaultForData_available() {
ShadowSubscriptionManager.setDefaultDataSubscriptionId(SUB_ID_2);
@@ -110,6 +123,7 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_IS_DUAL_SIM_ONBOARDING_ENABLED)
public void onSubscriptionsChanged_becomesDefaultForData_notAvailable() {
ShadowSubscriptionManager.setDefaultDataSubscriptionId(SUB_ID_2);
@@ -122,6 +136,7 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_IS_DUAL_SIM_ONBOARDING_ENABLED)
public void onSubscriptionsChanged_noLongerDefaultForData_available() {
ShadowSubscriptionManager.setDefaultDataSubscriptionId(SUB_ID_1);
@@ -134,6 +149,7 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_IS_DUAL_SIM_ONBOARDING_ENABLED)
public void getAvailabilityStatus_mobileDataChangWithDefaultDataSubId_returnUnavailable() {
ShadowSubscriptionManager.setDefaultDataSubscriptionId(SUB_ID_1);
@@ -144,6 +160,7 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_IS_DUAL_SIM_ONBOARDING_ENABLED)
public void getAvailabilityStatus_mobileDataChangWithoutDefaultDataSubId_returnAvailable() {
ShadowSubscriptionManager.setDefaultDataSubscriptionId(SUB_ID_1);
@@ -152,4 +169,16 @@
assertThat(mController.getAvailabilityStatus(SUB_ID_2)).isEqualTo(AVAILABLE);
}
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_IS_DUAL_SIM_ONBOARDING_ENABLED)
+ public void getAvailabilityStatus_flagIsDualSimOnboardingEnabledOn_returnUnavailable() {
+ ShadowSubscriptionManager.setDefaultDataSubscriptionId(SUB_ID_1);
+
+ mController.displayPreference(mPreferenceScreen);
+ mController.refreshPreference();
+
+ assertThat(mController.getAvailabilityStatus(SUB_ID_1))
+ .isEqualTo(CONDITIONALLY_UNAVAILABLE);
+ }
}
diff --git a/tests/robotests/src/com/android/settings/notification/MediaVolumePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/MediaVolumePreferenceControllerTest.java
index ed93473..a25f472 100644
--- a/tests/robotests/src/com/android/settings/notification/MediaVolumePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/MediaVolumePreferenceControllerTest.java
@@ -31,17 +31,23 @@
import android.media.AudioManager;
import android.media.session.MediaController;
import android.net.Uri;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import androidx.slice.builders.SliceAction;
import com.android.settings.media.MediaOutputIndicatorWorker;
import com.android.settings.slices.SliceBackgroundWorker;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.flags.Flags;
import com.android.settingslib.media.BluetoothMediaDevice;
import com.android.settingslib.media.MediaDevice;
import com.android.settingslib.media.MediaOutputConstants;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -60,16 +66,16 @@
"android.settings.MEDIA_BROADCAST_DIALOG";
private static MediaOutputIndicatorWorker sMediaOutputIndicatorWorker;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
private MediaVolumePreferenceController mController;
private Context mContext;
- @Mock
- private MediaController mMediaController;
- @Mock
- private MediaDevice mDevice1;
- @Mock
- private MediaDevice mDevice2;
+ @Mock private MediaController mMediaController;
+ @Mock private MediaDevice mDevice1;
+ @Mock private MediaDevice mDevice2;
@Before
public void setUp() {
@@ -77,8 +83,8 @@
mContext = RuntimeEnvironment.application;
mController = new MediaVolumePreferenceController(mContext);
- sMediaOutputIndicatorWorker = spy(
- new MediaOutputIndicatorWorker(mContext, VOLUME_MEDIA_URI));
+ sMediaOutputIndicatorWorker =
+ spy(new MediaOutputIndicatorWorker(mContext, VOLUME_MEDIA_URI));
when(mDevice1.isBLEDevice()).thenReturn(true);
when(mDevice2.isBLEDevice()).thenReturn(false);
}
@@ -101,8 +107,8 @@
@Test
public void isSliceableCorrectKey_returnsTrue() {
- final MediaVolumePreferenceController controller = new MediaVolumePreferenceController(
- mContext);
+ final MediaVolumePreferenceController controller =
+ new MediaVolumePreferenceController(mContext);
assertThat(controller.isSliceable()).isTrue();
}
@@ -112,6 +118,17 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
+ public void isSupportEndItem_flagOff_returnsFalse() {
+ doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
+ doReturn(false).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
+ doReturn(mDevice1).when(sMediaOutputIndicatorWorker).getCurrentConnectedMediaDevice();
+
+ assertThat(mController.isSupportEndItem()).isFalse();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void isSupportEndItem_withBleDevice_returnsTrue() {
doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
doReturn(false).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
@@ -121,6 +138,7 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void isSupportEndItem_notSupportedBroadcast_returnsFalse() {
doReturn(false).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
doReturn(mDevice1).when(sMediaOutputIndicatorWorker).getCurrentConnectedMediaDevice();
@@ -129,6 +147,7 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void isSupportEndItem_withNonBleDevice_returnsFalse() {
doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
doReturn(false).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
@@ -138,6 +157,7 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void isSupportEndItem_deviceIsBroadcastingAndConnectedToNonBleDevice_returnsTrue() {
doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
doReturn(true).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
@@ -147,6 +167,7 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void isSupportEndItem_deviceIsNotBroadcastingAndConnectedToNonBleDevice_returnsFalse() {
doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
doReturn(false).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
@@ -155,8 +176,20 @@
assertThat(mController.isSupportEndItem()).isFalse();
}
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
+ public void getSliceEndItem_flagOff_getsNullSliceAction() {
+ doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
+ doReturn(true).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
+ doReturn(mDevice2).when(sMediaOutputIndicatorWorker).getCurrentConnectedMediaDevice();
+
+ final SliceAction sliceAction = mController.getSliceEndItem(mContext);
+
+ assertThat(sliceAction).isNull();
+ }
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void getSliceEndItem_NotSupportEndItem_getsNullSliceAction() {
doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
doReturn(false).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
@@ -168,22 +201,26 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void getSliceEndItem_deviceIsBroadcasting_getsBroadcastIntent() {
doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
doReturn(mDevice1).when(sMediaOutputIndicatorWorker).getCurrentConnectedMediaDevice();
doReturn(true).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
- doReturn(mMediaController).when(sMediaOutputIndicatorWorker)
+ doReturn(mMediaController)
+ .when(sMediaOutputIndicatorWorker)
.getActiveLocalMediaController();
final SliceAction sliceAction = mController.getSliceEndItem(mContext);
final PendingIntent endItemPendingIntent = sliceAction.getAction();
- final PendingIntent expectedToggleIntent = getBroadcastIntent(
- MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
+ final PendingIntent expectedToggleIntent =
+ getBroadcastIntent(
+ MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
assertThat(endItemPendingIntent).isEqualTo(expectedToggleIntent);
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void getSliceEndItem_deviceIsNotBroadcasting_getsActivityIntent() {
final MediaDevice device = mock(BluetoothMediaDevice.class);
final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
@@ -192,7 +229,8 @@
doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
doReturn(device).when(sMediaOutputIndicatorWorker).getCurrentConnectedMediaDevice();
doReturn(false).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
- doReturn(mMediaController).when(sMediaOutputIndicatorWorker)
+ doReturn(mMediaController)
+ .when(sMediaOutputIndicatorWorker)
.getActiveLocalMediaController();
final SliceAction sliceAction = mController.getSliceEndItem(mContext);
@@ -215,13 +253,19 @@
private PendingIntent getBroadcastIntent(String action) {
final Intent intent = new Intent(action);
intent.setPackage(MediaOutputConstants.SYSTEMUI_PACKAGE_NAME);
- return PendingIntent.getBroadcast(mContext, 0 /* requestCode */, intent,
+ return PendingIntent.getBroadcast(
+ mContext,
+ 0 /* requestCode */,
+ intent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
}
private PendingIntent getActivityIntent(String action) {
final Intent intent = new Intent(action);
- return PendingIntent.getActivity(mContext, 0 /* requestCode */, intent,
+ return PendingIntent.getActivity(
+ mContext,
+ 0 /* requestCode */,
+ intent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
}
}
diff --git a/tests/robotests/src/com/android/settings/widget/RestrictedButtonTest.java b/tests/robotests/src/com/android/settings/widget/RestrictedButtonTest.java
index d696342..4b05b91 100644
--- a/tests/robotests/src/com/android/settings/widget/RestrictedButtonTest.java
+++ b/tests/robotests/src/com/android/settings/widget/RestrictedButtonTest.java
@@ -34,7 +34,6 @@
import com.android.settings.testutils.shadow.ShadowUserManager;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
@@ -44,7 +43,6 @@
import java.util.ArrayList;
import java.util.List;
-@Ignore("b/315133235")
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowUserManager.class, ShadowDevicePolicyManager.class})
public class RestrictedButtonTest {
diff --git a/tests/robotests/src/com/android/settings/widget/VideoPreferenceTest.java b/tests/robotests/src/com/android/settings/widget/VideoPreferenceTest.java
index 35dc666..530517f 100644
--- a/tests/robotests/src/com/android/settings/widget/VideoPreferenceTest.java
+++ b/tests/robotests/src/com/android/settings/widget/VideoPreferenceTest.java
@@ -35,18 +35,19 @@
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceViewHolder;
+import androidx.test.core.app.ApplicationProvider;
import com.android.settings.R;
import com.android.settings.testutils.shadow.ShadowSettingsMediaPlayer;
import org.junit.Before;
-import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.androidx.fragment.FragmentController;
@@ -55,7 +56,8 @@
public class VideoPreferenceTest {
private static final int VIDEO_WIDTH = 100;
private static final int VIDEO_HEIGHT = 150;
-
+ @Rule
+ public final MockitoRule mMockitoRule = MockitoJUnit.rule();
private VideoPreference.AnimationController mAnimationController;
@Mock
private ImageView fakePreview;
@@ -68,9 +70,7 @@
@Before
public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- mContext = RuntimeEnvironment.application;
+ mContext = ApplicationProvider.getApplicationContext();
mAnimationController = spy(
new MediaAnimationController(mContext, R.raw.sample_video));
mVideoPreference = new VideoPreference(mContext, null /* attrs */);
@@ -141,7 +141,6 @@
assertThat(mAnimationController.isPlaying()).isTrue();
}
- @Ignore("b/315133235")
@Test
@Config(qualifiers = "mcc999")
public void onViewVisible_createAnimationController() {
diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowAudioManager.java b/tests/robotests/testutils/com/android/settings/testutils/shadow/ShadowAudioManager.java
similarity index 92%
rename from tests/robotests/src/com/android/settings/testutils/shadow/ShadowAudioManager.java
rename to tests/robotests/testutils/com/android/settings/testutils/shadow/ShadowAudioManager.java
index 9c06665..b465a41 100644
--- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowAudioManager.java
+++ b/tests/robotests/testutils/com/android/settings/testutils/shadow/ShadowAudioManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2024 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.
@@ -38,6 +38,7 @@
import java.util.ArrayList;
import java.util.List;
+/** Robolectric shadow for the AudioManager. */
@Implements(value = AudioManager.class)
public class ShadowAudioManager extends org.robolectric.shadows.ShadowAudioManager {
private int mRingerMode;
@@ -58,11 +59,13 @@
mRingerMode = mode;
}
+ /** Register audio device callback. */
@Implementation
public void registerAudioDeviceCallback(AudioDeviceCallback callback, Handler handler) {
mDeviceCallbacks.add(callback);
}
+ /** Unregister audio device callback. */
@Implementation
public void unregisterAudioDeviceCallback(AudioDeviceCallback callback) {
if (mDeviceCallbacks.contains(callback)) {
@@ -79,10 +82,12 @@
return mMusicActiveRemotely;
}
+ /** Set output device. */
public void setOutputDevice(int deviceCodes) {
mDeviceCodes = deviceCodes;
}
+ /** Get devices for stream. */
@Implementation
public int getDevicesForStream(int streamType) {
switch (streamType) {
diff --git a/tests/spa_unit/src/com/android/settings/development/SensitiveContentProtectionPreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/development/SensitiveContentProtectionPreferenceControllerTest.kt
index 18acbba..023572b 100644
--- a/tests/spa_unit/src/com/android/settings/development/SensitiveContentProtectionPreferenceControllerTest.kt
+++ b/tests/spa_unit/src/com/android/settings/development/SensitiveContentProtectionPreferenceControllerTest.kt
@@ -22,6 +22,7 @@
import android.platform.test.flag.junit.DeviceFlagsValueProvider
import android.provider.Settings
import android.provider.Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS
+import android.view.flags.Flags.FLAG_SENSITIVE_CONTENT_APP_PROTECTION
import androidx.preference.Preference
import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreference
@@ -132,7 +133,8 @@
@Test
@RequiresFlagsDisabled(
FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION,
- FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ FLAG_SCREENSHARE_NOTIFICATION_HIDING,
+ FLAG_SENSITIVE_CONTENT_APP_PROTECTION)
fun isAvailable_flagsDisabled_returnFalse() {
assertFalse(controller.isAvailable)
}
@@ -148,4 +150,10 @@
fun isAvailable_screenshareNotificationHidingEnabled_returnTrue() {
assertTrue(controller.isAvailable)
}
-}
\ No newline at end of file
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_SENSITIVE_CONTENT_APP_PROTECTION)
+ fun isAvailable_screenshareSensitiveContentHidingEnabled_returnTrue() {
+ assertTrue(controller.isAvailable)
+ }
+}
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/DataUsagePreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/DataUsagePreferenceControllerTest.kt
index 6cd9151..5f80855 100644
--- a/tests/spa_unit/src/com/android/settings/network/telephony/DataUsagePreferenceControllerTest.kt
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/DataUsagePreferenceControllerTest.kt
@@ -33,6 +33,7 @@
import com.android.settings.datausage.DataUsageUtils
import com.android.settings.datausage.lib.DataUsageLib
import com.android.settings.datausage.lib.NetworkCycleDataRepository
+import com.android.settings.datausage.lib.NetworkStatsRepository.Companion.AllTimeRange
import com.android.settings.datausage.lib.NetworkUsageData
import com.android.settingslib.spa.testutils.waitUntil
import com.google.common.truth.Truth.assertThat
@@ -140,11 +141,14 @@
}
@Test
- fun updateState_noUsageData_shouldEnablePreference() = runBlocking {
+ fun updateState_noFistCycleUsageButOtherUsage_shouldEnablePreference() = runBlocking {
val usageData = NetworkUsageData(START_TIME, END_TIME, 0L)
repository.stub {
on { loadFirstCycle() } doReturn usageData
+ on { queryUsage(AllTimeRange) } doReturn
+ NetworkUsageData(AllTimeRange.lower, AllTimeRange.upper, 1L)
}
+ preference.isEnabled = false
controller.onViewCreated(TestLifecycleOwner())
@@ -153,6 +157,22 @@
}
@Test
+ fun updateState_noDataUsage_shouldDisablePreference() = runBlocking {
+ val usageData = NetworkUsageData(START_TIME, END_TIME, 0L)
+ repository.stub {
+ on { loadFirstCycle() } doReturn usageData
+ on { queryUsage(AllTimeRange) } doReturn
+ NetworkUsageData(AllTimeRange.lower, AllTimeRange.upper, 0L)
+ }
+ preference.isEnabled = true
+
+ controller.onViewCreated(TestLifecycleOwner())
+
+ waitUntil { !preference.isEnabled }
+ waitUntil { preference.summary?.contains("0 B used") == true }
+ }
+
+ @Test
fun updateState_shouldUseIecUnit() = runBlocking {
val usageData = NetworkUsageData(START_TIME, END_TIME, DataUnit.MEBIBYTES.toBytes(1))
repository.stub {
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/WifiCallingPreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/WifiCallingPreferenceControllerTest.kt
index fc53049..92776df 100644
--- a/tests/spa_unit/src/com/android/settings/network/telephony/WifiCallingPreferenceControllerTest.kt
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/WifiCallingPreferenceControllerTest.kt
@@ -29,7 +29,7 @@
import androidx.preference.PreferenceManager
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settings.network.telephony.ims.ImsMmTelRepository
+import com.android.settings.network.telephony.wificalling.WifiCallingRepository
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flowOf
@@ -60,9 +60,9 @@
private var callState = TelephonyManager.CALL_STATE_IDLE
- private object FakeImsMmTelRepository : ImsMmTelRepository {
- var wiFiMode = ImsMmTelManager.WIFI_MODE_UNKNOWN
- override fun getWiFiCallingMode() = wiFiMode
+ private val mockWifiCallingRepository = mock<WifiCallingRepository> {
+ on { getWiFiCallingMode() } doReturn ImsMmTelManager.WIFI_MODE_UNKNOWN
+ on { wifiCallingReadyFlow() } doReturn flowOf(true)
}
private val callingPreferenceCategoryController =
@@ -72,7 +72,7 @@
context = context,
key = TEST_KEY,
callStateFlowFactory = { flowOf(callState) },
- imsMmTelRepositoryFactory = { FakeImsMmTelRepository },
+ wifiCallingRepositoryFactory = { mockWifiCallingRepository },
).init(subId = SUB_ID, callingPreferenceCategoryController)
@Before
@@ -86,7 +86,9 @@
mockTelecomManager.stub {
on { getSimCallManagerForSubscription(SUB_ID) } doReturn null
}
- FakeImsMmTelRepository.wiFiMode = ImsMmTelManager.WIFI_MODE_WIFI_ONLY
+ mockWifiCallingRepository.stub {
+ on { getWiFiCallingMode() } doReturn ImsMmTelManager.WIFI_MODE_WIFI_ONLY
+ }
controller.onViewCreated(TestLifecycleOwner())
delay(100)
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/ims/ImsFeatureProvisionedFlowTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/ims/ImsFeatureProvisionedFlowTest.kt
new file mode 100644
index 0000000..75f933a
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/ims/ImsFeatureProvisionedFlowTest.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2024 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.network.telephony.ims
+
+import android.telephony.ims.ProvisioningManager
+import android.telephony.ims.ProvisioningManager.FeatureProvisioningCallback
+import android.telephony.ims.feature.MmTelFeature
+import android.telephony.ims.stub.ImsRegistrationImplBase
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.testutils.toListWithTimeout
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.async
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.mock
+
+@RunWith(AndroidJUnit4::class)
+class ImsFeatureProvisionedFlowTest {
+
+ private var callback: FeatureProvisioningCallback? = null
+
+ private val mockProvisioningManager = mock<ProvisioningManager> {
+ on { registerFeatureProvisioningChangedCallback(any(), any()) } doAnswer {
+ callback = it.arguments[1] as FeatureProvisioningCallback
+ callback?.onFeatureProvisioningChanged(CAPABILITY, TECH, true)
+ }
+ }
+
+ @Test
+ fun imsFeatureProvisionedFlow_sendInitialValue() = runBlocking {
+ val flow = imsFeatureProvisionedFlow(SUB_ID, CAPABILITY, TECH, mockProvisioningManager)
+
+ val state = flow.first()
+
+ assertThat(state).isTrue()
+ }
+
+ @Test
+ fun imsFeatureProvisionedFlow_changed(): Unit = runBlocking {
+ val listDeferred = async {
+ imsFeatureProvisionedFlow(SUB_ID, CAPABILITY, TECH, mockProvisioningManager)
+ .toListWithTimeout()
+ }
+ delay(100)
+
+ callback?.onFeatureProvisioningChanged(CAPABILITY, TECH, false)
+
+ assertThat(listDeferred.await().last()).isFalse()
+ }
+
+ private companion object {
+ const val SUB_ID = 1
+ const val CAPABILITY = MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE
+ const val TECH = ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN
+ }
+}
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/ims/ImsMmTelRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/ims/ImsMmTelRepositoryTest.kt
index d5142fa..24b081a 100644
--- a/tests/spa_unit/src/com/android/settings/network/telephony/ims/ImsMmTelRepositoryTest.kt
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/ims/ImsMmTelRepositoryTest.kt
@@ -17,39 +17,48 @@
package com.android.settings.network.telephony.ims
import android.content.Context
-import android.telephony.CarrierConfigManager
-import android.telephony.CarrierConfigManager.KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL
-import android.telephony.TelephonyManager
+import android.telephony.AccessNetworkConstants
import android.telephony.ims.ImsMmTelManager
-import androidx.core.os.persistableBundleOf
+import android.telephony.ims.ImsStateCallback
+import android.telephony.ims.feature.MmTelFeature
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.testutils.toListWithTimeout
import com.google.common.truth.Truth.assertThat
+import java.util.function.Consumer
+import kotlinx.coroutines.async
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.runBlocking
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.doThrow
+import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
-import org.mockito.kotlin.spy
import org.mockito.kotlin.stub
@RunWith(AndroidJUnit4::class)
class ImsMmTelRepositoryTest {
- private val mockTelephonyManager = mock<TelephonyManager> {
- on { createForSubscriptionId(SUB_ID) } doReturn mock
- }
+ private val context: Context = ApplicationProvider.getApplicationContext()
- private val mockCarrierConfigManager = mock<CarrierConfigManager>()
-
- private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
- on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager
- on { getSystemService(CarrierConfigManager::class.java) } doReturn mockCarrierConfigManager
- }
+ private var stateCallback: ImsStateCallback? = null
private val mockImsMmTelManager = mock<ImsMmTelManager> {
on { isVoWiFiSettingEnabled } doReturn true
on { getVoWiFiRoamingModeSetting() } doReturn ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED
on { getVoWiFiModeSetting() } doReturn ImsMmTelManager.WIFI_MODE_CELLULAR_PREFERRED
+ on { registerImsStateCallback(any(), any()) } doAnswer {
+ stateCallback = it.arguments[1] as ImsStateCallback
+ stateCallback?.onAvailable()
+ }
+ on { isSupported(eq(CAPABILITY), eq(TRANSPORT), any(), any()) } doAnswer {
+ @Suppress("UNCHECKED_CAST")
+ val consumer = it.arguments[3] as Consumer<Boolean>
+ consumer.accept(true)
+ }
}
private val repository = ImsMmTelRepositoryImpl(context, SUB_ID, mockImsMmTelManager)
@@ -60,42 +69,21 @@
on { isVoWiFiSettingEnabled } doReturn false
}
- val wiFiCallingMode = repository.getWiFiCallingMode()
+ val wiFiCallingMode = repository.getWiFiCallingMode(false)
assertThat(wiFiCallingMode).isEqualTo(ImsMmTelManager.WIFI_MODE_UNKNOWN)
}
@Test
- fun getWiFiCallingMode_roamingAndNotUseWfcHomeModeForRoaming_returnRoamingSetting() {
- mockTelephonyManager.stub {
- on { isNetworkRoaming } doReturn true
- }
- mockUseWfcHomeModeForRoaming(false)
-
- val wiFiCallingMode = repository.getWiFiCallingMode()
+ fun getWiFiCallingMode_useRoamingMode_returnRoamingSetting() {
+ val wiFiCallingMode = repository.getWiFiCallingMode(true)
assertThat(wiFiCallingMode).isEqualTo(mockImsMmTelManager.getVoWiFiRoamingModeSetting())
}
@Test
- fun getWiFiCallingMode_roamingAndUseWfcHomeModeForRoaming_returnHomeSetting() {
- mockTelephonyManager.stub {
- on { isNetworkRoaming } doReturn true
- }
- mockUseWfcHomeModeForRoaming(true)
-
- val wiFiCallingMode = repository.getWiFiCallingMode()
-
- assertThat(wiFiCallingMode).isEqualTo(mockImsMmTelManager.getVoWiFiModeSetting())
- }
-
- @Test
- fun getWiFiCallingMode_notRoaming_returnHomeSetting() {
- mockTelephonyManager.stub {
- on { isNetworkRoaming } doReturn false
- }
-
- val wiFiCallingMode = repository.getWiFiCallingMode()
+ fun getWiFiCallingMode_notSseRoamingMode_returnHomeSetting() {
+ val wiFiCallingMode = repository.getWiFiCallingMode(false)
assertThat(wiFiCallingMode).isEqualTo(mockImsMmTelManager.getVoWiFiModeSetting())
}
@@ -106,22 +94,42 @@
on { isVoWiFiSettingEnabled } doThrow IllegalArgumentException()
}
- val wiFiCallingMode = repository.getWiFiCallingMode()
+ val wiFiCallingMode = repository.getWiFiCallingMode(false)
assertThat(wiFiCallingMode).isEqualTo(ImsMmTelManager.WIFI_MODE_UNKNOWN)
}
- private fun mockUseWfcHomeModeForRoaming(config: Boolean) {
- mockCarrierConfigManager.stub {
- on {
- getConfigForSubId(SUB_ID, KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL)
- } doReturn persistableBundleOf(
- KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL to config,
- )
+ @Test
+ fun imsReadyFlow_sendInitialValue() = runBlocking {
+ val flow = repository.imsReadyFlow()
+
+ val state = flow.first()
+
+ assertThat(state).isTrue()
+ }
+
+ @Test
+ fun imsReadyFlow_changed(): Unit = runBlocking {
+ val listDeferred = async {
+ repository.imsReadyFlow().toListWithTimeout()
}
+ delay(100)
+
+ stateCallback?.onUnavailable(ImsStateCallback.REASON_IMS_SERVICE_NOT_READY)
+
+ assertThat(listDeferred.await().last()).isFalse()
+ }
+
+ @Test
+ fun isSupported() = runBlocking {
+ val isSupported = repository.isSupported(CAPABILITY, TRANSPORT)
+
+ assertThat(isSupported).isTrue()
}
private companion object {
const val SUB_ID = 1
+ const val CAPABILITY = MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE
+ const val TRANSPORT = AccessNetworkConstants.TRANSPORT_TYPE_WLAN
}
}
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/wificalling/WifiCallingRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/wificalling/WifiCallingRepositoryTest.kt
new file mode 100644
index 0000000..1f3acc2
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/wificalling/WifiCallingRepositoryTest.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2024 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.network.telephony.wificalling
+
+import android.content.Context
+import android.telephony.CarrierConfigManager
+import android.telephony.CarrierConfigManager.KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL
+import android.telephony.TelephonyManager
+import android.telephony.ims.ImsMmTelManager
+import androidx.core.os.persistableBundleOf
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.network.telephony.ims.ImsMmTelRepository
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.stub
+
+@RunWith(AndroidJUnit4::class)
+class WifiCallingRepositoryTest {
+
+ private val mockTelephonyManager = mock<TelephonyManager> {
+ on { createForSubscriptionId(SUB_ID) } doReturn mock
+ }
+
+ private val mockCarrierConfigManager = mock<CarrierConfigManager>()
+
+ private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
+ on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager
+ on { getSystemService(CarrierConfigManager::class.java) } doReturn mockCarrierConfigManager
+ }
+
+ private val mockImsMmTelRepository = mock<ImsMmTelRepository> {
+ on { getWiFiCallingMode(any()) } doReturn ImsMmTelManager.WIFI_MODE_UNKNOWN
+ }
+
+ private val repository = WifiCallingRepository(context, SUB_ID, mockImsMmTelRepository)
+
+ @Test
+ fun getWiFiCallingMode_roamingAndNotUseWfcHomeModeForRoaming_returnRoamingSetting() {
+ mockTelephonyManager.stub {
+ on { isNetworkRoaming } doReturn true
+ }
+ mockUseWfcHomeModeForRoaming(false)
+ mockImsMmTelRepository.stub {
+ on { getWiFiCallingMode(true) } doReturn ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED
+ }
+
+ val wiFiCallingMode = repository.getWiFiCallingMode()
+
+ assertThat(wiFiCallingMode).isEqualTo(ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED)
+ }
+
+ @Test
+ fun getWiFiCallingMode_roamingAndUseWfcHomeModeForRoaming_returnHomeSetting() {
+ mockTelephonyManager.stub {
+ on { isNetworkRoaming } doReturn true
+ }
+ mockUseWfcHomeModeForRoaming(true)
+ mockImsMmTelRepository.stub {
+ on { getWiFiCallingMode(false) } doReturn ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED
+ }
+
+ val wiFiCallingMode = repository.getWiFiCallingMode()
+
+ assertThat(wiFiCallingMode).isEqualTo(ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED)
+ }
+
+ @Test
+ fun getWiFiCallingMode_notRoaming_returnHomeSetting() {
+ mockTelephonyManager.stub {
+ on { isNetworkRoaming } doReturn false
+ }
+ mockImsMmTelRepository.stub {
+ on { getWiFiCallingMode(false) } doReturn ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED
+ }
+
+ val wiFiCallingMode = repository.getWiFiCallingMode()
+
+ assertThat(wiFiCallingMode).isEqualTo(ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED)
+ }
+
+ private fun mockUseWfcHomeModeForRoaming(config: Boolean) {
+ mockCarrierConfigManager.stub {
+ on {
+ getConfigForSubId(SUB_ID, KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL)
+ } doReturn persistableBundleOf(
+ KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL to config,
+ )
+ }
+ }
+
+ private companion object {
+ const val SUB_ID = 1
+ }
+}
diff --git a/tests/spa_unit/src/com/android/settings/wifi/WepNetworksPreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/wifi/WepNetworksPreferenceControllerTest.kt
index 994abbf..49e6a17 100644
--- a/tests/spa_unit/src/com/android/settings/wifi/WepNetworksPreferenceControllerTest.kt
+++ b/tests/spa_unit/src/com/android/settings/wifi/WepNetworksPreferenceControllerTest.kt
@@ -20,6 +20,8 @@
import android.net.wifi.WifiManager
import androidx.compose.ui.test.assertIsOff
import androidx.compose.ui.test.assertIsOn
+import androidx.compose.ui.test.isDisplayed
+import androidx.compose.ui.test.isNotDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.onRoot
@@ -28,12 +30,16 @@
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settings.R
+import com.android.settings.dashboard.DashboardFragment
import com.android.settings.spa.preference.ComposePreference
+import com.android.settingslib.spa.testutils.onDialogText
+import com.android.wifitrackerlib.WifiEntry
import java.util.function.Consumer
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito
import org.mockito.kotlin.any
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.doReturn
@@ -48,25 +54,30 @@
private var wepAllowed = true
- private val mockWifiManager = mock<WifiManager> {
+ private var mockWifiInfo = mock<android.net.wifi.WifiInfo> {
+ on { it.currentSecurityType } doReturn WifiEntry.SECURITY_EAP
+ on { it.ssid } doReturn SSID
+ }
+
+ private var mockWifiManager = mock<WifiManager> {
on { queryWepAllowed(any(), any()) } doAnswer {
@Suppress("UNCHECKED_CAST")
val consumer = it.arguments[1] as Consumer<Boolean>
consumer.accept(wepAllowed)
}
on { it.isWepSupported } doReturn true
+ on { it.connectionInfo } doReturn mockWifiInfo
}
private var context: Context =
spy(ApplicationProvider.getApplicationContext()) {
on { getSystemService(WifiManager::class.java) } doReturn mockWifiManager
}
- private var controller = WepNetworksPreferenceController(context, TEST_KEY)
+ private var controller = WepNetworksPreferenceController(context, TEST_KEY)
private val preference = ComposePreference(context).apply { key = TEST_KEY }
private val preferenceScreen = PreferenceManager(context).createPreferenceScreen(context)
-
@Before
fun setUp() {
preferenceScreen.addPreference(preference)
@@ -79,6 +90,7 @@
composeTestRule.setContent {
controller.Content()
}
+
composeTestRule.onNodeWithText(context.getString(R.string.wifi_allow_wep_networks))
.assertIsOn()
}
@@ -89,6 +101,7 @@
composeTestRule.setContent {
controller.Content()
}
+
composeTestRule.onNodeWithText(context.getString(R.string.wifi_allow_wep_networks))
.assertIsOff()
}
@@ -101,7 +114,6 @@
}
composeTestRule.onRoot().performClick()
-
composeTestRule.onNodeWithText(context.getString(R.string.wifi_allow_wep_networks))
.assertIsOn()
}
@@ -114,12 +126,38 @@
}
composeTestRule.onRoot().performClick()
-
composeTestRule.onNodeWithText(context.getString(R.string.wifi_allow_wep_networks))
.assertIsOff()
}
+ @Test
+ fun whenClick_wepAllowed_openDialog() {
+ wepAllowed = true
+ Mockito.`when`(mockWifiInfo.currentSecurityType).thenReturn(WifiEntry.SECURITY_WEP)
+ composeTestRule.setContent {
+ controller.Content()
+ }
+
+ composeTestRule.onRoot().performClick()
+ composeTestRule.onDialogText(context.getString(R.string.wifi_disconnect_button_text))
+ .isDisplayed()
+ }
+
+ @Test
+ fun whenClick_wepDisallowed_openDialog() {
+ wepAllowed = false
+ Mockito.`when`(mockWifiInfo.currentSecurityType).thenReturn(WifiEntry.SECURITY_WEP)
+ composeTestRule.setContent {
+ controller.Content()
+ }
+
+ composeTestRule.onRoot().performClick()
+ composeTestRule.onDialogText(context.getString(R.string.wifi_disconnect_button_text))
+ .isNotDisplayed()
+ }
+
private companion object {
const val TEST_KEY = "test_key"
+ const val SSID = "ssid"
}
}
\ No newline at end of file
diff --git a/tests/spa_unit/src/com/android/settings/wifi/details2/WifiPrivacyPageProviderTest.kt b/tests/spa_unit/src/com/android/settings/wifi/details2/WifiPrivacyPageProviderTest.kt
new file mode 100644
index 0000000..5c9a1a4
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/wifi/details2/WifiPrivacyPageProviderTest.kt
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2024 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.wifi.details2
+
+import android.content.Context
+import android.net.wifi.WifiConfiguration
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.assertIsOff
+import androidx.compose.ui.test.assertIsOn
+import androidx.compose.ui.test.assertIsSelectable
+import androidx.compose.ui.test.assertIsSelected
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performClick
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.R
+import com.android.wifitrackerlib.WifiEntry
+import com.google.common.truth.Truth
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
+
+@RunWith(AndroidJUnit4::class)
+class WifiPrivacyPageProviderTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ private val context: Context = ApplicationProvider.getApplicationContext()
+ private var mockWifiConfiguration = mock<WifiConfiguration>() {
+ on { isSendDhcpHostnameEnabled } doReturn true
+ }
+ private var mockWifiEntry = mock<WifiEntry>() {
+ on { canSetPrivacy() } doReturn true
+ on { privacy } doReturn 0
+ on { wifiConfiguration } doReturn mockWifiConfiguration
+ }
+
+ @Test
+ fun apnEditPageProvider_name() {
+ Truth.assertThat(WifiPrivacyPageProvider.name).isEqualTo("WifiPrivacy")
+ }
+
+ @Test
+ fun title_displayed() {
+ composeTestRule.setContent {
+ WifiPrivacyPage(mockWifiEntry)
+ }
+ composeTestRule.onNodeWithText(
+ context.getString(R.string.wifi_privacy_settings)
+ ).assertIsDisplayed()
+ }
+
+ @Test
+ fun category_mac_title_displayed() {
+ composeTestRule.setContent {
+ WifiPrivacyPage(mockWifiEntry)
+ }
+ composeTestRule.onNodeWithText(
+ context.getString(R.string.wifi_privacy_mac_settings)
+ ).assertIsDisplayed()
+ }
+
+ @Test
+ fun category_mac_list_displayed() {
+ composeTestRule.setContent {
+ WifiPrivacyPage(mockWifiEntry)
+ }
+ val wifiPrivacyEntries = context.resources.getStringArray(R.array.wifi_privacy_entries)
+ for (entry in wifiPrivacyEntries) {
+ composeTestRule.onNodeWithText(
+ entry
+ ).assertIsDisplayed()
+ }
+ }
+
+ @Test
+ fun category_mac_list_selectable() {
+ composeTestRule.setContent {
+ WifiPrivacyPage(mockWifiEntry)
+ }
+ val wifiPrivacyEntries = context.resources.getStringArray(R.array.wifi_privacy_entries)
+ for (entry in wifiPrivacyEntries) {
+ composeTestRule.onNodeWithText(
+ entry
+ ).assertIsSelectable()
+ }
+ }
+
+ @Test
+ fun category_mac_list_default_selected() {
+ composeTestRule.setContent {
+ WifiPrivacyPage(mockWifiEntry)
+ }
+ val wifiPrivacyEntries = context.resources.getStringArray(R.array.wifi_privacy_entries)
+ val wifiPrivacyValues = context.resources.getStringArray(R.array.wifi_privacy_values)
+ composeTestRule.onNodeWithText(
+ wifiPrivacyEntries[wifiPrivacyValues.indexOf("0")]
+ ).assertIsSelected()
+ }
+
+ @Test
+ fun category_mac_list_not_enabled() {
+ mockWifiEntry.stub {
+ on { canSetPrivacy() } doReturn false
+ }
+ composeTestRule.setContent {
+ WifiPrivacyPage(mockWifiEntry)
+ }
+ val wifiPrivacyEntries = context.resources.getStringArray(R.array.wifi_privacy_entries)
+ for (entry in wifiPrivacyEntries) {
+ composeTestRule.onNodeWithText(entry).assertIsNotEnabled()
+ }
+ }
+
+ @Test
+ fun category_send_device_name_title_displayed() {
+ composeTestRule.setContent {
+ WifiPrivacyPage(mockWifiEntry)
+ }
+ composeTestRule.onNodeWithText(
+ context.getString(R.string.wifi_privacy_device_name_settings)
+ ).assertIsDisplayed()
+ }
+
+ @Test
+ fun toggle_send_device_name_title_displayed() {
+ composeTestRule.setContent {
+ WifiPrivacyPage(mockWifiEntry)
+ }
+ composeTestRule.onNodeWithText(
+ context.getString(R.string.wifi_privacy_send_device_name_toggle_title)
+ ).assertIsDisplayed()
+ }
+
+ @Test
+ fun send_device_name_turnOn() {
+ composeTestRule.setContent {
+ WifiPrivacyPage(mockWifiEntry)
+ }
+ composeTestRule.onNodeWithText(
+ context.getString(R.string.wifi_privacy_send_device_name_toggle_title)
+ ).assertIsOn()
+ }
+
+ @Test
+ fun onClick_turnOff() {
+ composeTestRule.setContent {
+ WifiPrivacyPage(mockWifiEntry)
+ }
+
+ composeTestRule.onNodeWithText(
+ context.getString(R.string.wifi_privacy_send_device_name_toggle_title)
+ ).performClick()
+
+ composeTestRule.onNodeWithText(
+ context.getString(R.string.wifi_privacy_send_device_name_toggle_title)
+ ).assertIsOff()
+ }
+}
\ No newline at end of file
diff --git a/tests/spa_unit/src/com/android/settings/wifi/details2/WifiPrivacyPreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/wifi/details2/WifiPrivacyPreferenceControllerTest.kt
new file mode 100644
index 0000000..98997e4
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/wifi/details2/WifiPrivacyPreferenceControllerTest.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2024 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.wifi.details2
+
+import android.content.Context
+import android.content.Intent
+import android.net.wifi.WifiManager
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.core.os.bundleOf
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.R
+import com.android.settingslib.spa.framework.util.KEY_DESTINATION
+import com.google.common.truth.Truth
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.doNothing
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@RunWith(AndroidJUnit4::class)
+class WifiPrivacyPreferenceControllerTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ private val mockWifiManager = mock<WifiManager> {
+ on { isConnectedMacRandomizationSupported } doReturn true
+ }
+
+ private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
+ on { getSystemService(WifiManager::class.java) } doReturn mockWifiManager
+ doNothing().whenever(mock).startActivity(any())
+ }
+
+ private val controller = WifiPrivacyPreferenceController(context, TEST_KEY)
+
+ @Test
+ fun title_isDisplayed() {
+ composeTestRule.setContent {
+ CompositionLocalProvider(LocalContext provides context) {
+ controller.Content()
+ }
+ }
+
+ composeTestRule.onNodeWithText(context.getString(R.string.wifi_privacy_settings))
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun onClick_startWifiPrivacyPage() {
+ composeTestRule.setContent {
+ CompositionLocalProvider(LocalContext provides context) {
+ controller.setWifiEntryKey("")
+ controller.Content()
+ }
+ }
+
+ composeTestRule.onNodeWithText(context.getString(R.string.wifi_privacy_settings))
+ .performClick()
+
+ val intent = argumentCaptor<Intent> {
+ verify(context).startActivity(capture())
+ }.firstValue
+ Truth.assertThat(intent.getStringExtra(KEY_DESTINATION))
+ .isEqualTo(WifiPrivacyPageProvider.getRoute(""))
+ }
+
+ private companion object {
+ const val TEST_KEY = "test_key"
+ }
+}
\ No newline at end of file
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index 0f844d0..c8e886c 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -26,6 +26,7 @@
"androidx.test.rules",
"androidx.test.ext.junit",
"androidx.preference_preference",
+ "flag-junit",
"mockito-target-minus-junit4",
"platform-test-annotations",
"platform-test-rules",
diff --git a/tests/unit/src/com/android/settings/conecteddevice/threadnetwork/OWNERS b/tests/unit/src/com/android/settings/conecteddevice/threadnetwork/OWNERS
new file mode 100644
index 0000000..4a35359
--- /dev/null
+++ b/tests/unit/src/com/android/settings/conecteddevice/threadnetwork/OWNERS
@@ -0,0 +1 @@
+include platform/packages/modules/Connectivity:/thread/OWNERS
diff --git a/tests/unit/src/com/android/settings/conecteddevice/threadnetwork/ThreadNetworkPreferenceControllerTest.kt b/tests/unit/src/com/android/settings/conecteddevice/threadnetwork/ThreadNetworkPreferenceControllerTest.kt
new file mode 100644
index 0000000..644095d
--- /dev/null
+++ b/tests/unit/src/com/android/settings/conecteddevice/threadnetwork/ThreadNetworkPreferenceControllerTest.kt
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2024 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.connecteddevice.threadnetwork
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.net.thread.ThreadNetworkController.STATE_DISABLED
+import android.net.thread.ThreadNetworkController.STATE_DISABLING
+import android.net.thread.ThreadNetworkController.STATE_ENABLED
+import android.net.thread.ThreadNetworkController.StateCallback
+import android.net.thread.ThreadNetworkException
+import android.os.OutcomeReceiver
+import android.platform.test.flag.junit.SetFlagsRule
+import android.provider.Settings
+import androidx.core.content.ContextCompat
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.preference.PreferenceManager
+import androidx.preference.SwitchPreference
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.net.thread.platform.flags.Flags
+import com.android.settings.R
+import com.android.settings.core.BasePreferenceController.AVAILABLE
+import com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE
+import com.android.settings.core.BasePreferenceController.DISABLED_DEPENDENT_SETTING
+import com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE
+import com.android.settings.connecteddevice.threadnetwork.ThreadNetworkPreferenceController.BaseThreadNetworkController
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import java.util.concurrent.Executor
+
+/** Unit tests for [ThreadNetworkPreferenceController]. */
+@RunWith(AndroidJUnit4::class)
+class ThreadNetworkPreferenceControllerTest {
+ @get:Rule
+ val mSetFlagsRule = SetFlagsRule()
+ private lateinit var context: Context
+ private lateinit var executor: Executor
+ private lateinit var controller: ThreadNetworkPreferenceController
+ private lateinit var fakeThreadNetworkController: FakeThreadNetworkController
+ private lateinit var preference: SwitchPreference
+ private val broadcastReceiverArgumentCaptor = ArgumentCaptor.forClass(
+ BroadcastReceiver::class.java
+ )
+
+ @Before
+ fun setUp() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_THREAD_ENABLED_PLATFORM)
+ context = spy(ApplicationProvider.getApplicationContext<Context>())
+ executor = ContextCompat.getMainExecutor(context)
+ fakeThreadNetworkController = FakeThreadNetworkController(executor)
+ controller = newControllerWithThreadFeatureSupported(true)
+ val preferenceManager = PreferenceManager(context)
+ val preferenceScreen = preferenceManager.createPreferenceScreen(context)
+ preference = SwitchPreference(context)
+ preference.key = "thread_network_settings"
+ preferenceScreen.addPreference(preference)
+ controller.displayPreference(preferenceScreen)
+
+ Settings.Global.putInt(context.contentResolver, Settings.Global.AIRPLANE_MODE_ON, 0)
+ }
+
+ private fun newControllerWithThreadFeatureSupported(
+ present: Boolean
+ ): ThreadNetworkPreferenceController {
+ return ThreadNetworkPreferenceController(
+ context,
+ "thread_network_settings" /* key */,
+ executor,
+ if (present) fakeThreadNetworkController else null
+ )
+ }
+
+ @Test
+ fun availabilityStatus_flagDisabled_returnsConditionallyUnavailable() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_THREAD_ENABLED_PLATFORM)
+ assertThat(controller.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE)
+ }
+
+ @Test
+ fun availabilityStatus_airPlaneModeOn_returnsDisabledDependentSetting() {
+ Settings.Global.putInt(context.contentResolver, Settings.Global.AIRPLANE_MODE_ON, 1)
+ controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START)
+
+ assertThat(controller.getAvailabilityStatus()).isEqualTo(DISABLED_DEPENDENT_SETTING)
+ }
+
+ @Test
+ fun availabilityStatus_airPlaneModeOff_returnsAvailable() {
+ Settings.Global.putInt(context.contentResolver, Settings.Global.AIRPLANE_MODE_ON, 0)
+ controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START)
+
+ assertThat(controller.getAvailabilityStatus()).isEqualTo(AVAILABLE)
+ }
+
+ @Test
+ fun availabilityStatus_threadFeatureNotSupported_returnsUnsupported() {
+ controller = newControllerWithThreadFeatureSupported(false)
+ controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START)
+
+ assertThat(fakeThreadNetworkController.registeredStateCallback).isNull()
+ assertThat(controller.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE)
+ }
+
+ @Test
+ fun isChecked_threadSetEnabled_returnsTrue() {
+ fakeThreadNetworkController.setEnabled(true, executor) { }
+ controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START)
+
+ assertThat(controller.isChecked).isTrue()
+ }
+
+ @Test
+ fun isChecked_threadSetDisabled_returnsFalse() {
+ fakeThreadNetworkController.setEnabled(false, executor) { }
+ controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START)
+
+ assertThat(controller.isChecked).isFalse()
+ }
+
+ @Test
+ fun setChecked_setChecked_threadIsEnabled() {
+ controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START)
+
+ controller.setChecked(true)
+
+ assertThat(fakeThreadNetworkController.isEnabled).isTrue()
+ }
+
+ @Test
+ fun setChecked_setUnchecked_threadIsDisabled() {
+ controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START)
+
+ controller.setChecked(false)
+
+ assertThat(fakeThreadNetworkController.isEnabled).isFalse()
+ }
+
+ @Test
+ fun updatePreference_airPlaneModeOff_preferenceEnabled() {
+ Settings.Global.putInt(context.contentResolver, Settings.Global.AIRPLANE_MODE_ON, 0)
+ controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START)
+
+ assertThat(preference.isEnabled).isTrue()
+ assertThat(preference.summary).isEqualTo(
+ context.resources.getString(R.string.thread_network_settings_summary)
+ )
+ }
+
+ @Test
+ fun updatePreference_airPlaneModeOn_preferenceDisabled() {
+ Settings.Global.putInt(context.contentResolver, Settings.Global.AIRPLANE_MODE_ON, 1)
+ controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START)
+
+ assertThat(preference.isEnabled).isFalse()
+ assertThat(preference.summary).isEqualTo(
+ context.resources.getString(R.string.thread_network_settings_summary_airplane_mode)
+ )
+ }
+
+ @Test
+ fun updatePreference_airPlaneModeTurnedOn_preferenceDisabled() {
+ Settings.Global.putInt(context.contentResolver, Settings.Global.AIRPLANE_MODE_ON, 0)
+ startControllerAndCaptureCallbacks()
+
+ Settings.Global.putInt(context.contentResolver, Settings.Global.AIRPLANE_MODE_ON, 1)
+ broadcastReceiverArgumentCaptor.value.onReceive(context, Intent())
+
+ assertThat(preference.isEnabled).isFalse()
+ assertThat(preference.summary).isEqualTo(
+ context.resources.getString(R.string.thread_network_settings_summary_airplane_mode)
+ )
+ }
+
+ private fun startControllerAndCaptureCallbacks() {
+ controller.onStateChanged(mock(LifecycleOwner::class.java), Lifecycle.Event.ON_START)
+ verify(context)!!.registerReceiver(broadcastReceiverArgumentCaptor.capture(), any())
+ }
+
+ private class FakeThreadNetworkController(private val executor: Executor) :
+ BaseThreadNetworkController {
+ var isEnabled = true
+ private set
+ var registeredStateCallback: StateCallback? = null
+ private set
+
+ override fun setEnabled(
+ enabled: Boolean,
+ executor: Executor,
+ receiver: OutcomeReceiver<Void?, ThreadNetworkException>
+ ) {
+ isEnabled = enabled
+ if (registeredStateCallback != null) {
+ if (!isEnabled) {
+ executor.execute {
+ registeredStateCallback!!.onThreadEnableStateChanged(
+ STATE_DISABLING
+ )
+ }
+ executor.execute {
+ registeredStateCallback!!.onThreadEnableStateChanged(
+ STATE_DISABLED
+ )
+ }
+ } else {
+ executor.execute {
+ registeredStateCallback!!.onThreadEnableStateChanged(
+ STATE_ENABLED
+ )
+ }
+ }
+ }
+ executor.execute { receiver.onResult(null) }
+ }
+
+ override fun registerStateCallback(
+ executor: Executor,
+ callback: StateCallback
+ ) {
+ require(callback !== registeredStateCallback) { "callback is already registered" }
+ registeredStateCallback = callback
+ val enabledState =
+ if (isEnabled) STATE_ENABLED else STATE_DISABLED
+ executor.execute { registeredStateCallback!!.onThreadEnableStateChanged(enabledState) }
+ }
+
+ override fun unregisterStateCallback(callback: StateCallback) {
+ requireNotNull(registeredStateCallback) { "callback is already unregistered" }
+ registeredStateCallback = null
+ }
+ }
+}
diff --git a/tests/unit/src/com/android/settings/network/SubscriptionUtilTest.java b/tests/unit/src/com/android/settings/network/SubscriptionUtilTest.java
index 587e734..3b9ac9d 100644
--- a/tests/unit/src/com/android/settings/network/SubscriptionUtilTest.java
+++ b/tests/unit/src/com/android/settings/network/SubscriptionUtilTest.java
@@ -18,9 +18,13 @@
import static com.android.settings.network.SubscriptionUtil.KEY_UNIQUE_SUBSCRIPTION_DISPLAYNAME;
import static com.android.settings.network.SubscriptionUtil.SUB_ID;
+
import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
@@ -30,6 +34,8 @@
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
+import android.net.ConnectivityManager;
+import android.net.NetworkCapabilities;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
@@ -61,13 +67,15 @@
private static final CharSequence CARRIER_2 = "carrier2";
private Context mContext;
+ private NetworkCapabilities mNetworkCapabilities;
+
@Mock
private SubscriptionManager mSubMgr;
@Mock
private TelephonyManager mTelMgr;
@Mock
private Resources mResources;
-
+ @Mock private ConnectivityManager mConnectivityManager;
@Before
public void setUp() {
@@ -75,6 +83,7 @@
mContext = spy(ApplicationProvider.getApplicationContext());
when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(mSubMgr);
when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelMgr);
+ when(mContext.getSystemService(ConnectivityManager.class)).thenReturn(mConnectivityManager);
when(mTelMgr.getUiccSlotsInfo()).thenReturn(null);
}
@@ -588,4 +597,24 @@
assertThat(SubscriptionUtil.isValidCachedDisplayName(cacheString, originalName)).isFalse();
}
+
+ @Test
+ public void isConnectedToWifiOrDifferentSubId_hasWiFi_returnTrue() {
+ addNetworkTransportType(NetworkCapabilities.TRANSPORT_WIFI);
+
+ assertTrue(SubscriptionUtil.isConnectedToWifiOrDifferentSubId(mContext, SUBID_1));
+ }
+
+ @Test
+ public void isConnectedToWifiOrDifferentSubId_noData_and_noWiFi_returnFalse() {
+ addNetworkTransportType(NetworkCapabilities.TRANSPORT_BLUETOOTH);
+
+ assertFalse(SubscriptionUtil.isConnectedToWifiOrDifferentSubId(mContext, SUBID_1));
+ }
+
+ private void addNetworkTransportType(int networkType) {
+ mNetworkCapabilities =
+ new NetworkCapabilities.Builder().addTransportType(networkType).build();
+ when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(mNetworkCapabilities);
+ }
}