Merge changes from topics "ha-aics", "ha-aics-mute", "ha-local-data" into main
* changes:
[Ambient Volume] Ambient volume icon
[Ambient Volume] Show value with remote data
[Ambient Volume] Show value with local data
[Ambient Volume] UI of volume sliders in Settings
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 2295ee3..551a7de 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -5439,6 +5439,16 @@
</intent-filter>
</receiver>
+ <receiver
+ android:name=".accessibility.HighContrastTextMigrationReceiver"
+ android:permission="android.permission.MANAGE_ACCESSIBILITY"
+ android:exported="true"> <!-- Exported for SettingsProvider restore from backup. -->
+ <intent-filter>
+ <action android:name="android.intent.action.PRE_BOOT_COMPLETED"/>
+ <action android:name="com.android.settings.accessibility.ACTION_HIGH_CONTRAST_TEXT_RESTORED"/>
+ </intent-filter>
+ </receiver>
+
<activity
android:name="Settings$ChangeNfcTagAppsActivity"
android:exported="true"
diff --git a/aconfig/settings_datetime_flag_declarations.aconfig b/aconfig/settings_datetime_flag_declarations.aconfig
index 93d41d6..9951243 100644
--- a/aconfig/settings_datetime_flag_declarations.aconfig
+++ b/aconfig/settings_datetime_flag_declarations.aconfig
@@ -8,12 +8,3 @@
description: "Enable the time feedback feature, a button to launch feedback in Date & Time Settings"
bug: "283239837"
}
-
-flag {
- name: "revamp_toggles"
- # "location" is used by the Android System Time team for feature flags.
- namespace: "location"
- description: "Makes the use location toggle dependent on automatic time zone detection"
- bug: "296835792"
-}
-
diff --git a/res/drawable/ic_settings_mouse.xml b/res/drawable/ic_settings_mouse.xml
new file mode 100644
index 0000000..4ce13d1
--- /dev/null
+++ b/res/drawable/ic_settings_mouse.xml
@@ -0,0 +1,14 @@
+<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">
+ <group>
+ <clip-path
+ android:pathData="M0,0h24v24h-24z"/>
+ <path
+ android:pathData="M12,22C10.067,22 8.417,21.317 7.05,19.95C5.683,18.583 5,16.933 5,15V9C5,7.067 5.683,5.417 7.05,4.05C8.417,2.683 10.067,2 12,2C13.933,2 15.583,2.683 16.95,4.05C18.317,5.417 19,7.067 19,9V15C19,16.933 18.317,18.583 16.95,19.95C15.583,21.317 13.933,22 12,22ZM13,9H17C17,7.8 16.617,6.742 15.85,5.825C15.1,4.908 14.15,4.333 13,4.1V9ZM7,9H11V4.1C9.85,4.333 8.892,4.908 8.125,5.825C7.375,6.742 7,7.8 7,9ZM12,20C13.383,20 14.558,19.517 15.525,18.55C16.508,17.567 17,16.383 17,15V11H7V15C7,16.383 7.483,17.567 8.45,18.55C9.433,19.517 10.617,20 12,20Z"
+ android:fillColor="@android:color/white"/>
+ </group>
+</vector>
\ No newline at end of file
diff --git a/res/layout/advanced_bt_entity_header.xml b/res/layout/advanced_bt_entity_header.xml
index 37ae843..83657b1 100644
--- a/res/layout/advanced_bt_entity_header.xml
+++ b/res/layout/advanced_bt_entity_header.xml
@@ -76,6 +76,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
+ android:layoutDirection="ltr"
android:orientation="horizontal">
<include
diff --git a/res/layout/advanced_bt_entity_sub.xml b/res/layout/advanced_bt_entity_sub.xml
index 252ab47..77bc4f0 100644
--- a/res/layout/advanced_bt_entity_sub.xml
+++ b/res/layout/advanced_bt_entity_sub.xml
@@ -21,6 +21,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
+ android:layoutDirection="locale"
android:focusable="true"
android:orientation="vertical">
diff --git a/res/layout/apn_preference_layout.xml b/res/layout/apn_preference_layout.xml
deleted file mode 100644
index 9b6efe7..0000000
--- a/res/layout/apn_preference_layout.xml
+++ /dev/null
@@ -1,76 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 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:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:minHeight="?android:attr/listPreferredItemHeight"
- android:focusable="false"
- android:gravity="center_vertical">
-
- <RelativeLayout
- android:id="@+id/text_layout"
- android:layout_width="0dip"
- android:layout_height="wrap_content"
- android:gravity="center_vertical"
- android:layout_weight="1"
- android:focusable="true"
- android:clickable="true"
- android:background="?android:attr/selectableItemBackground">
-
- <TextView
- android:id="@android:id/title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:focusable="false"
- android:labelFor="@id/apn_radio_button_frame"
- android:singleLine="true"
- android:textAppearance="?android:attr/textAppearanceListItem" />
-
- <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:textAppearance="?android:attr/textAppearanceListItemSecondary"
- android:textColor="?android:attr/textColorSecondary"
- android:focusable="false"
- android:hyphenationFrequency="normalFast"
- android:lineBreakWordStyle="phrase"
- android:maxLines="2" />
-
- </RelativeLayout>
-
- <FrameLayout
- android:id="@+id/apn_radio_button_frame"
- android:layout_width="@dimen/min_tap_target_size"
- android:layout_height="@dimen/min_tap_target_size"
- android:layout_margin="8dp">
-
- <RadioButton
- android:id="@+id/apn_radiobutton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:clickable="false"
- android:focusable="false" />
- </FrameLayout>
-
-</LinearLayout>
diff --git a/res/layout/battery_chart_graph.xml b/res/layout/battery_chart_graph.xml
index 9e816ed..b84f38a 100644
--- a/res/layout/battery_chart_graph.xml
+++ b/res/layout/battery_chart_graph.xml
@@ -36,8 +36,7 @@
android:id="@+id/battery_chart_group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="vertical"
- android:alpha="0">
+ android:orientation="vertical">
<com.android.settings.fuelgauge.batteryusage.BatteryChartView
android:id="@+id/daily_battery_chart"
android:layout_width="match_parent"
@@ -53,7 +52,7 @@
android:layout_width="match_parent"
android:layout_height="@dimen/chartview_layout_height"
android:layout_marginBottom="16dp"
- android:visibility="visible"
+ android:visibility="gone"
android:contentDescription="@string/hourly_battery_usage_chart"
android:textAppearance="?android:attr/textAppearanceSmall"
settings:textColor="?android:attr/textColorSecondary" />
diff --git a/res/layout/display_topology_preference.xml b/res/layout/display_topology_preference.xml
index d2e4300..beaf816 100644
--- a/res/layout/display_topology_preference.xml
+++ b/res/layout/display_topology_preference.xml
@@ -16,8 +16,9 @@
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/display_topology_pane_holder"
android:importantForAccessibility="no"
- android:layout_height="160dp"
+ android:layout_height="wrap_content"
android:layout_width="match_parent"
android:paddingHorizontal="@dimen/display_topology_pane_margin"
android:orientation="horizontal">
@@ -27,14 +28,14 @@
android:layout_width="match_parent"
android:src="@drawable/display_topology_background"/>
<FrameLayout
- android:id="@+id/display_topology_container"
+ android:id="@+id/display_topology_pane_content"
android:layout_width="match_parent"
- android:layout_height="wrap_content"/>
+ android:layout_height="match_parent"/>
<TextView
android:id="@+id/topology_hint"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_gravity="top|center_horizontal"
- android:paddingTop="10dp"
- android:text="@string/external_display_topology_hint"/>
+ android:paddingBottom="10dp"
+ android:paddingTop="10dp" />
</FrameLayout>
diff --git a/res/raw/lottie_bubbles.json b/res/raw/lottie_bubbles.json
index e69de29..b0e5993 100644
--- a/res/raw/lottie_bubbles.json
+++ b/res/raw/lottie_bubbles.json
@@ -0,0 +1 @@
+{"v":"5.7.8","fr":60,"ip":0,"op":406,"w":412,"h":300,"nm":"Bubbles_EDU","ddd":0,"assets":[{"id":"comp_0","layers":[{"ddd":0,"ind":1,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[165.599,75.148,0],"ix":2,"l":2},"a":{"a":0,"k":[4,4,0],"ix":1,"l":2},"s":{"a":0,"k":[90,90,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-1.693],[1.693,0],[0,1.693],[-1.694,0]],"o":[[0,1.693],[-1.694,0],[0,-1.693],[1.693,0]],"v":[[3.066,0],[0.001,3.065],[-3.066,0],[0.001,-3.065]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[3.715,4.306],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":381,"st":-219,"bm":0}]},{"id":"comp_1","layers":[{"ddd":0,"ind":1,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[261.163,141.906,0],"ix":2,"l":2},"a":{"a":0,"k":[4,4,0],"ix":1,"l":2},"s":{"a":0,"k":[90,90,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-1.693],[1.693,0],[0,1.693],[-1.694,0]],"o":[[0,1.693],[-1.694,0],[0,-1.693],[1.693,0]],"v":[[3.066,0],[0.001,3.065],[-3.066,0],[0.001,-3.065]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[3.715,4.306],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":239,"st":20,"bm":0}]},{"id":"comp_2","layers":[{"ddd":0,"ind":1,"ty":4,"nm":".red400","cl":"red400","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[184.011,75.101,0],"ix":2,"l":2},"a":{"a":0,"k":[3.5,3.5,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0,0,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":158,"s":[0,0,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":180,"s":[90,90,100]},{"i":{"x":[0.6,0.6,0.6],"y":[1,1,1]},"o":{"x":[1,1,0.8],"y":[0,0,0]},"t":275,"s":[90,90,100]},{"t":297,"s":[0,0,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-1.693],[1.693,0],[0,1.693],[-1.694,0]],"o":[[0,1.693],[-1.694,0],[0,-1.693],[1.693,0]],"v":[[3.066,0],[0.001,3.065],[-3.066,0],[0.001,-3.065]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333333333,0.403921568627,0.360784313725,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":[3.464,3.623],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":620,"st":20,"bm":0}]},{"id":"comp_3","layers":[{"ddd":0,"ind":1,"ty":4,"nm":".yellow400","cl":"yellow400","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[203.168,75.101,0],"ix":2,"l":2},"a":{"a":0,"k":[3.5,3.5,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0,0,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":160,"s":[0,0,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":182,"s":[90,90,100]},{"i":{"x":[0.6,0.6,0.6],"y":[1,1,1]},"o":{"x":[1,1,0.8],"y":[0,0,0]},"t":273,"s":[90,90,100]},{"t":295,"s":[0,0,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-1.693],[1.693,0],[0,1.693],[-1.694,0]],"o":[[0,1.693],[-1.694,0],[0,-1.693],[1.693,0]],"v":[[3.066,0],[0.001,3.065],[-3.066,0],[0.001,-3.065]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.988235294118,0.788235294118,0.203921568627,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":[3.643,3.623],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":620,"st":20,"bm":0}]},{"id":"comp_4","layers":[{"ddd":0,"ind":1,"ty":4,"nm":".green400","cl":"green400","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[221.926,75.104,0],"ix":2,"l":2},"a":{"a":0,"k":[4,3.5,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0,0,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":162,"s":[0,0,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":184,"s":[90,90,100]},{"i":{"x":[0.6,0.6,0.6],"y":[1,1,1]},"o":{"x":[1,1,0.8],"y":[0,0,0]},"t":271,"s":[90,90,100]},{"t":293,"s":[0,0,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.004,-1.693],[1.693,-0.005],[0.005,1.693],[-1.694,0.004]],"o":[[0.005,1.693],[-1.693,0.004],[-0.004,-1.693],[1.693,-0.005]],"v":[[3.065,-0.008],[0.008,3.066],[-3.066,0.008],[-0.008,-3.065]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.356862745098,0.725490196078,0.454901960784,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":[3.725,3.634],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":620,"st":20,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Touch 4","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":254,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":272,"s":[59]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":273,"s":[59]},{"t":288,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[226.55,48.3,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[9,9,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.4,0.4],"y":[0,0]},"t":254,"s":[200,200]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0]},"t":272,"s":[150,150]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.4,0.4],"y":[0,0]},"t":273,"s":[150,150]},{"t":288,"s":[200,200]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":1000,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.909803926945,0.0941176489,0.537254929543,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":12,"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.983088254929,0.665331482887,0.832399487495,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":254,"op":288,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Touch 3","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":122,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":133,"s":[59]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":134,"s":[59]},{"t":149,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[252.3,140.8,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[9,9,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.4,0.4],"y":[0,0]},"t":122,"s":[200,200]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0]},"t":133,"s":[150,150]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.4,0.4],"y":[0,0]},"t":134,"s":[150,150]},{"t":149,"s":[200,200]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":1000,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.909803926945,0.0941176489,0.537254929543,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":12,"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.983088254929,0.665331482887,0.832399487495,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":122,"op":149,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".grey300","cl":"grey300","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":144,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":156,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":300,"s":[0]},{"t":312,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[205.95,154.75,0],"ix":2,"l":2},"a":{"a":0,"k":[67,1,0],"ix":1,"l":2},"s":{"a":0,"k":[90,90,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":[[0.832,1.105],[133.152,1.105]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854902020623,0.862745157878,0.878431432387,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":620,"st":20,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey300","cl":"grey300","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":144,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":156,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":300,"s":[0]},{"t":312,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,213.25,0],"ix":2,"l":2},"a":{"a":0,"k":[36,49,0],"ix":1,"l":2},"s":{"a":0,"k":[90,90,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[2.2,0],[0,0],[0,2.199],[0,0],[-2.2,0],[0,0],[0,-2.2],[0,0]],"o":[[0,0],[-2.2,0],[0,0],[0,-2.2],[0,0],[2.2,0],[0,0],[0,2.199]],"v":[[4.009,8.009],[-4.009,8.009],[-8.009,4.009],[-8.009,-4.009],[-4.009,-8.009],[4.009,-8.009],[8.009,-4.009],[8.009,4.009]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.854902020623,0.862745157878,0.878431432387,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":[63.647,57.865],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[2.2,0],[0,0],[0,2.2],[0,0],[-2.2,0],[0,0],[0,-2.2],[0,0]],"o":[[0,0],[-2.2,0],[0,0],[0,-2.2],[0,0],[2.2,0],[0,0],[0,2.2]],"v":[[4.009,8.008],[-4.009,8.008],[-8.009,4.008],[-8.009,-4.008],[-4.009,-8.008],[4.009,-8.008],[8.009,-4.008],[8.009,4.008]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.854902020623,0.862745157878,0.878431432387,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":[63.647,33.26],"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 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[2.2,0],[0,0],[0,2.2],[0,0],[-2.2,0],[0,0],[0,-2.2],[0,0]],"o":[[0,0],[-2.2,0],[0,0],[0,-2.2],[0,0],[2.2,0],[0,0],[0,2.2]],"v":[[4.009,8.008],[-4.009,8.008],[-8.009,4.008],[-8.009,-4.008],[-4.009,-8.008],[4.009,-8.008],[8.009,-4.008],[8.009,4.008]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.854902020623,0.862745157878,0.878431432387,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":[63.647,8.655],"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 3","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[2.199,0],[0,0],[0,2.199],[0,0],[-2.2,0],[0,0],[0,-2.2],[0,0]],"o":[[0,0],[-2.2,0],[0,0],[0,-2.2],[0,0],[2.199,0],[0,0],[0,2.199]],"v":[[4.009,8.009],[-4.009,8.009],[-8.009,4.009],[-8.009,-4.009],[-4.009,-8.009],[4.009,-8.009],[8.009,-4.009],[8.009,4.009]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.854902020623,0.862745157878,0.878431432387,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":[35.658,57.865],"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 4","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[2.199,0],[0,0],[0,2.2],[0,0],[-2.2,0],[0,0],[0,-2.2],[0,0]],"o":[[0,0],[-2.2,0],[0,0],[0,-2.2],[0,0],[2.199,0],[0,0],[0,2.2]],"v":[[4.009,8.008],[-4.009,8.008],[-8.009,4.008],[-8.009,-4.008],[-4.009,-8.008],[4.009,-8.008],[8.009,-4.008],[8.009,4.008]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.854902020623,0.862745157878,0.878431432387,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":[35.658,33.26],"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 5","np":2,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[2.199,0],[0,0],[0,2.2],[0,0],[-2.2,0],[0,0],[0,-2.2],[0,0]],"o":[[0,0],[-2.2,0],[0,0],[0,-2.2],[0,0],[2.199,0],[0,0],[0,2.2]],"v":[[4.009,8.008],[-4.009,8.008],[-8.009,4.008],[-8.009,-4.008],[-4.009,-8.008],[4.009,-8.008],[8.009,-4.008],[8.009,4.008]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.854902020623,0.862745157878,0.878431432387,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":[35.658,8.655],"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 6","np":2,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[2.2,0],[0,0],[0,2.199],[0,0],[-2.2,0],[0,0],[0,-2.2],[0,0]],"o":[[0,0],[-2.2,0],[0,0],[0,-2.2],[0,0],[2.2,0],[0,0],[0,2.199]],"v":[[4.009,8.009],[-4.009,8.009],[-8.009,4.009],[-8.009,-4.009],[-4.009,-8.009],[4.009,-8.009],[8.009,-4.009],[8.009,4.009]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.854902020623,0.862745157878,0.878431432387,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.353,57.865],"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 7","np":2,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[2.2,0],[0,0],[0,2.2],[0,0],[-2.2,0],[0,0],[0,-2.2],[0,0]],"o":[[0,0],[-2.2,0],[0,0],[0,-2.2],[0,0],[2.2,0],[0,0],[0,2.2]],"v":[[4.009,8.008],[-4.009,8.008],[-8.009,4.008],[-8.009,-4.008],[-4.009,-8.008],[4.009,-8.008],[8.009,-4.008],[8.009,4.008]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.854902020623,0.862745157878,0.878431432387,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.353,33.26],"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 8","np":2,"cix":2,"bm":0,"ix":8,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[2.2,0],[0,0],[0,2.2],[0,0],[-2.2,0],[0,0],[0,-2.2],[0,0]],"o":[[0,0],[-2.2,0],[0,0],[0,-2.2],[0,0],[2.2,0],[0,0],[0,2.2]],"v":[[4.009,8.008],[-4.009,8.008],[-8.009,4.008],[-8.009,-4.008],[-4.009,-8.008],[4.009,-8.008],[8.009,-4.008],[8.009,4.008]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.854902020623,0.862745157878,0.878431432387,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.353,8.655],"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 9","np":2,"cix":2,"bm":0,"ix":9,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-4.694],[4.694,0],[0,4.694],[-4.694,0]],"o":[[0,4.694],[-4.694,0],[0,-4.694],[4.694,0]],"v":[[8.5,0],[0,8.5],[-8.5,0],[0,-8.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.854902020623,0.862745157878,0.878431432387,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":[36,88.481],"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 10","np":2,"cix":2,"bm":0,"ix":10,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":620,"st":20,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".grey300","cl":"grey300","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":144,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":156,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":300,"s":[0]},{"t":312,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,264.1,0],"ix":2,"l":2},"a":{"a":0,"k":[16,1.5,0],"ix":1,"l":2},"s":{"a":0,"k":[90,90,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.34,0],[0,0],[0,-0.34],[-0.34,0],[0,0],[0,0.34]],"o":[[0,0],[-0.34,0],[0,0.34],[0,0],[0.34,0],[0,-0.34]],"v":[[14.795,-0.618],[-14.795,-0.618],[-15.413,0],[-14.795,0.618],[14.795,0.618],[15.413,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.854902020623,0.862745157878,0.878431432387,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":[16,1.309],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":620,"st":20,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".grey300","cl":"grey300","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,264.1,0],"ix":2,"l":2},"a":{"a":0,"k":[16,1.5,0],"ix":1,"l":2},"s":{"a":0,"k":[90,90,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.34,0],[0,0],[0,-0.34],[-0.34,0],[0,0],[0,0.34]],"o":[[0,0],[-0.34,0],[0,0.34],[0,0],[0.34,0],[0,-0.34]],"v":[[14.795,-0.618],[-14.795,-0.618],[-15.413,0],[-14.795,0.618],[14.795,0.618],[15.413,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.854902020623,0.862745157878,0.878431432387,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":[16,1.309],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":620,"st":20,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"ChatMatte","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,166.75,0],"ix":2,"l":2},"a":{"a":0,"k":[-0.575,-36.689,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.001,"y":0},"t":160,"s":[{"i":[[0,-1.657],[0,0],[1.657,0],[0,0],[0,1.657],[0,0],[-1.657,0],[0,0]],"o":[[0,0],[0,1.657],[0,0],[-1.657,0],[0,0],[0,-1.657],[0,0],[1.657,0]],"v":[[56.5,-83.478],[56.575,-89.522],[53.575,-86.522],[-53.425,-86.522],[-56.425,-89.522],[-56.5,-83.478],[-53.5,-86.478],[53.5,-86.478]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":181,"s":[{"i":[[0,-1.657],[0,0],[1.657,0],[0,0],[0,1.657],[0,0],[-1.657,0],[0,0]],"o":[[0,0],[0,1.657],[0,0],[-1.657,0],[0,0],[0,-1.657],[0,0],[1.657,0]],"v":[[56.5,-83.478],[56.5,83.478],[53.5,86.478],[-53.5,86.478],[-56.5,83.478],[-56.5,-83.478],[-53.5,-86.478],[53.5,-86.478]],"c":true}]},{"i":{"x":0.999,"y":1},"o":{"x":0.8,"y":0},"t":275,"s":[{"i":[[0,-1.657],[0,0],[1.657,0],[0,0],[0,1.657],[0,0],[-1.657,0],[0,0]],"o":[[0,0],[0,1.657],[0,0],[-1.657,0],[0,0],[0,-1.657],[0,0],[1.657,0]],"v":[[56.5,-83.478],[56.5,83.478],[53.5,86.478],[-53.5,86.478],[-56.5,83.478],[-56.5,-83.478],[-53.5,-86.478],[53.5,-86.478]],"c":true}]},{"t":299,"s":[{"i":[[0,-1.657],[0,0],[1.657,0],[0,0],[0,1.657],[0,0],[-1.657,0],[0,0]],"o":[[0,0],[0,1.657],[0,0],[-1.657,0],[0,0],[0,-1.657],[0,0],[1.657,0]],"v":[[56.5,-83.478],[56.575,-89.522],[53.575,-86.522],[-53.425,-86.522],[-56.425,-89.522],[-56.5,-83.478],[-53.5,-86.478],[53.5,-86.478]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.988235294118,0.909803921569,0.901960784314,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.575,-36.689],"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":160,"op":600,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Top Ui Notification Outlines","tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":160,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":171,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":286.428,"s":[100]},{"t":299,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[205.95,94,0],"ix":2,"l":2},"a":{"a":0,"k":[64,10.5,0],"ix":1,"l":2},"s":{"a":0,"k":[90,90,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-3.866],[3.866,0],[0,3.866],[-3.866,0]],"o":[[0,3.866],[-3.866,0],[0,-3.866],[3.866,0]],"v":[[7,0],[0,7],[-7,0],[0,-7]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803921569,0.941176470588,0.996078431373,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":[12.922,7.969],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[1.284,19.79],[126.691,19.79]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.947865325329,0.955448584463,0.959243056353,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.914,0],[0,0],[0,0.913],[0,0],[0.914,0],[0,0],[0,-0.939],[0,0]],"o":[[0,0],[0.914,0],[0,0],[0,-0.914],[0,0],[-0.94,0],[0,0],[0,0.913]],"v":[[-6.199,1.684],[6.199,1.684],[7.861,0.023],[7.861,-0.023],[6.199,-1.684],[-6.153,-1.684],[-7.861,0.023],[-7.861,0.023]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.914689307119,0.922272566253,0.932703414618,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":[32.414,10.243],"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 3","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.909,0],[0,0],[0,0.909],[0,0],[0.91,0],[0,0],[0,-0.944]],"o":[[0,0],[0.91,0],[0,0],[0,-0.909],[0,0],[-0.944,0],[0,0.909]],"v":[[-12.824,1.684],[12.824,1.684],[14.478,0.031],[14.478,-0.031],[12.824,-1.684],[-12.762,-1.684],[-14.478,0.031]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.914689307119,0.922272566253,0.932703414618,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":[39.031,5.695],"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 4","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[2.457,0],[0,0],[0,2.457],[-2.457,0],[0,-2.457]],"o":[[0,0],[-2.457,0],[0,-2.457],[2.457,0],[0,2.457]],"v":[[0,4.449],[0,4.449],[-4.448,-0.001],[0,-4.449],[4.448,-0.001]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803921569,0.941176470588,0.996078431373,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":[95.312,7.969],"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 5","np":2,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[2.457,0],[0,0],[0,2.457],[-2.457,0],[0,-2.457]],"o":[[0,0],[-2.457,0],[0,-2.457],[2.457,0],[0,2.457]],"v":[[0,4.449],[0,4.449],[-4.448,-0.001],[0,-4.449],[4.448,-0.001]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803921569,0.941176470588,0.996078431373,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":[106.902,7.969],"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 6","np":2,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[2.457,0],[0,0],[0,2.457],[-2.457,0],[0,-2.457]],"o":[[0,0],[-2.457,0],[0,-2.457],[2.457,0],[0,2.457]],"v":[[0.001,4.449],[0.001,4.449],[-4.449,-0.001],[0.001,-4.449],[4.449,-0.001]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803921569,0.941176470588,0.996078431373,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":[118.492,7.969],"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 7","np":2,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":620,"st":20,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"ChatMatte 2","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,166.75,0],"ix":2,"l":2},"a":{"a":0,"k":[-0.575,-36.689,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.001,"y":0},"t":160,"s":[{"i":[[0,-1.657],[0,0],[1.657,0],[0,0],[0,1.657],[0,0],[-1.657,0],[0,0]],"o":[[0,0],[0,1.657],[0,0],[-1.657,0],[0,0],[0,-1.657],[0,0],[1.657,0]],"v":[[56.5,-83.478],[56.575,-89.522],[53.575,-86.522],[-53.425,-86.522],[-56.425,-89.522],[-56.5,-83.478],[-53.5,-86.478],[53.5,-86.478]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":181,"s":[{"i":[[0,-1.657],[0,0],[1.657,0],[0,0],[0,1.657],[0,0],[-1.657,0],[0,0]],"o":[[0,0],[0,1.657],[0,0],[-1.657,0],[0,0],[0,-1.657],[0,0],[1.657,0]],"v":[[56.5,-83.478],[56.5,83.478],[53.5,86.478],[-53.5,86.478],[-56.5,83.478],[-56.5,-83.478],[-53.5,-86.478],[53.5,-86.478]],"c":true}]},{"i":{"x":0.999,"y":1},"o":{"x":0.8,"y":0},"t":275,"s":[{"i":[[0,-1.657],[0,0],[1.657,0],[0,0],[0,1.657],[0,0],[-1.657,0],[0,0]],"o":[[0,0],[0,1.657],[0,0],[-1.657,0],[0,0],[0,-1.657],[0,0],[1.657,0]],"v":[[56.5,-83.478],[56.5,83.478],[53.5,86.478],[-53.5,86.478],[-56.5,83.478],[-56.5,-83.478],[-53.5,-86.478],[53.5,-86.478]],"c":true}]},{"t":299,"s":[{"i":[[0,-1.657],[0,0],[1.657,0],[0,0],[0,1.657],[0,0],[-1.657,0],[0,0]],"o":[[0,0],[0,1.657],[0,0],[-1.657,0],[0,0],[0,-1.657],[0,0],[1.657,0]],"v":[[56.5,-83.478],[56.575,-89.522],[53.575,-86.522],[-53.425,-86.522],[-56.425,-89.522],[-56.5,-83.478],[-53.5,-86.478],[53.5,-86.478]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.988235294118,0.909803921569,0.901960784314,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.575,-36.689],"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":160,"op":600,"st":0,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Chat 01 Outlines","tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":160,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":174,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":283,"s":[100]},{"t":299,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":0,"k":188.85,"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.001],"y":[0]},"t":160,"s":[145.75]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":181,"s":[155.2]},{"i":{"x":[0.999],"y":[1]},"o":{"x":[0.8],"y":[0]},"t":275,"s":[155.2]},{"t":299,"s":[145.75]}],"ix":4}},"a":{"a":0,"k":[39,8.5,0],"ix":1,"l":2},"s":{"a":0,"k":[90,90,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.078,0],[0,0],[0,4.078],[4.078,0],[0,0],[0,-4.077]],"o":[[0,0],[4.078,0],[0,-4.077],[0,0],[-4.078,0],[0,4.078]],"v":[[-30.765,7.414],[30.765,7.414],[38.179,0],[30.765,-7.414],[-30.765,-7.414],[-38.179,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.914689307119,0.922272566253,0.932703414618,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":[38.617,8.627],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":620,"st":20,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"ChatMatte 3","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,166.75,0],"ix":2,"l":2},"a":{"a":0,"k":[-0.575,-36.689,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.001,"y":0},"t":160,"s":[{"i":[[0,-1.657],[0,0],[1.657,0],[0,0],[0,1.657],[0,0],[-1.657,0],[0,0]],"o":[[0,0],[0,1.657],[0,0],[-1.657,0],[0,0],[0,-1.657],[0,0],[1.657,0]],"v":[[56.5,-83.478],[56.575,-89.522],[53.575,-86.522],[-53.425,-86.522],[-56.425,-89.522],[-56.5,-83.478],[-53.5,-86.478],[53.5,-86.478]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":181,"s":[{"i":[[0,-1.657],[0,0],[1.657,0],[0,0],[0,1.657],[0,0],[-1.657,0],[0,0]],"o":[[0,0],[0,1.657],[0,0],[-1.657,0],[0,0],[0,-1.657],[0,0],[1.657,0]],"v":[[56.5,-83.478],[56.5,83.478],[53.5,86.478],[-53.5,86.478],[-56.5,83.478],[-56.5,-83.478],[-53.5,-86.478],[53.5,-86.478]],"c":true}]},{"i":{"x":0.999,"y":1},"o":{"x":0.8,"y":0},"t":275,"s":[{"i":[[0,-1.657],[0,0],[1.657,0],[0,0],[0,1.657],[0,0],[-1.657,0],[0,0]],"o":[[0,0],[0,1.657],[0,0],[-1.657,0],[0,0],[0,-1.657],[0,0],[1.657,0]],"v":[[56.5,-83.478],[56.5,83.478],[53.5,86.478],[-53.5,86.478],[-56.5,83.478],[-56.5,-83.478],[-53.5,-86.478],[53.5,-86.478]],"c":true}]},{"t":299,"s":[{"i":[[0,-1.657],[0,0],[1.657,0],[0,0],[0,1.657],[0,0],[-1.657,0],[0,0]],"o":[[0,0],[0,1.657],[0,0],[-1.657,0],[0,0],[0,-1.657],[0,0],[1.657,0]],"v":[[56.5,-83.478],[56.575,-89.522],[53.575,-86.522],[-53.425,-86.522],[-56.425,-89.522],[-56.5,-83.478],[-53.5,-86.478],[53.5,-86.478]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.988235294118,0.909803921569,0.901960784314,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.575,-36.689],"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":160,"op":600,"st":0,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Chat 02 Outlines","tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":160,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":176,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":280.715,"s":[100]},{"t":299,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":0,"k":223.05,"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.001],"y":[0]},"t":160,"s":[162.4]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":181,"s":[171.85]},{"i":{"x":[0.999],"y":[1]},"o":{"x":[0.8],"y":[0]},"t":275,"s":[171.85]},{"t":299,"s":[162.4]}],"ix":4}},"a":{"a":0,"k":[39,8,0],"ix":1,"l":2},"s":{"a":0,"k":[90,90,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[4.077,0],[0,0],[0,4.078],[-4.077,0],[0,0],[0,-4.077]],"o":[[0,0],[-4.077,0],[0,-4.077],[0,0],[4.077,0],[0,4.078]],"v":[[30.766,7.414],[-30.765,7.414],[-38.179,0],[-30.765,-7.414],[30.766,-7.414],[38.179,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803981407,0.941176530427,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":[38.797,8.291],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":620,"st":20,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"ChatMatte 4","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,166.75,0],"ix":2,"l":2},"a":{"a":0,"k":[-0.575,-36.689,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.001,"y":0},"t":160,"s":[{"i":[[0,-1.657],[0,0],[1.657,0],[0,0],[0,1.657],[0,0],[-1.657,0],[0,0]],"o":[[0,0],[0,1.657],[0,0],[-1.657,0],[0,0],[0,-1.657],[0,0],[1.657,0]],"v":[[56.5,-83.478],[56.575,-89.522],[53.575,-86.522],[-53.425,-86.522],[-56.425,-89.522],[-56.5,-83.478],[-53.5,-86.478],[53.5,-86.478]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":181,"s":[{"i":[[0,-1.657],[0,0],[1.657,0],[0,0],[0,1.657],[0,0],[-1.657,0],[0,0]],"o":[[0,0],[0,1.657],[0,0],[-1.657,0],[0,0],[0,-1.657],[0,0],[1.657,0]],"v":[[56.5,-83.478],[56.5,83.478],[53.5,86.478],[-53.5,86.478],[-56.5,83.478],[-56.5,-83.478],[-53.5,-86.478],[53.5,-86.478]],"c":true}]},{"i":{"x":0.999,"y":1},"o":{"x":0.8,"y":0},"t":275,"s":[{"i":[[0,-1.657],[0,0],[1.657,0],[0,0],[0,1.657],[0,0],[-1.657,0],[0,0]],"o":[[0,0],[0,1.657],[0,0],[-1.657,0],[0,0],[0,-1.657],[0,0],[1.657,0]],"v":[[56.5,-83.478],[56.5,83.478],[53.5,86.478],[-53.5,86.478],[-56.5,83.478],[-56.5,-83.478],[-53.5,-86.478],[53.5,-86.478]],"c":true}]},{"t":299,"s":[{"i":[[0,-1.657],[0,0],[1.657,0],[0,0],[0,1.657],[0,0],[-1.657,0],[0,0]],"o":[[0,0],[0,1.657],[0,0],[-1.657,0],[0,0],[0,-1.657],[0,0],[1.657,0]],"v":[[56.5,-83.478],[56.575,-89.522],[53.575,-86.522],[-53.425,-86.522],[-56.425,-89.522],[-56.5,-83.478],[-53.5,-86.478],[53.5,-86.478]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.988235294118,0.909803921569,0.901960784314,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.575,-36.689],"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":160,"op":600,"st":0,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Chat 03 Outlines","tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":160,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":178,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":278.428,"s":[100]},{"t":299,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":0,"k":223.05,"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.001],"y":[0]},"t":160,"s":[182.65]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":181,"s":[192.1]},{"i":{"x":[0.999],"y":[1]},"o":{"x":[0.8],"y":[0]},"t":275,"s":[192.1]},{"t":299,"s":[182.65]}],"ix":4}},"a":{"a":0,"k":[39,11.5,0],"ix":1,"l":2},"s":{"a":0,"k":[90,90,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[5.904,0],[0,0],[0,5.904],[-5.904,0],[0,0],[0,-5.905]],"o":[[0,0],[-5.904,0],[0,-5.905],[0,0],[5.905,0],[0,5.904]],"v":[[27.445,10.736],[-27.444,10.736],[-38.179,0.001],[-27.444,-10.736],[27.444,-10.736],[38.179,0.001]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803981407,0.941176530427,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":[38.797,11.276],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":620,"st":20,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"ChatMatte 5","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,166.75,0],"ix":2,"l":2},"a":{"a":0,"k":[-0.575,-36.689,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.001,"y":0},"t":160,"s":[{"i":[[0,-1.657],[0,0],[1.657,0],[0,0],[0,1.657],[0,0],[-1.657,0],[0,0]],"o":[[0,0],[0,1.657],[0,0],[-1.657,0],[0,0],[0,-1.657],[0,0],[1.657,0]],"v":[[56.5,-83.478],[56.575,-89.522],[53.575,-86.522],[-53.425,-86.522],[-56.425,-89.522],[-56.5,-83.478],[-53.5,-86.478],[53.5,-86.478]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":181,"s":[{"i":[[0,-1.657],[0,0],[1.657,0],[0,0],[0,1.657],[0,0],[-1.657,0],[0,0]],"o":[[0,0],[0,1.657],[0,0],[-1.657,0],[0,0],[0,-1.657],[0,0],[1.657,0]],"v":[[56.5,-83.478],[56.5,83.478],[53.5,86.478],[-53.5,86.478],[-56.5,83.478],[-56.5,-83.478],[-53.5,-86.478],[53.5,-86.478]],"c":true}]},{"i":{"x":0.999,"y":1},"o":{"x":0.8,"y":0},"t":275,"s":[{"i":[[0,-1.657],[0,0],[1.657,0],[0,0],[0,1.657],[0,0],[-1.657,0],[0,0]],"o":[[0,0],[0,1.657],[0,0],[-1.657,0],[0,0],[0,-1.657],[0,0],[1.657,0]],"v":[[56.5,-83.478],[56.5,83.478],[53.5,86.478],[-53.5,86.478],[-56.5,83.478],[-56.5,-83.478],[-53.5,-86.478],[53.5,-86.478]],"c":true}]},{"t":299,"s":[{"i":[[0,-1.657],[0,0],[1.657,0],[0,0],[0,1.657],[0,0],[-1.657,0],[0,0]],"o":[[0,0],[0,1.657],[0,0],[-1.657,0],[0,0],[0,-1.657],[0,0],[1.657,0]],"v":[[56.5,-83.478],[56.575,-89.522],[53.575,-86.522],[-53.425,-86.522],[-56.425,-89.522],[-56.5,-83.478],[-53.5,-86.478],[53.5,-86.478]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.988235294118,0.909803921569,0.901960784314,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.575,-36.689],"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":160,"op":600,"st":0,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":"Chat 04 Outlines","tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":160,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":180,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":276.143,"s":[100]},{"t":299,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":0,"k":188.85,"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.001],"y":[0]},"t":160,"s":[202]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":181,"s":[211.45]},{"i":{"x":[0.999],"y":[1]},"o":{"x":[0.8],"y":[0]},"t":275,"s":[211.45]},{"t":299,"s":[202]}],"ix":4}},"a":{"a":0,"k":[39,8,0],"ix":1,"l":2},"s":{"a":0,"k":[90,90,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.078,0],[0,0],[0,4.078],[4.078,0],[0,0],[0,-4.078]],"o":[[0,0],[4.078,0],[0,-4.078],[0,0],[-4.078,0],[0,4.078]],"v":[[-30.765,7.414],[30.765,7.414],[38.179,0],[30.765,-7.414],[-30.765,-7.414],[-38.179,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.914689307119,0.922272566253,0.932703414618,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":[38.617,8.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":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":620,"st":20,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":"ChatMatte 6","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,166.75,0],"ix":2,"l":2},"a":{"a":0,"k":[-0.575,-36.689,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.001,"y":0},"t":160,"s":[{"i":[[0,-1.657],[0,0],[1.657,0],[0,0],[0,1.657],[0,0],[-1.657,0],[0,0]],"o":[[0,0],[0,1.657],[0,0],[-1.657,0],[0,0],[0,-1.657],[0,0],[1.657,0]],"v":[[56.5,-83.478],[56.575,-89.522],[53.575,-86.522],[-53.425,-86.522],[-56.425,-89.522],[-56.5,-83.478],[-53.5,-86.478],[53.5,-86.478]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":181,"s":[{"i":[[0,-1.657],[0,0],[1.657,0],[0,0],[0,1.657],[0,0],[-1.657,0],[0,0]],"o":[[0,0],[0,1.657],[0,0],[-1.657,0],[0,0],[0,-1.657],[0,0],[1.657,0]],"v":[[56.5,-83.478],[56.5,83.478],[53.5,86.478],[-53.5,86.478],[-56.5,83.478],[-56.5,-83.478],[-53.5,-86.478],[53.5,-86.478]],"c":true}]},{"i":{"x":0.999,"y":1},"o":{"x":0.8,"y":0},"t":275,"s":[{"i":[[0,-1.657],[0,0],[1.657,0],[0,0],[0,1.657],[0,0],[-1.657,0],[0,0]],"o":[[0,0],[0,1.657],[0,0],[-1.657,0],[0,0],[0,-1.657],[0,0],[1.657,0]],"v":[[56.5,-83.478],[56.5,83.478],[53.5,86.478],[-53.5,86.478],[-56.5,83.478],[-56.5,-83.478],[-53.5,-86.478],[53.5,-86.478]],"c":true}]},{"t":299,"s":[{"i":[[0,-1.657],[0,0],[1.657,0],[0,0],[0,1.657],[0,0],[-1.657,0],[0,0]],"o":[[0,0],[0,1.657],[0,0],[-1.657,0],[0,0],[0,-1.657],[0,0],[1.657,0]],"v":[[56.5,-83.478],[56.575,-89.522],[53.575,-86.522],[-53.425,-86.522],[-56.425,-89.522],[-56.5,-83.478],[-53.5,-86.478],[53.5,-86.478]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.988235294118,0.909803921569,0.901960784314,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.575,-36.689],"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":160,"op":600,"st":0,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":"Chat 05 Outlines","tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":160,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":181,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":275,"s":[100]},{"t":299,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":0,"k":205.95,"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.001],"y":[0]},"t":160,"s":[227.65]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":181,"s":[237.1]},{"i":{"x":[0.999],"y":[1]},"o":{"x":[0.8],"y":[0]},"t":275,"s":[237.1]},{"t":299,"s":[227.65]}],"ix":4}},"a":{"a":0,"k":[58,8.5,0],"ix":1,"l":2},"s":{"a":0,"k":[90,90,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[2.457,0],[0,0],[0,2.456],[-2.457,0],[0,-2.457]],"o":[[0,0],[-2.457,0],[0,-2.457],[2.457,0],[0,2.456]],"v":[[0,4.449],[0,4.449],[-4.448,0.001],[0,-4.449],[4.448,0.001]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803921569,0.941176470588,0.996078431373,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":[4.892,8.624],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[2.457,0],[0,0],[0,2.456],[-2.457,0],[0,-2.457]],"o":[[0,0],[-2.457,0],[0,-2.457],[2.457,0],[0,2.456]],"v":[[0,4.449],[0,4.449],[-4.448,0.001],[0,-4.449],[4.448,0.001]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803921569,0.941176470588,0.996078431373,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":[16.481,8.624],"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 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[2.457,0],[0,0],[0,2.456],[-2.457,0],[0,-2.457]],"o":[[0,0],[-2.457,0],[0,-2.457],[2.457,0],[0,2.456]],"v":[[0.001,4.449],[0.001,4.449],[-4.449,0.001],[0.001,-4.449],[4.449,0.001]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803921569,0.941176470588,0.996078431373,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":[28.071,8.624],"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 3","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-3.79,0],[0,0],[0,3.79],[0,0],[3.79,0],[0,0],[0,-4.034],[0,0]],"o":[[0,0],[3.79,0],[0,0],[0,-3.791],[0,0],[-4.033,0],[0,0],[0,3.79]],"v":[[-33.09,7.414],[33.092,7.414],[39.982,0.522],[39.982,-0.523],[33.092,-7.414],[-32.648,-7.414],[-39.982,-0.08],[-39.982,0.522]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.914689307119,0.922272566253,0.932703414618,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":[74.994,8.624],"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 4","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":620,"st":20,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":"Chat Pointer Outlines","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":165,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":292.143,"s":[100]},{"t":293.28515625,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.001,"y":0},"t":165,"s":[158.7,84.325,0],"to":[0,-0.412,0],"ti":[0,0.412,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":168,"s":[158.7,81.85,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.999,"y":1},"o":{"x":0.8,"y":0},"t":289.857,"s":[158.7,81.85,0],"to":[0,0.413,0],"ti":[0,-0.413,0]},{"t":293.28515625,"s":[158.7,84.325,0]}],"ix":2,"l":2},"a":{"a":0,"k":[3.5,3,0],"ix":1,"l":2},"s":{"a":0,"k":[90,90,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],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-2.786,1.115],[-0.001,-1.115],[2.786,1.115]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,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":[3.874,1.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":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":616,"st":16,"bm":0},{"ddd":0,"ind":20,"ty":4,"nm":"ChatMatte 7","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":160,"s":[24]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":165,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":293.285,"s":[100]},{"t":299,"s":[24]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,166.75,0],"ix":2,"l":2},"a":{"a":0,"k":[-0.575,-36.689,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.001,"y":0},"t":160,"s":[{"i":[[0,-1.657],[0,0],[1.657,0],[0,0],[0,1.657],[0,0],[-1.657,0],[0,0]],"o":[[0,0],[0,1.657],[0,0],[-1.657,0],[0,0],[0,-1.657],[0,0],[1.657,0]],"v":[[56.5,-83.478],[56.575,-89.522],[53.575,-86.522],[-53.425,-86.522],[-56.425,-89.522],[-56.5,-83.478],[-53.5,-86.478],[53.5,-86.478]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":181,"s":[{"i":[[0,-1.657],[0,0],[1.657,0],[0,0],[0,1.657],[0,0],[-1.657,0],[0,0]],"o":[[0,0],[0,1.657],[0,0],[-1.657,0],[0,0],[0,-1.657],[0,0],[1.657,0]],"v":[[56.5,-83.478],[56.5,83.478],[53.5,86.478],[-53.5,86.478],[-56.5,83.478],[-56.5,-83.478],[-53.5,-86.478],[53.5,-86.478]],"c":true}]},{"i":{"x":0.999,"y":1},"o":{"x":0.8,"y":0},"t":275,"s":[{"i":[[0,-1.657],[0,0],[1.657,0],[0,0],[0,1.657],[0,0],[-1.657,0],[0,0]],"o":[[0,0],[0,1.657],[0,0],[-1.657,0],[0,0],[0,-1.657],[0,0],[1.657,0]],"v":[[56.5,-83.478],[56.5,83.478],[53.5,86.478],[-53.5,86.478],[-56.5,83.478],[-56.5,-83.478],[-53.5,-86.478],[53.5,-86.478]],"c":true}]},{"t":299,"s":[{"i":[[0,-1.657],[0,0],[1.657,0],[0,0],[0,1.657],[0,0],[-1.657,0],[0,0]],"o":[[0,0],[0,1.657],[0,0],[-1.657,0],[0,0],[0,-1.657],[0,0],[1.657,0]],"v":[[56.5,-83.478],[56.575,-89.522],[53.575,-86.522],[-53.425,-86.522],[-56.425,-89.522],[-56.5,-83.478],[-53.5,-86.478],[53.5,-86.478]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,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.575,-36.689],"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":160,"op":600,"st":0,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":".blue300","cl":"blue300","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":144,"s":[100]},{"t":154,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[244.662,135.587,0],"ix":2,"l":2},"a":{"a":0,"k":[38,8.125,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.001,0.001,0.001],"y":[0,0,0]},"t":45,"s":[0,0,100]},{"i":{"x":[0.71,0.71,0.833],"y":[1,1,1]},"o":{"x":[0.318,0.318,0.001],"y":[0,0,0]},"t":59,"s":[94.5,94.5,100]},{"t":68,"s":[90,90,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],[0,0],[1.244,0],[0,0],[0,-1.244],[0,0],[-1.244,0],[0,0],[0,1.244],[0,0]],"o":[[0,0],[0,0],[0,-1.244],[0,0],[-1.244,0],[0,0],[0,1.244],[0,0],[1.244,0],[0,0],[0,0]],"v":[[18.328,0],[17.101,-1.227],[17.101,-4.883],[14.84,-7.145],[-16.067,-7.145],[-18.328,-4.883],[-18.328,4.883],[-16.067,7.145],[14.84,7.145],[17.101,4.883],[17.101,1.227]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.541176470588,0.705882352941,0.972549019608,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":[19.046,8.106],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":620,"st":20,"bm":0},{"ddd":0,"ind":22,"ty":4,"nm":".grey300","cl":"grey300","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":[[0,0],[5.243,0],[0,0],[0,5.244],[0,0],[-5.243,0],[0,0],[0,-5.244]],"o":[[0,5.244],[0,0],[-5.243,0],[0,0],[0,-5.244],[0,0],[5.243,0],[0,0]],"v":[[64.188,114.112],[54.679,123.622],[-54.679,123.622],[-64.188,114.112],[-64.188,-114.112],[-54.679,-123.622],[54.679,-123.622],[64.188,-114.112]],"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,1.313],[0,0],[1.313,0],[0,0],[6.554,0],[0,0],[0,-6.555],[0,0],[-6.554,0],[0,0],[0,6.555],[0,0],[0,1.313],[0,0],[1.313,0],[0,0]],"o":[[0,0],[0,-1.313],[0,0],[0,-6.555],[0,0],[-6.554,0],[0,0],[0,6.555],[0,0],[6.554,0],[0,0],[1.313,0],[0,0],[0,-1.313],[0,0],[1.313,0]],"v":[[68.943,-52.302],[68.943,-61.811],[66.566,-64.188],[66.566,-114.112],[54.679,-126],[-54.679,-126],[-66.566,-114.112],[-66.566,114.112],[-54.679,126],[54.679,126],[66.566,114.112],[66.566,-2.378],[68.943,-4.755],[68.943,-28.528],[66.566,-30.906],[66.566,-49.924]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.854901969433,0.86274510622,0.878431379795,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1800,"st":0,"bm":0},{"ddd":0,"ind":23,"ty":0,"nm":"BlueDot","parent":24,"refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[62.89,101.169,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[111.111,111.111,100],"ix":6,"l":2}},"ao":0,"w":412,"h":300,"ip":239,"op":620,"st":239,"bm":0},{"ddd":0,"ind":24,"ty":4,"nm":".blue50","cl":"blue50","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.437],"y":[1]},"o":{"x":[0.637],"y":[0]},"t":294,"s":[159.299]},{"t":324,"s":[254.963]}],"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.152],"y":[1]},"o":{"x":[0.414],"y":[0]},"t":306,"s":[68.848]},{"t":350,"s":[135.585]}],"ix":4}},"a":{"a":0,"k":[11,11,0],"ix":1,"l":2},"s":{"a":0,"k":[90,90,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-4.694],[4.694,0],[0,4.694],[-4.694,0]],"o":[[0,4.694],[-4.694,0],[0,-4.694],[4.694,0]],"v":[[8.5,0],[0,8.5],[-8.5,0],[0,-8.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803921569,0.941176470588,0.996078431373,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":[11,11],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":239,"op":600,"st":0,"bm":0},{"ddd":0,"ind":25,"ty":0,"nm":"BlueDot","parent":26,"refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-43.429,27.058,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[111.111,111.111,100],"ix":6,"l":2}},"ao":0,"w":412,"h":300,"ip":0,"op":239,"st":0,"bm":0},{"ddd":0,"ind":26,"ty":4,"nm":".blue50","cl":"blue50","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.4,"y":0},"t":144,"s":[254.963,135.566,0],"to":[0.004,-63.556,0],"ti":[94.364,1.013,0]},{"t":168,"s":[159.299,68.86,0],"h":1},{"i":{"x":0.6,"y":1},"o":{"x":0.8,"y":0},"t":288.551,"s":[158.486,68.672,0],"to":[82.114,1.013,0],"ti":[-0.996,-58.556,0]},{"t":332,"s":[254.65,134.91,0]}],"ix":2,"l":2},"a":{"a":0,"k":[11,11,0],"ix":1,"l":2},"s":{"a":0,"k":[90,90,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-4.694],[4.694,0],[0,4.694],[-4.694,0]],"o":[[0,4.694],[-4.694,0],[0,-4.694],[4.694,0]],"v":[[8.5,0],[0,8.5],[-8.5,0],[0,-8.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803921569,0.941176470588,0.996078431373,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":[11,11],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":239,"st":0,"bm":0},{"ddd":0,"ind":27,"ty":0,"nm":"RedDot","parent":28,"refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40.056,99.722,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[111.111,111.111,100],"ix":6,"l":2}},"ao":0,"w":412,"h":300,"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":28,"ty":4,"nm":".red50","cl":"red50","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":144,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":155.854,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":302,"s":[100]},{"t":315,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.4,"y":0},"t":144,"s":[254.775,135.175,0],"to":[-0.412,-66.188,0],"ti":[73.312,0.488,0]},{"t":169,"s":[178.05,68.8,0],"h":1},{"i":{"x":0.6,"y":1},"o":{"x":0.8,"y":0},"t":287.242,"s":[178.05,68.8,0],"to":[73.312,0.488,0],"ti":[-0.413,-66.188,0]},{"t":329,"s":[254.775,135.175,0]}],"ix":2,"l":2},"a":{"a":0,"k":[9,9.5,0],"ix":1,"l":2},"s":{"a":0,"k":[90,90,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-4.694],[4.695,0],[0,4.695],[-4.694,0]],"o":[[0,4.695],[-4.694,0],[0,-4.694],[4.695,0]],"v":[[8.5,0],[0,8.5],[-8.5,0],[0,-8.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.988235294118,0.909803921569,0.901960784314,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.054,9.655],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":620,"st":20,"bm":0},{"ddd":0,"ind":29,"ty":0,"nm":"YellowDot","parent":30,"refId":"comp_3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[19.056,99.722,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[111.111,111.111,100],"ix":6,"l":2}},"ao":0,"w":412,"h":300,"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":30,"ty":4,"nm":".yellow50","cl":"yellow50","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":146.734,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158.588,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":300.885,"s":[100]},{"t":314,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.4,"y":0},"t":144,"s":[254.55,134.95,0],"to":[0.3,-65.925,0],"ti":[57.75,0.225,0]},{"t":170,"s":[196.95,68.8,0],"h":1},{"i":{"x":0.6,"y":1},"o":{"x":0.8,"y":0},"t":285.93,"s":[196.95,68.8,0],"to":[57.75,0.225,0],"ti":[0.3,-65.925,0]},{"t":327,"s":[254.55,134.95,0]}],"ix":2,"l":2},"a":{"a":0,"k":[9,9.5,0],"ix":1,"l":2},"s":{"a":0,"k":[90,90,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-4.694],[4.694,0],[0,4.695],[-4.694,0]],"o":[[0,4.695],[-4.694,0],[0,-4.694],[4.694,0]],"v":[[8.5,0],[0,8.5],[-8.5,0],[0,-8.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.996078431373,0.96862745098,0.878431372549,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.234,9.655],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":620,"st":20,"bm":0},{"ddd":0,"ind":31,"ty":0,"nm":"GreenDot","refId":"comp_4","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":600,"st":0,"bm":0},{"ddd":0,"ind":32,"ty":4,"nm":".green50","cl":"green50","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":149.471,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":161.324,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":297.301,"s":[100]},{"t":312,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.4,"y":0},"t":144,"s":[255,134.95,0],"to":[-0.3,-65.925,0],"ti":[39.225,0.225,0]},{"t":171,"s":[215.4,68.8,0],"h":1},{"i":{"x":0.6,"y":1},"o":{"x":0.8,"y":0},"t":284.621,"s":[215.4,68.8,0],"to":[39.225,0.225,0],"ti":[-0.3,-65.925,0]},{"t":325,"s":[255,134.95,0]}],"ix":2,"l":2},"a":{"a":0,"k":[9.5,9.5,0],"ix":1,"l":2},"s":{"a":0,"k":[90,90,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.013,-4.694],[4.695,-0.012],[0.013,4.694],[-4.695,0.013]],"o":[[0.013,4.694],[-4.694,0.013],[-0.013,-4.694],[4.694,-0.012]],"v":[[8.5,-0.023],[0.022,8.499],[-8.5,0.023],[-0.022,-8.501]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.901960784314,0.956862745098,0.917647058824,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.297,9.684],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":620,"st":20,"bm":0},{"ddd":0,"ind":33,"ty":4,"nm":"BlueDot","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[240.657,75.109,0],"ix":2,"l":2},"a":{"a":0,"k":[3.5,3.5,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0,0,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":164,"s":[0,0,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":186,"s":[90,90,100]},{"i":{"x":[0.6,0.6,0.6],"y":[1,1,1]},"o":{"x":[1,1,0.8],"y":[0,0,0]},"t":269,"s":[90,90,100]},{"t":291,"s":[0,0,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-1.693],[1.693,0],[0,1.693],[-1.694,0]],"o":[[0,1.693],[-1.694,0],[0,-1.693],[1.693,0]],"v":[[3.066,0],[0.001,3.065],[-3.066,0],[0.001,-3.065]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.297618701411,0.5469255036,0.959243056353,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":[3.331,3.623],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":620,"st":20,"bm":0},{"ddd":0,"ind":34,"ty":4,"nm":"BlueBubble","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":152.205,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":164.059,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":293.715,"s":[100]},{"t":309.25,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.4,"y":0},"t":144,"s":[254.775,134.725,0],"to":[-0.15,-65.475,0],"ti":[20.362,0.188,0]},{"t":172,"s":[233.85,68.8,0],"h":1},{"i":{"x":0.6,"y":1},"o":{"x":0.8,"y":0},"t":283.311,"s":[233.85,68.8,0],"to":[20.362,0.188,0],"ti":[-0.15,-65.475,0]},{"t":325,"s":[254.775,134.725,0]}],"ix":2,"l":2},"a":{"a":0,"k":[9,9.5,0],"ix":1,"l":2},"s":{"a":0,"k":[90,90,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-4.694],[4.694,0],[0,4.695],[-4.694,0]],"o":[[0,4.695],[-4.694,0],[0,-4.694],[4.694,0]],"v":[[8.5,0],[0,8.5],[-8.5,0],[0,-8.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803921569,0.941176470588,0.996078431373,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.922,9.655],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":620,"st":20,"bm":0},{"ddd":0,"ind":35,"ty":4,"nm":"+","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":155,"s":[0]},{"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":283,"s":[100]},{"t":300,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.4,"y":0},"t":144,"s":[254.668,134.855,0],"to":[-0.387,-11.006,0],"ti":[2.531,0.405,0]},{"t":173,"s":[252.345,68.82,0],"h":1},{"i":{"x":0.6,"y":1},"o":{"x":0.8,"y":0},"t":282,"s":[252.345,68.82,0],"to":[2.531,0.405,0],"ti":[-0.304,-9.922,0]},{"t":325,"s":[254.668,134.855,0]}],"ix":2,"l":2},"a":{"a":0,"k":[9,9,0],"ix":1,"l":2},"s":{"a":0,"k":[90,90,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],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[3,0.5],[-3,0.5],[-3,-0.5],[3,-0.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.204000001795,0.455000005984,0.877999997606,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.797,9.063],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"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":[[0.5,3],[-0.5,3],[-0.5,-3],[0.5,-3]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.204000001795,0.455000005984,0.877999997606,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.797,9.063],"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 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.013,-4.694],[4.695,-0.012],[0.013,4.695],[-4.695,0.013]],"o":[[0.013,4.694],[-4.694,0.013],[-0.012,-4.694],[4.694,-0.012]],"v":[[8.5,-0.023],[0.021,8.499],[-8.5,0.023],[-0.022,-8.501]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,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.797,9.064],"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 3","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":36,"ty":4,"nm":".grey600","cl":"grey600","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":156,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":172,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":284,"s":[100]},{"t":316,"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":[67,133,0],"ix":1,"l":2},"s":{"a":0,"k":[96.6,94,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[2.77,0],[0,0],[0,2.769],[0,0],[-5.334,0.268],[0,0],[0,-2.769],[0,0]],"o":[[0,0],[-2.769,0],[0,0],[0.09,-4.634],[0,0],[2.77,0],[0,0],[0,2.769]],"v":[[58.38,132.51],[-59.674,132.51],[-67.363,124.149],[-67.363,-122.821],[-58.315,-132.51],[58.38,-132.51],[67.298,-126.81],[67.298,124.149]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960784314,0.525490196078,0.545098039216,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[66.985,133.061],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":620,"st":20,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index febdb04..5de6222 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -1425,6 +1425,14 @@
<item>sat</item>
</string-array>
+ <!-- A list for measurement system. [DO NOT TRANSLATE] -->
+ <string-array name="measurement_system">
+ <item>default</item>
+ <item>metric</item>
+ <item>ussystem</item>
+ <item>uksystem</item>
+ </string-array>
+
<!-- Screen flash notification color when activating -->
<array name="screen_flash_notification_preset_opacity_colors">
<item>@color/screen_flash_preset_opacity_color_01</item>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 200253a..87c3ce9 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -50,8 +50,6 @@
<attr name="android:textAppearance" />
</declare-styleable>
- <attr name="apnPreferenceStyle" format="reference" />
-
<attr name="slicePreferenceStyle" format="reference" />
<attr name="cardPreferenceStyle" format="reference" />
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 3300cc6..5a408d2 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -506,6 +506,8 @@
<string name="first_day_of_week_preferences_title">First day of week</string>
<!-- The title of the menu entry of Numbers system preference. [CHAR LIMIT=50] -->
<string name="numbers_preferences_title">Numbers preferences</string>
+ <!-- The title of the menu entry of Measurement system preference. [CHAR LIMIT=50] -->
+ <string name="measurement_system_preferences_title">Measurement system</string>
<!-- The summary of default string for each regional preference. [CHAR LIMIT=50] -->
<string name="default_string_of_regional_preference">Use default</string>
<!-- The title of Celsius for preference of temperature unit. [CHAR LIMIT=50] -->
@@ -532,6 +534,12 @@
<string name="desc_regional_pref_footer_learn_more">Learn more about language preferences.</string>
<!-- Support link for the regional preference page. [CHAR LIMIT=NONE]-->
<string name="regional_pref_footer_learn_more_link" translatable="false">https://support.google.com/android?p=regional_preferences</string>
+ <!-- The title of metric for preference of measurement system. [CHAR LIMIT=50] -->
+ <string name="metric_measurement_system">Metric</string>
+ <!-- The title of imperial US for preference of measurement system. [CHAR LIMIT=50] -->
+ <string name="us_measurement_system">Imperial (US)</string>
+ <!-- The title of imperial UK for preference of measurement system. [CHAR LIMIT=50] -->
+ <string name="uk_measurement_system">Imperial (UK)</string>
<!-- Category for the terms of address. [CHAR LIMIT=NONE]-->
<string name="category_title_terms_of_address">Additional preferences</string>
@@ -4522,6 +4530,8 @@
<string name="language_settings">Languages & input</string>
<!-- Title of setting on main settings screen. This item will take the user to the screen to tweak settings related to languages -->
<string name="languages_settings">Languages</string>
+ <!-- Title of setting on main settings screen. This item will take the user to the screen to tweak settings related to language and region-->
+ <string name="language_and_region_settings">Language & region</string>
<!-- Title of setting on main settings screen. This item will take the user to the screen to tweak settings related to keyboards -->
<string name="keyboard_settings">Keyboard</string>
<!-- Text displayed when user has restriction DISALLOW_CONFIG_LOCALE [CHAR LIMIT=NONE]-->
@@ -4588,17 +4598,17 @@
<!-- Title for the 'Bounce keys' preference switch. [CHAR LIMIT=35] -->
<string name="bounce_keys">Bounce keys</string>
<!-- Summary text for the 'Bounce keys' preference sub-screen. [CHAR LIMIT=300] -->
- <string name="bounce_keys_summary">The keyboard ignores quickly repeated presses of the same key </string>
+ <string name="bounce_keys_summary">Ignore quickly repeated presses of the same key</string>
<!-- Title for the 'Bounce keys' threshold dialog. [CHAR LIMIT=35] -->
- <string name="bounce_keys_dialog_title">Bounce key threshold</string>
+ <string name="bounce_keys_dialog_title">Bounce key delay</string>
<!-- Subtitle for the 'Bounce keys' threshold dialog. [CHAR LIMIT=300] -->
- <string name="bounce_keys_dialog_subtitle">Choose the duration of time your keyboard ignores repeated key presses</string>
+ <string name="bounce_keys_dialog_subtitle">Choose how long the keyboard ignores repeated keystrokes</string>
<!-- Option title for the input setting keys threshold dialog for 200 millisecond. [CHAR LIMIT=35] -->
- <string name="input_setting_keys_dialog_option_200">0.2s</string>
+ <string name="input_setting_keys_dialog_option_200">0.2 seconds</string>
<!-- Option title for the input setting keys threshold dialog for 400 millisecond. [CHAR LIMIT=35] -->
- <string name="input_setting_keys_dialog_option_400">0.4s</string>
+ <string name="input_setting_keys_dialog_option_400">0.4 seconds</string>
<!-- Option title for the input setting keys threshold dialog for 600 millisecond. [CHAR LIMIT=35] -->
- <string name="input_setting_keys_dialog_option_600">0.6s</string>
+ <string name="input_setting_keys_dialog_option_600">0.6 seconds</string>
<!-- Option title for the input setting keys threshold dialog for custom value. [CHAR LIMIT=35] -->
<string name="input_setting_keys_custom_title">Custom</string>
<!-- Option subtitle for the input setting keys threshold dialog for custom value. [CHAR LIMIT=35] -->
@@ -4607,7 +4617,7 @@
<!-- Title for the 'Slow keys' preference switch. [CHAR LIMIT=35] -->
<string name="slow_keys">Slow keys</string>
<!-- Summary text for the 'Slow keys' preference sub-screen. [CHAR LIMIT=300] -->
- <string name="slow_keys_summary">Adjusts the time it takes for a key press to activate</string>
+ <string name="slow_keys_summary">Change how long you need to hold down a key before it\'s registered</string>
<!-- Title for the 'Sticky keys' preference switch. [CHAR LIMIT=35] -->
<string name="sticky_keys">Sticky keys</string>
<!-- Summary text for the 'Sticky keys' preference sub-screen. [CHAR LIMIT=300] -->
@@ -4658,6 +4668,8 @@
<string name="trackpad_mouse_settings">Touchpad & mouse</string>
<!-- Title for the button to trigger the 'mouse settings' page if connect with a mouse. [CHAR LIMIT=35] -->
<string name="mouse_settings">Mouse</string>
+ <!-- Summary text for mouse setting page. [CHAR LIMIT=35] -->
+ <string name="mouse_settings_summary">Pointer speed, swap buttons, button customisation</string>
<!-- Summary text for the 'trackpad settings' page. [CHAR LIMIT=100] -->
<string name="trackpad_settings_summary">Pointer speed, gestures</string>
@@ -5403,6 +5415,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>
+ <!-- Content for the notification to high contrast text. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_notification_high_contrast_text_content">High contrast text has a new look and feel.</string>
+ <!-- Action for the notification to high contrast text. [CHAR LIMIT=35] -->
+ <string name="accessibility_notification_high_contrast_text_action">Open Settings</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] -->
@@ -8814,13 +8830,13 @@
<!-- Configure notifications: Value for lockscreen notifications: show all notifications [CHAR LIMIT=60] -->
<string name="lock_screen_notifs_show_full_list">Full list</string>
- <!-- Configure notifications: Summary for lockscreen notifications: show all notifications [CHAR LIMIT=100] -->
+ <!-- Configure notifications: Summary for lockscreen notifications: show all notifications [CHAR LIMIT=NONE] -->
<string name="lock_screen_notifs_full_list_desc">The current default placement is a full shelf and notification stack.</string>
<!-- Configure notifications: Value for lockscreen notifications: show compact notifications (minimalism on) [CHAR LIMIT=60] -->
<string name="lock_screen_notifs_show_compact">Compact</string>
- <!-- Configure notifications: Summary for lockscreen notifications: show compact notifications (minimalism on) [CHAR LIMIT=60] -->
+ <!-- Configure notifications: Summary for lockscreen notifications: show compact notifications (minimalism on) [CHAR LIMIT=NONE] -->
<string name="lock_screen_notifs_compact_desc">New notifications are collapsed into a shelf on your lockscreen.</string>
<!-- Configure notifications: Title for determining which notifications appear on the lock screen [CHAR LIMIT=60] -->
@@ -8885,16 +8901,16 @@
<!-- Security > Choose PIN/PW/Pattern > Notification redaction interstitial: Message asking the user how they want their profile notifications to appear when the device is locked [CHAR LIMIT=NONE] -->
<string name="lock_screen_notifications_interstitial_message_profile">When your device is locked, how do you want profile notifications to show?</string>
- <!-- Notification Settings > Notifications on lock screen > Title for hiding seen notifications toggle. [CHAR LIMIT=30] -->
+ <!-- Notification Settings > Notifications on lock screen > Title for hiding seen notifications toggle. [CHAR LIMIT=60] -->
<string name="lock_screen_notification_hide_seen_title">Hide seen notifications</string>
- <!-- Notification Settings > Notifications on lock screen > Summary for hiding seen notifications toggle. [CHAR LIMIT=30] -->
+ <!-- Notification Settings > Notifications on lock screen > Summary for hiding seen notifications toggle. [CHAR LIMIT=100] -->
<string name="lock_screen_notification_hide_seen_summary">Seen notifications are removed from the lock screen.</string>
- <!-- Notification Settings > Notifications on lock screen > Title for hiding silent notifications toggle. [CHAR LIMIT=30] -->
+ <!-- Notification Settings > Notifications on lock screen > Title for hiding silent notifications toggle. [CHAR LIMIT=60] -->
<string name="lock_screen_notification_hide_silent_title">Hide silent notifications</string>
- <!-- Notification Settings > Notifications on lock screen > Summary for hiding seen notifications toggle. [CHAR LIMIT=30] -->
+ <!-- Notification Settings > Notifications on lock screen > Summary for hiding silent notifications toggle. [CHAR LIMIT=100] -->
<string name="lock_screen_notification_hide_silent_summary">Silent notifications and conversations are removed from the lock screen.</string>
<!-- Security > Choose PIN/PW/Pattern > Notification redaction interstitial: Title for the screen asking the user how they want their profile notifications to appear when the device is locked [CHAR LIMIT=30] -->
diff --git a/res/values/styles_preference.xml b/res/values/styles_preference.xml
index ec0a747..c7638d3 100644
--- a/res/values/styles_preference.xml
+++ b/res/values/styles_preference.xml
@@ -20,7 +20,6 @@
<resources>
<style name="SettingsPreferenceTheme" parent="@style/PreferenceTheme.SettingsLib">
- <item name="apnPreferenceStyle">@style/ApnPreference</item>
<item name="cardPreferenceStyle">@style/CardPreference</item>
<item name="slicePreferenceStyle">@style/SlicePreference</item>
<item name="seekBarPreferenceStyle">@style/SettingsSeekBarPreference</item>
@@ -32,10 +31,6 @@
<item name="preferenceFragmentCompatStyle">@style/SetupWizardPreferenceFragmentStyle</item>
</style>
- <style name="ApnPreference" parent="@style/Preference.Material">
- <item name="android:layout">@layout/apn_preference_layout</item>
- </style>
-
<style name="CardPreference" parent="@style/Preference.Material">
<item name="android:layout">@layout/card_preference_layout</item>
</style>
diff --git a/res/values/styles_preference_expressive.xml b/res/values/styles_preference_expressive.xml
index a6fe2f1..278fe00 100644
--- a/res/values/styles_preference_expressive.xml
+++ b/res/values/styles_preference_expressive.xml
@@ -20,7 +20,6 @@
<resources>
<style name="SettingsPreferenceTheme.Expressive" parent="@style/PreferenceTheme.SettingsLib.Expressive">
- <item name="apnPreferenceStyle">@style/ApnPreference</item>
<item name="cardPreferenceStyle">@style/CardPreference</item>
<item name="slicePreferenceStyle">@style/SlicePreference</item>
<item name="seekBarPreferenceStyle">@style/SettingsSeekBarPreference</item>
diff --git a/res/xml/accessibility_text_reading_options.xml b/res/xml/accessibility_text_reading_options.xml
index 795c4ffb9..8eed107 100644
--- a/res/xml/accessibility_text_reading_options.xml
+++ b/res/xml/accessibility_text_reading_options.xml
@@ -25,7 +25,7 @@
android:key="preview"
android:selectable="false"/>
- <com.android.settings.widget.LabeledSeekBarPreference
+ <com.android.settings.accessibility.AccessibilitySeekBarPreference
android:key="font_size"
android:summary="@string/short_summary_font_size"
android:title="@string/title_font_size"
@@ -35,7 +35,7 @@
settings:iconStartContentDescription="@string/font_size_make_smaller_desc"
settings:keywords="@string/keywords_font_size" />
- <com.android.settings.widget.LabeledSeekBarPreference
+ <com.android.settings.accessibility.AccessibilitySeekBarPreference
android:key="display_size"
android:summary="@string/screen_zoom_short_summary"
android:title="@string/screen_zoom_title"
diff --git a/res/xml/app_data_usage.xml b/res/xml/app_data_usage.xml
index 88f60ef..3135f59 100644
--- a/res/xml/app_data_usage.xml
+++ b/res/xml/app_data_usage.xml
@@ -20,11 +20,15 @@
android:key="app_data_usage_screen"
android:title="@string/data_usage_app_summary_title">
+ <com.android.settingslib.widget.IntroPreference
+ android:key="app_header"
+ android:order="-10000"/>
+
<com.android.settings.datausage.SpinnerPreference
android:key="cycle"
settings:controller="com.android.settings.datausage.AppDataUsageCycleController" />
- <com.android.settings.spa.preference.ComposePreference
+ <com.android.settings.spa.preference.ComposeGroupSectionPreference
android:key="app_data_usage_summary"
settings:controller="com.android.settings.datausage.AppDataUsageSummaryController"/>
diff --git a/res/xml/connected_devices_advanced.xml b/res/xml/connected_devices_advanced.xml
index 779555b..f491055 100644
--- a/res/xml/connected_devices_advanced.xml
+++ b/res/xml/connected_devices_advanced.xml
@@ -42,7 +42,7 @@
android:order="-7"
android:title="@string/nfc_quick_toggle_title"
settings:controller="com.android.settings.connecteddevice.NfcAndPaymentFragmentController"
- settings:searchable="false"
+ settings:searchable="true"
settings:useAdminDisabledSummary="true"
settings:userRestriction="no_near_field_communication_radio" />
diff --git a/res/xml/date_time_prefs.xml b/res/xml/date_time_prefs.xml
index d8643be..fe0fd7e 100644
--- a/res/xml/date_time_prefs.xml
+++ b/res/xml/date_time_prefs.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2008 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.
@@ -94,10 +94,6 @@
android:key="time_format_preference_category"
android:title="@string/time_format_category_title"
settings:keywords="@string/keywords_time_format">
- <SwitchPreferenceCompat
- android:key="auto_24hour"
- android:title="@string/date_time_24hour_auto"
- settings:controller="com.android.settings.datetime.AutoTimeFormatPreferenceController" />
<SwitchPreferenceCompat
android:key="24 hour"
diff --git a/res/xml/date_time_prefs_revamped.xml b/res/xml/date_time_prefs_revamped.xml
deleted file mode 100644
index fe0fd7e..0000000
--- a/res/xml/date_time_prefs_revamped.xml
+++ /dev/null
@@ -1,104 +0,0 @@
-<?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:title="@string/date_and_time"
- settings:keywords="@string/keywords_date_and_time">
-
- <com.android.settingslib.RestrictedSwitchPreference
- android:key="auto_time"
- android:title="@string/date_time_auto"
- android:summary="@string/summary_placeholder"
- settings:userRestriction="no_config_date_time"
- settings:controller="com.android.settings.datetime.AutoTimePreferenceController" />
-
- <com.android.settingslib.RestrictedPreference
- android:key="date"
- android:title="@string/date_time_set_date_title"
- android:summary="@string/summary_placeholder"
- settings:userRestriction="no_config_date_time"
- settings:controller="com.android.settings.datetime.DatePreferenceController" />
-
- <com.android.settingslib.RestrictedPreference
- android:key="time"
- android:title="@string/date_time_set_time_title"
- android:summary="@string/summary_placeholder"
- settings:userRestriction="no_config_date_time"
- settings:controller="com.android.settings.datetime.TimePreferenceController" />
-
- <PreferenceCategory
- android:key="timezone_preference_category"
- android:title="@string/date_time_set_timezone_title">
-
- <com.android.settingslib.RestrictedSwitchPreference
- android:key="auto_zone"
- android:title="@string/zone_auto_title"
- android:summary="@string/summary_placeholder"
- settings:userRestriction="no_config_date_time"
- settings:controller="com.android.settings.datetime.AutoTimeZonePreferenceController" />
-
- <com.android.settingslib.widget.BannerMessagePreference
- android:key="location_time_zone_detection_status"
- android:title="@string/location_time_zone_detection_status_title"
- settings:controller="com.android.settings.datetime.LocationProviderStatusPreferenceController"/>
-
- <!-- This preference gets removed if location-based time zone detection is not supported -->
- <SwitchPreferenceCompat
- android:key="location_time_zone_detection"
- android:title="@string/location_time_zone_detection_toggle_title"
- android:summary="@string/summary_placeholder"
- settings:controller="com.android.settings.datetime.LocationTimeZoneDetectionPreferenceController"/>
-
- <com.android.settingslib.RestrictedPreference
- android:key="timezone"
- android:title="@string/date_time_set_timezone_title"
- android:summary="@string/summary_placeholder"
- android:fragment="com.android.settings.datetime.timezone.TimeZoneSettings"
- settings:userRestriction="no_config_date_time"
- settings:keywords="@string/keywords_time_zone"
- settings:controller="com.android.settings.datetime.TimeZonePreferenceController" />
-
- </PreferenceCategory>
-
- <!-- An optional preference category for feedback. Only displayed up if enabled via flags and config. -->
- <PreferenceCategory
- android:key="time_feedback_preference_category"
- android:title="@string/time_feedback_category_title"
- settings:keywords="@string/keywords_time_feedback_category"
- settings:controller="com.android.settings.datetime.TimeFeedbackPreferenceCategoryController">
-
- <Preference
- android:key="time_feedback"
- android:title="@string/time_feedback_title"
- settings:keywords="@string/keywords_time_feedback"
- settings:controller="com.android.settings.datetime.TimeFeedbackPreferenceController" />
-
- </PreferenceCategory>
-
- <PreferenceCategory
- android:key="time_format_preference_category"
- android:title="@string/time_format_category_title"
- settings:keywords="@string/keywords_time_format">
-
- <SwitchPreferenceCompat
- android:key="24 hour"
- android:title="@string/date_time_24hour"
- settings:controller="com.android.settings.datetime.TimeFormatPreferenceController" />
- </PreferenceCategory>
-
-</PreferenceScreen>
diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml
index 25bc062..2849238 100644
--- a/res/xml/development_settings.xml
+++ b/res/xml/development_settings.xml
@@ -569,6 +569,13 @@
android:entries="@array/overlay_display_devices_entries"
android:entryValues="@array/overlay_display_devices_values" />
+ <ListPreference
+ android:entries="@array/shade_display_awareness_entries"
+ android:entryValues="@array/shade_display_awareness_values"
+ android:key="shade_display_awareness"
+ android:summary="@string/summary_placeholder"
+ android:title="@string/shade_display_awareness_title" />
+
<com.android.settings.display.DensityPreference
android:key="density"
android:title="@string/developer_smallest_width" />
diff --git a/res/xml/language_and_region_settings.xml b/res/xml/language_and_region_settings.xml
new file mode 100644
index 0000000..5626f22
--- /dev/null
+++ b/res/xml/language_and_region_settings.xml
@@ -0,0 +1,127 @@
+<?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:title="@string/language_and_region_settings"
+ android:key="language_and_region_settings">
+ <PreferenceCategory
+ android:key="languages_category"
+ android:title="@string/locale_picker_category_title">
+ <Preference
+ android:key="phone_language"
+ android:title="@string/system_language"
+ android:fragment="com.android.settings.localepicker.LocaleListEditor"
+ settings:controller="com.android.settings.language.PhoneLanguagePreferenceController" />
+ </PreferenceCategory>
+
+ <PreferenceCategory
+ android:key="more_language_settings_category"
+ android:title="@string/more_language_settings_category"
+ settings:controller="com.android.settings.language.MoreLanguagesSettingsCategoryController">
+ <Preference
+ android:key="apps_language_in_more_language_settings"
+ android:title="@string/app_locales_picker_menu_title"
+ android:summary="@string/app_locale_picker_summary"
+ android:fragment="com.android.settings.applications.manageapplications.ManageApplications"
+ settings:controller="com.android.settings.applications.appinfo.NewAppsLocalePreferenceController">
+ <extra
+ android:name="classname"
+ android:value="com.android.settings.applications.appinfo.AppLocaleDetails" />
+ </Preference>
+ </PreferenceCategory>
+
+ <PreferenceCategory
+ android:key="regional_preferences_category"
+ android:title="@string/regional_preferences_category_title"
+ settings:controller="com.android.settings.regionalpreferences.RegionalPreferencesCategoryController">
+
+ <Preference
+ android:key="temperature_preference"
+ android:title="@string/temperature_preferences_title"
+ android:summary="@string/default_string_of_regional_preference"
+ settings:controller="com.android.settings.regionalpreferences.NewTemperatureUnitController"
+ settings:fragment="com.android.settings.regionalpreferences.TemperatureUnitFragment">
+ <extra
+ android:name="arg_key_regional_preference"
+ android:value="mu"/>
+ </Preference>
+
+ <Preference
+ android:key="key_measurement_system"
+ android:title="@string/measurement_system_preferences_title"
+ android:summary="@string/default_string_of_regional_preference"
+ settings:controller="com.android.settings.regionalpreferences.MeasurementSystemController"
+ settings:fragment="com.android.settings.regionalpreferences.MeasurementSystemItemFragment">
+ <extra
+ android:name="arg_key_regional_preference"
+ android:value="ms"/>
+ </Preference>
+
+ <Preference
+ android:key="first_day_of_week_preference"
+ android:title="@string/first_day_of_week_preferences_title"
+ android:summary="@string/default_string_of_regional_preference"
+ settings:controller="com.android.settings.regionalpreferences.NewFirstDayOfWeekController"
+ settings:fragment="com.android.settings.regionalpreferences.FirstDayOfWeekItemFragment">
+ <extra
+ android:name="arg_key_regional_preference"
+ android:value="fw"/>
+ </Preference>
+
+ <Preference
+ android:key="numbering_system_preference"
+ android:title="@string/numbers_preferences_title"
+ android:summary="@string/default_string_of_regional_preference"
+ settings:controller="com.android.settings.regionalpreferences.NewNumberingSystemController"
+ settings:fragment="com.android.settings.regionalpreferences.NumberingPreferencesFragment">
+ <extra
+ android:name="arg_key_regional_preference"
+ android:value="arg_value_language_select"/>
+ </Preference>
+ </PreferenceCategory>
+
+ <PreferenceCategory
+ android:key="speech_category"
+ android:title="@string/speech_category_title">
+ <com.android.settings.widget.GearPreference
+ android:key="voice_input_settings"
+ android:title="@string/voice_input_settings_title"
+ android:fragment="com.android.settings.language.DefaultVoiceInputPicker" />
+
+ <Preference
+ android:key="on_device_recognition_settings"
+ android:title="@string/on_device_recognition_settings_title"
+ android:summary="@string/on_device_recognition_settings_summary"
+ settings:controller=
+ "com.android.settings.language.OnDeviceRecognitionPreferenceController" />
+
+ <Preference
+ android:key="tts_settings_summary"
+ android:title="@string/tts_settings_title"
+ android:fragment="com.android.settings.tts.TextToSpeechSettings"
+ settings:searchable="false"/>
+ </PreferenceCategory>
+
+ <com.android.settingslib.widget.FooterPreference
+ android:key="new_regional_pref_footer"
+ android:title="@string/title_regional_pref_footer"
+ android:selectable="false"
+ settings:searchable="false"
+ settings:controller="com.android.settings.regionalpreferences.NewRegionalFooterPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/language_settings.xml b/res/xml/language_settings.xml
index f9f423e..eccbbc1 100644
--- a/res/xml/language_settings.xml
+++ b/res/xml/language_settings.xml
@@ -50,61 +50,6 @@
</PreferenceCategory>
<PreferenceCategory
- android:key="more_language_settings_category"
- android:title="@string/more_language_settings_category"
- settings:controller="com.android.settings.language.MoreLanguagesSettingsCategoryController">
- <Preference
- android:key="apps_language_in_more_language_settings"
- android:title="@string/app_locales_picker_menu_title"
- android:summary="@string/app_locale_picker_summary"
- android:fragment="com.android.settings.applications.manageapplications.ManageApplications"
- settings:controller="com.android.settings.applications.appinfo.NewAppsLocalePreferenceController">
- <extra
- android:name="classname"
- android:value="com.android.settings.applications.appinfo.AppLocaleDetails" />
- </Preference>
- </PreferenceCategory>
-
- <PreferenceCategory
- android:key="regional_preferences_category"
- android:title="@string/regional_preferences_category_title"
- settings:controller="com.android.settings.regionalpreferences.RegionalPreferencesCategoryController">
-
- <Preference
- android:key="temperature_preference"
- android:title="@string/temperature_preferences_title"
- android:summary="@string/default_string_of_regional_preference"
- settings:controller="com.android.settings.regionalpreferences.NewTemperatureUnitController"
- settings:fragment="com.android.settings.regionalpreferences.TemperatureUnitFragment">
- <extra
- android:name="arg_key_regional_preference"
- android:value="mu"/>
- </Preference>
-
- <Preference
- android:key="first_day_of_week_preference"
- android:title="@string/first_day_of_week_preferences_title"
- android:summary="@string/default_string_of_regional_preference"
- settings:controller="com.android.settings.regionalpreferences.NewFirstDayOfWeekController"
- settings:fragment="com.android.settings.regionalpreferences.FirstDayOfWeekItemFragment">
- <extra
- android:name="arg_key_regional_preference"
- android:value="fw"/>
- </Preference>
-
- <Preference
- android:key="numbering_system_preference"
- android:title="@string/numbers_preferences_title"
- android:summary="@string/default_string_of_regional_preference"
- settings:controller="com.android.settings.regionalpreferences.NewNumberingSystemController"
- settings:fragment="com.android.settings.regionalpreferences.NumberingPreferencesFragment">
- <extra
- android:name="arg_key_regional_preference"
- android:value="arg_value_language_select"/>
- </Preference>
- </PreferenceCategory>
-
- <PreferenceCategory
android:key="speech_category"
android:title="@string/speech_category_title">
<com.android.settings.widget.GearPreference
@@ -125,11 +70,4 @@
android:fragment="com.android.settings.tts.TextToSpeechSettings"
settings:searchable="false"/>
</PreferenceCategory>
-
- <com.android.settingslib.widget.FooterPreference
- android:key="new_regional_pref_footer"
- android:title="@string/title_regional_pref_footer"
- android:selectable="false"
- settings:searchable="false"
- settings:controller="com.android.settings.regionalpreferences.NewRegionalFooterPreferenceController"/>
</PreferenceScreen>
diff --git a/res/xml/mobile_network_settings.xml b/res/xml/mobile_network_settings.xml
index a1a2276..8c948ff 100644
--- a/res/xml/mobile_network_settings.xml
+++ b/res/xml/mobile_network_settings.xml
@@ -18,7 +18,7 @@
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:key="mobile_network_pref_screen">
- <com.android.settings.spa.preference.ComposeMainSwitchPreference
+ <com.android.settings.spa.preference.ComposeGroupSectionPreference
android:key="use_sim_switch"
settings:controller="com.android.settings.network.telephony.MobileNetworkSwitchController"/>
diff --git a/res/xml/regional_preferences_measurement_system.xml b/res/xml/regional_preferences_measurement_system.xml
new file mode 100644
index 0000000..73ac03d
--- /dev/null
+++ b/res/xml/regional_preferences_measurement_system.xml
@@ -0,0 +1,34 @@
+<?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:title="@string/measurement_system_preferences_title"
+ android:key="regional_preference_measurement_system">
+
+ <com.android.settingslib.widget.TopIntroPreference
+ android:key="measurement_system_intro"
+ android:title="@string/regional_preferences_option_page_sub_title"
+ android:persistent="false" />
+
+ <PreferenceCategory
+ android:key="measurement_system_item_category"
+ android:title="@string/summary_placeholder"
+ android:layout="@layout/preference_category_no_label"
+ settings:controller="com.android.settings.regionalpreferences.MeasurementSystemItemCategoryController"/>
+
+</PreferenceScreen>
diff --git a/res/xml/system_dashboard_fragment.xml b/res/xml/system_dashboard_fragment.xml
index 83cdf64..a9d7444 100644
--- a/res/xml/system_dashboard_fragment.xml
+++ b/res/xml/system_dashboard_fragment.xml
@@ -30,6 +30,15 @@
settings:controller="com.android.settings.language.LanguagePreferenceController"/>
<Preference
+ android:key="language_and_region_settings"
+ android:title="@string/language_and_region_settings"
+ android:summary="@string/languages_setting_summary"
+ android:icon="@drawable/ic_settings_languages"
+ android:order="-260"
+ android:fragment="com.android.settings.language.LanguageAndRegionSettings"
+ settings:controller="com.android.settings.language.LanguageAndRegionPreferenceController"/>
+
+ <Preference
android:key="Keyboard_settings"
android:title="@string/keyboard_settings"
android:icon="@drawable/ic_settings_keyboards"
@@ -47,6 +56,24 @@
settings:controller="com.android.settings.inputmethod.TouchpadAndMouseSettingsController"/>
<Preference
+ android:key="touchpad_settings"
+ android:title="@string/trackpad_settings"
+ android:summary="@string/trackpad_settings_summary"
+ android:icon="@drawable/ic_settings_trackpad"
+ android:order="-253"
+ android:fragment="com.android.settings.inputmethod.TouchpadSettingFragment"
+ settings:controller="com.android.settings.inputmethod.TouchpadSettingsController"/>
+
+ <Preference
+ android:key="mouse_settings"
+ android:title="@string/mouse_settings"
+ android:summary="@string/mouse_settings_summary"
+ android:icon="@drawable/ic_settings_mouse"
+ android:order="-252"
+ android:fragment="com.android.settings.inputmethod.MouseSettingFragment"
+ settings:controller="com.android.settings.inputmethod.MouseSettingsController"/>
+
+ <Preference
android:key="gesture_settings"
android:title="@string/gesture_preference_title"
android:icon="@drawable/ic_settings_gestures"
diff --git a/src/com/android/settings/SettingsService.kt b/src/com/android/settings/SettingsService.kt
index 70713c3..1783a7d 100644
--- a/src/com/android/settings/SettingsService.kt
+++ b/src/com/android/settings/SettingsService.kt
@@ -41,7 +41,7 @@
override fun hasPermission(
application: Application,
- myUid: Int,
+ callingPid: Int,
callingUid: Int,
request: PreferenceSetterRequest,
) = true
@@ -52,7 +52,7 @@
override fun hasPermission(
application: Application,
- myUid: Int,
+ callingPid: Int,
callingUid: Int,
request: PreferenceGetterRequest,
) = true
diff --git a/src/com/android/settings/accessibility/AccessibilityHearingAidsFragment.java b/src/com/android/settings/accessibility/AccessibilityHearingAidsFragment.java
index 09e2d97..3bda864 100644
--- a/src/com/android/settings/accessibility/AccessibilityHearingAidsFragment.java
+++ b/src/com/android/settings/accessibility/AccessibilityHearingAidsFragment.java
@@ -97,17 +97,6 @@
}
@Override
- protected ComponentName getTileComponentName() {
- return AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_TILE_COMPONENT_NAME;
- }
-
- @Override
- protected CharSequence getTileTooltipContent(int type) {
- // No tooltip to be shown
- return null;
- }
-
- @Override
protected boolean showGeneralCategory() {
// Have static preference under dynamically created PreferenceCategory KEY_GENERAL_CATEGORY.
// In order to modify that, we need to use our own PreferenceCategory for this page.
diff --git a/src/com/android/settings/accessibility/AccessibilitySeekBarPreference.kt b/src/com/android/settings/accessibility/AccessibilitySeekBarPreference.kt
new file mode 100644
index 0000000..b98c0e2
--- /dev/null
+++ b/src/com/android/settings/accessibility/AccessibilitySeekBarPreference.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.accessibility
+
+import android.content.Context
+import android.os.Bundle
+import android.os.Parcelable
+import android.util.AttributeSet
+import com.android.settings.widget.LabeledSeekBarPreference
+
+/**
+ * Add a custom AccessibilitySeekBarPreference with tool tip window for font size and display size.
+ */
+open class AccessibilitySeekBarPreference(context: Context, attrs: AttributeSet?) :
+ LabeledSeekBarPreference(context, attrs) {
+
+ var needsQSTooltipReshow = false
+ private var tooltipWindow: AccessibilityQuickSettingsTooltipWindow? = null
+
+ override fun onSaveInstanceState(): Parcelable {
+ val state = Bundle()
+ state.putParcelable(null, super.onSaveInstanceState())
+ if (needsQSTooltipReshow || tooltipWindow?.isShowing == true) {
+ state.putBoolean(KEY_SAVED_QS_TOOLTIP_RESHOW, /* value= */ true)
+ }
+ return state
+ }
+
+ override fun onRestoreInstanceState(state: Parcelable?) {
+ val bundle = state as Bundle
+ super.onRestoreInstanceState(bundle.getParcelable(null, Parcelable::class.java))
+ if (bundle.containsKey(KEY_SAVED_QS_TOOLTIP_RESHOW)) {
+ needsQSTooltipReshow = bundle.getBoolean(KEY_SAVED_QS_TOOLTIP_RESHOW)
+ }
+ }
+
+ /** To generate a tooltip window and return it. */
+ fun createTooltipWindow(): AccessibilityQuickSettingsTooltipWindow =
+ AccessibilityQuickSettingsTooltipWindow(context).also { tooltipWindow = it }
+
+ /** To dismiss the tooltip window. */
+ fun dismissTooltip() {
+ val tooltip = tooltipWindow
+ if (tooltip?.isShowing == true) {
+ tooltip.dismiss()
+ tooltipWindow = null
+ }
+ }
+
+ companion object {
+ private const val KEY_SAVED_QS_TOOLTIP_RESHOW = "qs_tooltip_reshow"
+ }
+}
diff --git a/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragment.java b/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragment.java
index 23c3fd4..2a22902 100644
--- a/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragment.java
+++ b/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragment.java
@@ -20,10 +20,8 @@
import static com.android.settings.accessibility.AccessibilityDialogUtils.DialogEnums;
import static com.android.settings.accessibility.AccessibilityUtil.getShortcutSummaryList;
import static com.android.settings.accessibility.ToggleFeaturePreferenceFragment.KEY_GENERAL_CATEGORY;
-import static com.android.settings.accessibility.ToggleFeaturePreferenceFragment.KEY_SAVED_QS_TOOLTIP_TYPE;
import android.annotation.SuppressLint;
-import android.app.Activity;
import android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
@@ -44,7 +42,6 @@
import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.settings.R;
-import com.android.settings.accessibility.AccessibilityUtil.QuickSettingsTooltipType;
import com.android.settings.accessibility.shortcuts.EditShortcutsPreferenceFragment;
import com.android.settings.dashboard.RestrictedDashboardFragment;
@@ -60,16 +57,12 @@
public abstract class AccessibilityShortcutPreferenceFragment extends RestrictedDashboardFragment
implements ShortcutPreference.OnClickCallback {
private static final String KEY_SHORTCUT_PREFERENCE = "shortcut_preference";
- protected static final String KEY_SAVED_QS_TOOLTIP_RESHOW = "qs_tooltip_reshow";
protected ShortcutPreference mShortcutPreference;
protected Dialog mDialog;
private AccessibilityManager.TouchExplorationStateChangeListener
mTouchExplorationStateChangeListener;
private AccessibilitySettingsContentObserver mSettingsContentObserver;
- private AccessibilityQuickSettingsTooltipWindow mTooltipWindow;
- private boolean mNeedsQSTooltipReshow = false;
- private int mNeedsQSTooltipType = QuickSettingsTooltipType.GUIDE_TO_EDIT;
public AccessibilityShortcutPreferenceFragment(String restrictionKey) {
super(restrictionKey);
@@ -81,26 +74,10 @@
/** Returns the accessibility feature name. */
protected abstract CharSequence getLabelName();
- /** Returns the accessibility tile component name. */
- protected abstract ComponentName getTileComponentName();
-
- /** Returns the accessibility tile tooltip content. */
- protected abstract CharSequence getTileTooltipContent(@QuickSettingsTooltipType int type);
-
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- // Restore the user shortcut type and tooltip.
- if (savedInstanceState != null) {
- if (savedInstanceState.containsKey(KEY_SAVED_QS_TOOLTIP_RESHOW)) {
- mNeedsQSTooltipReshow = savedInstanceState.getBoolean(KEY_SAVED_QS_TOOLTIP_RESHOW);
- }
- if (savedInstanceState.containsKey(KEY_SAVED_QS_TOOLTIP_TYPE)) {
- mNeedsQSTooltipType = savedInstanceState.getInt(KEY_SAVED_QS_TOOLTIP_TYPE);
- }
- }
-
final int resId = getPreferenceScreenResId();
if (resId <= 0) {
final PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(
@@ -142,21 +119,6 @@
}
@Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
-
- // Reshow tooltip when activity recreate, such as rotate device.
- if (mNeedsQSTooltipReshow) {
- view.post(() -> {
- final Activity activity = getActivity();
- if (activity != null && !activity.isFinishing()) {
- showQuickSettingsTooltipIfNeeded();
- }
- });
- }
- }
-
- @Override
public void onResume() {
super.onResume();
@@ -178,16 +140,6 @@
}
@Override
- public void onSaveInstanceState(Bundle outState) {
- final boolean isTooltipWindowShowing = mTooltipWindow != null && mTooltipWindow.isShowing();
- if (mNeedsQSTooltipReshow || isTooltipWindowShowing) {
- outState.putBoolean(KEY_SAVED_QS_TOOLTIP_RESHOW, /* value= */ true);
- outState.putInt(KEY_SAVED_QS_TOOLTIP_TYPE, mNeedsQSTooltipType);
- }
- super.onSaveInstanceState(outState);
- }
-
- @Override
public Dialog onCreateDialog(int dialogId) {
switch (dialogId) {
case DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL:
@@ -289,7 +241,6 @@
*/
private void callOnTutorialDialogButtonClicked(DialogInterface dialog, int which) {
dialog.dismiss();
- showQuickSettingsTooltipIfNeeded();
}
@VisibleForTesting
@@ -363,26 +314,6 @@
}
/**
- * Shows the quick settings tooltip if the quick settings feature is assigned. The tooltip only
- * shows once.
- *
- * @param type The quick settings tooltip type
- */
- protected void showQuickSettingsTooltipIfNeeded(@QuickSettingsTooltipType int type) {
- mNeedsQSTooltipType = type;
- showQuickSettingsTooltipIfNeeded();
- }
-
- /**
- * @deprecated made obsolete by quick settings rollout.
- *
- * (TODO 367414968: finish removal.)
- */
- @Deprecated
- private void showQuickSettingsTooltipIfNeeded() {
- }
-
- /**
* Returns the user preferred shortcut types or the default shortcut types if not set
*/
@ShortcutConstants.UserShortcutType
diff --git a/src/com/android/settings/accessibility/HighContrastTextMigrationReceiver.java b/src/com/android/settings/accessibility/HighContrastTextMigrationReceiver.java
new file mode 100644
index 0000000..ee3537b
--- /dev/null
+++ b/src/com/android/settings/accessibility/HighContrastTextMigrationReceiver.java
@@ -0,0 +1,158 @@
+/*
+ * 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.android.settings.SettingsActivity.EXTRA_FRAGMENT_ARG_KEY;
+import static com.android.settings.SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS;
+import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
+import static com.android.settings.accessibility.AccessibilityUtil.State.ON;
+
+import android.annotation.IntDef;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.graphics.hwui.flags.Flags;
+import com.android.settings.R;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Handling smooth migration to the new high contrast text appearance
+ */
+public class HighContrastTextMigrationReceiver extends BroadcastReceiver {
+ private static final String TAG = HighContrastTextMigrationReceiver.class.getSimpleName();
+ @VisibleForTesting
+ static final String NOTIFICATION_CHANNEL = "high_contrast_text_notification_channel";
+ @VisibleForTesting
+ static final String ACTION_RESTORED =
+ "com.android.settings.accessibility.ACTION_HIGH_CONTRAST_TEXT_RESTORED";
+ @VisibleForTesting
+ static final int NOTIFICATION_ID = 1;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ PromptState.UNKNOWN,
+ PromptState.PROMPT_SHOWN,
+ PromptState.PROMPT_UNNECESSARY,
+ })
+ public @interface PromptState {
+ int UNKNOWN = 0;
+ int PROMPT_SHOWN = 1;
+ int PROMPT_UNNECESSARY = 2;
+ }
+
+ @Override
+ public void onReceive(@NonNull Context context, @NonNull Intent intent) {
+ if (!Flags.highContrastTextSmallTextRect()) {
+ return;
+ }
+
+ if (ACTION_RESTORED.equals(intent.getAction())) {
+ Log.i(TAG, "HCT attempted to be restored from backup; showing notification for userId: "
+ + context.getUserId());
+ Settings.Secure.putInt(context.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_HCT_RECT_PROMPT_STATUS,
+ PromptState.PROMPT_SHOWN);
+ showNotification(context);
+ } else if (Intent.ACTION_PRE_BOOT_COMPLETED.equals(intent.getAction())) {
+ final boolean hasSeenPromptIfNecessary = Settings.Secure.getInt(
+ context.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_HCT_RECT_PROMPT_STATUS, PromptState.UNKNOWN)
+ != PromptState.UNKNOWN;
+ if (hasSeenPromptIfNecessary) {
+ Log.i(TAG, "Has seen HCT prompt if necessary; skip HCT migration for userId: "
+ + context.getUserId());
+ return;
+ }
+
+ final boolean isHctEnabled = Settings.Secure.getInt(context.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED, OFF) == ON;
+ if (isHctEnabled) {
+ Log.i(TAG, "HCT enabled before OTA update; performing migration for userId: "
+ + context.getUserId());
+ Settings.Secure.putInt(context.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED,
+ OFF);
+ Settings.Secure.putInt(context.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_HCT_RECT_PROMPT_STATUS,
+ PromptState.PROMPT_SHOWN);
+ showNotification(context);
+ } else {
+ Log.i(TAG,
+ "HCT was not enabled before OTA update; not performing migration for "
+ + "userId: " + context.getUserId());
+ Settings.Secure.putInt(context.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_HCT_RECT_PROMPT_STATUS,
+ PromptState.PROMPT_UNNECESSARY);
+ }
+ }
+ }
+
+ private void showNotification(Context context) {
+ Notification.Builder notificationBuilder = new Notification.Builder(context,
+ NOTIFICATION_CHANNEL)
+ .setSmallIcon(R.drawable.ic_settings_24dp)
+ .setContentTitle(context.getString(
+ R.string.accessibility_toggle_high_text_contrast_preference_title))
+ .setContentText(context.getString(
+ R.string.accessibility_notification_high_contrast_text_content))
+ .setAutoCancel(true);
+
+ Intent settingsIntent = new Intent(Settings.ACTION_TEXT_READING_SETTINGS);
+ settingsIntent.setPackage(context.getPackageName());
+ if (settingsIntent.resolveActivity(context.getPackageManager()) != null) {
+ Bundle fragmentArgs = new Bundle();
+ fragmentArgs.putString(EXTRA_FRAGMENT_ARG_KEY,
+ TextReadingPreferenceFragment.HIGH_TEXT_CONTRAST_KEY);
+ settingsIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, fragmentArgs);
+ PendingIntent settingsPendingIntent = PendingIntent.getActivity(context,
+ /* requestCode = */ 0, settingsIntent, PendingIntent.FLAG_IMMUTABLE);
+
+ Notification.Action settingsAction = new Notification.Action.Builder(
+ /* icon= */ null,
+ context.getString(
+ R.string.accessibility_notification_high_contrast_text_action),
+ settingsPendingIntent
+ ).build();
+
+ notificationBuilder.addAction(settingsAction);
+ }
+
+ NotificationManager notificationManager =
+ context.getSystemService(NotificationManager.class);
+ NotificationChannel notificationChannel = new NotificationChannel(
+ NOTIFICATION_CHANNEL,
+ context.getString(
+ R.string.accessibility_toggle_high_text_contrast_preference_title),
+ NotificationManager.IMPORTANCE_LOW);
+ notificationManager.createNotificationChannel(notificationChannel);
+ notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
+ }
+}
diff --git a/src/com/android/settings/accessibility/HighTextContrastPreferenceController.java b/src/com/android/settings/accessibility/HighTextContrastPreferenceController.java
index 7a3f4f6..c28af91 100644
--- a/src/com/android/settings/accessibility/HighTextContrastPreferenceController.java
+++ b/src/com/android/settings/accessibility/HighTextContrastPreferenceController.java
@@ -22,6 +22,7 @@
import androidx.preference.PreferenceScreen;
import androidx.preference.TwoStatePreference;
+import com.android.graphics.hwui.flags.Flags;
import com.android.settings.R;
import com.android.settings.accessibility.TextReadingPreferenceFragment.EntryPoint;
import com.android.settings.core.TogglePreferenceController;
@@ -60,6 +61,20 @@
isChecked ? 1 : 0,
AccessibilityStatsLogUtils.convertToEntryPoint(mEntryPoint));
+ if (Flags.highContrastTextSmallTextRect()) {
+ // Set PROMPT_UNNECESSARY when the user modifies the HighContrastText setting
+ // This is needed for the following scenario:
+ // On Android 16, create secondary user, ACTION_PRE_BOOT_COMPLETED won't be sent to
+ // the secondary user. The user enables HCT.
+ // When updating OS to Android 17, ACTION_PRE_BOOT_COMPLETED will be sent to the
+ // secondary user when switch to the secondary user.
+ // If the prompt status is not updated in Android 16, we would automatically disable
+ // HCT and show the HCT prompt, which is an undesired behavior.
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_HCT_RECT_PROMPT_STATUS,
+ HighContrastTextMigrationReceiver.PromptState.PROMPT_UNNECESSARY);
+ }
+
return Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED, (isChecked ? 1 : 0));
}
diff --git a/src/com/android/settings/accessibility/LaunchAccessibilityActivityPreferenceFragment.java b/src/com/android/settings/accessibility/LaunchAccessibilityActivityPreferenceFragment.java
index e49078b..f729cc7 100644
--- a/src/com/android/settings/accessibility/LaunchAccessibilityActivityPreferenceFragment.java
+++ b/src/com/android/settings/accessibility/LaunchAccessibilityActivityPreferenceFragment.java
@@ -72,11 +72,6 @@
}
@Override
- protected void onPreferenceToggled(String preferenceKey, boolean enabled) {
- // Do nothing.
- }
-
- @Override
protected void onProcessArguments(Bundle arguments) {
super.onProcessArguments(arguments);
mComponentName = arguments.getParcelable(AccessibilitySettings.EXTRA_COMPONENT_NAME);
diff --git a/src/com/android/settings/accessibility/PreviewSizeSeekBarController.java b/src/com/android/settings/accessibility/PreviewSizeSeekBarController.java
index 7f10af7..3c62231 100644
--- a/src/com/android/settings/accessibility/PreviewSizeSeekBarController.java
+++ b/src/com/android/settings/accessibility/PreviewSizeSeekBarController.java
@@ -19,7 +19,6 @@
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
-import android.os.Bundle;
import android.os.Handler;
import android.widget.SeekBar;
@@ -28,31 +27,27 @@
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
-import com.android.settings.widget.LabeledSeekBarPreference;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
-import com.android.settingslib.core.lifecycle.events.OnCreate;
import com.android.settingslib.core.lifecycle.events.OnDestroy;
-import com.android.settingslib.core.lifecycle.events.OnSaveInstanceState;
+import com.android.settingslib.core.lifecycle.events.OnStart;
+import com.android.settingslib.core.lifecycle.events.OnStop;
import com.google.android.setupcompat.util.WizardManagerHelper;
import java.util.Optional;
/**
- * The controller of {@link LabeledSeekBarPreference} that listens to display size and font size
- * settings changes and updates preview size threshold smoothly.
+ * The controller of {@link AccessibilitySeekBarPreference} that listens to display size and font
+ * size settings changes and updates preview size threshold smoothly.
*/
abstract class PreviewSizeSeekBarController extends BasePreferenceController implements
- TextReadingResetController.ResetStateListener, LifecycleObserver, OnCreate,
- OnDestroy, OnSaveInstanceState {
+ TextReadingResetController.ResetStateListener, LifecycleObserver, OnStart, OnStop,
+ OnDestroy {
private final PreviewSizeData<? extends Number> mSizeData;
- private static final String KEY_SAVED_QS_TOOLTIP_RESHOW = "qs_tooltip_reshow";
private boolean mSeekByTouch;
private Optional<ProgressInteractionListener> mInteractionListener = Optional.empty();
- private LabeledSeekBarPreference mSeekBarPreference;
+ private AccessibilitySeekBarPreference mSeekBarPreference;
private int mLastProgress;
- private boolean mNeedsQSTooltipReshow = false;
- private AccessibilityQuickSettingsTooltipWindow mTooltipWindow;
private final Handler mHandler;
private String[] mStateLabels = null;
@@ -101,30 +96,21 @@
}
@Override
- public void onCreate(Bundle savedInstanceState) {
- // Restore the tooltip.
- if (savedInstanceState != null
- && savedInstanceState.containsKey(KEY_SAVED_QS_TOOLTIP_RESHOW)) {
- mNeedsQSTooltipReshow = savedInstanceState.getBoolean(KEY_SAVED_QS_TOOLTIP_RESHOW);
+ public void onStart() {
+ if (mSeekBarPreference.getNeedsQSTooltipReshow()) {
+ mHandler.post(this::showQuickSettingsTooltipIfNeeded);
}
}
@Override
+ public void onStop() {
+ // all the messages/callbacks will be removed.
+ mHandler.removeCallbacksAndMessages(null);
+ }
+
+ @Override
public void onDestroy() {
- // remove runnables in the queue.
- mHandler.removeCallbacksAndMessages(null);
- final boolean isTooltipWindowShowing = mTooltipWindow != null && mTooltipWindow.isShowing();
- if (isTooltipWindowShowing) {
- mTooltipWindow.dismiss();
- }
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- final boolean isTooltipWindowShowing = mTooltipWindow != null && mTooltipWindow.isShowing();
- if (mNeedsQSTooltipReshow || isTooltipWindowShowing) {
- outState.putBoolean(KEY_SAVED_QS_TOOLTIP_RESHOW, /* value= */ true);
- }
+ mSeekBarPreference.dismissTooltip();
}
void setInteractionListener(ProgressInteractionListener interactionListener) {
@@ -148,9 +134,6 @@
mSeekBarPreference.setProgress(initialIndex);
mSeekBarPreference.setContinuousUpdates(true);
mSeekBarPreference.setOnSeekBarChangeListener(mSeekBarChangeListener);
- if (mNeedsQSTooltipReshow) {
- mHandler.post(this::showQuickSettingsTooltipIfNeeded);
- }
setSeekbarStateDescription(mSeekBarPreference.getProgress());
}
@@ -216,7 +199,8 @@
return;
}
- if (!mNeedsQSTooltipReshow && AccessibilityQuickSettingUtils.hasValueInSharedPreferences(
+ if (!mSeekBarPreference.getNeedsQSTooltipReshow()
+ && AccessibilityQuickSettingUtils.hasValueInSharedPreferences(
mContext, tileComponentName)) {
// Returns if quick settings tooltip only show once.
return;
@@ -228,14 +212,15 @@
// is not ready when we would like to show the tooltip. If the seekbar is not ready,
// we give up showing the tooltip and also do not reshow it in the future.
if (mSeekBarPreference.getSeekbar() != null) {
- mTooltipWindow = new AccessibilityQuickSettingsTooltipWindow(mContext);
- mTooltipWindow.setup(getTileTooltipContent(),
+ final AccessibilityQuickSettingsTooltipWindow tooltipWindow =
+ mSeekBarPreference.createTooltipWindow();
+ tooltipWindow.setup(getTileTooltipContent(),
R.drawable.accessibility_auto_added_qs_tooltip_illustration);
- mTooltipWindow.showAtTopCenter(mSeekBarPreference.getSeekbar());
+ tooltipWindow.showAtTopCenter(mSeekBarPreference.getSeekbar());
}
AccessibilityQuickSettingUtils.optInValueToSharedPreferences(mContext,
tileComponentName);
- mNeedsQSTooltipReshow = false;
+ mSeekBarPreference.setNeedsQSTooltipReshow(false);
}
/** Returns the accessibility Quick Settings tile component name. */
diff --git a/src/com/android/settings/accessibility/RemoveAnimationsPreference.kt b/src/com/android/settings/accessibility/RemoveAnimationsPreference.kt
index 65519e9..ddf2590 100644
--- a/src/com/android/settings/accessibility/RemoveAnimationsPreference.kt
+++ b/src/com/android/settings/accessibility/RemoveAnimationsPreference.kt
@@ -65,11 +65,15 @@
override fun storage(context: Context): KeyValueStore = RemoveAnimationsStorage(context)
- override fun getReadPermit(context: Context, myUid: Int, callingUid: Int) =
+ override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) =
ReadWritePermit.ALLOW
- override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) =
- ReadWritePermit.ALLOW
+ override fun getWritePermit(
+ context: Context,
+ value: Boolean?,
+ callingPid: Int,
+ callingUid: Int,
+ ) = ReadWritePermit.ALLOW
override val sensitivityLevel
get() = SensitivityLevel.NO_SENSITIVITY
diff --git a/src/com/android/settings/accessibility/TextReadingPreviewController.java b/src/com/android/settings/accessibility/TextReadingPreviewController.java
index a983105..99f1f3f 100644
--- a/src/com/android/settings/accessibility/TextReadingPreviewController.java
+++ b/src/com/android/settings/accessibility/TextReadingPreviewController.java
@@ -33,7 +33,6 @@
import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.instrumentation.SettingsStatsLog;
import com.android.settings.display.PreviewPagerAdapter;
-import com.android.settings.widget.LabeledSeekBarPreference;
import java.util.Objects;
@@ -58,8 +57,8 @@
private int mLastDisplayProgress;
private long mLastCommitTime;
private TextReadingPreviewPreference mPreviewPreference;
- private LabeledSeekBarPreference mFontSizePreference;
- private LabeledSeekBarPreference mDisplaySizePreference;
+ private AccessibilitySeekBarPreference mFontSizePreference;
+ private AccessibilitySeekBarPreference mDisplaySizePreference;
@EntryPoint
private int mEntryPoint;
diff --git a/src/com/android/settings/accessibility/ToggleColorInversionPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleColorInversionPreferenceFragment.java
index e286225..b779f9d 100644
--- a/src/com/android/settings/accessibility/ToggleColorInversionPreferenceFragment.java
+++ b/src/com/android/settings/accessibility/ToggleColorInversionPreferenceFragment.java
@@ -107,10 +107,6 @@
if (enabled == isEnabled) {
return;
}
-
- if (enabled) {
- showQuickSettingsTooltipIfNeeded(QuickSettingsTooltipType.GUIDE_TO_DIRECT_USE);
- }
logAccessibilityServiceEnabled(mComponentName, enabled);
Settings.Secure.putInt(getContentResolver(), ENABLED, enabled ? ON : OFF);
}
diff --git a/src/com/android/settings/accessibility/ToggleDaltonizerPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleDaltonizerPreferenceFragment.java
index 5b2df5a..fe51e69 100644
--- a/src/com/android/settings/accessibility/ToggleDaltonizerPreferenceFragment.java
+++ b/src/com/android/settings/accessibility/ToggleDaltonizerPreferenceFragment.java
@@ -158,10 +158,6 @@
if (enabled == isEnabled) {
return;
}
-
- if (enabled) {
- showQuickSettingsTooltipIfNeeded(QuickSettingsTooltipType.GUIDE_TO_DIRECT_USE);
- }
logAccessibilityServiceEnabled(mComponentName, enabled);
Settings.Secure.putInt(getContentResolver(), ENABLED, enabled ? ON : OFF);
}
diff --git a/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java b/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java
index 65a1cd4..49f22bf 100644
--- a/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java
+++ b/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java
@@ -21,7 +21,6 @@
import static com.android.settings.accessibility.AccessibilityDialogUtils.DialogEnums;
import static com.android.settings.accessibility.AccessibilityUtil.getShortcutSummaryList;
-import android.app.Activity;
import android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
@@ -85,8 +84,6 @@
public static final String KEY_SHORTCUT_PREFERENCE = "shortcut_preference";
protected static final String KEY_TOP_INTRO_PREFERENCE = "top_intro";
protected static final String KEY_HTML_DESCRIPTION_PREFERENCE = "html_description";
- protected static final String KEY_SAVED_QS_TOOLTIP_RESHOW = "qs_tooltip_reshow";
- protected static final String KEY_SAVED_QS_TOOLTIP_TYPE = "qs_tooltip_type";
protected static final String KEY_ANIMATED_IMAGE = "animated_image";
// For html description of accessibility service, must follow the rule, such as
// <img src="R.drawable.fileName"/>, a11y settings will get the resources successfully.
@@ -112,10 +109,6 @@
private CharSequence mDescription;
private TouchExplorationStateChangeListener mTouchExplorationStateChangeListener;
private AccessibilitySettingsContentObserver mSettingsContentObserver;
-
- private AccessibilityQuickSettingsTooltipWindow mTooltipWindow;
- private boolean mNeedsQSTooltipReshow = false;
- private int mNeedsQSTooltipType = QuickSettingsTooltipType.GUIDE_TO_EDIT;
private ImageView mImageGetterCacheView;
protected final Html.ImageGetter mImageGetter = (String str) -> {
if (str != null && str.startsWith(IMG_PREFIX)) {
@@ -133,16 +126,6 @@
super.onCreate(savedInstanceState);
onProcessArguments(getArguments());
- // Restore the user shortcut type and tooltip.
- if (savedInstanceState != null) {
- if (savedInstanceState.containsKey(KEY_SAVED_QS_TOOLTIP_RESHOW)) {
- mNeedsQSTooltipReshow = savedInstanceState.getBoolean(KEY_SAVED_QS_TOOLTIP_RESHOW);
- }
- if (savedInstanceState.containsKey(KEY_SAVED_QS_TOOLTIP_TYPE)) {
- mNeedsQSTooltipType = savedInstanceState.getInt(KEY_SAVED_QS_TOOLTIP_TYPE);
- }
- }
-
final int resId = getPreferenceScreenResId();
if (resId <= 0) {
final PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(
@@ -227,16 +210,6 @@
final SettingsMainSwitchBar switchBar = settingsActivity.getSwitchBar();
switchBar.hide();
- // Reshow tooltip when activity recreate, such as rotate device.
- if (mNeedsQSTooltipReshow) {
- view.post(() -> {
- final Activity activity = getActivity();
- if (activity != null && !activity.isFinishing()) {
- showQuickSettingsTooltipIfNeeded();
- }
- });
- }
-
writeConfigDefaultAccessibilityServiceIntoShortcutTargetServiceIfNeeded(getContext());
}
@@ -262,23 +235,9 @@
}
@Override
- public void onSaveInstanceState(Bundle outState) {
- final boolean isTooltipWindowShowing = mTooltipWindow != null && mTooltipWindow.isShowing();
- if (mNeedsQSTooltipReshow || isTooltipWindowShowing) {
- outState.putBoolean(KEY_SAVED_QS_TOOLTIP_RESHOW, /* value= */ true);
- outState.putInt(KEY_SAVED_QS_TOOLTIP_TYPE, mNeedsQSTooltipType);
- }
- super.onSaveInstanceState(outState);
- }
-
- @Override
public void onDestroyView() {
super.onDestroyView();
removeActionBarToggleSwitch();
- final boolean isTooltipWindowShowing = mTooltipWindow != null && mTooltipWindow.isShowing();
- if (isTooltipWindowShowing) {
- mTooltipWindow.dismiss();
- }
}
@Override
@@ -314,7 +273,12 @@
/** Returns the accessibility tile component name. */
abstract ComponentName getTileComponentName();
- /** Returns the accessibility tile tooltip content. */
+ /** Returns the accessibility tile component name.
+ *
+ * @deprecated unused, as this class no longer displays tile tooltips.
+ *
+ * (TODO 367414968: finish removal.)*/
+ @Deprecated
abstract CharSequence getTileTooltipContent(@QuickSettingsTooltipType int type);
protected void updateToggleServiceTitle(SettingsMainSwitchPreference switchPreference) {
@@ -332,9 +296,6 @@
}
protected void onPreferenceToggled(String preferenceKey, boolean enabled) {
- if (enabled) {
- showQuickSettingsTooltipIfNeeded();
- }
}
protected void onInstallSwitchPreferenceToggleSwitch() {
@@ -646,7 +607,6 @@
*/
private void callOnTutorialDialogButtonClicked(DialogInterface dialog, int which) {
dialog.dismiss();
- showQuickSettingsTooltipIfNeeded();
}
protected void updateShortcutPreferenceData() {
@@ -737,26 +697,6 @@
}
}
- /**
- * Shows the quick settings tooltip if the quick settings feature is assigned. The tooltip only
- * shows once.
- *
- * @param type The quick settings tooltip type
- */
- protected void showQuickSettingsTooltipIfNeeded(@QuickSettingsTooltipType int type) {
- mNeedsQSTooltipType = type;
- showQuickSettingsTooltipIfNeeded();
- }
-
- /**
- * @deprecated made obsolete by quick settings rollout.
- *
- * (TODO 367414968: finish removal.)
- */
- @Deprecated
- private void showQuickSettingsTooltipIfNeeded() {
- }
-
/** Returns user visible name of the tile by given {@link ComponentName}. */
protected CharSequence loadTileLabel(Context context, ComponentName componentName) {
final PackageManager packageManager = context.getPackageManager();
diff --git a/src/com/android/settings/accessibility/ToggleReduceBrightColorsPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleReduceBrightColorsPreferenceFragment.java
index 16911f6..ff14021 100644
--- a/src/com/android/settings/accessibility/ToggleReduceBrightColorsPreferenceFragment.java
+++ b/src/com/android/settings/accessibility/ToggleReduceBrightColorsPreferenceFragment.java
@@ -149,9 +149,6 @@
@Override
protected void onPreferenceToggled(String preferenceKey, boolean enabled) {
- if (enabled) {
- showQuickSettingsTooltipIfNeeded(QuickSettingsTooltipType.GUIDE_TO_DIRECT_USE);
- }
logAccessibilityServiceEnabled(mComponentName, enabled);
mColorDisplayManager.setReduceBrightColorsActivated(enabled);
}
diff --git a/src/com/android/settings/accessibility/VibrationMainSwitchPreference.kt b/src/com/android/settings/accessibility/VibrationMainSwitchPreference.kt
index 70a0033..3265447 100644
--- a/src/com/android/settings/accessibility/VibrationMainSwitchPreference.kt
+++ b/src/com/android/settings/accessibility/VibrationMainSwitchPreference.kt
@@ -30,16 +30,16 @@
import com.android.settingslib.metadata.PreferenceLifecycleContext
import com.android.settingslib.metadata.PreferenceLifecycleProvider
import com.android.settingslib.metadata.ReadWritePermit
-import com.android.settingslib.preference.MainSwitchPreferenceBinding
-/**
- * Accessibility settings for vibration.
- */
+/** Accessibility settings for vibration. */
// LINT.IfChange
-class VibrationMainSwitchPreference : MainSwitchPreference(
- key = Settings.System.VIBRATE_ON,
- title = R.string.accessibility_vibration_primary_switch_title,
-), PreferenceLifecycleProvider, OnCheckedChangeListener {
+class VibrationMainSwitchPreference :
+ MainSwitchPreference(
+ key = Settings.System.VIBRATE_ON,
+ title = R.string.accessibility_vibration_primary_switch_title,
+ ),
+ PreferenceLifecycleProvider,
+ OnCheckedChangeListener {
override val keywords: Int
get() = R.string.keywords_accessibility_vibration_primary_switch
@@ -48,20 +48,26 @@
override fun storage(context: Context): KeyValueStore =
VibrationMainSwitchToggleStorage(SettingsSystemStore.get(context))
- override fun getReadPermit(context: Context, myUid: Int, callingUid: Int) =
+ override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) =
ReadWritePermit.ALLOW
- override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) =
- ReadWritePermit.ALLOW
+ override fun getWritePermit(
+ context: Context,
+ value: Boolean?,
+ callingPid: Int,
+ callingUid: Int,
+ ) = ReadWritePermit.ALLOW
override fun onResume(context: PreferenceLifecycleContext) {
vibrator = context.getSystemService(Vibrator::class.java)
- context.findPreference<com.android.settingslib.widget.MainSwitchPreference>(key)
+ context
+ .findPreference<com.android.settingslib.widget.MainSwitchPreference>(key)
?.addOnSwitchChangeListener(this)
}
override fun onPause(context: PreferenceLifecycleContext) {
- context.findPreference<com.android.settingslib.widget.MainSwitchPreference>(key)
+ context
+ .findPreference<com.android.settingslib.widget.MainSwitchPreference>(key)
?.removeOnSwitchChangeListener(this)
}
@@ -69,16 +75,16 @@
if (isChecked) {
// Play a haptic as preview for the main toggle only when touch feedback is enabled.
VibrationPreferenceConfig.playVibrationPreview(
- vibrator, VibrationAttributes.USAGE_TOUCH
+ vibrator,
+ VibrationAttributes.USAGE_TOUCH,
)
}
}
/** Provides SettingsStore for vibration main switch with custom default value. */
@Suppress("UNCHECKED_CAST")
- private class VibrationMainSwitchToggleStorage(
- private val settingsStore: SettingsStore,
- ) : KeyedObservableDelegate<String>(settingsStore), KeyValueStore {
+ private class VibrationMainSwitchToggleStorage(private val settingsStore: SettingsStore) :
+ KeyedObservableDelegate<String>(settingsStore), KeyValueStore {
override fun contains(key: String) = settingsStore.contains(key)
diff --git a/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdater.java b/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdater.java
index 14f55b8..bd160e1 100644
--- a/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdater.java
+++ b/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdater.java
@@ -39,6 +39,7 @@
private final AudioManager mAudioManager;
private final LocalBluetoothManager mLocalBtManager;
+ private int mAudioMode;
public AvailableMediaBluetoothDeviceUpdater(
Context context,
@@ -47,21 +48,23 @@
super(context, devicePreferenceCallback, metricsCategory);
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
mLocalBtManager = Utils.getLocalBtManager(context);
+ mAudioMode = mAudioManager.getMode();
}
@Override
public void onAudioModeChanged() {
+ // TODO: move to background thread
+ mAudioMode = mAudioManager.getMode();
forceUpdate();
}
@Override
public boolean isFilterMatched(CachedBluetoothDevice cachedDevice) {
- final int audioMode = mAudioManager.getMode();
final int currentAudioProfile;
- if (audioMode == AudioManager.MODE_RINGTONE
- || audioMode == AudioManager.MODE_IN_CALL
- || audioMode == AudioManager.MODE_IN_COMMUNICATION) {
+ if (mAudioMode == AudioManager.MODE_RINGTONE
+ || mAudioMode == AudioManager.MODE_IN_CALL
+ || mAudioMode == AudioManager.MODE_IN_COMMUNICATION) {
// in phone call
currentAudioProfile = BluetoothProfile.HEADSET;
} else {
diff --git a/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdater.java b/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdater.java
index 2107569..7cc874c 100644
--- a/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdater.java
+++ b/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdater.java
@@ -39,26 +39,29 @@
private static final String PREF_KEY_PREFIX = "connected_bt_";
private final AudioManager mAudioManager;
+ private int mAudioMode;
public ConnectedBluetoothDeviceUpdater(Context context,
DevicePreferenceCallback devicePreferenceCallback, int metricsCategory) {
super(context, devicePreferenceCallback, metricsCategory);
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ mAudioMode = mAudioManager.getMode();
}
@Override
public void onAudioModeChanged() {
+ // TODO: move to background thread
+ mAudioMode = mAudioManager.getMode();
forceUpdate();
}
@Override
public boolean isFilterMatched(CachedBluetoothDevice cachedDevice) {
- final int audioMode = mAudioManager.getMode();
final int currentAudioProfile;
- if (audioMode == AudioManager.MODE_RINGTONE
- || audioMode == AudioManager.MODE_IN_CALL
- || audioMode == AudioManager.MODE_IN_COMMUNICATION) {
+ if (mAudioMode == AudioManager.MODE_RINGTONE
+ || mAudioMode == AudioManager.MODE_IN_CALL
+ || mAudioMode == AudioManager.MODE_IN_COMMUNICATION) {
// in phone call
currentAudioProfile = BluetoothProfile.HEADSET;
} else {
diff --git a/src/com/android/settings/connecteddevice/BluetoothPreference.kt b/src/com/android/settings/connecteddevice/BluetoothPreference.kt
index c9b3953..93b2f20 100644
--- a/src/com/android/settings/connecteddevice/BluetoothPreference.kt
+++ b/src/com/android/settings/connecteddevice/BluetoothPreference.kt
@@ -52,10 +52,15 @@
override val restrictionKeys: Array<String>
get() = arrayOf(UserManager.DISALLOW_BLUETOOTH, UserManager.DISALLOW_CONFIG_BLUETOOTH)
- override fun getReadPermit(context: Context, myUid: Int, callingUid: Int) =
+ override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) =
ReadWritePermit.ALLOW
- override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) =
+ override fun getWritePermit(
+ context: Context,
+ value: Boolean?,
+ callingPid: Int,
+ callingUid: Int,
+ ) =
when {
isSatelliteOn(context, 3000) ||
(value == true &&
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java
index 25a9135..c86222c 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java
@@ -21,6 +21,8 @@
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamMediaService.DEVICES;
import static java.util.Collections.emptyList;
+import static java.util.Collections.emptyMap;
+import static java.util.stream.Collectors.toMap;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeAudioContentMetadata;
@@ -48,7 +50,9 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
+import java.util.function.Function;
import java.util.stream.Stream;
import javax.annotation.Nullable;
@@ -149,6 +153,16 @@
.toList();
}
+ /** Retrieves a list of all LE broadcast receive states keyed by each active device. */
+ public Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> getAllSourcesByDevice() {
+ if (mLeBroadcastAssistant == null) {
+ Log.w(TAG, "getAllSourcesByDevice(): LeBroadcastAssistant is null!");
+ return emptyMap();
+ }
+ return getConnectedBluetoothDevices(mBluetoothManager, /* inSharingOnly= */ true).stream()
+ .collect(toMap(Function.identity(), mLeBroadcastAssistant::getAllSources));
+ }
+
/** Retrieves a list of all LE broadcast receive states from sinks with source present. */
@VisibleForTesting
public List<BluetoothLeBroadcastReceiveState> getAllPresentSources() {
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallback.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallback.java
index f003431..87cea2c 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallback.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallback.java
@@ -16,7 +16,6 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams;
-
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothLeBroadcastReceiveState;
@@ -43,13 +42,13 @@
super.onReceiveStateChanged(sink, sourceId, state);
if (AudioStreamsHelper.isConnected(state)) {
- mCategoryController.handleSourceConnected(state);
+ mCategoryController.handleSourceConnected(sink, state);
} else if (AudioStreamsHelper.isBadCode(state)) {
mCategoryController.handleSourceConnectBadCode(state);
} else if (BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext)
&& AudioStreamsHelper.hasSourcePresent(state)) {
// Keep this check as the last, source might also present in above states
- mCategoryController.handleSourcePresent(state);
+ mCategoryController.handleSourcePresent(sink, state);
}
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java
index f0a0c5b..6831c5a 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java
@@ -17,10 +17,12 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams;
import static java.util.Collections.emptyList;
+import static java.util.stream.Collectors.toMap;
import android.app.AlertDialog;
import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothProfile;
@@ -49,6 +51,8 @@
import java.util.Comparator;
import java.util.List;
+import java.util.Map;
+import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@@ -391,34 +395,19 @@
// Expect one of the following:
// 1) No preference existed, create new preference with state SOURCE_ADDED
// 2) Any other state, move to SOURCE_ADDED
- void handleSourceConnected(BluetoothLeBroadcastReceiveState receiveState) {
+ void handleSourceConnected(
+ BluetoothDevice device, BluetoothLeBroadcastReceiveState receiveState) {
if (DEBUG) {
Log.d(TAG, "handleSourceConnected()");
}
if (!AudioStreamsHelper.isConnected(receiveState)) {
return;
}
-
var broadcastIdConnected = receiveState.getBroadcastId();
- if (mSourceFromQrCode != null && mSourceFromQrCode.getBroadcastId() == UNSET_BROADCAST_ID) {
- // mSourceFromQrCode could have no broadcast Id, we fill in the broadcast Id from the
- // connected source receiveState.
- if (DEBUG) {
- Log.d(
- TAG,
- "handleSourceConnected() : processing mSourceFromQrCode with broadcastId"
- + " unset");
- }
- boolean updated =
- maybeUpdateId(
- AudioStreamsHelper.getBroadcastName(receiveState),
- receiveState.getBroadcastId());
- if (updated && mBroadcastIdToPreferenceMap.containsKey(UNSET_BROADCAST_ID)) {
- var preference = mBroadcastIdToPreferenceMap.remove(UNSET_BROADCAST_ID);
- mBroadcastIdToPreferenceMap.put(receiveState.getBroadcastId(), preference);
- }
- }
-
+ Optional<BluetoothLeBroadcastMetadata> metadata =
+ getMetadataMatchingByBroadcastId(
+ device, receiveState.getSourceId(), broadcastIdConnected);
+ handleQrCodeWithUnsetBroadcastIdIfNeeded(metadata, receiveState);
mBroadcastIdToPreferenceMap.compute(
broadcastIdConnected,
(k, existingPreference) -> {
@@ -428,7 +417,12 @@
// we retrieves the connected source during onStart() from
// AudioStreamsHelper#getAllConnectedSources() even before the source is
// founded by scanning.
- return addNewPreference(receiveState, AudioStreamState.SOURCE_ADDED);
+ return metadata.isPresent()
+ ? addNewPreference(
+ metadata.get(),
+ AudioStreamState.SOURCE_ADDED,
+ SourceOriginForLogging.UNKNOWN)
+ : addNewPreference(receiveState, AudioStreamState.SOURCE_ADDED);
}
if (existingPreference.getAudioStreamState() == AudioStreamState.WAIT_FOR_SYNC
&& existingPreference.getAudioStreamBroadcastId() == UNSET_BROADCAST_ID
@@ -473,7 +467,8 @@
// Find preference by receiveState and decide next state.
// Expect one preference existed, move to SOURCE_PRESENT
- void handleSourcePresent(BluetoothLeBroadcastReceiveState receiveState) {
+ void handleSourcePresent(
+ BluetoothDevice device, BluetoothLeBroadcastReceiveState receiveState) {
if (DEBUG) {
Log.d(TAG, "handleSourcePresent()");
}
@@ -482,25 +477,10 @@
}
var broadcastIdConnected = receiveState.getBroadcastId();
- if (mSourceFromQrCode != null && mSourceFromQrCode.getBroadcastId() == UNSET_BROADCAST_ID) {
- // mSourceFromQrCode could have no broadcast Id, we fill in the broadcast Id from the
- // connected source receiveState.
- if (DEBUG) {
- Log.d(
- TAG,
- "handleSourcePresent() : processing mSourceFromQrCode with broadcastId"
- + " unset");
- }
- boolean updated =
- maybeUpdateId(
- AudioStreamsHelper.getBroadcastName(receiveState),
- receiveState.getBroadcastId());
- if (updated && mBroadcastIdToPreferenceMap.containsKey(UNSET_BROADCAST_ID)) {
- var preference = mBroadcastIdToPreferenceMap.remove(UNSET_BROADCAST_ID);
- mBroadcastIdToPreferenceMap.put(receiveState.getBroadcastId(), preference);
- }
- }
-
+ Optional<BluetoothLeBroadcastMetadata> metadata =
+ getMetadataMatchingByBroadcastId(
+ device, receiveState.getSourceId(), broadcastIdConnected);
+ handleQrCodeWithUnsetBroadcastIdIfNeeded(metadata, receiveState);
mBroadcastIdToPreferenceMap.compute(
broadcastIdConnected,
(k, existingPreference) -> {
@@ -511,7 +491,12 @@
// we retrieves the connected source during onStart() from
// AudioStreamsHelper#getAllPresentSources() even before the source is
// founded by scanning.
- return addNewPreference(receiveState, AudioStreamState.SOURCE_PRESENT);
+ return metadata.isPresent()
+ ? addNewPreference(
+ metadata.get(),
+ AudioStreamState.SOURCE_PRESENT,
+ SourceOriginForLogging.UNKNOWN)
+ : addNewPreference(receiveState, AudioStreamState.SOURCE_PRESENT);
}
if (existingPreference.getAudioStreamState() == AudioStreamState.WAIT_FOR_SYNC
&& existingPreference.getAudioStreamBroadcastId() == UNSET_BROADCAST_ID
@@ -598,28 +583,85 @@
// Handle QR code scan, display currently connected streams then start scanning
// sequentially
handleSourceFromQrCodeIfExists();
+ Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> sources =
+ mAudioStreamsHelper.getAllSourcesByDevice();
+ Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> connectedSources =
+ getConnectedSources(sources);
if (isAudioSharingHysteresisModeFixAvailable(mContext)) {
// With hysteresis mode, we prioritize showing connected sources first.
// If no connected sources are found, we then show present sources.
- List<BluetoothLeBroadcastReceiveState> sources =
- mAudioStreamsHelper.getAllConnectedSources();
- if (!sources.isEmpty()) {
- sources.forEach(this::handleSourceConnected);
+ if (!connectedSources.isEmpty()) {
+ connectedSources.forEach(
+ (device, stateList) ->
+ stateList.forEach(
+ state -> handleSourceConnected(device, state)));
} else {
- mAudioStreamsHelper
- .getAllPresentSources()
- .forEach(this::handleSourcePresent);
+ Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>>
+ presentSources = getPresentSources(sources);
+ presentSources.forEach(
+ (device, stateList) ->
+ stateList.forEach(
+ state -> handleSourcePresent(device, state)));
}
} else {
- mAudioStreamsHelper
- .getAllConnectedSources()
- .forEach(this::handleSourceConnected);
+ connectedSources.forEach(
+ (device, stateList) ->
+ stateList.forEach(
+ state -> handleSourceConnected(device, state)));
}
mLeBroadcastAssistant.startSearchingForSources(emptyList());
mMediaControlHelper.start();
});
}
+ private Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> getConnectedSources(
+ Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> sources) {
+ return sources.entrySet().stream()
+ .filter(
+ entry ->
+ entry.getValue().stream().anyMatch(AudioStreamsHelper::isConnected))
+ .collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
+ }
+
+ private Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> getPresentSources(
+ Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> sources) {
+ return sources.entrySet().stream()
+ .filter(
+ entry ->
+ entry.getValue().stream()
+ .anyMatch(AudioStreamsHelper::hasSourcePresent))
+ .collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
+ }
+
+ private Optional<BluetoothLeBroadcastMetadata> getMetadataMatchingByBroadcastId(
+ BluetoothDevice device, int sourceId, int broadcastId) {
+ return Optional.ofNullable(
+ mLeBroadcastAssistant != null
+ ? mLeBroadcastAssistant.getSourceMetadata(device, sourceId)
+ : null)
+ .filter(m -> m.getBroadcastId() == broadcastId);
+ }
+
+ private void handleQrCodeWithUnsetBroadcastIdIfNeeded(
+ Optional<BluetoothLeBroadcastMetadata> metadata,
+ BluetoothLeBroadcastReceiveState receiveState) {
+ if (mSourceFromQrCode != null && mSourceFromQrCode.getBroadcastId() == UNSET_BROADCAST_ID) {
+ if (DEBUG) {
+ Log.d(TAG, "Processing mSourceFromQrCode with unset broadcastId");
+ }
+ boolean updated =
+ maybeUpdateId(
+ metadata.isPresent()
+ ? AudioStreamsHelper.getBroadcastName(metadata.get())
+ : AudioStreamsHelper.getBroadcastName(receiveState),
+ receiveState.getBroadcastId());
+ if (updated && mBroadcastIdToPreferenceMap.containsKey(UNSET_BROADCAST_ID)) {
+ var preference = mBroadcastIdToPreferenceMap.remove(UNSET_BROADCAST_ID);
+ mBroadcastIdToPreferenceMap.put(receiveState.getBroadcastId(), preference);
+ }
+ }
+ }
+
private void stopScanning() {
if (mLeBroadcastAssistant == null) {
Log.w(TAG, "stopScanning(): LeBroadcastAssistant is null!");
diff --git a/src/com/android/settings/connecteddevice/display/DisplayTopology.kt b/src/com/android/settings/connecteddevice/display/DisplayTopology.kt
index 162d9d2..9cac772 100644
--- a/src/com/android/settings/connecteddevice/display/DisplayTopology.kt
+++ b/src/com/android/settings/connecteddevice/display/DisplayTopology.kt
@@ -16,14 +16,32 @@
package com.android.settings.connecteddevice.display
+import android.app.WallpaperManager
import com.android.settings.R
import android.content.Context
+import android.graphics.Color
import android.graphics.Point
import android.graphics.PointF
import android.graphics.RectF
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.ColorDrawable
+import android.hardware.display.DisplayManager
+import android.hardware.display.DisplayTopology
+import android.hardware.display.DisplayTopology.TreeNode.POSITION_BOTTOM
+import android.hardware.display.DisplayTopology.TreeNode.POSITION_LEFT
+import android.hardware.display.DisplayTopology.TreeNode.POSITION_RIGHT
+import android.hardware.display.DisplayTopology.TreeNode.POSITION_TOP
+import android.util.Log
+import android.view.ViewGroup
+import android.view.ViewTreeObserver
+import android.widget.Button
+import android.widget.FrameLayout
+import android.widget.TextView
+import androidx.annotation.VisibleForTesting
import androidx.preference.Preference
+import androidx.preference.PreferenceViewHolder
import java.util.Locale
@@ -41,8 +59,20 @@
* left corner of the pane. It uses a scale optimized for showing all displays with minimal or no
* scrolling. The display coordinates are floating point and the origin can be in any position. In
* practice the origin will be the upper-left coordinate of the primary display.
+ *
+ * @param paneWidth width of the pane in view coordinates
+ * @param minEdgeLength the smallest length permitted of a display block. This should be set based
+ * on accessibility requirements, but also accounting for padding that appears
+ * around each button.
+ * @param maxBlockRatio the highest allowed ratio of block size to display size. For instance, a
+ * value of 0.05 means the block will at most be 1/20 the size of the display
+ * it represents. This limit may be breached to account for minEdgeLength,
+ * which is considered an a11y requirement.
+ * @param displaysPos the absolute topology coordinates for each display in the topology.
*/
-class TopologyScale(paneWidth : Int, displaysPos : Collection<RectF>) {
+class TopologyScale(
+ paneWidth : Int, minEdgeLength : Int, maxBlockRatio : Float,
+ displaysPos : Collection<RectF>) {
/** Scale of block sizes to real-world display sizes. Should be less than 1. */
val blockRatio : Float
@@ -69,16 +99,14 @@
biggestDisplayHeight = max(biggestDisplayHeight, pos.height())
}
- // Set height according to the width and the aspect ratio of the display bounds.
- // 0.05 is a reasonable limit to the size of display blocks. It appears to match the
- // ratio used in the ChromeOS topology editor. It prevents blocks from being too large,
- // which would make dragging and dropping awkward.
- val rawBlockRatio = min(0.05, paneWidth.toDouble() * 0.6 / displayBounds.width())
+ // Set height according to the width and the aspect ratio of the display bounds limitted by
+ // maxBlockRatio. It prevents blocks from being too large, which would make dragging and
+ // dropping awkward.
+ val rawBlockRatio = min(maxBlockRatio, paneWidth.toFloat() * 0.6f / displayBounds.width())
// If the `ratio` is set too low because one of the displays will have an edge less than
- // 48dp long, increase it such that the smallest edge is that long. This may override the
- // 0.05 limit since it is more important than it.
- blockRatio = max(48.0 / smallestDisplayDim, rawBlockRatio).toFloat()
+ // minEdgeLength(dp) long, increase it such that the smallest edge is that long.
+ blockRatio = max(minEdgeLength.toFloat() / smallestDisplayDim, rawBlockRatio).toFloat()
// Essentially, we just set the pane height based on the pre-determined pane width and the
// aspect ratio of the display bounds. But we may need to increase it slightly to achieve
@@ -99,9 +127,9 @@
// such that the display bounds rect is centered in the pane.
// It is unlikely that either of these coordinates will be negative since blockRatio has
// been chosen to allow 20% padding around each side of the display blocks. However, the
- // a11y requirement applied above (48.0 / smallestDisplayDim) may cause the blocks to not
- // fit. This should be rare in practice, and can be worked around by moving the settings UI
- // to a larger display.
+ // a11y requirement applied above (minEdgeLength / smallestDisplayDim) may cause the blocks
+ // to not fit. This should be rare in practice, and can be worked around by moving the
+ // settings UI to a larger display.
val blockMostLeft = (paneWidth - displayBounds.width() * blockRatio) / 2
val blockMostTop = (paneHeight - displayBounds.height() * blockRatio) / 2
@@ -133,11 +161,27 @@
const val PREFERENCE_KEY = "display_topology_preference"
+/** dp of padding on each side of a display block. */
+const val BLOCK_PADDING = 2
+
/**
* DisplayTopologyPreference allows the user to change the display topology
* when there is one or more extended display attached.
*/
-class DisplayTopologyPreference(context : Context) : Preference(context) {
+class DisplayTopologyPreference(context : Context)
+ : Preference(context), ViewTreeObserver.OnGlobalLayoutListener {
+ @VisibleForTesting lateinit var mPaneContent : FrameLayout
+ @VisibleForTesting lateinit var mPaneHolder : FrameLayout
+ @VisibleForTesting lateinit var mTopologyHint : TextView
+
+ @VisibleForTesting var injector : Injector
+
+ /**
+ * This is needed to prevent a repopulation of the pane causing another
+ * relayout and vice-versa ad infinitum.
+ */
+ private var mPaneNeedsRefresh = false
+
init {
layoutResource = R.layout.display_topology_preference
@@ -145,5 +189,108 @@
isSelectable = false
key = PREFERENCE_KEY
+
+ injector = Injector()
+ }
+
+ override fun onBindViewHolder(holder: PreferenceViewHolder) {
+ super.onBindViewHolder(holder)
+
+ val newPane = holder.findViewById(R.id.display_topology_pane_content) as FrameLayout
+ if (this::mPaneContent.isInitialized) {
+ if (newPane == mPaneContent) {
+ return
+ }
+ mPaneContent.viewTreeObserver.removeOnGlobalLayoutListener(this)
+ }
+ mPaneContent = newPane
+ mPaneHolder = holder.itemView as FrameLayout
+ mTopologyHint = holder.findViewById(R.id.topology_hint) as TextView
+ mPaneContent.viewTreeObserver.addOnGlobalLayoutListener(this)
+ }
+
+ override fun onAttached() {
+ // We don't know if topology changes happened when we were detached, as it is impossible to
+ // listen at that time (we must remove listeners when detaching). Setting this flag makes
+ // the following onGlobalLayout call refresh the pane.
+ mPaneNeedsRefresh = true
+ }
+
+ override fun onGlobalLayout() {
+ if (mPaneNeedsRefresh) {
+ mPaneNeedsRefresh = false
+ refreshPane()
+ }
+ }
+
+ open class Injector {
+ open fun displayTopology(context : Context) : DisplayTopology? {
+ val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
+ return displayManager.displayTopology
+ }
+
+ open fun wallpaper(context : Context) : Drawable {
+ return WallpaperManager.getInstance(context).drawable ?: ColorDrawable(Color.BLACK)
+ }
+ }
+
+ private fun calcAbsRects(
+ dest : MutableMap<Int, RectF>, n : DisplayTopology.TreeNode, x : Float, y : Float) {
+ dest.put(n.displayId, RectF(x, y, x + n.width, y + n.height))
+
+ for (c in n.children) {
+ val (xoff, yoff) = when (c.position) {
+ POSITION_LEFT -> Pair(-c.width, +c.offset)
+ POSITION_RIGHT -> Pair(+n.width, +c.offset)
+ POSITION_TOP -> Pair(+c.offset, -c.height)
+ POSITION_BOTTOM -> Pair(+c.offset, +n.height)
+ else -> throw IllegalStateException("invalid position for display: ${c}")
+ }
+ calcAbsRects(dest, c, x + xoff, y + yoff)
+ }
+ }
+
+ private fun refreshPane() {
+ mPaneContent.removeAllViews()
+
+ val root = injector.displayTopology(context)?.root
+ if (root == null) {
+ // This occurs when no topology is active.
+ // TODO(b/352648432): show main display or mirrored displays rather than an empty pane.
+ mTopologyHint.text = ""
+ return
+ }
+ mTopologyHint.text = context.getString(R.string.external_display_topology_hint)
+
+ val blocksPos = buildMap { calcAbsRects(this, root, x = 0f, y = 0f) }
+
+ val scaling = TopologyScale(
+ mPaneContent.width, minEdgeLength = 60, maxBlockRatio = 0.12f, blocksPos.values)
+ mPaneHolder.layoutParams.let {
+ if (it.height != scaling.paneHeight) {
+ it.height = scaling.paneHeight
+ mPaneHolder.layoutParams = it
+ }
+ }
+ val wallpaper = injector.wallpaper(context)
+ blocksPos.values.forEach { p ->
+ Button(context).apply {
+ isScrollContainer = false
+ isVerticalScrollBarEnabled = false
+ isHorizontalScrollBarEnabled = false
+ background = wallpaper
+ val topLeft = scaling.displayToPaneCoor(PointF(p.left, p.top))
+ val bottomRight = scaling.displayToPaneCoor(PointF(p.right, p.bottom))
+
+ mPaneContent.addView(this)
+
+ val layout = layoutParams
+ layout.width = bottomRight.x - topLeft.x - BLOCK_PADDING * 2
+ layout.height = bottomRight.y - topLeft.y - BLOCK_PADDING * 2
+ layoutParams = layout
+ x = (topLeft.x + BLOCK_PADDING).toFloat()
+ y = (topLeft.y + BLOCK_PADDING).toFloat()
+ }
+ }
}
}
diff --git a/src/com/android/settings/datausage/AppDataUsage.java b/src/com/android/settings/datausage/AppDataUsage.java
index 8480c5c..d8e238c 100644
--- a/src/com/android/settings/datausage/AppDataUsage.java
+++ b/src/com/android/settings/datausage/AppDataUsage.java
@@ -18,6 +18,7 @@
import static com.android.settings.datausage.lib.AppDataUsageRepository.getAppUid;
import static com.android.settings.datausage.lib.AppDataUsageRepository.getAppUidList;
+import static com.android.settings.spa.app.appinfo.AppInfoSettingsProvider.startAppInfoSettings;
import android.app.Activity;
import android.app.settings.SettingsEnums;
@@ -45,13 +46,14 @@
import com.android.settings.datausage.lib.NetworkTemplates;
import com.android.settings.fuelgauge.datasaver.DynamicDenylistManager;
import com.android.settings.network.SubscriptionUtil;
-import com.android.settings.widget.EntityHeaderController;
+import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.AppItem;
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.RestrictedSwitchPreference;
import com.android.settingslib.net.UidDetail;
import com.android.settingslib.net.UidDetailProvider;
+import com.android.settingslib.widget.IntroPreference;
import kotlin.Unit;
@@ -65,6 +67,8 @@
private static final String TAG = "AppDataUsage";
static final String ARG_APP_ITEM = "app_item";
+ @VisibleForTesting
+ static final String ARG_APP_HEADER = "app_header";
static final String ARG_NETWORK_TEMPLATE = "network_template";
static final String ARG_NETWORK_CYCLES = "network_cycles";
static final String ARG_SELECTED_CYCLE = "selected_cycle";
@@ -176,7 +180,7 @@
removePreference(KEY_RESTRICT_BACKGROUND);
}
- addEntityHeader();
+ setupIntroPreference();
}
@Override
@@ -320,32 +324,32 @@
}
@VisibleForTesting
- void addEntityHeader() {
- String pkg = mPackages.size() != 0 ? mPackages.valueAt(0) : null;
- int uid = 0;
- if (pkg != null) {
+ void setupIntroPreference() {
+ final Preference pref = getPreferenceScreen().findPreference(ARG_APP_HEADER);
+ if (pref != null) {
+ pref.setIcon(mIcon);
+ pref.setTitle(mLabel);
+ pref.setSelectable(true);
+ }
+ }
+
+ @Override
+ public boolean onPreferenceTreeClick(Preference preference) {
+ if (!(preference instanceof IntroPreference)) return false;
+
+ String pkg = !mPackages.isEmpty() ? mPackages.valueAt(0) : null;
+ if (mAppItem.key > 0 && pkg != null) {
try {
- uid = mPackageManager.getPackageUidAsUser(pkg,
+ int uid = mPackageManager.getPackageUidAsUser(pkg,
UserHandle.getUserId(mAppItem.key));
+ startAppInfoSettings(pkg, uid, this, 0 /* request */,
+ FeatureFactory.getFeatureFactory().getMetricsFeatureProvider()
+ .getMetricsCategory(this));
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Skipping UID because cannot find package " + pkg);
}
}
-
- final boolean showInfoButton = mAppItem.key > 0;
-
- final Activity activity = getActivity();
- final Preference pref = EntityHeaderController
- .newInstance(activity, this, null /* header */)
- .setUid(uid)
- .setHasAppInfoLink(showInfoButton)
- .setButtonActions(EntityHeaderController.ActionType.ACTION_NONE,
- EntityHeaderController.ActionType.ACTION_NONE)
- .setIcon(mIcon)
- .setLabel(mLabel)
- .setPackageName(pkg)
- .done(getPrefContext());
- getPreferenceScreen().addPreference(pref);
+ return true;
}
@Override
diff --git a/src/com/android/settings/datausage/AppDataUsageSummaryController.kt b/src/com/android/settings/datausage/AppDataUsageSummaryController.kt
index 233e107..fb7101d 100644
--- a/src/com/android/settings/datausage/AppDataUsageSummaryController.kt
+++ b/src/com/android/settings/datausage/AppDataUsageSummaryController.kt
@@ -17,7 +17,6 @@
package com.android.settings.datausage
import android.content.Context
-import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.res.stringResource
@@ -28,6 +27,7 @@
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.settingslib.spa.widget.ui.Category
import com.android.settingslib.spaprivileged.framework.compose.getPlaceholder
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.map
@@ -60,7 +60,7 @@
@Composable
override fun Content() {
- Column {
+ Category {
val totalUsage by totalUsageFlow.collectAsStateWithLifecycle(emptyDataUsage)
val foregroundUsage by foregroundUsageFlow.collectAsStateWithLifecycle(emptyDataUsage)
val backgroundUsage by backgroundUsageFlow.collectAsStateWithLifecycle(emptyDataUsage)
diff --git a/src/com/android/settings/datausage/DataSaverMainSwitchPreference.kt b/src/com/android/settings/datausage/DataSaverMainSwitchPreference.kt
index 23cfadc..79d3dc3 100644
--- a/src/com/android/settings/datausage/DataSaverMainSwitchPreference.kt
+++ b/src/com/android/settings/datausage/DataSaverMainSwitchPreference.kt
@@ -36,11 +36,15 @@
override fun storage(context: Context) = createDataStore(context)
- override fun getReadPermit(context: Context, myUid: Int, callingUid: Int) =
+ override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) =
ReadWritePermit.ALLOW
- override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) =
- ReadWritePermit.ALLOW
+ override fun getWritePermit(
+ context: Context,
+ value: Boolean?,
+ callingPid: Int,
+ callingUid: Int,
+ ) = ReadWritePermit.ALLOW
override val sensitivityLevel
get() = SensitivityLevel.NO_SENSITIVITY
diff --git a/src/com/android/settings/datausage/SpinnerPreference.java b/src/com/android/settings/datausage/SpinnerPreference.java
index febdead..a1b0f90 100644
--- a/src/com/android/settings/datausage/SpinnerPreference.java
+++ b/src/com/android/settings/datausage/SpinnerPreference.java
@@ -25,8 +25,10 @@
import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
+import com.android.settingslib.widget.GroupSectionDividerMixin;
-public class SpinnerPreference extends Preference implements CycleAdapter.SpinnerInterface {
+public class SpinnerPreference extends Preference implements CycleAdapter.SpinnerInterface,
+ GroupSectionDividerMixin {
private CycleAdapter mAdapter;
@Nullable
diff --git a/src/com/android/settings/datetime/AutoTimeFormatPreferenceController.java b/src/com/android/settings/datetime/AutoTimeFormatPreferenceController.java
deleted file mode 100644
index 44e7cc6..0000000
--- a/src/com/android/settings/datetime/AutoTimeFormatPreferenceController.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2017 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.datetime;
-
-import android.content.Context;
-import android.provider.Settings;
-import android.provider.Settings.System;
-import android.text.format.DateFormat;
-
-import com.android.settings.R;
-import com.android.settings.core.TogglePreferenceController;
-
-import java.util.Locale;
-
-public class AutoTimeFormatPreferenceController extends TogglePreferenceController {
-
- public AutoTimeFormatPreferenceController(Context context, String preferenceKey) {
- super(context, preferenceKey);
- }
-
- @Override
- public int getAvailabilityStatus() {
- return AVAILABLE;
- }
-
- @Override
- public boolean isChecked() {
- return isAutoTimeFormatSelection(mContext);
- }
-
- @Override
- public boolean setChecked(boolean isChecked) {
- Boolean is24Hour;
- if (isChecked) {
- is24Hour = null;
- } else {
- is24Hour = is24HourLocale(mContext.getResources().getConfiguration().locale);
- }
- TimeFormatPreferenceController.update24HourFormat(mContext, is24Hour);
- return true;
- }
-
- @Override
- public int getSliceHighlightMenuRes() {
- return R.string.menu_key_system;
- }
-
- boolean is24HourLocale(Locale locale) {
- return DateFormat.is24HourLocale(locale);
- }
-
- /**
- * Returns if the system is currently configured to pick the time format automatically based on
- * the locale.
- */
- static boolean isAutoTimeFormatSelection(Context context) {
- return Settings.System.getString(context.getContentResolver(), System.TIME_12_24) == null;
- }
-}
diff --git a/src/com/android/settings/datetime/AutoTimeZonePreferenceController.java b/src/com/android/settings/datetime/AutoTimeZonePreferenceController.java
index 3d8f801..ecd416d 100644
--- a/src/com/android/settings/datetime/AutoTimeZonePreferenceController.java
+++ b/src/com/android/settings/datetime/AutoTimeZonePreferenceController.java
@@ -32,7 +32,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.core.TogglePreferenceController;
-import com.android.settings.flags.Flags;
public class AutoTimeZonePreferenceController extends TogglePreferenceController {
@@ -107,19 +106,17 @@
TimeZoneConfiguration.Builder configuration = new TimeZoneConfiguration.Builder()
.setAutoDetectionEnabled(isChecked);
- if (Flags.revampToggles()) {
- // "Use location for time zone" is only used if "Automatic time zone" is enabled. If
- // the user toggles off automatic time zone, set the toggle off and disable the toggle.
- int geoDetectionCapability = mTimeManager
- .getTimeZoneCapabilitiesAndConfig()
- .getCapabilities()
- .getConfigureGeoDetectionEnabledCapability();
+ // "Use location for time zone" is only used if "Automatic time zone" is enabled. If
+ // the user toggles off automatic time zone, set the toggle off and disable the toggle.
+ int geoDetectionCapability = mTimeManager
+ .getTimeZoneCapabilitiesAndConfig()
+ .getCapabilities()
+ .getConfigureGeoDetectionEnabledCapability();
- if (!isChecked
- && (geoDetectionCapability == CAPABILITY_NOT_APPLICABLE
- || geoDetectionCapability == CAPABILITY_POSSESSED)) {
- configuration.setGeoDetectionEnabled(false);
- }
+ if (!isChecked
+ && (geoDetectionCapability == CAPABILITY_NOT_APPLICABLE
+ || geoDetectionCapability == CAPABILITY_POSSESSED)) {
+ configuration.setGeoDetectionEnabled(false);
}
boolean result = mTimeManager.updateTimeZoneConfiguration(configuration.build());
diff --git a/src/com/android/settings/datetime/DateTimeSettings.java b/src/com/android/settings/datetime/DateTimeSettings.java
index e5c13bf..f3c11d4 100644
--- a/src/com/android/settings/datetime/DateTimeSettings.java
+++ b/src/com/android/settings/datetime/DateTimeSettings.java
@@ -23,7 +23,6 @@
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
-import com.android.settings.flags.Flags;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.search.SearchIndexable;
@@ -50,9 +49,6 @@
@Override
protected int getPreferenceScreenResId() {
- if (Flags.revampToggles()) {
- return R.xml.date_time_prefs_revamped;
- }
return R.xml.date_time_prefs;
}
@@ -123,6 +119,5 @@
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
- new BaseSearchIndexProvider(
- Flags.revampToggles() ? R.xml.date_time_prefs_revamped : R.xml.date_time_prefs);
+ new BaseSearchIndexProvider(R.xml.date_time_prefs);
}
diff --git a/src/com/android/settings/datetime/LocationTimeZoneDetectionPreferenceController.java b/src/com/android/settings/datetime/LocationTimeZoneDetectionPreferenceController.java
index 52d49ac..d475d9d 100644
--- a/src/com/android/settings/datetime/LocationTimeZoneDetectionPreferenceController.java
+++ b/src/com/android/settings/datetime/LocationTimeZoneDetectionPreferenceController.java
@@ -32,7 +32,6 @@
import com.android.settings.R;
import com.android.settings.core.InstrumentedPreferenceFragment;
import com.android.settings.core.TogglePreferenceController;
-import com.android.settings.flags.Flags;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.core.lifecycle.events.OnStop;
@@ -68,7 +67,7 @@
// forceRefresh set to true as the location toggle may have been turned off by switching off
// automatic time zone
TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
- getTimeZoneCapabilitiesAndConfig(/*forceRefresh=*/ Flags.revampToggles());
+ getTimeZoneCapabilitiesAndConfig(/*forceRefresh=*/ true);
TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
return configuration.isGeoDetectionEnabled();
}
@@ -137,11 +136,7 @@
if (capability == CAPABILITY_NOT_SUPPORTED || capability == CAPABILITY_NOT_ALLOWED) {
return UNSUPPORTED_ON_DEVICE;
} else if (capability == CAPABILITY_NOT_APPLICABLE || capability == CAPABILITY_POSSESSED) {
- if (Flags.revampToggles()) {
- return isAutoTimeZoneEnabled() ? AVAILABLE : DISABLED_DEPENDENT_SETTING;
- } else {
- return AVAILABLE;
- }
+ return isAutoTimeZoneEnabled() ? AVAILABLE : DISABLED_DEPENDENT_SETTING;
} else {
throw new IllegalStateException("Unknown capability=" + capability);
}
@@ -151,10 +146,8 @@
public void updateState(Preference preference) {
super.updateState(preference);
- if (Flags.revampToggles()) {
- // enable / disable the toggle based on automatic time zone being enabled or not
- preference.setEnabled(isAutoTimeZoneEnabled());
- }
+ // enable / disable the toggle based on automatic time zone being enabled or not
+ preference.setEnabled(isAutoTimeZoneEnabled());
}
@Override
diff --git a/src/com/android/settings/datetime/TimeFormatPreferenceController.java b/src/com/android/settings/datetime/TimeFormatPreferenceController.java
index 2dee76e..c400f4e 100644
--- a/src/com/android/settings/datetime/TimeFormatPreferenceController.java
+++ b/src/com/android/settings/datetime/TimeFormatPreferenceController.java
@@ -25,7 +25,6 @@
import com.android.settings.R;
import com.android.settings.core.TogglePreferenceController;
-import com.android.settings.flags.Flags;
import java.util.Calendar;
import java.util.Date;
@@ -73,11 +72,6 @@
if (mIsFromSUW) {
return DISABLED_DEPENDENT_SETTING;
}
- if (!Flags.revampToggles()) {
- if (AutoTimeFormatPreferenceController.isAutoTimeFormatSelection(mContext)) {
- return DISABLED_DEPENDENT_SETTING;
- }
- }
return AVAILABLE;
}
@@ -120,12 +114,12 @@
return DateFormat.is24HourFormat(mContext);
}
- static void update24HourFormat(Context context, Boolean is24Hour) {
+ private static void update24HourFormat(Context context, Boolean is24Hour) {
set24Hour(context, is24Hour);
timeUpdated(context, is24Hour);
}
- static void timeUpdated(Context context, Boolean is24Hour) {
+ private static void timeUpdated(Context context, Boolean is24Hour) {
Intent timeChanged = new Intent(Intent.ACTION_TIME_CHANGED);
timeChanged.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
int timeFormatPreference;
@@ -139,9 +133,8 @@
context.sendBroadcast(timeChanged);
}
- static void set24Hour(Context context, Boolean is24Hour) {
- String value = is24Hour == null ? null :
- is24Hour ? HOURS_24 : HOURS_12;
+ private static void set24Hour(Context context, boolean is24Hour) {
+ String value = is24Hour ? HOURS_24 : HOURS_12;
Settings.System.putString(context.getContentResolver(),
Settings.System.TIME_12_24, value);
}
diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
index 9c13794..605f3bb 100644
--- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
+++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
@@ -837,6 +837,7 @@
controllers.add(new ForceEnableNotesRolePreferenceController(context));
controllers.add(new GrammaticalGenderPreferenceController(context));
controllers.add(new SensitiveContentProtectionPreferenceController(context));
+ controllers.add(new ShadeDisplayAwarenessPreferenceController(context));
return controllers;
}
diff --git a/src/com/android/settings/development/ShadeDisplayAwarenessPreferenceController.java b/src/com/android/settings/development/ShadeDisplayAwarenessPreferenceController.java
new file mode 100644
index 0000000..0551112
--- /dev/null
+++ b/src/com/android/settings/development/ShadeDisplayAwarenessPreferenceController.java
@@ -0,0 +1,89 @@
+/*
+ * 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.development;
+
+import android.content.Context;
+import android.provider.Settings;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+
+import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settingslib.R;
+import com.android.settingslib.development.DeveloperOptionsPreferenceController;
+
+public class ShadeDisplayAwarenessPreferenceController extends DeveloperOptionsPreferenceController
+ implements Preference.OnPreferenceChangeListener, PreferenceControllerMixin,
+ RebootConfirmationDialogHost {
+
+ private static final int SHADE_DISPLAY_AWARENESS_DEFAULT = 0;
+ private static final String SHADE_DISPLAY_AWARENESS_KEY = "shade_display_awareness";
+
+ private final String[] mListValues;
+ private final String[] mListSummaries;
+
+ public ShadeDisplayAwarenessPreferenceController(Context context) {
+ super(context);
+
+ mListValues = mContext.getResources().getStringArray(
+ R.array.shade_display_awareness_values);
+ mListSummaries = mContext.getResources().getStringArray(
+ R.array.shade_display_awareness_summaries);
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return SHADE_DISPLAY_AWARENESS_KEY;
+ }
+
+ @Override
+ public boolean onPreferenceChange(@NonNull Preference preference, Object newValue) {
+ Settings.Global.putString(mContext.getContentResolver(),
+ Settings.Global.DEVELOPMENT_SHADE_DISPLAY_AWARENESS, newValue.toString());
+ updateShadeDisplayAwareness((ListPreference) mPreference);
+ return true;
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ updateShadeDisplayAwareness((ListPreference) mPreference);
+ }
+
+ private void updateShadeDisplayAwareness(ListPreference preference) {
+ String currentValue = Settings.Global.getString(mContext.getContentResolver(),
+ Settings.Global.DEVELOPMENT_SHADE_DISPLAY_AWARENESS);
+ int index = SHADE_DISPLAY_AWARENESS_DEFAULT; // Defaults to value is device-display (0)
+ for (int i = 0; i < mListValues.length; i++) {
+ if (TextUtils.equals(currentValue, mListValues[i])) {
+ index = i;
+ break;
+ }
+ }
+ preference.setValue(mListValues[index]);
+ preference.setSummary(mListSummaries[index]);
+ }
+
+ @Override
+ protected void onDeveloperOptionsSwitchDisabled() {
+ super.onDeveloperOptionsSwitchDisabled();
+ Settings.Global.putString(mContext.getContentResolver(),
+ Settings.Global.DEVELOPMENT_SHADE_DISPLAY_AWARENESS,
+ mListValues[SHADE_DISPLAY_AWARENESS_DEFAULT]);
+ }
+}
diff --git a/src/com/android/settings/display/AdaptiveSleepPreference.kt b/src/com/android/settings/display/AdaptiveSleepPreference.kt
index a160001..7cc320d 100644
--- a/src/com/android/settings/display/AdaptiveSleepPreference.kt
+++ b/src/com/android/settings/display/AdaptiveSleepPreference.kt
@@ -77,11 +77,15 @@
override fun storage(context: Context): KeyValueStore = Storage(context)
- override fun getReadPermit(context: Context, myUid: Int, callingUid: Int) =
+ override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) =
ReadWritePermit.ALLOW
- override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) =
- ReadWritePermit.ALLOW
+ override fun getWritePermit(
+ context: Context,
+ value: Boolean?,
+ callingPid: Int,
+ callingUid: Int,
+ ) = ReadWritePermit.ALLOW
override val sensitivityLevel
get() = SensitivityLevel.NO_SENSITIVITY
diff --git a/src/com/android/settings/display/AmbientDisplayAlwaysOnPreference.kt b/src/com/android/settings/display/AmbientDisplayAlwaysOnPreference.kt
index e50b00b..7a6df8e 100644
--- a/src/com/android/settings/display/AmbientDisplayAlwaysOnPreference.kt
+++ b/src/com/android/settings/display/AmbientDisplayAlwaysOnPreference.kt
@@ -71,11 +71,15 @@
override fun storage(context: Context): KeyValueStore = Storage(context)
- override fun getReadPermit(context: Context, myUid: Int, callingUid: Int) =
+ override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) =
ReadWritePermit.ALLOW
- override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) =
- ReadWritePermit.ALLOW
+ override fun getWritePermit(
+ context: Context,
+ value: Boolean?,
+ callingPid: Int,
+ callingUid: Int,
+ ) = ReadWritePermit.ALLOW
override val sensitivityLevel
get() = SensitivityLevel.NO_SENSITIVITY
diff --git a/src/com/android/settings/display/AutoBrightnessScreen.kt b/src/com/android/settings/display/AutoBrightnessScreen.kt
index 32d70ad..319d95b 100644
--- a/src/com/android/settings/display/AutoBrightnessScreen.kt
+++ b/src/com/android/settings/display/AutoBrightnessScreen.kt
@@ -65,11 +65,15 @@
override fun storage(context: Context): KeyValueStore =
AutoBrightnessDataStore(SettingsSystemStore.get(context))
- override fun getReadPermit(context: Context, myUid: Int, callingUid: Int) =
+ override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) =
ReadWritePermit.ALLOW
- override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) =
- ReadWritePermit.ALLOW
+ override fun getWritePermit(
+ context: Context,
+ value: Boolean?,
+ callingPid: Int,
+ callingUid: Int,
+ ) = ReadWritePermit.ALLOW
override val sensitivityLevel
get() = SensitivityLevel.NO_SENSITIVITY
diff --git a/src/com/android/settings/display/BatteryPercentageSwitchPreference.kt b/src/com/android/settings/display/BatteryPercentageSwitchPreference.kt
index 25623b3..fb2b74c 100644
--- a/src/com/android/settings/display/BatteryPercentageSwitchPreference.kt
+++ b/src/com/android/settings/display/BatteryPercentageSwitchPreference.kt
@@ -49,11 +49,15 @@
com.android.internal.R.bool.config_battery_percentage_setting_available
)
- override fun getReadPermit(context: Context, myUid: Int, callingUid: Int) =
+ override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) =
ReadWritePermit.ALLOW
- override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) =
- ReadWritePermit.ALLOW
+ override fun getWritePermit(
+ context: Context,
+ value: Boolean?,
+ callingPid: Int,
+ callingUid: Int,
+ ) = ReadWritePermit.ALLOW
override val sensitivityLevel
get() = SensitivityLevel.NO_SENSITIVITY
diff --git a/src/com/android/settings/display/PeakRefreshRateSwitchPreference.kt b/src/com/android/settings/display/PeakRefreshRateSwitchPreference.kt
index 3240616..32d2910 100644
--- a/src/com/android/settings/display/PeakRefreshRateSwitchPreference.kt
+++ b/src/com/android/settings/display/PeakRefreshRateSwitchPreference.kt
@@ -50,11 +50,15 @@
override fun storage(context: Context): KeyValueStore =
PeakRefreshRateStore(context, SettingsSystemStore.get(context))
- override fun getReadPermit(context: Context, myUid: Int, callingUid: Int) =
+ override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) =
ReadWritePermit.ALLOW
- override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) =
- ReadWritePermit.ALLOW
+ override fun getWritePermit(
+ context: Context,
+ value: Boolean?,
+ callingPid: Int,
+ callingUid: Int,
+ ) = ReadWritePermit.ALLOW
override val sensitivityLevel
get() = SensitivityLevel.NO_SENSITIVITY
diff --git a/src/com/android/settings/display/darkmode/DarkModeScreen.kt b/src/com/android/settings/display/darkmode/DarkModeScreen.kt
index 7f8087a..807b0ae 100644
--- a/src/com/android/settings/display/darkmode/DarkModeScreen.kt
+++ b/src/com/android/settings/display/darkmode/DarkModeScreen.kt
@@ -71,11 +71,15 @@
override val keywords: Int
get() = R.string.keywords_dark_ui_mode
- override fun getReadPermit(context: Context, myUid: Int, callingUid: Int) =
+ override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) =
ReadWritePermit.ALLOW
- override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) =
- ReadWritePermit.ALLOW
+ override fun getWritePermit(
+ context: Context,
+ value: Boolean?,
+ callingPid: Int,
+ callingUid: Int,
+ ) = ReadWritePermit.ALLOW
override val sensitivityLevel
get() = SensitivityLevel.NO_SENSITIVITY
diff --git a/src/com/android/settings/fuelgauge/BatteryHeaderPreference.kt b/src/com/android/settings/fuelgauge/BatteryHeaderPreference.kt
index 95d73dd..49f219f 100644
--- a/src/com/android/settings/fuelgauge/BatteryHeaderPreference.kt
+++ b/src/com/android/settings/fuelgauge/BatteryHeaderPreference.kt
@@ -102,10 +102,10 @@
override fun getMaxValue(context: Context): Int = 100
- override fun getReadPermit(context: Context, myUid: Int, callingUid: Int) =
+ override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) =
ReadWritePermit.ALLOW
- override fun getWritePermit(context: Context, value: Int?, myUid: Int, callingUid: Int) =
+ override fun getWritePermit(context: Context, value: Int?, callingPid: Int, callingUid: Int) =
ReadWritePermit.DISALLOW
companion object {
diff --git a/src/com/android/settings/fuelgauge/batterysaver/BatterySaverPreference.kt b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverPreference.kt
index 93da40d..ce1c1be 100644
--- a/src/com/android/settings/fuelgauge/batterysaver/BatterySaverPreference.kt
+++ b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverPreference.kt
@@ -43,11 +43,15 @@
override fun storage(context: Context) = BatterySaverStore(context)
- override fun getReadPermit(context: Context, myUid: Int, callingUid: Int) =
+ override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) =
ReadWritePermit.ALLOW
- override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) =
- ReadWritePermit.ALLOW
+ override fun getWritePermit(
+ context: Context,
+ value: Boolean?,
+ callingPid: Int,
+ callingUid: Int,
+ ) = ReadWritePermit.ALLOW
override val sensitivityLevel
get() = SensitivityLevel.NO_SENSITIVITY
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java
index 5e17f4b..2edbf99 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java
@@ -274,7 +274,6 @@
}
if (mDailyChartView != dailyChartView || mHourlyChartView != hourlyChartView) {
mHandler.post(() -> setBatteryChartViewInner(dailyChartView, hourlyChartView));
- animateBatteryChartViewGroup();
}
if (mBatteryChartViewGroup != null) {
final View grandparentView = (View) mBatteryChartViewGroup.getParent();
diff --git a/src/com/android/settings/gestures/OneHandedSettings.java b/src/com/android/settings/gestures/OneHandedSettings.java
index 0378888..0a2599e 100644
--- a/src/com/android/settings/gestures/OneHandedSettings.java
+++ b/src/com/android/settings/gestures/OneHandedSettings.java
@@ -22,9 +22,9 @@
import android.content.Context;
import android.os.Bundle;
import android.os.UserHandle;
-import android.util.Log;
import android.view.LayoutInflater;
import android.view.ViewGroup;
+import android.widget.CompoundButton;
import androidx.recyclerview.widget.RecyclerView;
@@ -33,7 +33,6 @@
import com.android.settings.R;
import com.android.settings.accessibility.AccessibilityFragmentUtils;
import com.android.settings.accessibility.AccessibilityShortcutPreferenceFragment;
-import com.android.settings.accessibility.AccessibilityUtil.QuickSettingsTooltipType;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.search.SearchIndexable;
import com.android.settingslib.search.SearchIndexableRaw;
@@ -82,12 +81,7 @@
final MainSwitchPreference mainSwitchPreference =
getPreferenceScreen().findPreference(ONE_HANDED_MAIN_SWITCH_KEY);
- mainSwitchPreference.addOnSwitchChangeListener((switchView, isChecked) -> {
- switchView.setChecked(isChecked);
- if (isChecked) {
- showQuickSettingsTooltipIfNeeded(QuickSettingsTooltipType.GUIDE_TO_DIRECT_USE);
- }
- });
+ mainSwitchPreference.addOnSwitchChangeListener(CompoundButton::setChecked);
}
@Override
@@ -146,24 +140,6 @@
}
@Override
- protected ComponentName getTileComponentName() {
- return AccessibilityShortcutController.ONE_HANDED_TILE_COMPONENT_NAME;
- }
-
- @Override
- protected CharSequence getTileTooltipContent(@QuickSettingsTooltipType int type) {
- final Context context = getContext();
- if (context == null) {
- Log.w(TAG, "OneHandedSettings not attached to a context.");
- return null;
- }
- return type == QuickSettingsTooltipType.GUIDE_TO_EDIT
- ? context.getText(R.string.accessibility_one_handed_mode_qs_tooltip_content)
- : context.getText(
- R.string.accessibility_one_handed_mode_auto_added_qs_tooltip_content);
- }
-
- @Override
protected int getPreferenceScreenResId() {
return R.xml.one_handed_settings;
}
diff --git a/src/com/android/settings/inputmethod/TouchpadAndMouseSettingsController.java b/src/com/android/settings/inputmethod/TouchpadAndMouseSettingsController.java
index 86e044c..163027b 100644
--- a/src/com/android/settings/inputmethod/TouchpadAndMouseSettingsController.java
+++ b/src/com/android/settings/inputmethod/TouchpadAndMouseSettingsController.java
@@ -26,6 +26,7 @@
import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settings.keyboard.Flags;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.core.lifecycle.events.OnStop;
@@ -85,13 +86,15 @@
@Override
public int getAvailabilityStatus() {
+ boolean isNewPageFlagDisabled = !Flags.keyboardAndTouchpadA11yNewPageEnabled();
boolean isFeatureOn = FeatureFlagUtils
.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_TRACKPAD);
boolean isTouchpad = InputPeripheralsSettingsUtils.isTouchpad();
boolean isPointerCustomizationEnabled =
android.view.flags.Flags.enableVectorCursorA11ySettings();
boolean isMouse = InputPeripheralsSettingsUtils.isMouse();
- return (isFeatureOn && isTouchpad) || (isPointerCustomizationEnabled && isMouse) ? AVAILABLE
+ return ((isFeatureOn && isTouchpad) || (isPointerCustomizationEnabled && isMouse))
+ && isNewPageFlagDisabled ? AVAILABLE
: CONDITIONALLY_UNAVAILABLE;
}
}
diff --git a/src/com/android/settings/language/DefaultVoiceInputPreferenceController.java b/src/com/android/settings/language/DefaultVoiceInputPreferenceController.java
index 74c156c..c0b65df 100644
--- a/src/com/android/settings/language/DefaultVoiceInputPreferenceController.java
+++ b/src/com/android/settings/language/DefaultVoiceInputPreferenceController.java
@@ -22,9 +22,12 @@
import android.content.pm.PackageManager;
import android.text.TextUtils;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
+import com.android.internal.annotations.Initializer;
import com.android.settings.applications.defaultapps.DefaultAppPreferenceController;
import com.android.settingslib.applications.DefaultAppInfo;
import com.android.settingslib.core.lifecycle.Lifecycle;
@@ -43,7 +46,8 @@
private Preference mPreference;
private Context mContext;
- public DefaultVoiceInputPreferenceController(Context context, Lifecycle lifecycle) {
+ public DefaultVoiceInputPreferenceController(
+ @NonNull Context context, @Nullable Lifecycle lifecycle) {
super(context);
mContext = context;
mHelper = new VoiceInputHelper(context);
@@ -65,6 +69,7 @@
}
@Override
+ @Initializer
public void displayPreference(PreferenceScreen screen) {
mScreen = screen;
mPreference = screen.findPreference(getPreferenceKey());
diff --git a/src/com/android/settings/language/LanguageAndRegionPreferenceController.java b/src/com/android/settings/language/LanguageAndRegionPreferenceController.java
new file mode 100644
index 0000000..4e554ed
--- /dev/null
+++ b/src/com/android/settings/language/LanguageAndRegionPreferenceController.java
@@ -0,0 +1,44 @@
+/*
+ * 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.language;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.flags.Flags;
+
+/**
+ * This is a display controller for new language activity entry.
+ * TODO(b/379962955): When new layout is on board, all old layouts should be removed.
+ */
+public class LanguageAndRegionPreferenceController extends BasePreferenceController {
+
+ public LanguageAndRegionPreferenceController(@NonNull Context context, @NonNull String key) {
+ super(context, key);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ if (!Flags.regionalPreferencesApiEnabled()) {
+ return CONDITIONALLY_UNAVAILABLE;
+ }
+ // TODO: Add setComponentEnabledSetting after LanguageAndRegionSettingsActivity is created.
+ return AVAILABLE;
+ }
+}
diff --git a/src/com/android/settings/language/LanguageAndRegionSettings.java b/src/com/android/settings/language/LanguageAndRegionSettings.java
new file mode 100644
index 0000000..a405878
--- /dev/null
+++ b/src/com/android/settings/language/LanguageAndRegionSettings.java
@@ -0,0 +1,129 @@
+/*
+ * 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.language;
+
+import android.app.Activity;
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.settings.R;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.flags.Flags;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.widget.PreferenceCategoryController;
+import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.search.SearchIndexable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SearchIndexable
+public class LanguageAndRegionSettings extends DashboardFragment {
+
+ private static final String KEY_SPEECH_CATEGORY = "speech_category";
+ private static final String KEY_ON_DEVICE_RECOGNITION = "on_device_recognition_settings";
+ private static final String KEY_TEXT_TO_SPEECH = "tts_settings_summary";
+
+ private static final String TAG = "LanguageAndRegionSettings";
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.SETTINGS_LANGUAGES_CATEGORY;
+ }
+
+ @Override
+ protected String getLogTag() {
+ return TAG;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ // Hack to update action bar title. It's necessary to refresh title because this page user
+ // can change locale from here and fragment won't relaunch. Once language changes, title
+ // must display in the new language.
+ final Activity activity = getActivity();
+ if (activity == null) {
+ return;
+ }
+ activity.setTitle(R.string.languages_settings);
+ }
+
+ @Override
+ public @Nullable String getPreferenceScreenBindingKey(@NonNull Context context) {
+ return LanguageSettingScreen.KEY;
+ }
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.language_and_region_settings;
+ }
+
+ protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
+ return buildPreferenceControllers(context, getSettingsLifecycle());
+ }
+
+ private static List<AbstractPreferenceController> buildPreferenceControllers(
+ @NonNull Context context, @Nullable Lifecycle lifecycle) {
+ final List<AbstractPreferenceController> controllers = new ArrayList<>();
+
+ final DefaultVoiceInputPreferenceController defaultVoiceInputPreferenceController =
+ new DefaultVoiceInputPreferenceController(context, lifecycle);
+ final TtsPreferenceController ttsPreferenceController =
+ new TtsPreferenceController(context, KEY_TEXT_TO_SPEECH);
+ final OnDeviceRecognitionPreferenceController onDeviceRecognitionPreferenceController =
+ new OnDeviceRecognitionPreferenceController(context, KEY_ON_DEVICE_RECOGNITION);
+
+ controllers.add(defaultVoiceInputPreferenceController);
+ controllers.add(ttsPreferenceController);
+ List<AbstractPreferenceController> speechCategoryChildren = new ArrayList<>(
+ List.of(defaultVoiceInputPreferenceController, ttsPreferenceController));
+
+ if (onDeviceRecognitionPreferenceController.isAvailable()) {
+ controllers.add(onDeviceRecognitionPreferenceController);
+ speechCategoryChildren.add(onDeviceRecognitionPreferenceController);
+ }
+
+ controllers.add(new PreferenceCategoryController(context, KEY_SPEECH_CATEGORY)
+ .setChildren(speechCategoryChildren));
+
+ return controllers;
+ }
+
+ public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider(R.xml.language_and_region_settings) {
+
+ @Override
+ @NonNull
+ public List<AbstractPreferenceController> createPreferenceControllers(
+ @NonNull Context context) {
+ return buildPreferenceControllers(context, null);
+ }
+
+ @Override
+ protected boolean isPageSearchEnabled(Context context) {
+ if (Flags.regionalPreferencesApiEnabled()) {
+ return true;
+ }
+ return false;
+ }
+ };
+}
diff --git a/src/com/android/settings/language/LanguagePreferenceController.java b/src/com/android/settings/language/LanguagePreferenceController.java
index 84624a2..90aaec4 100644
--- a/src/com/android/settings/language/LanguagePreferenceController.java
+++ b/src/com/android/settings/language/LanguagePreferenceController.java
@@ -22,6 +22,7 @@
import com.android.settings.Settings;
import com.android.settings.core.BasePreferenceController;
+import com.android.settings.flags.Flags;
/**
* This is a display controller for new language activity entry.
@@ -34,6 +35,10 @@
@Override
public int getAvailabilityStatus() {
+ if (Flags.regionalPreferencesApiEnabled()) {
+ setActivityEnabled(mContext, Settings.LanguageSettingsActivity.class, false);
+ return CONDITIONALLY_UNAVAILABLE;
+ }
setActivityEnabled(mContext, Settings.LanguageSettingsActivity.class, true);
return AVAILABLE;
}
diff --git a/src/com/android/settings/language/LanguageSettings.java b/src/com/android/settings/language/LanguageSettings.java
index d992ff2..58df053 100644
--- a/src/com/android/settings/language/LanguageSettings.java
+++ b/src/com/android/settings/language/LanguageSettings.java
@@ -25,6 +25,7 @@
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.flags.Flags;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.widget.PreferenceCategoryController;
import com.android.settingslib.core.AbstractPreferenceController;
@@ -116,6 +117,9 @@
}
@Override
protected boolean isPageSearchEnabled(Context context) {
+ if (Flags.regionalPreferencesApiEnabled()) {
+ return false;
+ }
return true;
}
};
diff --git a/src/com/android/settings/network/AdaptiveConnectivityTogglePreference.kt b/src/com/android/settings/network/AdaptiveConnectivityTogglePreference.kt
index c29ec6e..77a141e 100644
--- a/src/com/android/settings/network/AdaptiveConnectivityTogglePreference.kt
+++ b/src/com/android/settings/network/AdaptiveConnectivityTogglePreference.kt
@@ -35,11 +35,15 @@
override fun storage(context: Context): KeyValueStore =
AdaptiveConnectivityToggleStorage(context, SettingsSecureStore.get(context))
- override fun getReadPermit(context: Context, myUid: Int, callingUid: Int) =
+ override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) =
ReadWritePermit.ALLOW
- override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) =
- ReadWritePermit.ALLOW
+ override fun getWritePermit(
+ context: Context,
+ value: Boolean?,
+ callingPid: Int,
+ callingUid: Int,
+ ) = ReadWritePermit.ALLOW
override val sensitivityLevel
get() = SensitivityLevel.NO_SENSITIVITY
diff --git a/src/com/android/settings/network/AirplaneModePreference.kt b/src/com/android/settings/network/AirplaneModePreference.kt
index e3b7f55..c9377eb 100644
--- a/src/com/android/settings/network/AirplaneModePreference.kt
+++ b/src/com/android/settings/network/AirplaneModePreference.kt
@@ -65,10 +65,15 @@
override val restrictionKeys
get() = arrayOf(UserManager.DISALLOW_AIRPLANE_MODE)
- override fun getReadPermit(context: Context, myUid: Int, callingUid: Int) =
+ override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) =
ReadWritePermit.ALLOW
- override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) =
+ override fun getWritePermit(
+ context: Context,
+ value: Boolean?,
+ callingPid: Int,
+ callingUid: Int,
+ ) =
when {
isSatelliteOn(context) || isInEcmMode(context) -> ReadWritePermit.DISALLOW
else -> ReadWritePermit.ALLOW
diff --git a/src/com/android/settings/network/MobileDataPreference.kt b/src/com/android/settings/network/MobileDataPreference.kt
index d285a8c..04649d9 100644
--- a/src/com/android/settings/network/MobileDataPreference.kt
+++ b/src/com/android/settings/network/MobileDataPreference.kt
@@ -16,6 +16,7 @@
package com.android.settings.network
+import android.Manifest
import android.content.Context
import android.telephony.SubscriptionManager
import com.android.settings.R
@@ -45,11 +46,30 @@
override fun storage(context: Context): KeyValueStore = MobileDataStorage(context)
- override fun getReadPermit(context: Context, myUid: Int, callingUid: Int) =
+ override fun getReadPermissions(context: Context) =
+ arrayOf(
+ // required by TelephonyManager.isDataEnabledForReason
+ Manifest.permission.ACCESS_NETWORK_STATE,
+ Manifest.permission.READ_PHONE_STATE,
+ Manifest.permission.MODIFY_PHONE_STATE,
+ Manifest.permission.READ_BASIC_PHONE_STATE,
+ )
+
+ override fun getWritePermissions(context: Context) =
+ arrayOf(
+ // required by TelephonyManager.setDataEnabledForReason
+ Manifest.permission.MODIFY_PHONE_STATE
+ )
+
+ override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) =
ReadWritePermit.ALLOW
- override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) =
- ReadWritePermit.ALLOW
+ override fun getWritePermit(
+ context: Context,
+ value: Boolean?,
+ callingPid: Int,
+ callingUid: Int,
+ ) = ReadWritePermit.ALLOW
override val sensitivityLevel
get() = SensitivityLevel.LOW_SENSITIVITY
diff --git a/src/com/android/settings/network/NetworkProviderSettings.java b/src/com/android/settings/network/NetworkProviderSettings.java
index c776987..1fc9101 100644
--- a/src/com/android/settings/network/NetworkProviderSettings.java
+++ b/src/com/android/settings/network/NetworkProviderSettings.java
@@ -706,7 +706,7 @@
forget(mSelectedWifiEntry);
return true;
case MENU_ID_SHARE:
- WifiDppUtils.showLockScreen(getContext(),
+ WifiDppUtils.showLockScreenForWifiSharing(getContext(),
() -> launchWifiDppConfiguratorActivity(mSelectedWifiEntry));
return true;
case MENU_ID_MODIFY:
diff --git a/src/com/android/settings/network/OWNERS b/src/com/android/settings/network/OWNERS
index a63a825..ad3c9da 100644
--- a/src/com/android/settings/network/OWNERS
+++ b/src/com/android/settings/network/OWNERS
@@ -1,12 +1,8 @@
# Default reviewers for this and subdirectories.
-allenwtsu@google.com
-andychou@google.com
-bonianchen@google.com
-changbetty@google.com
-leechou@google.com
+chaohuiw@google.com
+evanwu@google.com
songferngwang@google.com
tomhsu@google.com
wengsu@google.com
-zoeychen@google.com
# Emergency approvers in case the above are not available
diff --git a/src/com/android/settings/network/apn/ApnPreference.java b/src/com/android/settings/network/apn/ApnPreference.java
index 55258c1..3ba3508 100644
--- a/src/com/android/settings/network/apn/ApnPreference.java
+++ b/src/com/android/settings/network/apn/ApnPreference.java
@@ -24,31 +24,29 @@
import android.net.Uri;
import android.provider.Telephony;
import android.telephony.SubscriptionManager;
-import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.RadioButton;
-import android.widget.RelativeLayout;
import android.widget.Toast;
-import androidx.annotation.Nullable;
+import androidx.annotation.NonNull;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
import com.android.settings.flags.Flags;
import com.android.settings.spa.SpaActivity;
+import com.android.settingslib.widget.TwoTargetPreference;
/**
* Preference of APN UI entry
*/
-public class ApnPreference extends Preference
- implements CompoundButton.OnCheckedChangeListener, View.OnClickListener {
+public class ApnPreference extends TwoTargetPreference
+ implements CompoundButton.OnCheckedChangeListener, Preference.OnPreferenceClickListener {
private static final String TAG = "ApnPreference";
private boolean mIsChecked = false;
- @Nullable
- private RadioButton mRadioButton = null;
+ private RadioButton mRadioButton;
private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
private boolean mProtectFromCheckedChange = false;
private boolean mDefaultSelectable = true;
@@ -57,50 +55,36 @@
/**
* Constructor of Preference
*/
- public ApnPreference(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- // Primary target and radio button could be selectable, but entire preference itself is not
- // selectable.
- setSelectable(false);
- }
-
- /**
- * Constructor of Preference
- */
- public ApnPreference(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.apnPreferenceStyle);
- }
-
- /**
- * Constructor of Preference
- */
public ApnPreference(Context context) {
- this(context, null);
+ super(context);
+ setOnPreferenceClickListener(this);
}
@Override
- public void onBindViewHolder(PreferenceViewHolder view) {
- super.onBindViewHolder(view);
+ public void onBindViewHolder(PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
- final RelativeLayout textArea = (RelativeLayout) view.findViewById(R.id.text_layout);
- textArea.setOnClickListener(this);
-
- final View radioButtonFrame = view.itemView.requireViewById(R.id.apn_radio_button_frame);
- final RadioButton rb = view.itemView.requireViewById(R.id.apn_radiobutton);
- mRadioButton = rb;
- if (mDefaultSelectable) {
- radioButtonFrame.setOnClickListener((v) -> {
- rb.performClick();
- });
- rb.setOnCheckedChangeListener(this);
-
- mProtectFromCheckedChange = true;
- rb.setChecked(mIsChecked);
- mProtectFromCheckedChange = false;
- radioButtonFrame.setVisibility(View.VISIBLE);
- } else {
- radioButtonFrame.setVisibility(View.GONE);
+ final RadioButton rb = (RadioButton) holder.findViewById(android.R.id.checkbox);
+ final View radioButtonFrame = holder.findViewById(android.R.id.widget_frame);
+ if (rb == null || radioButtonFrame == null) {
+ throw new RuntimeException("Failed to load system layout.");
}
+
+ mRadioButton = rb;
+ radioButtonFrame.setOnClickListener(v -> rb.performClick());
+ rb.setOnCheckedChangeListener(this);
+ setIsChecked(mIsChecked);
+ rb.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ protected boolean shouldHideSecondTarget() {
+ return !mDefaultSelectable;
+ }
+
+ @Override
+ protected int getSecondTargetResId() {
+ return R.layout.preference_widget_radiobutton;
}
/**
@@ -118,6 +102,7 @@
/**
* Change the preference status.
*/
+ @Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Log.i(TAG, "ID: " + getKey() + " :" + isChecked);
if (mProtectFromCheckedChange) {
@@ -130,19 +115,14 @@
}
@Override
- public void onClick(View layoutView) {
- super.onClick();
+ public boolean onPreferenceClick(@NonNull Preference preference) {
final Context context = getContext();
final int pos = Integer.parseInt(getKey());
- if (context == null) {
- Log.w(TAG, "No context available for pos=" + pos);
- return;
- }
if (mHideDetails) {
Toast.makeText(context, context.getString(R.string.cannot_change_apn_toast),
Toast.LENGTH_LONG).show();
- return;
+ return true;
}
final Uri url = ContentUris.withAppendedId(Telephony.Carriers.CONTENT_URI, pos);
@@ -156,6 +136,7 @@
editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
context.startActivity(editIntent);
}
+ return true;
}
public void setDefaultSelectable(boolean defaultSelectable) {
diff --git a/src/com/android/settings/network/telephony/SubscriptionRepository.kt b/src/com/android/settings/network/telephony/SubscriptionRepository.kt
index 43bba07..33c1e655 100644
--- a/src/com/android/settings/network/telephony/SubscriptionRepository.kt
+++ b/src/com/android/settings/network/telephony/SubscriptionRepository.kt
@@ -40,6 +40,7 @@
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.shareIn
+import java.util.stream.Collectors
private const val TAG = "SubscriptionRepository"
@@ -154,6 +155,18 @@
.conflate()
.flowOn(Dispatchers.Default)
+ fun removableSubscriptionInfoListFlow(): Flow<List<SubscriptionInfo>> {
+ return subscriptionsChangedFlow()
+ .map {
+ subscriptionManager.availableSubscriptionInfoList?.stream()
+ ?.filter { sub: SubscriptionInfo -> !sub.isEmbedded }
+ ?.collect(Collectors.toList()) ?: emptyList()
+ }
+ .conflate()
+ .onEach { Log.d(TAG, "getRemovableSubscriptionVisibleFlow: $it") }
+ .flowOn(Dispatchers.Default)
+ }
+
@OptIn(ExperimentalCoroutinesApi::class)
fun phoneNumberFlow(subId: Int): Flow<String?> =
activeSubscriptionInfoFlow(subId).flatMapLatest { subInfo ->
diff --git a/src/com/android/settings/network/tether/BluetoothTetherSwitchPreference.kt b/src/com/android/settings/network/tether/BluetoothTetherSwitchPreference.kt
index a22df12..a6a400b 100644
--- a/src/com/android/settings/network/tether/BluetoothTetherSwitchPreference.kt
+++ b/src/com/android/settings/network/tether/BluetoothTetherSwitchPreference.kt
@@ -77,11 +77,15 @@
return !dataSaverBackend.isDataSaverEnabled
}
- override fun getReadPermit(context: Context, myUid: Int, callingUid: Int) =
+ override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) =
ReadWritePermit.ALLOW
- override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) =
- ReadWritePermit.ALLOW
+ override fun getWritePermit(
+ context: Context,
+ value: Boolean?,
+ callingPid: Int,
+ callingUid: Int,
+ ) = ReadWritePermit.ALLOW
override val sensitivityLevel: Int
get() = SensitivityLevel.LOW_SENSITIVITY
diff --git a/src/com/android/settings/notification/CallVolumePreference.kt b/src/com/android/settings/notification/CallVolumePreference.kt
index 031687f..0e45b23 100644
--- a/src/com/android/settings/notification/CallVolumePreference.kt
+++ b/src/com/android/settings/notification/CallVolumePreference.kt
@@ -76,10 +76,10 @@
}
}
- override fun getReadPermit(context: Context, myUid: Int, callingUid: Int) =
+ override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) =
ReadWritePermit.ALLOW
- override fun getWritePermit(context: Context, value: Int?, myUid: Int, callingUid: Int) =
+ override fun getWritePermit(context: Context, value: Int?, callingPid: Int, callingUid: Int) =
ReadWritePermit.ALLOW
override val sensitivityLevel
diff --git a/src/com/android/settings/notification/MediaVolumePreference.kt b/src/com/android/settings/notification/MediaVolumePreference.kt
index a6d9c41..b644381 100644
--- a/src/com/android/settings/notification/MediaVolumePreference.kt
+++ b/src/com/android/settings/notification/MediaVolumePreference.kt
@@ -77,10 +77,10 @@
}
}
- override fun getReadPermit(context: Context, myUid: Int, callingUid: Int) =
+ override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) =
ReadWritePermit.ALLOW
- override fun getWritePermit(context: Context, value: Int?, myUid: Int, callingUid: Int) =
+ override fun getWritePermit(context: Context, value: Int?, callingPid: Int, callingUid: Int) =
ReadWritePermit.ALLOW
override val sensitivityLevel
diff --git a/src/com/android/settings/notification/SeparateRingVolumePreference.kt b/src/com/android/settings/notification/SeparateRingVolumePreference.kt
index 6a22b12..0e557c4 100644
--- a/src/com/android/settings/notification/SeparateRingVolumePreference.kt
+++ b/src/com/android/settings/notification/SeparateRingVolumePreference.kt
@@ -94,10 +94,10 @@
}
}
- override fun getReadPermit(context: Context, myUid: Int, callingUid: Int) =
+ override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) =
ReadWritePermit.ALLOW
- override fun getWritePermit(context: Context, value: Int?, myUid: Int, callingUid: Int) =
+ override fun getWritePermit(context: Context, value: Int?, callingPid: Int, callingUid: Int) =
ReadWritePermit.ALLOW
override val sensitivityLevel
diff --git a/src/com/android/settings/regionalpreferences/ExtensionTypes.java b/src/com/android/settings/regionalpreferences/ExtensionTypes.java
index b860d29..5a2057b 100644
--- a/src/com/android/settings/regionalpreferences/ExtensionTypes.java
+++ b/src/com/android/settings/regionalpreferences/ExtensionTypes.java
@@ -25,12 +25,14 @@
public static final String FIRST_DAY_OF_WEEK = "fw";
public static final String NUMBERING_SYSTEM = "nu";
public static final String TEMPERATURE_UNIT = "mu";
+ public static final String MEASUREMENT_SYSTEM = "ms";
@StringDef({
FIRST_DAY_OF_WEEK,
CALENDAR,
TEMPERATURE_UNIT,
- NUMBERING_SYSTEM
+ NUMBERING_SYSTEM,
+ MEASUREMENT_SYSTEM
})
public @interface Values {}
}
diff --git a/src/com/android/settings/regionalpreferences/MeasurementSystemController.java b/src/com/android/settings/regionalpreferences/MeasurementSystemController.java
new file mode 100644
index 0000000..1b6daaf
--- /dev/null
+++ b/src/com/android/settings/regionalpreferences/MeasurementSystemController.java
@@ -0,0 +1,68 @@
+/**
+ * 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.regionalpreferences;
+
+import android.content.Context;
+import android.os.LocaleList;
+
+import androidx.annotation.NonNull;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.flags.Flags;
+
+import java.util.Locale;
+
+/** A controller for the entry of measurement system page */
+public class MeasurementSystemController extends BasePreferenceController {
+ private static final String TAG = "MeasurementSystemController";
+ public MeasurementSystemController(@NonNull Context context, @NonNull String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ if (Flags.regionalPreferencesApiEnabled()) {
+ return AVAILABLE;
+ }
+ return CONDITIONALLY_UNAVAILABLE;
+ }
+
+ @Override
+ @NonNull
+ public CharSequence getSummary() {
+ LocaleList localeList = LocaleList.getDefault();
+ Locale locale = localeList.get(0);
+ return getMeasurementSystem(locale);
+ }
+
+ private String getMeasurementSystem(Locale locale) {
+ String type = locale.getUnicodeLocaleType(
+ RegionalPreferencesDataUtils.EXTENSION_TYPE_MEASUREMENT_SYSTEM);
+ if (type != null) {
+ if (type.equals(RegionalPreferencesDataUtils.MEASUREMENT_SYSTEM_METRIC)) {
+ return mContext.getString(R.string.metric_measurement_system);
+ }
+ if (type.equals(RegionalPreferencesDataUtils.MEASUREMENT_SYSTEM_UK)) {
+ return mContext.getString(R.string.uk_measurement_system);
+ }
+ return mContext.getString(R.string.us_measurement_system);
+ } else {
+ return mContext.getString(R.string.default_string_of_regional_preference);
+ }
+ }
+}
diff --git a/src/com/android/settings/regionalpreferences/MeasurementSystemItemCategoryController.java b/src/com/android/settings/regionalpreferences/MeasurementSystemItemCategoryController.java
new file mode 100644
index 0000000..03b8817
--- /dev/null
+++ b/src/com/android/settings/regionalpreferences/MeasurementSystemItemCategoryController.java
@@ -0,0 +1,62 @@
+/**
+ * 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.regionalpreferences;
+
+import android.content.Context;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.widget.PreferenceCategoryController;
+
+/** Category preference controller for measurement system preferences. */
+public class MeasurementSystemItemCategoryController extends PreferenceCategoryController {
+
+ private static final String LOG_TAG = "MeasurementSystemItemCategoryController";
+ private static final String KEY_PREFERENCE_CATEGORY_MEASUREMENT_SYSTEM_ITEM =
+ "measurement_system_item_category";
+ private static final String KEY_PREFERENCE_MEASUREMENT_SYSTEM_ITEM =
+ "measurement_system_item_list";
+
+ public MeasurementSystemItemCategoryController(@NonNull Context context, @NonNull String key) {
+ super(context, key);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+
+ @Override
+ public void displayPreference(@NonNull PreferenceScreen screen) {
+ super.displayPreference(screen);
+ PreferenceCategory preferenceCategory =
+ screen.findPreference(KEY_PREFERENCE_CATEGORY_MEASUREMENT_SYSTEM_ITEM);
+ if (preferenceCategory == null) {
+ Log.d(LOG_TAG, "displayPreference(), Can not find the category.");
+ return;
+ }
+ preferenceCategory.setVisible(isAvailable());
+ MeasurementSystemItemListController measurementSystemItemListController =
+ new MeasurementSystemItemListController(
+ mContext,
+ KEY_PREFERENCE_MEASUREMENT_SYSTEM_ITEM);
+ measurementSystemItemListController.displayPreference(screen);
+ }
+}
diff --git a/src/com/android/settings/regionalpreferences/MeasurementSystemItemFragment.java b/src/com/android/settings/regionalpreferences/MeasurementSystemItemFragment.java
new file mode 100644
index 0000000..231a34e
--- /dev/null
+++ b/src/com/android/settings/regionalpreferences/MeasurementSystemItemFragment.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.regionalpreferences;
+
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+
+import com.android.settings.R;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.flags.Flags;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.search.SearchIndexable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Main fragment to display measurement system. */
+@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
+public class MeasurementSystemItemFragment extends DashboardFragment {
+
+ private static final String LOG_TAG = "MeasurementSystemItemFragment";
+ private static final String KEY_PREFERENCE_CATEGORY_MEASUREMENT_SYSTEM_ITEM =
+ "measurement_system_item_category";
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.regional_preferences_measurement_system;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.MEASUREMENT_SYSTEM_PREFERENCE;
+ }
+
+ @Override
+ protected String getLogTag() {
+ return LOG_TAG;
+ }
+
+ @Override
+ protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
+ final List<AbstractPreferenceController> controllers = new ArrayList<>();
+ controllers.add(new MeasurementSystemItemCategoryController(context,
+ KEY_PREFERENCE_CATEGORY_MEASUREMENT_SYSTEM_ITEM));
+ return controllers;
+ }
+
+ public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider(R.xml.regional_preferences_measurement_system) {
+ @Override
+ protected boolean isPageSearchEnabled(Context context) {
+ if (!Flags.regionalPreferencesApiEnabled()) {
+ return false;
+ }
+ return true;
+ }
+ };
+}
diff --git a/src/com/android/settings/regionalpreferences/MeasurementSystemItemListController.java b/src/com/android/settings/regionalpreferences/MeasurementSystemItemListController.java
new file mode 100644
index 0000000..061c4f8
--- /dev/null
+++ b/src/com/android/settings/regionalpreferences/MeasurementSystemItemListController.java
@@ -0,0 +1,69 @@
+/**
+ * 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.regionalpreferences;
+
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+
+import com.android.settings.R;
+
+/** A controller for handling all measurement system preferences. */
+public class MeasurementSystemItemListController extends
+ RegionalPreferenceListBasePreferenceController {
+
+ private static final String KEY_PREFERENCE_CATEGORY_MEASUREMENT_SYSTEM_ITEM =
+ "measurement_system_item_category";
+ private static final String KEY_PREFERENCE_MEASUREMENT_SYSTEM_ITEM =
+ "measurement_system_item_list";
+
+ public MeasurementSystemItemListController(@NonNull Context context, @NonNull String key) {
+ super(context, key);
+ }
+
+ @Override
+ protected String getPreferenceTitle(String item) {
+ return RegionalPreferencesDataUtils.measurementSystemConverter(mContext, item);
+ }
+
+ @Override
+ protected String getPreferenceCategoryKey() {
+ return KEY_PREFERENCE_CATEGORY_MEASUREMENT_SYSTEM_ITEM;
+ }
+
+ @Override
+ @NonNull
+ public String getPreferenceKey() {
+ return KEY_PREFERENCE_MEASUREMENT_SYSTEM_ITEM;
+ }
+
+ @Override
+ protected String getExtensionTypes() {
+ return ExtensionTypes.MEASUREMENT_SYSTEM;
+ }
+
+ @Override
+ protected String[] getUnitValues() {
+ return mContext.getResources().getStringArray(R.array.measurement_system);
+ }
+
+ @Override
+ protected int getMetricsActionKey() {
+ return SettingsEnums.ACTION_SET_MEASUREMENT_SYSTEM;
+ }
+}
diff --git a/src/com/android/settings/regionalpreferences/RegionalPreferenceListBasePreferenceController.java b/src/com/android/settings/regionalpreferences/RegionalPreferenceListBasePreferenceController.java
index dda0579..606307f 100644
--- a/src/com/android/settings/regionalpreferences/RegionalPreferenceListBasePreferenceController.java
+++ b/src/com/android/settings/regionalpreferences/RegionalPreferenceListBasePreferenceController.java
@@ -65,8 +65,8 @@
RegionalPreferencesDataUtils.savePreference(mContext, getExtensionTypes(),
item.equals(RegionalPreferencesDataUtils.DEFAULT_VALUE)
? null : item);
- String metrics =
- getMetricsActionKey() == SettingsEnums.ACTION_SET_FIRST_DAY_OF_WEEK ? ""
+ String metrics = shouldUseEmptyMetrics()
+ ? ""
: getPreferenceTitle(value) + " > " + getPreferenceTitle(item);
mMetricsFeatureProvider.action(mContext, getMetricsActionKey(), metrics);
});
@@ -79,6 +79,14 @@
return AVAILABLE;
}
+ private boolean shouldUseEmptyMetrics() {
+ if (getMetricsActionKey() == SettingsEnums.ACTION_SET_FIRST_DAY_OF_WEEK
+ || getMetricsActionKey() == SettingsEnums.ACTION_SET_MEASUREMENT_SYSTEM) {
+ return true;
+ }
+ return false;
+ }
+
protected abstract String getPreferenceTitle(String item);
protected abstract String getPreferenceCategoryKey();
diff --git a/src/com/android/settings/regionalpreferences/RegionalPreferencesDataUtils.java b/src/com/android/settings/regionalpreferences/RegionalPreferencesDataUtils.java
index d1ae40b..669bcdd 100644
--- a/src/com/android/settings/regionalpreferences/RegionalPreferencesDataUtils.java
+++ b/src/com/android/settings/regionalpreferences/RegionalPreferencesDataUtils.java
@@ -17,6 +17,7 @@
package com.android.settings.regionalpreferences;
import android.content.Context;
+import android.icu.util.LocaleData;
import android.icu.util.ULocale;
import android.os.LocaleList;
import android.provider.Settings;
@@ -32,6 +33,10 @@
/** Provides utils for regional preferences. */
public class RegionalPreferencesDataUtils {
static final String DEFAULT_VALUE = "default";
+ static final String EXTENSION_TYPE_MEASUREMENT_SYSTEM = "ms";
+ static final String MEASUREMENT_SYSTEM_METRIC = "metric";
+ static final String MEASUREMENT_SYSTEM_UK = "uksystem";
+ static final String MEASUREMENT_SYSTEM_US = "ussystem";
static String getDefaultUnicodeExtensionData(Context contxt, String type) {
// 1. Check cache data in Settings provider.
@@ -118,4 +123,30 @@
return context.getString(R.string.default_string_of_regional_preference);
}
}
+
+ static String measurementSystemConverter(Context context, String unit) {
+ switch (unit) {
+ case MEASUREMENT_SYSTEM_METRIC:
+ return context.getString(R.string.metric_measurement_system);
+ case MEASUREMENT_SYSTEM_UK:
+ return context.getString(R.string.uk_measurement_system);
+ case MEASUREMENT_SYSTEM_US:
+ return context.getString(R.string.us_measurement_system);
+ default:
+ return context.getString(R.string.default_string_of_regional_preference);
+ }
+ }
+
+ static String getDefaultMeasurementSystem() {
+ LocaleList localeList = LocaleList.getDefault();
+ Locale locale = localeList.get(0);
+ ULocale uLocale = ULocale.forLocale(locale);
+ if (LocaleData.getMeasurementSystem(uLocale) == LocaleData.MeasurementSystem.SI) {
+ return RegionalPreferencesDataUtils.MEASUREMENT_SYSTEM_METRIC;
+ }
+ if (LocaleData.getMeasurementSystem(uLocale) == LocaleData.MeasurementSystem.UK) {
+ return RegionalPreferencesDataUtils.MEASUREMENT_SYSTEM_UK;
+ }
+ return RegionalPreferencesDataUtils.MEASUREMENT_SYSTEM_US;
+ }
}
diff --git a/src/com/android/settings/service/PreferenceService.kt b/src/com/android/settings/service/PreferenceService.kt
index 3a67762..81ab584 100644
--- a/src/com/android/settings/service/PreferenceService.kt
+++ b/src/com/android/settings/service/PreferenceService.kt
@@ -16,9 +16,9 @@
package com.android.settings.service
+import android.app.Application
import android.os.Binder
import android.os.OutcomeReceiver
-import android.os.Process
import android.service.settings.preferences.GetValueRequest
import android.service.settings.preferences.GetValueResult
import android.service.settings.preferences.MetadataRequest
@@ -26,43 +26,60 @@
import android.service.settings.preferences.SetValueRequest
import android.service.settings.preferences.SetValueResult
import android.service.settings.preferences.SettingsPreferenceService
+import com.android.settingslib.graph.GetPreferenceGraphApiHandler
+import com.android.settingslib.graph.GetPreferenceGraphRequest
import com.android.settingslib.graph.PreferenceGetterApiHandler
+import com.android.settingslib.graph.PreferenceGetterFlags
import com.android.settingslib.graph.PreferenceSetterApiHandler
import com.android.settingslib.ipc.ApiPermissionChecker
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
+import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
-import java.lang.Exception
class PreferenceService : SettingsPreferenceService() {
- private val scope = CoroutineScope(Job() + Dispatchers.Main)
+ private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
private val getApiHandler = PreferenceGetterApiHandler(1, ApiPermissionChecker.alwaysAllow())
private val setApiHandler = PreferenceSetterApiHandler(2, ApiPermissionChecker.alwaysAllow())
+ private val graphApi = GraphProvider(3)
override fun onGetAllPreferenceMetadata(
request: MetadataRequest,
- callback: OutcomeReceiver<MetadataResult, Exception>
+ callback: OutcomeReceiver<MetadataResult, Exception>,
) {
- // TODO(379750656): Update graph API to be usable outside SettingsLib
- callback.onError(UnsupportedOperationException("Not yet supported"))
+ // MUST get pid/uid in binder thread
+ val callingPid = Binder.getCallingPid()
+ val callingUid = Binder.getCallingUid()
+ scope.launch {
+ val graphProto =
+ graphApi.invoke(
+ application,
+ callingPid,
+ callingUid,
+ GetPreferenceGraphRequest(
+ includeValue = false,
+ flags = PreferenceGetterFlags.METADATA,
+ ),
+ )
+ val result = transformCatalystGetMetadataResponse(this@PreferenceService, graphProto)
+ callback.onResult(result)
+ }
}
override fun onGetPreferenceValue(
request: GetValueRequest,
- callback: OutcomeReceiver<GetValueResult, Exception>
+ callback: OutcomeReceiver<GetValueResult, Exception>,
) {
- scope.launch(Dispatchers.IO) {
+ // MUST get pid/uid in binder thread
+ val callingPid = Binder.getCallingPid()
+ val callingUid = Binder.getCallingUid()
+ scope.launch {
val apiRequest = transformFrameworkGetValueRequest(request)
- val response = getApiHandler.invoke(application, Process.myUid(),
- Binder.getCallingPid(), apiRequest)
- val result = transformCatalystGetValueResponse(
- this@PreferenceService,
- request,
- response
- )
+ val response = getApiHandler.invoke(application, callingPid, callingUid, apiRequest)
+ val result =
+ transformCatalystGetValueResponse(this@PreferenceService, request, response)
if (result == null) {
callback.onError(IllegalStateException("No response"))
} else {
@@ -73,20 +90,32 @@
override fun onSetPreferenceValue(
request: SetValueRequest,
- callback: OutcomeReceiver<SetValueResult, Exception>
+ callback: OutcomeReceiver<SetValueResult, Exception>,
) {
- scope.launch(Dispatchers.IO) {
+ // MUST get pid/uid in binder thread
+ val callingPid = Binder.getCallingPid()
+ val callingUid = Binder.getCallingUid()
+ scope.launch {
val apiRequest = transformFrameworkSetValueRequest(request)
if (apiRequest == null) {
callback.onResult(
SetValueResult.Builder(SetValueResult.RESULT_INVALID_REQUEST).build()
)
} else {
- val response = setApiHandler.invoke(application, Process.myUid(),
- Binder.getCallingPid(), apiRequest)
+ val response = setApiHandler.invoke(application, callingPid, callingUid, apiRequest)
callback.onResult(transformCatalystSetValueResponse(response))
}
}
}
+
+ // Basic implementation - we already have permission to access Graph for Metadata via superclass
+ private class GraphProvider(override val id: Int) : GetPreferenceGraphApiHandler(emptySet()) {
+ override fun hasPermission(
+ application: Application,
+ callingPid: Int,
+ callingUid: Int,
+ request: GetPreferenceGraphRequest,
+ ) = true
+ }
}
diff --git a/src/com/android/settings/service/PreferenceServiceRequestTransformer.kt b/src/com/android/settings/service/PreferenceServiceRequestTransformer.kt
index 7a4c7fc..18307e0 100644
--- a/src/com/android/settings/service/PreferenceServiceRequestTransformer.kt
+++ b/src/com/android/settings/service/PreferenceServiceRequestTransformer.kt
@@ -19,6 +19,7 @@
import android.content.Context
import android.service.settings.preferences.GetValueRequest
import android.service.settings.preferences.GetValueResult
+import android.service.settings.preferences.MetadataResult
import android.service.settings.preferences.SetValueRequest
import android.service.settings.preferences.SetValueResult
import android.service.settings.preferences.SettingsPreferenceMetadata
@@ -34,9 +35,55 @@
import com.android.settingslib.graph.proto.PreferenceProto
import com.android.settingslib.graph.proto.PreferenceValueProto
import com.android.settingslib.graph.getText
+import com.android.settingslib.graph.proto.PreferenceGraphProto
+import com.android.settingslib.graph.proto.PreferenceOrGroupProto
import com.android.settingslib.graph.toIntent
import com.android.settingslib.metadata.SensitivityLevel
+/** Transform Catalyst Graph result to Framework GET METADATA result */
+fun transformCatalystGetMetadataResponse(
+ context: Context,
+ graph: PreferenceGraphProto
+): MetadataResult {
+ val preferences = mutableSetOf<PreferenceWithScreen>()
+ // recursive function to visit all nodes in preference group
+ fun traverseGroupOrPref(
+ screenKey: String,
+ groupOrPref: PreferenceOrGroupProto,
+ ) {
+ when (groupOrPref.kindCase) {
+ PreferenceOrGroupProto.KindCase.PREFERENCE ->
+ preferences.add(
+ PreferenceWithScreen(screenKey, groupOrPref.preference)
+ )
+ PreferenceOrGroupProto.KindCase.GROUP -> {
+ for (child in groupOrPref.group.preferencesList) {
+ traverseGroupOrPref(screenKey, child)
+ }
+ }
+ else -> {}
+ }
+ }
+ // traverse all screens and all preferences on screen
+ for ((screenKey, screen) in graph.screensMap) {
+ for (groupOrPref in screen.root.preferencesList) {
+ traverseGroupOrPref(screenKey, groupOrPref)
+ }
+ }
+
+ return if (preferences.isNotEmpty()) {
+ MetadataResult.Builder(MetadataResult.RESULT_OK)
+ .setMetadataList(
+ preferences.map {
+ it.preference.toMetadata(context, it.screenKey)
+ }
+ )
+ .build()
+ } else {
+ MetadataResult.Builder(MetadataResult.RESULT_UNSUPPORTED).build()
+ }
+}
+
/** Translate Framework GET VALUE request to Catalyst GET VALUE request */
fun transformFrameworkGetValueRequest(
request: GetValueRequest,
@@ -133,6 +180,11 @@
return SetValueResult.Builder(resultCode).build()
}
+private data class PreferenceWithScreen(
+ val screenKey: String,
+ val preference: PreferenceProto,
+)
+
private fun PreferenceProto.toMetadata(
context: Context,
screenKey: String
diff --git a/src/com/android/settings/sim/receivers/SimSlotChangeHandler.java b/src/com/android/settings/sim/receivers/SimSlotChangeHandler.java
deleted file mode 100644
index f808924..0000000
--- a/src/com/android/settings/sim/receivers/SimSlotChangeHandler.java
+++ /dev/null
@@ -1,432 +0,0 @@
-/*
- * Copyright (C) 2020 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.sim.receivers;
-
-import static android.content.Context.MODE_PRIVATE;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.os.Looper;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
-import android.telephony.UiccCardInfo;
-import android.telephony.UiccPortInfo;
-import android.telephony.UiccSlotInfo;
-import android.util.Log;
-
-import com.android.settings.flags.Flags;
-import com.android.settings.network.SubscriptionUtil;
-import com.android.settings.network.UiccSlotUtil;
-import com.android.settings.network.UiccSlotsException;
-import com.android.settings.sim.ChooseSimActivity;
-import com.android.settings.sim.DsdsDialogActivity;
-import com.android.settings.sim.SimActivationNotifier;
-import com.android.settings.sim.SimNotificationService;
-import com.android.settings.sim.SwitchToEsimConfirmDialogActivity;
-
-import com.google.common.collect.ImmutableList;
-
-import java.util.List;
-import java.util.stream.Collectors;
-
-import javax.annotation.Nullable;
-
-/** Perform actions after a slot change event is triggered. */
-public class SimSlotChangeHandler {
- private static final String TAG = "SimSlotChangeHandler";
-
- private static final String EUICC_PREFS = "euicc_prefs";
- // Shared preference keys
- private static final String KEY_REMOVABLE_SLOT_STATE = "removable_slot_state";
- private static final String KEY_SUW_PSIM_ACTION = "suw_psim_action";
- // User's last removable SIM insertion / removal action during SUW.
- private static final int LAST_USER_ACTION_IN_SUW_NONE = 0;
- private static final int LAST_USER_ACTION_IN_SUW_INSERT = 1;
- private static final int LAST_USER_ACTION_IN_SUW_REMOVE = 2;
-
- private static volatile SimSlotChangeHandler sSlotChangeHandler;
-
- /** Returns a SIM slot change handler singleton. */
- public static SimSlotChangeHandler get() {
- if (sSlotChangeHandler == null) {
- synchronized (SimSlotChangeHandler.class) {
- if (sSlotChangeHandler == null) {
- sSlotChangeHandler = new SimSlotChangeHandler();
- }
- }
- }
- return sSlotChangeHandler;
- }
-
- private SubscriptionManager mSubMgr;
- private TelephonyManager mTelMgr;
- private Context mContext;
-
- void onSlotsStatusChange(Context context) {
- init(context);
-
- if (Looper.myLooper() == Looper.getMainLooper()) {
- throw new IllegalStateException("Cannot be called from main thread.");
- }
-
- UiccSlotInfo removableSlotInfo = getRemovableUiccSlotInfo();
- if (removableSlotInfo == null) {
- Log.e(TAG, "Unable to find the removable slot. Do nothing.");
- return;
- }
- Log.i(TAG, "The removableSlotInfo: " + removableSlotInfo);
- int lastRemovableSlotState = getLastRemovableSimSlotState(mContext);
- int currentRemovableSlotState = removableSlotInfo.getCardStateInfo();
- Log.d(TAG,
- "lastRemovableSlotState: " + lastRemovableSlotState + ",currentRemovableSlotState: "
- + currentRemovableSlotState);
- boolean isRemovableSimInserted =
- lastRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_ABSENT
- && currentRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_PRESENT;
- boolean isRemovableSimRemoved =
- lastRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_PRESENT
- && currentRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_ABSENT;
-
- // Sets the current removable slot state.
- setRemovableSimSlotState(mContext, currentRemovableSlotState);
-
- if (mTelMgr.getActiveModemCount() > 1) {
- if (!isRemovableSimInserted) {
- Log.d(TAG, "Removable Sim is not inserted in DSDS mode. Do nothing.");
- return;
- }
-
- if (Flags.isDualSimOnboardingEnabled()) {
- // ForNewUi, when the user inserts the psim, showing the sim onboarding for the user
- // to setup the sim switching or the default data subscription in DSDS.
- // Will show dialog for below case.
- // 1. the psim slot is not active.
- // 2. there are one or more active sim.
- handleRemovableSimInsertWhenDsds(removableSlotInfo);
- return;
- } else if (!isMultipleEnabledProfilesSupported()) {
- Log.d(TAG, "The device is already in DSDS mode and no MEP. Do nothing.");
- return;
- } else if (isMultipleEnabledProfilesSupported()) {
- handleRemovableSimInsertUnderDsdsMep(removableSlotInfo);
- return;
- }
- }
-
- if (isRemovableSimInserted) {
- handleSimInsert(removableSlotInfo);
- return;
- }
- if (isRemovableSimRemoved) {
- handleSimRemove(removableSlotInfo);
- return;
- }
- Log.i(TAG, "Do nothing on slot status changes.");
- }
-
- void onSuwFinish(Context context) {
- init(context);
-
- if (Looper.myLooper() == Looper.getMainLooper()) {
- throw new IllegalStateException("Cannot be called from main thread.");
- }
-
- if (mTelMgr.getActiveModemCount() > 1) {
- Log.i(TAG, "The device is already in DSDS mode. Do nothing.");
- return;
- }
-
- UiccSlotInfo removableSlotInfo = getRemovableUiccSlotInfo();
- if (removableSlotInfo == null) {
- Log.e(TAG, "Unable to find the removable slot. Do nothing.");
- return;
- }
-
- boolean embeddedSimExist = getGroupedEmbeddedSubscriptions().size() != 0;
- int removableSlotAction = getSuwRemovableSlotAction(mContext);
- setSuwRemovableSlotAction(mContext, LAST_USER_ACTION_IN_SUW_NONE);
-
- if (embeddedSimExist
- && removableSlotInfo.getCardStateInfo() == UiccSlotInfo.CARD_STATE_INFO_PRESENT) {
- if (mTelMgr.isMultiSimSupported() == TelephonyManager.MULTISIM_ALLOWED) {
- Log.i(TAG, "DSDS condition satisfied. Show notification.");
- SimNotificationService.scheduleSimNotification(
- mContext, SimActivationNotifier.NotificationType.ENABLE_DSDS);
- } else if (removableSlotAction == LAST_USER_ACTION_IN_SUW_INSERT) {
- Log.i(
- TAG,
- "Both removable SIM and eSIM are present. DSDS condition doesn't"
- + " satisfied. User inserted pSIM during SUW. Show choose SIM"
- + " screen.");
- startChooseSimActivity(true);
- }
- } else if (removableSlotAction == LAST_USER_ACTION_IN_SUW_REMOVE) {
- handleSimRemove(removableSlotInfo);
- }
- }
-
- private void init(Context context) {
- mSubMgr =
- (SubscriptionManager)
- context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
- mTelMgr = context.getSystemService(TelephonyManager.class);
- mContext = context;
- }
-
- private void handleSimInsert(UiccSlotInfo removableSlotInfo) {
- Log.i(TAG, "Handle SIM inserted.");
- if (!isSuwFinished(mContext)) {
- Log.i(TAG, "Still in SUW. Handle SIM insertion after SUW is finished");
- setSuwRemovableSlotAction(mContext, LAST_USER_ACTION_IN_SUW_INSERT);
- return;
- }
- if (removableSlotInfo.getPorts().stream().findFirst().get().isActive()) {
- Log.i(TAG, "The removable slot is already active. Do nothing.");
- return;
- }
-
- if (hasActiveEsimSubscription()) {
- if (mTelMgr.isMultiSimSupported() == TelephonyManager.MULTISIM_ALLOWED) {
- Log.i(TAG, "Enabled profile exists. DSDS condition satisfied.");
- if (Flags.isDualSimOnboardingEnabled()) {
- // enable dsds by sim onboarding flow
- handleRemovableSimInsertWhenDsds(removableSlotInfo);
- } else {
- startDsdsDialogActivity();
- }
- } else {
- Log.i(TAG, "Enabled profile exists. DSDS condition not satisfied.");
- startChooseSimActivity(true);
- }
- return;
- }
-
- Log.i(
- TAG,
- "No enabled eSIM profile. Ready to switch to removable slot and show"
- + " notification.");
- try {
- UiccSlotUtil.switchToRemovableSlot(
- UiccSlotUtil.INVALID_PHYSICAL_SLOT_ID, mContext.getApplicationContext());
- } catch (UiccSlotsException e) {
- Log.e(TAG, "Failed to switch to removable slot.");
- return;
- }
- SimNotificationService.scheduleSimNotification(
- mContext, SimActivationNotifier.NotificationType.SWITCH_TO_REMOVABLE_SLOT);
- }
-
- private void handleSimRemove(UiccSlotInfo removableSlotInfo) {
- Log.i(TAG, "Handle SIM removed.");
-
- if (!isSuwFinished(mContext)) {
- Log.i(TAG, "Still in SUW. Handle SIM removal after SUW is finished");
- setSuwRemovableSlotAction(mContext, LAST_USER_ACTION_IN_SUW_REMOVE);
- return;
- }
-
- List<SubscriptionInfo> groupedEmbeddedSubscriptions = getGroupedEmbeddedSubscriptions();
- if (groupedEmbeddedSubscriptions.size() == 0 || !removableSlotInfo.getPorts().stream()
- .findFirst().get().isActive()) {
- Log.i(TAG, "eSIM slot is active or no subscriptions exist. Do nothing."
- + " The removableSlotInfo: " + removableSlotInfo
- + ", groupedEmbeddedSubscriptions: " + groupedEmbeddedSubscriptions);
- return;
- }
-
- // If there is only 1 eSIM profile exists, we ask the user if they want to switch to that
- // profile.
- if (groupedEmbeddedSubscriptions.size() == 1) {
- Log.i(TAG, "Only 1 eSIM profile found. Ask user's consent to switch.");
- startSwitchSlotConfirmDialogActivity(groupedEmbeddedSubscriptions.get(0));
- return;
- }
-
- // If there are more than 1 eSIM profiles installed, we show a screen to let users to choose
- // the number they want to use.
- Log.i(TAG, "Multiple eSIM profiles found. Ask user which subscription to use.");
- startChooseSimActivity(false);
- }
-
- private boolean hasOtherActiveSubInfo(int psimSubId) {
- List<SubscriptionInfo> activeSubs = SubscriptionUtil.getActiveSubscriptions(mSubMgr);
- return activeSubs.stream()
- .anyMatch(subscriptionInfo -> subscriptionInfo.getSubscriptionId() != psimSubId);
- }
-
- private boolean hasAnyPortActiveInSlot(UiccSlotInfo removableSlotInfo) {
- return removableSlotInfo.getPorts().stream().anyMatch(UiccPortInfo::isActive);
- }
-
- private void handleRemovableSimInsertWhenDsds(UiccSlotInfo removableSlotInfo) {
- Log.i(TAG, "ForNewUi: Handle Removable SIM inserted");
- List<SubscriptionInfo> subscriptionInfos = getAvailableRemovableSubscription();
- if (subscriptionInfos.isEmpty()) {
- Log.e(TAG, "Unable to find the removable subscriptionInfo. Do nothing.");
- return;
- }
- Log.d(TAG, "getAvailableRemovableSubscription:" + subscriptionInfos);
- int psimSubId = subscriptionInfos.get(0).getSubscriptionId();
- if (!hasAnyPortActiveInSlot(removableSlotInfo) || hasOtherActiveSubInfo(psimSubId)) {
- Log.d(TAG, "ForNewUi Start Setup flow");
- startSimConfirmDialogActivity(psimSubId);
- }
- }
-
- private void handleRemovableSimInsertUnderDsdsMep(UiccSlotInfo removableSlotInfo) {
- Log.i(TAG, "Handle Removable SIM inserted under DSDS+Mep.");
-
- if (removableSlotInfo.getPorts().stream().findFirst().get().isActive()) {
- Log.i(TAG, "The removable slot is already active. Do nothing. removableSlotInfo: "
- + removableSlotInfo);
- return;
- }
-
- List<SubscriptionInfo> subscriptionInfos = getAvailableRemovableSubscription();
- if (subscriptionInfos.isEmpty()) {
- Log.e(TAG, "Unable to find the removable subscriptionInfo. Do nothing.");
- return;
- }
- Log.d(TAG, "getAvailableRemovableSubscription:" + subscriptionInfos);
- startSimConfirmDialogActivity(subscriptionInfos.get(0).getSubscriptionId());
- }
-
- private int getLastRemovableSimSlotState(Context context) {
- final SharedPreferences prefs = context.getSharedPreferences(EUICC_PREFS, MODE_PRIVATE);
- return prefs.getInt(KEY_REMOVABLE_SLOT_STATE, UiccSlotInfo.CARD_STATE_INFO_ABSENT);
- }
-
- private void setRemovableSimSlotState(Context context, int state) {
- final SharedPreferences prefs = context.getSharedPreferences(EUICC_PREFS, MODE_PRIVATE);
- prefs.edit().putInt(KEY_REMOVABLE_SLOT_STATE, state).apply();
- Log.d(TAG, "setRemovableSimSlotState: " + state);
- }
-
- private int getSuwRemovableSlotAction(Context context) {
- final SharedPreferences prefs = context.getSharedPreferences(EUICC_PREFS, MODE_PRIVATE);
- return prefs.getInt(KEY_SUW_PSIM_ACTION, LAST_USER_ACTION_IN_SUW_NONE);
- }
-
- private void setSuwRemovableSlotAction(Context context, int action) {
- final SharedPreferences prefs = context.getSharedPreferences(EUICC_PREFS, MODE_PRIVATE);
- prefs.edit().putInt(KEY_SUW_PSIM_ACTION, action).apply();
- }
-
- @Nullable
- private UiccSlotInfo getRemovableUiccSlotInfo() {
- UiccSlotInfo[] slotInfos = mTelMgr.getUiccSlotsInfo();
- if (slotInfos == null) {
- Log.e(TAG, "slotInfos is null. Unable to get slot infos.");
- return null;
- }
- for (UiccSlotInfo slotInfo : slotInfos) {
- if (slotInfo != null && slotInfo.isRemovable()) {
- return slotInfo;
- }
- }
- return null;
- }
-
- private static boolean isSuwFinished(Context context) {
- try {
- // DEVICE_PROVISIONED is 0 if still in setup wizard. 1 if setup completed.
- return Settings.Global.getInt(
- context.getContentResolver(), Settings.Global.DEVICE_PROVISIONED)
- == 1;
- } catch (Settings.SettingNotFoundException e) {
- Log.e(TAG, "Cannot get DEVICE_PROVISIONED from the device.", e);
- return false;
- }
- }
-
- private boolean hasActiveEsimSubscription() {
- List<SubscriptionInfo> activeSubs = SubscriptionUtil.getActiveSubscriptions(mSubMgr);
- return activeSubs.stream().anyMatch(SubscriptionInfo::isEmbedded);
- }
-
- private List<SubscriptionInfo> getGroupedEmbeddedSubscriptions() {
- List<SubscriptionInfo> groupedSubscriptions =
- SubscriptionUtil.getSelectableSubscriptionInfoList(mContext);
- if (groupedSubscriptions == null) {
- return ImmutableList.of();
- }
- return ImmutableList.copyOf(
- groupedSubscriptions.stream()
- .filter(sub -> sub.isEmbedded())
- .collect(Collectors.toList()));
- }
-
- protected List<SubscriptionInfo> getAvailableRemovableSubscription() {
- List<SubscriptionInfo> removableSubscriptions =
- SubscriptionUtil.getAvailableSubscriptions(mContext);
- return ImmutableList.copyOf(
- removableSubscriptions.stream()
- // ToDo: This condition is for psim only. If device supports removable
- // esim, it needs an new condition.
- .filter(sub -> !sub.isEmbedded())
- .collect(Collectors.toList()));
- }
-
- private void startChooseSimActivity(boolean psimInserted) {
- Intent intent = ChooseSimActivity.getIntent(mContext);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.putExtra(ChooseSimActivity.KEY_HAS_PSIM, psimInserted);
- mContext.startActivityAsUser(intent, UserHandle.SYSTEM);
- }
-
- private void startSwitchSlotConfirmDialogActivity(SubscriptionInfo subscriptionInfo) {
- Intent intent = new Intent(mContext, SwitchToEsimConfirmDialogActivity.class);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.putExtra(SwitchToEsimConfirmDialogActivity.KEY_SUB_TO_ENABLE, subscriptionInfo);
- mContext.startActivityAsUser(intent, UserHandle.SYSTEM);
- }
-
- private void startDsdsDialogActivity() {
- Intent intent = new Intent(mContext, DsdsDialogActivity.class);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- mContext.startActivityAsUser(intent, UserHandle.SYSTEM);
- }
-
- private void startSimConfirmDialogActivity(int subId) {
- if (!isSuwFinished(mContext)) {
- Log.d(TAG, "Still in SUW. Do nothing");
- return;
- }
- if (!SubscriptionManager.isUsableSubscriptionId(subId)) {
- Log.i(TAG, "Unable to enable subscription due to invalid subscription ID.");
- return;
- }
- Log.d(TAG, "Start ToggleSubscriptionDialogActivity with " + subId + " under DSDS+Mep.");
- SubscriptionUtil.startToggleSubscriptionDialogActivity(mContext, subId, true, true);
- }
-
- private boolean isMultipleEnabledProfilesSupported() {
- List<UiccCardInfo> cardInfos = mTelMgr.getUiccCardsInfo();
- if (cardInfos == null) {
- Log.d(TAG, "UICC cards info list is empty.");
- return false;
- }
- return cardInfos.stream().anyMatch(
- cardInfo -> cardInfo.isMultipleEnabledProfilesSupported());
- }
-
- private SimSlotChangeHandler() {}
-}
diff --git a/src/com/android/settings/sim/receivers/SimSlotChangeHandler.kt b/src/com/android/settings/sim/receivers/SimSlotChangeHandler.kt
new file mode 100644
index 0000000..940184a
--- /dev/null
+++ b/src/com/android/settings/sim/receivers/SimSlotChangeHandler.kt
@@ -0,0 +1,480 @@
+/*
+ * Copyright (C) 2020 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.sim.receivers
+
+import android.content.Context
+import android.content.Intent
+import android.os.Looper
+import android.os.UserHandle
+import android.provider.Settings
+import android.provider.Settings.SettingNotFoundException
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import android.telephony.TelephonyManager
+import android.telephony.UiccCardInfo
+import android.telephony.UiccSlotInfo
+import android.util.Log
+import com.android.settings.flags.Flags
+import com.android.settings.network.SubscriptionUtil
+import com.android.settings.network.UiccSlotUtil
+import com.android.settings.network.UiccSlotsException
+import com.android.settings.network.telephony.SubscriptionRepository
+import com.android.settings.sim.ChooseSimActivity
+import com.android.settings.sim.DsdsDialogActivity
+import com.android.settings.sim.SimActivationNotifier
+import com.android.settings.sim.SimNotificationService
+import com.android.settings.sim.SwitchToEsimConfirmDialogActivity
+import com.google.common.collect.ImmutableList
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.flow.firstOrNull
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import kotlinx.coroutines.withTimeoutOrNull
+import java.util.stream.Collectors
+import kotlin.concurrent.Volatile
+
+/** Perform actions after a slot change event is triggered. */
+class SimSlotChangeHandler private constructor() {
+ private var mSubMgr: SubscriptionManager? = null
+ private var mTelMgr: TelephonyManager? = null
+ private var mContext: Context? = null
+
+ fun onSlotsStatusChange(context: Context) {
+ init(context)
+
+ check(Looper.myLooper() != Looper.getMainLooper()) { "Cannot be called from main thread." }
+
+ val removableSlotInfo = removableUiccSlotInfo
+ if (removableSlotInfo == null) {
+ Log.e(TAG, "Unable to find the removable slot. Do nothing.")
+ return
+ }
+ Log.i(
+ TAG,
+ "The removableSlotInfo: $removableSlotInfo"
+ )
+ val lastRemovableSlotState = getLastRemovableSimSlotState(mContext!!)
+ val currentRemovableSlotState = removableSlotInfo.cardStateInfo
+ Log.d(
+ TAG,
+ ("lastRemovableSlotState: " + lastRemovableSlotState + ",currentRemovableSlotState: "
+ + currentRemovableSlotState)
+ )
+ val isRemovableSimInserted =
+ lastRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_ABSENT
+ && currentRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_PRESENT
+ val isRemovableSimRemoved =
+ lastRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_PRESENT
+ && currentRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_ABSENT
+
+ // Sets the current removable slot state.
+ setRemovableSimSlotState(mContext!!, currentRemovableSlotState)
+
+ if (mTelMgr!!.activeModemCount > 1) {
+ if (!isRemovableSimInserted) {
+ Log.d(TAG, "Removable Sim is not inserted in DSDS mode. Do nothing.")
+ return
+ }
+
+ if (Flags.isDualSimOnboardingEnabled()) {
+ // ForNewUi, when the user inserts the psim, showing the sim onboarding for the user
+ // to setup the sim switching or the default data subscription in DSDS.
+ // Will show dialog for below case.
+ // 1. the psim slot is not active.
+ // 2. there are one or more active sim.
+ handleRemovableSimInsertWhenDsds(removableSlotInfo)
+ return
+ } else if (!isMultipleEnabledProfilesSupported) {
+ Log.d(TAG, "The device is already in DSDS mode and no MEP. Do nothing.")
+ return
+ } else if (isMultipleEnabledProfilesSupported) {
+ handleRemovableSimInsertUnderDsdsMep(removableSlotInfo)
+ return
+ }
+ }
+
+ if (isRemovableSimInserted) {
+ handleSimInsert(removableSlotInfo)
+ return
+ }
+ if (isRemovableSimRemoved) {
+ handleSimRemove(removableSlotInfo)
+ return
+ }
+ Log.i(TAG, "Do nothing on slot status changes.")
+ }
+
+ fun onSuwFinish(context: Context) {
+ init(context)
+
+ check(Looper.myLooper() != Looper.getMainLooper()) { "Cannot be called from main thread." }
+
+ if (mTelMgr!!.activeModemCount > 1) {
+ Log.i(TAG, "The device is already in DSDS mode. Do nothing.")
+ return
+ }
+
+ val removableSlotInfo = removableUiccSlotInfo
+ if (removableSlotInfo == null) {
+ Log.e(TAG, "Unable to find the removable slot. Do nothing.")
+ return
+ }
+
+ val embeddedSimExist = groupedEmbeddedSubscriptions.size != 0
+ val removableSlotAction = getSuwRemovableSlotAction(mContext!!)
+ setSuwRemovableSlotAction(mContext!!, LAST_USER_ACTION_IN_SUW_NONE)
+
+ if (embeddedSimExist
+ && removableSlotInfo.cardStateInfo == UiccSlotInfo.CARD_STATE_INFO_PRESENT
+ ) {
+ if (mTelMgr!!.isMultiSimSupported() == TelephonyManager.MULTISIM_ALLOWED) {
+ Log.i(TAG, "DSDS condition satisfied. Show notification.")
+ SimNotificationService.scheduleSimNotification(
+ mContext, SimActivationNotifier.NotificationType.ENABLE_DSDS
+ )
+ } else if (removableSlotAction == LAST_USER_ACTION_IN_SUW_INSERT) {
+ Log.i(
+ TAG,
+ ("Both removable SIM and eSIM are present. DSDS condition doesn't"
+ + " satisfied. User inserted pSIM during SUW. Show choose SIM"
+ + " screen.")
+ )
+ startChooseSimActivity(true)
+ }
+ } else if (removableSlotAction == LAST_USER_ACTION_IN_SUW_REMOVE) {
+ handleSimRemove(removableSlotInfo)
+ }
+ }
+
+ private fun init(context: Context) {
+ mSubMgr =
+ context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) as SubscriptionManager
+ mTelMgr = context.getSystemService(TelephonyManager::class.java)
+ mContext = context
+ }
+
+ private fun handleSimInsert(removableSlotInfo: UiccSlotInfo) {
+ Log.i(TAG, "Handle SIM inserted.")
+ if (!isSuwFinished(mContext!!)) {
+ Log.i(TAG, "Still in SUW. Handle SIM insertion after SUW is finished")
+ setSuwRemovableSlotAction(mContext!!, LAST_USER_ACTION_IN_SUW_INSERT)
+ return
+ }
+ if (removableSlotInfo.ports.stream().findFirst().get().isActive) {
+ Log.i(TAG, "The removable slot is already active. Do nothing.")
+ return
+ }
+
+ if (hasActiveEsimSubscription()) {
+ if (mTelMgr!!.isMultiSimSupported() == TelephonyManager.MULTISIM_ALLOWED) {
+ Log.i(TAG, "Enabled profile exists. DSDS condition satisfied.")
+ if (Flags.isDualSimOnboardingEnabled()) {
+ // enable dsds by sim onboarding flow
+ handleRemovableSimInsertWhenDsds(removableSlotInfo)
+ } else {
+ startDsdsDialogActivity()
+ }
+ } else {
+ Log.i(TAG, "Enabled profile exists. DSDS condition not satisfied.")
+ startChooseSimActivity(true)
+ }
+ return
+ }
+
+ Log.i(
+ TAG,
+ "No enabled eSIM profile. Ready to switch to removable slot and show"
+ + " notification."
+ )
+ try {
+ UiccSlotUtil.switchToRemovableSlot(
+ UiccSlotUtil.INVALID_PHYSICAL_SLOT_ID, mContext!!.applicationContext
+ )
+ } catch (e: UiccSlotsException) {
+ Log.e(TAG, "Failed to switch to removable slot.")
+ return
+ }
+ SimNotificationService.scheduleSimNotification(
+ mContext, SimActivationNotifier.NotificationType.SWITCH_TO_REMOVABLE_SLOT
+ )
+ }
+
+ private fun handleSimRemove(removableSlotInfo: UiccSlotInfo) {
+ Log.i(TAG, "Handle SIM removed.")
+
+ if (!isSuwFinished(mContext!!)) {
+ Log.i(TAG, "Still in SUW. Handle SIM removal after SUW is finished")
+ setSuwRemovableSlotAction(mContext!!, LAST_USER_ACTION_IN_SUW_REMOVE)
+ return
+ }
+
+ val groupedEmbeddedSubscriptions =
+ groupedEmbeddedSubscriptions
+ if (groupedEmbeddedSubscriptions.isEmpty() || !removableSlotInfo.ports.stream()
+ .findFirst().get().isActive
+ ) {
+ Log.i(
+ TAG, ("eSIM slot is active or no subscriptions exist. Do nothing."
+ + " The removableSlotInfo: " + removableSlotInfo
+ + ", groupedEmbeddedSubscriptions: " + groupedEmbeddedSubscriptions)
+ )
+ return
+ }
+
+ // If there is only 1 eSIM profile exists, we ask the user if they want to switch to that
+ // profile.
+ if (groupedEmbeddedSubscriptions.size == 1) {
+ Log.i(TAG, "Only 1 eSIM profile found. Ask user's consent to switch.")
+ startSwitchSlotConfirmDialogActivity(groupedEmbeddedSubscriptions[0])
+ return
+ }
+
+ // If there are more than 1 eSIM profiles installed, we show a screen to let users to choose
+ // the number they want to use.
+ Log.i(TAG, "Multiple eSIM profiles found. Ask user which subscription to use.")
+ startChooseSimActivity(false)
+ }
+
+ private fun hasOtherActiveSubInfo(psimSubId: Int): Boolean {
+ val activeSubs = SubscriptionUtil.getActiveSubscriptions(mSubMgr)
+ return activeSubs.stream()
+ .anyMatch { subscriptionInfo -> subscriptionInfo.subscriptionId != psimSubId }
+ }
+
+ private fun hasAnyPortActiveInSlot(removableSlotInfo: UiccSlotInfo): Boolean {
+ return removableSlotInfo.ports.stream().anyMatch { slot -> slot.isActive }
+ }
+
+ private fun handleRemovableSimInsertWhenDsds(removableSlotInfo: UiccSlotInfo) {
+ Log.i(TAG, "ForNewUi: Handle Removable SIM inserted")
+ CoroutineScope(Dispatchers.Default + SupervisorJob()).launch {
+ withContext(Dispatchers.Default) {
+ val subscriptionInfos =
+ withTimeoutOrNull(DEFAULT_WAIT_AFTER_SIM_INSERTED_TIMEOUT_MILLIS) {
+ SubscriptionRepository(mContext!!)
+ .removableSubscriptionInfoListFlow()
+ .firstOrNull { it.isNotEmpty() }
+ }
+
+ if (subscriptionInfos.isNullOrEmpty()) {
+ Log.e(TAG, "Unable to find the removable subscriptionInfo. Do nothing.")
+ return@withContext
+ }
+ Log.d(
+ TAG,
+ "getAvailableRemovableSubscription:$subscriptionInfos"
+ )
+ val psimSubId = subscriptionInfos[0].subscriptionId
+ if (!hasAnyPortActiveInSlot(removableSlotInfo)
+ || hasOtherActiveSubInfo(psimSubId)) {
+ Log.d(TAG, "ForNewUi Start Setup flow")
+ startSimConfirmDialogActivity(psimSubId)
+ }
+ }
+ }
+ }
+
+ private fun handleRemovableSimInsertUnderDsdsMep(removableSlotInfo: UiccSlotInfo) {
+ Log.i(TAG, "Handle Removable SIM inserted under DSDS+Mep.")
+
+ if (removableSlotInfo.ports.stream().findFirst().get().isActive) {
+ Log.i(
+ TAG, "The removable slot is already active. Do nothing. removableSlotInfo: "
+ + removableSlotInfo
+ )
+ return
+ }
+
+ val subscriptionInfos =
+ availableRemovableSubscription
+ if (subscriptionInfos.isEmpty()) {
+ Log.e(TAG, "Unable to find the removable subscriptionInfo. Do nothing.")
+ return
+ }
+ Log.d(
+ TAG,
+ "getAvailableRemovableSubscription:$subscriptionInfos"
+ )
+ startSimConfirmDialogActivity(subscriptionInfos[0].subscriptionId)
+ }
+
+ private fun getLastRemovableSimSlotState(context: Context): Int {
+ val prefs = context.getSharedPreferences(EUICC_PREFS, Context.MODE_PRIVATE)
+ return prefs.getInt(KEY_REMOVABLE_SLOT_STATE, UiccSlotInfo.CARD_STATE_INFO_ABSENT)
+ }
+
+ private fun setRemovableSimSlotState(context: Context, state: Int) {
+ val prefs = context.getSharedPreferences(EUICC_PREFS, Context.MODE_PRIVATE)
+ prefs.edit().putInt(KEY_REMOVABLE_SLOT_STATE, state).apply()
+ Log.d(TAG, "setRemovableSimSlotState: $state")
+ }
+
+ private fun getSuwRemovableSlotAction(context: Context): Int {
+ val prefs = context.getSharedPreferences(EUICC_PREFS, Context.MODE_PRIVATE)
+ return prefs.getInt(KEY_SUW_PSIM_ACTION, LAST_USER_ACTION_IN_SUW_NONE)
+ }
+
+ private fun setSuwRemovableSlotAction(context: Context, action: Int) {
+ val prefs = context.getSharedPreferences(EUICC_PREFS, Context.MODE_PRIVATE)
+ prefs.edit().putInt(KEY_SUW_PSIM_ACTION, action).apply()
+ }
+
+ private val removableUiccSlotInfo: UiccSlotInfo?
+ get() {
+ val slotInfos = mTelMgr!!.uiccSlotsInfo
+ if (slotInfos == null) {
+ Log.e(
+ TAG,
+ "slotInfos is null. Unable to get slot infos."
+ )
+ return null
+ }
+ for (slotInfo in slotInfos) {
+ if (slotInfo != null && slotInfo.isRemovable) {
+ return slotInfo
+ }
+ }
+ return null
+ }
+
+ private fun hasActiveEsimSubscription(): Boolean {
+ val activeSubs = SubscriptionUtil.getActiveSubscriptions(mSubMgr)
+ return activeSubs.stream().anyMatch { subscriptionInfo -> subscriptionInfo.isEmbedded }
+ }
+
+ private val groupedEmbeddedSubscriptions: List<SubscriptionInfo>
+ get() {
+ val groupedSubscriptions =
+ SubscriptionUtil.getSelectableSubscriptionInfoList(mContext)
+ ?: return ImmutableList.of()
+ return ImmutableList.copyOf(
+ groupedSubscriptions.stream()
+ .filter { sub: SubscriptionInfo -> sub.isEmbedded }
+ .collect(Collectors.toList()))
+ }
+
+ protected val availableRemovableSubscription: List<SubscriptionInfo>
+ get() {
+ val removableSubscriptions =
+ SubscriptionUtil.getAvailableSubscriptions(mContext)
+ return ImmutableList.copyOf(
+ removableSubscriptions.stream()
+ // ToDo: This condition is for psim only. If device supports removable
+ // esim, it needs an new condition.
+ .filter { sub: SubscriptionInfo -> !sub.isEmbedded }
+ .collect(Collectors.toList()))
+ }
+
+ private fun startChooseSimActivity(psimInserted: Boolean) {
+ val intent = ChooseSimActivity.getIntent(mContext)
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ intent.putExtra(ChooseSimActivity.KEY_HAS_PSIM, psimInserted)
+ mContext!!.startActivityAsUser(intent, UserHandle.SYSTEM)
+ }
+
+ private fun startSwitchSlotConfirmDialogActivity(subscriptionInfo: SubscriptionInfo) {
+ val intent = Intent(
+ mContext,
+ SwitchToEsimConfirmDialogActivity::class.java
+ )
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ intent.putExtra(SwitchToEsimConfirmDialogActivity.KEY_SUB_TO_ENABLE, subscriptionInfo)
+ mContext!!.startActivityAsUser(intent, UserHandle.SYSTEM)
+ }
+
+ private fun startDsdsDialogActivity() {
+ val intent = Intent(mContext, DsdsDialogActivity::class.java)
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ mContext!!.startActivityAsUser(intent, UserHandle.SYSTEM)
+ }
+
+ private fun startSimConfirmDialogActivity(subId: Int) {
+ if (!isSuwFinished(mContext!!)) {
+ Log.d(TAG, "Still in SUW. Do nothing")
+ return
+ }
+ if (!SubscriptionManager.isUsableSubscriptionId(subId)) {
+ Log.i(TAG, "Unable to enable subscription due to invalid subscription ID.")
+ return
+ }
+ Log.d(
+ TAG,
+ "Start ToggleSubscriptionDialogActivity with $subId under DSDS+Mep."
+ )
+ SubscriptionUtil.startToggleSubscriptionDialogActivity(mContext, subId, true, true)
+ }
+
+ private val isMultipleEnabledProfilesSupported: Boolean
+ get() {
+ val cardInfos = mTelMgr!!.uiccCardsInfo
+ if (cardInfos == null) {
+ Log.d(
+ TAG,
+ "UICC cards info list is empty."
+ )
+ return false
+ }
+ return cardInfos.stream()
+ .anyMatch { cardInfo: UiccCardInfo -> cardInfo.isMultipleEnabledProfilesSupported }
+ }
+
+ private fun isSuwFinished(context: Context): Boolean {
+ try {
+ // DEVICE_PROVISIONED is 0 if still in setup wizard. 1 if setup completed.
+ return (Settings.Global.getInt(
+ context.contentResolver, Settings.Global.DEVICE_PROVISIONED)
+ == 1)
+ } catch (e: SettingNotFoundException) {
+ Log.e(TAG, "Cannot get DEVICE_PROVISIONED from the device.", e)
+ return false
+ }
+ }
+
+ companion object {
+ private const val TAG = "SimSlotChangeHandler"
+
+ private const val EUICC_PREFS = "euicc_prefs"
+
+ // Shared preference keys
+ private const val KEY_REMOVABLE_SLOT_STATE = "removable_slot_state"
+ private const val KEY_SUW_PSIM_ACTION = "suw_psim_action"
+
+ // User's last removable SIM insertion / removal action during SUW.
+ private const val LAST_USER_ACTION_IN_SUW_NONE = 0
+ private const val LAST_USER_ACTION_IN_SUW_INSERT = 1
+ private const val LAST_USER_ACTION_IN_SUW_REMOVE = 2
+
+ private const val DEFAULT_WAIT_AFTER_SIM_INSERTED_TIMEOUT_MILLIS: Long = 25 * 1000L
+
+ @Volatile
+ private var slotChangeHandler: SimSlotChangeHandler? = null
+
+ /** Returns a SIM slot change handler singleton. */
+ @JvmStatic
+ fun get(): SimSlotChangeHandler? {
+ if (slotChangeHandler == null) {
+ synchronized(SimSlotChangeHandler::class.java) {
+ if (slotChangeHandler == null) {
+ slotChangeHandler = SimSlotChangeHandler()
+ }
+ }
+ }
+ return slotChangeHandler
+ }
+ }
+}
diff --git a/src/com/android/settings/spa/preference/ComposePreference.kt b/src/com/android/settings/spa/preference/ComposePreference.kt
index 91b2d8a..57aa386 100644
--- a/src/com/android/settings/spa/preference/ComposePreference.kt
+++ b/src/com/android/settings/spa/preference/ComposePreference.kt
@@ -28,7 +28,7 @@
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.widget.GroupSectionDividerMixin
-open class ComposeMainSwitchPreference @JvmOverloads constructor(
+open class ComposeGroupSectionPreference @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
diff --git a/src/com/android/settings/wifi/WifiSwitchPreference.kt b/src/com/android/settings/wifi/WifiSwitchPreference.kt
index ba6fb02..9549754 100644
--- a/src/com/android/settings/wifi/WifiSwitchPreference.kt
+++ b/src/com/android/settings/wifi/WifiSwitchPreference.kt
@@ -92,10 +92,15 @@
return true
}
- override fun getReadPermit(context: Context, myUid: Int, callingUid: Int) =
+ override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) =
ReadWritePermit.ALLOW
- override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) =
+ override fun getWritePermit(
+ context: Context,
+ value: Boolean?,
+ callingPid: Int,
+ callingUid: Int,
+ ) =
when {
(value == true && !context.isRadioAllowed()) || isSatelliteOn(context) ->
ReadWritePermit.DISALLOW
diff --git a/src/com/android/settings/wifi/calling/WifiCallingMainSwitchPreference.kt b/src/com/android/settings/wifi/calling/WifiCallingMainSwitchPreference.kt
index f6056f4..07adac0 100644
--- a/src/com/android/settings/wifi/calling/WifiCallingMainSwitchPreference.kt
+++ b/src/com/android/settings/wifi/calling/WifiCallingMainSwitchPreference.kt
@@ -60,10 +60,15 @@
override fun createWidget(context: Context) = SettingsMainSwitchPreference(context)
- override fun getReadPermit(context: Context, myUid: Int, callingUid: Int) =
+ override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) =
ReadWritePermit.ALLOW
- override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) =
+ override fun getWritePermit(
+ context: Context,
+ value: Boolean?,
+ callingPid: Int,
+ callingUid: Int,
+ ) =
when {
value == true &&
(DisclaimerItemFactory.create(context, subId).isNotEmpty() ||
diff --git a/src/com/android/settings/wifi/details2/AddDevicePreferenceController2.java b/src/com/android/settings/wifi/details2/AddDevicePreferenceController2.java
index 8f9741a..4ffe279 100644
--- a/src/com/android/settings/wifi/details2/AddDevicePreferenceController2.java
+++ b/src/com/android/settings/wifi/details2/AddDevicePreferenceController2.java
@@ -57,7 +57,8 @@
@Override
public boolean handlePreferenceTreeClick(Preference preference) {
if (KEY_ADD_DEVICE.equals(preference.getKey())) {
- WifiDppUtils.showLockScreen(mContext, () -> launchWifiDppConfiguratorQrCodeScanner());
+ WifiDppUtils.showLockScreenForWifiSharing(mContext,
+ () -> launchWifiDppConfiguratorQrCodeScanner());
return true; /* click is handled */
}
diff --git a/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java b/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java
index a8d7f41..ecddecf 100644
--- a/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java
+++ b/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java
@@ -980,7 +980,8 @@
* Share the wifi network with QR code.
*/
private void shareNetwork() {
- WifiDppUtils.showLockScreen(mContext, () -> launchWifiDppConfiguratorActivity());
+ WifiDppUtils.showLockScreenForWifiSharing(mContext,
+ () -> launchWifiDppConfiguratorActivity());
}
/**
diff --git a/src/com/android/settings/wifi/dpp/WifiDppUtils.java b/src/com/android/settings/wifi/dpp/WifiDppUtils.java
index 23a6a54..24ab496 100644
--- a/src/com/android/settings/wifi/dpp/WifiDppUtils.java
+++ b/src/com/android/settings/wifi/dpp/WifiDppUtils.java
@@ -16,6 +16,8 @@
package com.android.settings.wifi.dpp;
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.Intent;
@@ -33,6 +35,9 @@
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.Utils;
@@ -58,6 +63,8 @@
* @see WifiQrCode
*/
public class WifiDppUtils {
+ private static final String TAG = "WifiDppUtils";
+
/**
* The fragment tag specified to FragmentManager for container activities to manage fragments.
*/
@@ -109,7 +116,15 @@
private static final Duration VIBRATE_DURATION_QR_CODE_RECOGNITION = Duration.ofMillis(3);
- private static final String AES_CBC_PKCS7_PADDING = "AES/CBC/PKCS7Padding";
+ /**
+ * Parameters to check whether the device has been locked recently
+ */
+ @VisibleForTesting
+ public static final String AES_CBC_PKCS7_PADDING = "AES/CBC/PKCS7Padding";
+ @VisibleForTesting
+ public static final String WIFI_SHARING_KEY_ALIAS = "wifi_sharing_auth_key";
+ @VisibleForTesting
+ public static final int WIFI_SHARING_MAX_UNLOCK_SECONDS = 60;
/**
* Returns whether the device support WiFi DPP.
@@ -426,51 +441,75 @@
* Shows authentication screen to confirm credentials (pin, pattern or password) for the current
* user of the device.
*
- * @param context The {@code Context} used to get {@code KeyguardManager} service
+ * @param context The {@code Context} used to get {@code KeyguardManager} service
* @param successRunnable The {@code Runnable} which will be executed if the user does not setup
* device security or if lock screen is unlocked
*/
- public static void showLockScreen(Context context, Runnable successRunnable) {
- final KeyguardManager keyguardManager = (KeyguardManager) context.getSystemService(
- Context.KEYGUARD_SERVICE);
-
- if (keyguardManager.isKeyguardSecure()) {
- final BiometricPrompt.AuthenticationCallback authenticationCallback =
- new BiometricPrompt.AuthenticationCallback() {
- @Override
- public void onAuthenticationSucceeded(
- BiometricPrompt.AuthenticationResult result) {
- successRunnable.run();
- }
-
- @Override
- public void onAuthenticationError(int errorCode, CharSequence errString) {
- //Do nothing
- }
- };
-
- final int userId = UserHandle.myUserId();
-
- final BiometricPrompt.Builder builder = new BiometricPrompt.Builder(context)
- .setTitle(context.getText(R.string.wifi_dpp_lockscreen_title));
-
- if (keyguardManager.isDeviceSecure()) {
- builder.setDeviceCredentialAllowed(true);
- builder.setTextForDeviceCredential(
- null /* title */,
- Utils.getConfirmCredentialStringForUser(
- context, userId, Utils.getCredentialType(context, userId)),
- null /* description */);
- }
-
- final BiometricPrompt bp = builder.build();
- final Handler handler = new Handler(Looper.getMainLooper());
- bp.authenticate(new CancellationSignal(),
- runnable -> handler.post(runnable),
- authenticationCallback);
- } else {
+ public static void showLockScreen(@NonNull Context context, @NonNull Runnable successRunnable) {
+ KeyguardManager keyguardManager = context.getSystemService(KeyguardManager.class);
+ if (keyguardManager == null || !keyguardManager.isKeyguardSecure()) {
successRunnable.run();
+ return;
}
+ showLockScreen(context, successRunnable, keyguardManager);
+ }
+
+ /**
+ * Shows authentication screen to confirm credentials (pin, pattern or password) for the
+ * current user of the device. But if the device has been unlocked recently, the
+ * authentication screen will be skipped.
+ *
+ * @param context The {@code Context} used to get {@code KeyguardManager} service
+ * @param successRunnable The {@code Runnable} which will be executed if the user does not setup
+ * device security or if lock screen is unlocked
+ */
+ public static void showLockScreenForWifiSharing(@NonNull Context context,
+ @NonNull Runnable successRunnable) {
+ KeyguardManager keyguardManager = context.getSystemService(KeyguardManager.class);
+ if (keyguardManager == null || !keyguardManager.isKeyguardSecure()) {
+ successRunnable.run();
+ return;
+ }
+ if (isUnlockedWithinSeconds(WIFI_SHARING_KEY_ALIAS, WIFI_SHARING_MAX_UNLOCK_SECONDS)) {
+ Log.d(TAG, "Bypassing the lock screen because the device was unlocked recently.");
+ successRunnable.run();
+ return;
+ }
+ showLockScreen(context, successRunnable, keyguardManager);
+ }
+
+ @SuppressLint("MissingPermission")
+ private static void showLockScreen(@NonNull Context context, @NonNull Runnable successRunnable,
+ @NonNull KeyguardManager keyguardManager) {
+ BiometricPrompt.AuthenticationCallback authenticationCallback =
+ new BiometricPrompt.AuthenticationCallback() {
+ @Override
+ public void onAuthenticationSucceeded(
+ BiometricPrompt.AuthenticationResult result) {
+ successRunnable.run();
+ }
+
+ @Override
+ public void onAuthenticationError(int errorCode, CharSequence errString) {
+ //Do nothing
+ }
+ };
+ int userId = UserHandle.myUserId();
+ BiometricPrompt.Builder builder = new BiometricPrompt.Builder(context)
+ .setTitle(context.getText(R.string.wifi_dpp_lockscreen_title));
+ if (keyguardManager.isDeviceSecure()) {
+ builder.setDeviceCredentialAllowed(true);
+ builder.setTextForDeviceCredential(
+ null /* title */,
+ Utils.getConfirmCredentialStringForUser(
+ context, userId, Utils.getCredentialType(context, userId)),
+ null /* description */);
+ }
+ BiometricPrompt bp = builder.build();
+ Handler handler = new Handler(Looper.getMainLooper());
+ bp.authenticate(new CancellationSignal(),
+ runnable -> handler.post(runnable),
+ authenticationCallback);
}
/**
diff --git a/src/com/android/settings/wifi/tether/WifiHotspotSwitchPreference.kt b/src/com/android/settings/wifi/tether/WifiHotspotSwitchPreference.kt
index 931583a..2f72c4a 100644
--- a/src/com/android/settings/wifi/tether/WifiHotspotSwitchPreference.kt
+++ b/src/com/android/settings/wifi/tether/WifiHotspotSwitchPreference.kt
@@ -106,11 +106,15 @@
override val restrictionKeys
get() = arrayOf(UserManager.DISALLOW_WIFI_TETHERING)
- override fun getReadPermit(context: Context, myUid: Int, callingUid: Int) =
+ override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) =
ReadWritePermit.ALLOW
- override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) =
- ReadWritePermit.ALLOW
+ override fun getWritePermit(
+ context: Context,
+ value: Boolean?,
+ callingPid: Int,
+ callingUid: Int,
+ ) = ReadWritePermit.ALLOW
override val sensitivityLevel
get() = SensitivityLevel.HIGH_SENSITIVITY
diff --git a/src/com/android/settings/wifi/tether/WifiTetherSSIDPreferenceController.java b/src/com/android/settings/wifi/tether/WifiTetherSSIDPreferenceController.java
index 1bcff1e..d2d26ab 100644
--- a/src/com/android/settings/wifi/tether/WifiTetherSSIDPreferenceController.java
+++ b/src/com/android/settings/wifi/tether/WifiTetherSSIDPreferenceController.java
@@ -123,7 +123,7 @@
}
private void shareHotspotNetwork(Intent intent) {
- WifiDppUtils.showLockScreen(mContext, () -> {
+ WifiDppUtils.showLockScreenForWifiSharing(mContext, () -> {
mMetricsFeatureProvider.action(SettingsEnums.PAGE_UNKNOWN,
SettingsEnums.ACTION_SETTINGS_SHARE_WIFI_HOTSPOT_QR_CODE,
SettingsEnums.SETTINGS_WIFI_DPP_CONFIGURATOR,
diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragmentTest.java
index 5973d26..552a4a7 100644
--- a/tests/robotests/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragmentTest.java
@@ -19,7 +19,6 @@
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
-import static com.android.settings.accessibility.AccessibilityUtil.QuickSettingsTooltipType;
import static com.google.common.truth.Truth.assertThat;
@@ -37,7 +36,6 @@
import android.content.Intent;
import android.icu.text.CaseMap;
import android.os.Bundle;
-import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.view.LayoutInflater;
import android.view.View;
@@ -57,7 +55,6 @@
import com.android.settings.testutils.shadow.ShadowFragment;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
@@ -83,18 +80,11 @@
PLACEHOLDER_PACKAGE_NAME + "tile.placeholder";
private static final ComponentName PLACEHOLDER_COMPONENT_NAME = new ComponentName(
PLACEHOLDER_PACKAGE_NAME, PLACEHOLDER_CLASS_NAME);
- private static final ComponentName PLACEHOLDER_TILE_COMPONENT_NAME = new ComponentName(
- PLACEHOLDER_PACKAGE_NAME, PLACEHOLDER_TILE_CLASS_NAME);
- private static final String PLACEHOLDER_TILE_TOOLTIP_CONTENT =
- PLACEHOLDER_PACKAGE_NAME + "tooltip_content";
- private static final String PLACEHOLDER_DIALOG_TITLE = "title";
private static final String SOFTWARE_SHORTCUT_KEY =
Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS;
private static final String HARDWARE_SHORTCUT_KEY =
Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
- @Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private TestAccessibilityShortcutPreferenceFragment mFragment;
private PreferenceScreen mScreen;
private Context mContext = ApplicationProvider.getApplicationContext();
@@ -153,14 +143,6 @@
@Test
@Config(shadows = ShadowFragment.class)
- public void showQuickSettingsTooltipIfNeeded_dontShowTooltipView() {
- mFragment.showQuickSettingsTooltipIfNeeded(QuickSettingsTooltipType.GUIDE_TO_EDIT);
-
- assertThat(getLatestPopupWindow()).isNull();
- }
-
- @Test
- @Config(shadows = ShadowFragment.class)
public void showGeneralCategory_shouldInitCategory() {
final Bundle savedInstanceState = new Bundle();
when(mFragment.showGeneralCategory()).thenReturn(true);
@@ -261,16 +243,6 @@
}
@Override
- protected ComponentName getTileComponentName() {
- return PLACEHOLDER_TILE_COMPONENT_NAME;
- }
-
- @Override
- protected CharSequence getTileTooltipContent(@QuickSettingsTooltipType int type) {
- return PLACEHOLDER_TILE_TOOLTIP_CONTENT;
- }
-
- @Override
public int getUserShortcutTypes() {
return 0;
}
diff --git a/tests/robotests/src/com/android/settings/accessibility/HighContrastTextMigrationReceiverTest.java b/tests/robotests/src/com/android/settings/accessibility/HighContrastTextMigrationReceiverTest.java
new file mode 100644
index 0000000..0fedddc
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/accessibility/HighContrastTextMigrationReceiverTest.java
@@ -0,0 +1,241 @@
+/*
+ * 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.android.settings.SettingsActivity.EXTRA_FRAGMENT_ARG_KEY;
+import static com.android.settings.SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS;
+import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
+import static com.android.settings.accessibility.AccessibilityUtil.State.ON;
+import static com.android.settings.accessibility.HighContrastTextMigrationReceiver.ACTION_RESTORED;
+import static com.android.settings.accessibility.HighContrastTextMigrationReceiver.NOTIFICATION_CHANNEL;
+import static com.android.settings.accessibility.HighContrastTextMigrationReceiver.NOTIFICATION_ID;
+import static com.android.settings.accessibility.HighContrastTextMigrationReceiver.PromptState.PROMPT_SHOWN;
+import static com.android.settings.accessibility.HighContrastTextMigrationReceiver.PromptState.PROMPT_UNNECESSARY;
+import static com.android.settings.accessibility.HighContrastTextMigrationReceiver.PromptState.UNKNOWN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.provider.Settings;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.graphics.hwui.flags.Flags;
+import com.android.settings.R;
+import com.android.settings.Utils;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.Shadows;
+import org.robolectric.shadows.ShadowNotification;
+import org.robolectric.shadows.ShadowNotificationManager;
+import org.robolectric.shadows.ShadowPackageManager;
+
+/** Tests for {@link HighContrastTextMigrationReceiver}. */
+@RunWith(RobolectricTestRunner.class)
+public class HighContrastTextMigrationReceiverTest {
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ private HighContrastTextMigrationReceiver mReceiver;
+ private ShadowNotificationManager mShadowNotificationManager;
+
+ @Before
+ public void setUp() {
+ NotificationManager notificationManager =
+ mContext.getSystemService(NotificationManager.class);
+ mShadowNotificationManager = Shadows.shadowOf(notificationManager);
+
+ // Setup Settings app as a system app
+ ShadowPackageManager shadowPm = Shadows.shadowOf(mContext.getPackageManager());
+ ComponentName textReadingComponent = new ComponentName(Utils.SETTINGS_PACKAGE_NAME,
+ com.android.settings.Settings.TextReadingSettingsActivity.class.getName());
+ ActivityInfo activityInfo = shadowPm.addActivityIfNotPresent(textReadingComponent);
+ activityInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+ shadowPm.addOrUpdateActivity(activityInfo);
+
+ mReceiver = new HighContrastTextMigrationReceiver();
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
+ public void onReceive_flagOff_settingsNotSet() {
+ mReceiver.onReceive(mContext, new Intent(ACTION_RESTORED));
+
+ assertPromptStateAndHctState(/* promptState= */ UNKNOWN, /* hctState= */ OFF);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
+ public void onRestored_hctStateOn_showPromptHctKeepsOn() {
+ setPromptStateAndHctState(/* promptState= */ UNKNOWN, /* hctState= */ ON);
+
+ mReceiver.onReceive(mContext, new Intent(ACTION_RESTORED));
+
+ assertPromptStateAndHctState(/* promptState= */ PROMPT_SHOWN, ON);
+ verifyNotificationSent();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
+ public void onRestored_hctStateOff_showPromptHctKeepsOff() {
+ setPromptStateAndHctState(/* promptState= */ UNKNOWN, /* hctState= */ OFF);
+
+ mReceiver.onReceive(mContext, new Intent(ACTION_RESTORED));
+
+ assertPromptStateAndHctState(/* promptState= */ PROMPT_SHOWN, OFF);
+ verifyNotificationSent();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
+ public void onPreBootCompleted_promptStateUnknownHctOn_showPromptAndAutoDisableHct() {
+ setPromptStateAndHctState(/* promptState= */ UNKNOWN, /* hctState= */ ON);
+
+ Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
+ mReceiver.onReceive(mContext, intent);
+
+ assertPromptStateAndHctState(/* promptState= */ PROMPT_SHOWN, /* hctState= */ OFF);
+ verifyNotificationSent();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
+ public void onPreBootCompleted_promptStateUnknownAndHctOff_promptIsUnnecessaryHctKeepsOff() {
+ setPromptStateAndHctState(/* promptState= */ UNKNOWN, /* hctState= */ OFF);
+
+ Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
+ mReceiver.onReceive(mContext, intent);
+
+ assertPromptStateAndHctState(/* promptState= */ PROMPT_UNNECESSARY, /* hctState= */ OFF);
+ verifyNotificationNotSent();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
+ public void onPreBootCompleted_promptStateShownAndHctOn_promptStateUnchangedHctKeepsOn() {
+ setPromptStateAndHctState(/* promptState= */ PROMPT_SHOWN, /* hctState= */ ON);
+
+ Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
+ mReceiver.onReceive(mContext, intent);
+
+ assertPromptStateAndHctState(/* promptState= */ PROMPT_SHOWN, /* hctState= */ ON);
+ verifyNotificationNotSent();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
+ public void onPreBootCompleted_promptStateShownAndHctOff_promptStateUnchangedHctKeepsOff() {
+ setPromptStateAndHctState(/* promptState= */ PROMPT_SHOWN, /* hctState= */ OFF);
+
+ Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
+ mReceiver.onReceive(mContext, intent);
+
+ assertPromptStateAndHctState(/* promptState= */ PROMPT_SHOWN, /* hctState= */ OFF);
+ verifyNotificationNotSent();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
+ public void onPreBootCompleted_promptStateUnnecessaryAndHctOn_promptStateUnchangedHctKeepsOn() {
+ setPromptStateAndHctState(/* promptState= */ PROMPT_UNNECESSARY, /* hctState= */ ON);
+
+ Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
+ mReceiver.onReceive(mContext, intent);
+
+ assertPromptStateAndHctState(/* promptState= */ PROMPT_UNNECESSARY, /* hctState= */ ON);
+ verifyNotificationNotSent();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
+ public void onPreBootCompleted_promptStateUnnecessaryHctOff_promptStateUnchangedHctKeepsOff() {
+ setPromptStateAndHctState(/* promptState= */ PROMPT_UNNECESSARY, /* hctState= */ OFF);
+
+ Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
+ mReceiver.onReceive(mContext, intent);
+
+ assertPromptStateAndHctState(/* promptState= */ PROMPT_UNNECESSARY, /* hctState= */ OFF);
+ verifyNotificationNotSent();
+ }
+
+ private void verifyNotificationNotSent() {
+ Notification notification = mShadowNotificationManager.getNotification(NOTIFICATION_ID);
+ assertThat(notification).isNull();
+ }
+
+ private void verifyNotificationSent() {
+ // Verify hct channel created
+ assertThat(mShadowNotificationManager.getNotificationChannels().stream().anyMatch(
+ channel -> channel.getId().equals(NOTIFICATION_CHANNEL))).isTrue();
+
+ // Verify hct notification is sent with correct content
+ Notification notification = mShadowNotificationManager.getNotification(NOTIFICATION_ID);
+ assertThat(notification).isNotNull();
+
+ ShadowNotification shadowNotification = Shadows.shadowOf(notification);
+ assertThat(shadowNotification.getContentTitle()).isEqualTo(mContext.getString(
+ R.string.accessibility_toggle_high_text_contrast_preference_title));
+ assertThat(shadowNotification.getContentText()).isEqualTo(
+ mContext.getString(R.string.accessibility_notification_high_contrast_text_content));
+
+ assertThat(notification.actions.length).isEqualTo(1);
+ assertThat(notification.actions[0].title.toString()).isEqualTo(
+ mContext.getString(R.string.accessibility_notification_high_contrast_text_action));
+
+ PendingIntent pendingIntent = notification.actions[0].actionIntent;
+ Intent settingsIntent = Shadows.shadowOf(pendingIntent).getSavedIntent();
+ Bundle fragmentArgs = settingsIntent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
+ assertThat(fragmentArgs).isNotNull();
+ assertThat(fragmentArgs.getString(EXTRA_FRAGMENT_ARG_KEY))
+ .isEqualTo(TextReadingPreferenceFragment.HIGH_TEXT_CONTRAST_KEY);
+ }
+
+ private void assertPromptStateAndHctState(
+ @HighContrastTextMigrationReceiver.PromptState int promptState,
+ @AccessibilityUtil.State int hctState) {
+ assertThat(Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_HCT_RECT_PROMPT_STATUS, UNKNOWN))
+ .isEqualTo(promptState);
+ assertThat(Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED, OFF))
+ .isEqualTo(hctState);
+ }
+
+ private void setPromptStateAndHctState(
+ @HighContrastTextMigrationReceiver.PromptState int promptState,
+ @AccessibilityUtil.State int hctState) {
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_HCT_RECT_PROMPT_STATUS, promptState);
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED, hctState);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/accessibility/PreviewSizeSeekBarControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/PreviewSizeSeekBarControllerTest.java
index 05273fc..ba9eaa5 100644
--- a/tests/robotests/src/com/android/settings/accessibility/PreviewSizeSeekBarControllerTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/PreviewSizeSeekBarControllerTest.java
@@ -29,7 +29,6 @@
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
-import android.os.Bundle;
import android.view.LayoutInflater;
import android.widget.PopupWindow;
import android.widget.SeekBar;
@@ -44,7 +43,6 @@
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.testutils.shadow.ShadowFragment;
-import com.android.settings.widget.LabeledSeekBarPreference;
import com.android.settingslib.testutils.shadow.ShadowInteractionJankMonitor;
import com.google.android.setupcompat.util.WizardManagerHelper;
@@ -77,7 +75,7 @@
private Activity mContext;
private PreviewSizeSeekBarController mSeekBarController;
private FontSizeData mFontSizeData;
- private LabeledSeekBarPreference mSeekBarPreference;
+ private AccessibilitySeekBarPreference mSeekBarPreference;
private PreferenceScreen mPreferenceScreen;
private TestFragment mFragment;
@@ -109,7 +107,7 @@
mPreferenceScreen = spy(new PreferenceScreen(mContext, /* attrs= */ null));
when(mPreferenceScreen.getPreferenceManager()).thenReturn(mPreferenceManager);
doReturn(mPreferenceScreen).when(mFragment).getPreferenceScreen();
- mSeekBarPreference = spy(new LabeledSeekBarPreference(mContext, /* attrs= */ null));
+ mSeekBarPreference = spy(new AccessibilitySeekBarPreference(mContext, /* attrs= */ null));
mSeekBarPreference.setKey(FONT_SIZE_KEY);
LayoutInflater inflater = LayoutInflater.from(mContext);
@@ -246,12 +244,11 @@
@Test
@Config(shadows = ShadowFragment.class)
- public void restoreValueFromSavedInstanceState_showTooltipView() {
- final Bundle savedInstanceState = new Bundle();
- savedInstanceState.putBoolean(KEY_SAVED_QS_TOOLTIP_RESHOW, /* value= */ true);
- mSeekBarController.onCreate(savedInstanceState);
+ public void enabledNeedsQSTooltipReshow_showTooltipView() {
+ mSeekBarPreference.setNeedsQSTooltipReshow(true);
mSeekBarController.displayPreference(mPreferenceScreen);
+ mSeekBarController.onStart();
ShadowLooper.idleMainLooper();
assertThat(getLatestPopupWindow().isShowing()).isTrue();
diff --git a/tests/robotests/src/com/android/settings/accessibility/TextReadingPreviewControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/TextReadingPreviewControllerTest.java
index f768e42..375952f 100644
--- a/tests/robotests/src/com/android/settings/accessibility/TextReadingPreviewControllerTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/TextReadingPreviewControllerTest.java
@@ -28,7 +28,6 @@
import com.android.settings.display.PreviewPagerAdapter;
import com.android.settings.testutils.shadow.ShadowInteractionJankMonitor;
-import com.android.settings.widget.LabeledSeekBarPreference;
import org.junit.Before;
import org.junit.Test;
@@ -54,8 +53,8 @@
private final Context mContext = ApplicationProvider.getApplicationContext();
private TextReadingPreviewController mPreviewController;
private TextReadingPreviewPreference mPreviewPreference;
- private LabeledSeekBarPreference mFontSizePreference;
- private LabeledSeekBarPreference mDisplaySizePreference;
+ private AccessibilitySeekBarPreference mFontSizePreference;
+ private AccessibilitySeekBarPreference mDisplaySizePreference;
@Mock
private DisplaySizeData mDisplaySizeData;
@@ -73,8 +72,8 @@
mPreviewPreference = spy(new TextReadingPreviewPreference(mContext, /* attr= */ null));
mPreviewController = new TextReadingPreviewController(mContext, PREVIEW_KEY, fontSizeData,
mDisplaySizeData);
- mFontSizePreference = new LabeledSeekBarPreference(mContext, /* attr= */ null);
- mDisplaySizePreference = new LabeledSeekBarPreference(mContext, /* attr= */ null);
+ mFontSizePreference = new AccessibilitySeekBarPreference(mContext, /* attr= */ null);
+ mDisplaySizePreference = new AccessibilitySeekBarPreference(mContext, /* attr= */ null);
}
@Test
diff --git a/tests/robotests/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragmentTest.java
index 86322f9..6dbb8b5 100644
--- a/tests/robotests/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragmentTest.java
@@ -316,14 +316,6 @@
}
@Test
- @Config(shadows = ShadowFragment.class)
- public void showQuickSettingsTooltipIfNeeded_dontShowTooltipView() {
- mFragment.showQuickSettingsTooltipIfNeeded(QuickSettingsTooltipType.GUIDE_TO_EDIT);
-
- assertThat(getLatestPopupWindow()).isNull();
- }
-
- @Test
public void getShortcutTypeSummary_shortcutSummaryIsCorrectlySet() {
final PreferredShortcut userPreferredShortcut = new PreferredShortcut(
PLACEHOLDER_COMPONENT_NAME.flattenToString(),
diff --git a/tests/robotests/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdaterTest.java
index 9609af4..2251c3b 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdaterTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdaterTest.java
@@ -124,24 +124,17 @@
when(mCachedBluetoothDevice.getDrawableWithDescription()).thenReturn(pairs);
when(mCachedBluetoothDevice.getMemberDevice()).thenReturn(ImmutableSet.of());
- mBluetoothDeviceUpdater =
- spy(
- new AvailableMediaBluetoothDeviceUpdater(
- mContext, mDevicePreferenceCallback, /* metricsCategory= */ 0));
- mBluetoothDeviceUpdater.setPrefContext(mContext);
mPreference =
new BluetoothDevicePreference(
mContext,
mCachedBluetoothDevice,
false,
BluetoothDevicePreference.SortType.TYPE_DEFAULT);
- doNothing().when(mBluetoothDeviceUpdater).addPreference(any());
- doNothing().when(mBluetoothDeviceUpdater).removePreference(any());
}
@Test
public void onAudioModeChanged_hfpDeviceConnected_inCall_addPreference() {
- mAudioManager.setMode(AudioManager.MODE_IN_CALL);
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_IN_CALL);
when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
.thenReturn(true);
when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true);
@@ -153,7 +146,7 @@
@Test
public void onAudioModeChanged_hfpDeviceConnected_notInCall_removePreference() {
- mAudioManager.setMode(AudioManager.MODE_NORMAL);
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
.thenReturn(true);
when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true);
@@ -165,7 +158,7 @@
@Test
public void onAudioModeChanged_a2dpDeviceConnected_inCall_removePreference() {
- mAudioManager.setMode(AudioManager.MODE_IN_CALL);
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_IN_CALL);
when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
.thenReturn(true);
when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true);
@@ -177,7 +170,7 @@
@Test
public void onAudioModeChanged_a2dpDeviceConnected_notInCall_addPreference() {
- mAudioManager.setMode(AudioManager.MODE_NORMAL);
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
.thenReturn(true);
when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true);
@@ -189,7 +182,7 @@
@Test
public void onProfileConnectionStateChanged_a2dpDeviceConnected_notInCall_addPreference() {
- mAudioManager.setMode(AudioManager.MODE_NORMAL);
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
.thenReturn(true);
when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true);
@@ -202,7 +195,7 @@
@Test
public void onProfileConnectionStateChanged_a2dpDeviceConnected_inCall_removePreference() {
- mAudioManager.setMode(AudioManager.MODE_IN_CALL);
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_IN_CALL);
when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
.thenReturn(true);
when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true);
@@ -215,7 +208,7 @@
@Test
public void onProfileConnectionStateChanged_hfpDeviceConnected_notInCall_removePreference() {
- mAudioManager.setMode(AudioManager.MODE_NORMAL);
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
.thenReturn(true);
when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true);
@@ -228,7 +221,7 @@
@Test
public void onProfileConnectionStateChanged_hfpDeviceConnected_inCall_addPreference() {
- mAudioManager.setMode(AudioManager.MODE_IN_CALL);
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_IN_CALL);
when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
.thenReturn(true);
when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true);
@@ -241,7 +234,7 @@
@Test
public void onProfileConnectionStateChanged_ashaHearingAidConnected_notInCall_addPreference() {
- mAudioManager.setMode(AudioManager.MODE_NORMAL);
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
.thenReturn(true);
when(mCachedBluetoothDevice.isConnectedAshaHearingAidDevice()).thenReturn(true);
@@ -256,7 +249,7 @@
@Test
public void onProfileConnectionStateChanged_ashaHearingAidConnected_inCall_addPreference() {
- mAudioManager.setMode(AudioManager.MODE_IN_CALL);
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_IN_CALL);
when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
.thenReturn(true);
when(mCachedBluetoothDevice.isConnectedAshaHearingAidDevice()).thenReturn(true);
@@ -272,7 +265,7 @@
@Test
public void onProfileConnectionStateChanged_leaConnected_notInCallSharingFlagOff_addPref() {
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
- mAudioManager.setMode(AudioManager.MODE_NORMAL);
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
.thenReturn(true);
when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
@@ -292,7 +285,7 @@
@Test
public void onProfileConnectionStateChanged_leaConnected_notInCallNotInSharing_addPref() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
- mAudioManager.setMode(AudioManager.MODE_NORMAL);
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
.thenReturn(true);
when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
@@ -309,7 +302,7 @@
@Test
public void onProfileConnectionStateChanged_leaConnected_inCallSharingFlagOff_addPref() {
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
- mAudioManager.setMode(AudioManager.MODE_IN_CALL);
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_IN_CALL);
when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
.thenReturn(true);
when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
@@ -326,7 +319,7 @@
@Test
public void onProfileConnectionStateChanged_leaConnected_inCallNotInSharing_addPref() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
- mAudioManager.setMode(AudioManager.MODE_IN_CALL);
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_IN_CALL);
when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
.thenReturn(true);
when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
@@ -344,7 +337,7 @@
public void onProfileConnectionStateChanged_leaConnected_notInCallInSharing_removePref() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
- mAudioManager.setMode(AudioManager.MODE_NORMAL);
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
.thenReturn(true);
when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
@@ -367,7 +360,7 @@
onProfileConnectionStateChanged_leaConnected_noInCallInSharing_hysteresis_removePref() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
- mAudioManager.setMode(AudioManager.MODE_NORMAL);
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
.thenReturn(true);
when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
@@ -388,7 +381,7 @@
public void onProfileConnectionStateChanged_leaConnected_inCallSharing_removePref() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
- mAudioManager.setMode(AudioManager.MODE_NORMAL);
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
.thenReturn(true);
when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
@@ -410,7 +403,7 @@
public void onProfileConnectionStateChanged_leaConnected_inCallSharing_hysteresis_removePref() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
- mAudioManager.setMode(AudioManager.MODE_NORMAL);
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
.thenReturn(true);
when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
@@ -430,7 +423,7 @@
@Test
public void
onProfileConnectionStateChanged_deviceIsNotInList_notInCall_invokesRemovePreference() {
- mAudioManager.setMode(AudioManager.MODE_NORMAL);
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
.thenReturn(true);
when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
@@ -446,7 +439,7 @@
@Test
public void onProfileConnectionStateChanged_deviceIsNotInList_inCall_invokesRemovePreference() {
- mAudioManager.setMode(AudioManager.MODE_IN_CALL);
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_IN_CALL);
when(mBluetoothDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class)))
.thenReturn(true);
when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
@@ -462,6 +455,7 @@
@Test
public void onProfileConnectionStateChanged_deviceDisconnected_removePreference() {
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL);
mBluetoothDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.A2DP);
@@ -470,8 +464,19 @@
@Test
public void onClick_Preference_setActive() {
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL);
mBluetoothDeviceUpdater.onPreferenceClick(mPreference);
verify(mDevicePreferenceCallback).onDeviceClick(mPreference);
}
+
+ private void setUpDeviceUpdaterWithAudioMode(int audioMode) {
+ mAudioManager.setMode(audioMode);
+ mBluetoothDeviceUpdater =
+ spy(new AvailableMediaBluetoothDeviceUpdater(
+ mContext, mDevicePreferenceCallback, /* metricsCategory= */ 0));
+ mBluetoothDeviceUpdater.setPrefContext(mContext);
+ doNothing().when(mBluetoothDeviceUpdater).addPreference(any());
+ doNothing().when(mBluetoothDeviceUpdater).removePreference(any());
+ }
}
diff --git a/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java
index b2449da..f68a8d4 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java
@@ -112,16 +112,11 @@
when(mCachedBluetoothDevice.getAddress()).thenReturn(MAC_ADDRESS);
when(mCachedBluetoothDevice.getDrawableWithDescription()).thenReturn(pairs);
mShadowCachedBluetoothDeviceManager.setCachedDevicesCopy(mCachedDevices);
- mBluetoothDeviceUpdater = spy(new ConnectedBluetoothDeviceUpdater(mContext,
- mDevicePreferenceCallback, /* metricsCategory= */ 0));
- mBluetoothDeviceUpdater.setPrefContext(mContext);
- doNothing().when(mBluetoothDeviceUpdater).addPreference(any());
- doNothing().when(mBluetoothDeviceUpdater).removePreference(any());
}
@Test
public void onAudioModeChanged_hfpDeviceConnected_notInCall_addPreference() {
- mAudioManager.setMode(AudioManager.MODE_NORMAL);
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater.
isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true);
@@ -133,7 +128,7 @@
@Test
public void onAudioModeChanged_hfpDeviceConnected_inCall_removePreference() {
- mAudioManager.setMode(AudioManager.MODE_IN_CALL);
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_IN_CALL);
when(mBluetoothDeviceUpdater.
isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true);
@@ -145,7 +140,7 @@
@Test
public void onAudioModeChanged_a2dpDeviceConnected_notInCall_removePreference() {
- mAudioManager.setMode(AudioManager.MODE_NORMAL);
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater.
isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true);
@@ -157,7 +152,7 @@
@Test
public void onAudioModeChanged_a2dpDeviceConnected_inCall_addPreference() {
- mAudioManager.setMode(AudioManager.MODE_IN_CALL);
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_IN_CALL);
when(mBluetoothDeviceUpdater.
isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true);
@@ -169,7 +164,7 @@
@Test
public void onProfileConnectionStateChanged_a2dpDeviceConnected_inCall_addPreference() {
- mAudioManager.setMode(AudioManager.MODE_IN_CALL);
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_IN_CALL);
when(mBluetoothDeviceUpdater.
isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true);
@@ -182,7 +177,7 @@
@Test
public void onProfileConnectionStateChanged_deviceIsNotInList_inCall_invokesRemovePreference() {
- mAudioManager.setMode(AudioManager.MODE_IN_CALL);
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_IN_CALL);
when(mBluetoothDeviceUpdater.
isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true);
@@ -196,7 +191,7 @@
@Test
public void onProfileConnectionStateChanged_a2dpDeviceConnected_notInCall_removePreference() {
- mAudioManager.setMode(AudioManager.MODE_NORMAL);
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater.
isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true);
@@ -209,7 +204,7 @@
@Test
public void onProfileConnectionStateChanged_hfpDeviceConnected_inCall_removePreference() {
- mAudioManager.setMode(AudioManager.MODE_IN_CALL);
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_IN_CALL);
when(mBluetoothDeviceUpdater.
isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true);
@@ -222,7 +217,7 @@
@Test
public void onProfileConnectionStateChanged_hfpDeviceConnected_notInCall_addPreference() {
- mAudioManager.setMode(AudioManager.MODE_NORMAL);
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater.
isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true);
@@ -236,7 +231,7 @@
@Test
public void onProfileConnectionStateChanged_ashaHearingAidConnected_inCall_removePreference()
{
- mAudioManager.setMode(AudioManager.MODE_IN_CALL);
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_IN_CALL);
when(mBluetoothDeviceUpdater.
isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedAshaHearingAidDevice()).thenReturn(true);
@@ -250,7 +245,7 @@
@Test
public void onProfileConnectionStateChanged_ashaHearingAidConnected_notInCall_removePreference()
{
- mAudioManager.setMode(AudioManager.MODE_NORMAL);
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater.
isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedAshaHearingAidDevice()).thenReturn(true);
@@ -263,7 +258,7 @@
@Test
public void onProfileConnectionStateChanged_leAudioDeviceConnected_inCall_removesPreference() {
- mAudioManager.setMode(AudioManager.MODE_IN_CALL);
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_IN_CALL);
when(mBluetoothDeviceUpdater
.isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
@@ -277,7 +272,7 @@
@Test
public void onProfileConnectionStateChanged_leAudioDeviceConnected_notInCall_removesPreference()
{
- mAudioManager.setMode(AudioManager.MODE_NORMAL);
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater
.isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
@@ -290,7 +285,7 @@
@Test
public void onProfileConnectionStateChanged_deviceIsNotInList_inCall_invokesRemovesPreference()
{
- mAudioManager.setMode(AudioManager.MODE_IN_CALL);
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_IN_CALL);
when(mBluetoothDeviceUpdater
.isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
@@ -305,7 +300,7 @@
@Test
public void onProfileConnectionStateChanged_deviceIsNotInList_notInCall_invokesRemovesPreference
() {
- mAudioManager.setMode(AudioManager.MODE_NORMAL);
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater
.isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
@@ -319,6 +314,7 @@
@Test
public void onProfileConnectionStateChanged_deviceDisconnected_removePreference() {
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL);
mBluetoothDeviceUpdater.onProfileConnectionStateChanged(mCachedBluetoothDevice,
BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.A2DP);
@@ -327,6 +323,7 @@
@Test
public void addPreference_addPreference_shouldHideSecondTarget() {
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL);
BluetoothDevicePreference btPreference =
new BluetoothDevicePreference(mContext, mCachedBluetoothDevice,
true, BluetoothDevicePreference.SortType.TYPE_DEFAULT);
@@ -340,7 +337,7 @@
@Test
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
public void update_notExclusiveManagedDevice_addDevice() {
- mAudioManager.setMode(AudioManager.MODE_NORMAL);
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater
.isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true);
@@ -356,7 +353,7 @@
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
public void update_exclusivelyManagedDevice_packageNotInstalled_addDevice()
throws Exception {
- mAudioManager.setMode(AudioManager.MODE_NORMAL);
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater
.isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true);
@@ -376,7 +373,7 @@
throws Exception {
ApplicationInfo appInfo = new ApplicationInfo();
appInfo.enabled = false;
- mAudioManager.setMode(AudioManager.MODE_NORMAL);
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater
.isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true);
@@ -393,7 +390,7 @@
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
public void update_exclusivelyManagedDevice_packageInstalledAndEnabled_removePreference()
throws Exception {
- mAudioManager.setMode(AudioManager.MODE_NORMAL);
+ setUpDeviceUpdaterWithAudioMode(AudioManager.MODE_NORMAL);
when(mBluetoothDeviceUpdater
.isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true);
@@ -407,4 +404,13 @@
verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice);
verify(mBluetoothDeviceUpdater, never()).addPreference(mCachedBluetoothDevice);
}
+
+ private void setUpDeviceUpdaterWithAudioMode(int audioMode) {
+ mAudioManager.setMode(audioMode);
+ mBluetoothDeviceUpdater = spy(new ConnectedBluetoothDeviceUpdater(mContext,
+ mDevicePreferenceCallback, /* metricsCategory= */ 0));
+ mBluetoothDeviceUpdater.setPrefContext(mContext);
+ doNothing().when(mBluetoothDeviceUpdater).addPreference(any());
+ doNothing().when(mBluetoothDeviceUpdater).removePreference(any());
+ }
}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallbackTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallbackTest.java
index 4e962c7..6aff8c3 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallbackTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallbackTest.java
@@ -54,7 +54,7 @@
@RunWith(RobolectricTestRunner.class)
@Config(
shadows = {
- ShadowBluetoothAdapter.class,
+ ShadowBluetoothAdapter.class,
})
public class AudioStreamsProgressCategoryCallbackTest {
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@@ -70,8 +70,8 @@
@Before
public void setUp() {
mSetFlagsRule.disableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
- ShadowBluetoothAdapter shadowBluetoothAdapter = Shadow.extract(
- BluetoothAdapter.getDefaultAdapter());
+ ShadowBluetoothAdapter shadowBluetoothAdapter =
+ Shadow.extract(BluetoothAdapter.getDefaultAdapter());
shadowBluetoothAdapter.setEnabled(true);
shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
BluetoothStatusCodes.FEATURE_SUPPORTED);
@@ -87,7 +87,7 @@
when(mState.getBisSyncState()).thenReturn(bisSyncState);
mCallback.onReceiveStateChanged(mDevice, /* sourceId= */ 0, mState);
- verify(mController).handleSourceConnected(any());
+ verify(mController).handleSourceConnected(any(), any());
}
@Test
@@ -102,7 +102,7 @@
when(mSourceDevice.getAddress()).thenReturn(address);
mCallback.onReceiveStateChanged(mDevice, /* sourceId= */ 0, mState);
- verify(mController).handleSourcePresent(any());
+ verify(mController).handleSourcePresent(any(), any());
}
@Test
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryControllerTest.java
index 78d4d6e..f042329 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryControllerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryControllerTest.java
@@ -32,6 +32,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -41,7 +42,7 @@
import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf;
-import static java.util.Collections.emptyList;
+import static java.util.Collections.emptyMap;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
@@ -93,6 +94,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
@RunWith(RobolectricTestRunner.class)
@Config(
@@ -134,8 +136,8 @@
@Before
public void setUp() {
- ShadowBluetoothAdapter shadowBluetoothAdapter = Shadow.extract(
- BluetoothAdapter.getDefaultAdapter());
+ ShadowBluetoothAdapter shadowBluetoothAdapter =
+ Shadow.extract(BluetoothAdapter.getDefaultAdapter());
shadowBluetoothAdapter.setEnabled(true);
shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
BluetoothStatusCodes.FEATURE_SUPPORTED);
@@ -143,7 +145,7 @@
BluetoothStatusCodes.FEATURE_SUPPORTED);
ShadowAudioStreamsHelper.setUseMock(mAudioStreamsHelper);
when(mAudioStreamsHelper.getLeBroadcastAssistant()).thenReturn(mLeBroadcastAssistant);
- when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(emptyList());
+ when(mAudioStreamsHelper.getAllSourcesByDevice()).thenReturn(emptyMap());
mSetFlagsRule.disableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBtManager;
@@ -310,14 +312,12 @@
// Setup a device
ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
- List<BluetoothLeBroadcastReceiveState> connectedList = new ArrayList<>();
// Empty connected device list
- when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(connectedList);
+ when(mAudioStreamsHelper.getAllSourcesByDevice()).thenReturn(emptyMap());
mController.onStart(mLifecycleOwner);
shadowOf(Looper.getMainLooper()).idle();
- verify(mAudioStreamsHelper).getAllPresentSources();
verify(mLeBroadcastAssistant).startSearchingForSources(any());
var dialog = ShadowAlertDialog.getLatestAlertDialog();
@@ -355,7 +355,7 @@
}
@Test
- public void testOnStart_handleSourceAlreadyConnected() {
+ public void testOnStart_handleSourceAlreadyConnected_useNameFromMetadata() {
// Setup a device
ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
@@ -363,8 +363,14 @@
BluetoothLeBroadcastReceiveState connected =
createConnectedMock(ALREADY_CONNECTED_BROADCAST_ID);
List<BluetoothLeBroadcastReceiveState> list = new ArrayList<>();
+ var data = mock(BluetoothLeAudioContentMetadata.class);
+ when(connected.getSubgroupMetadata()).thenReturn(ImmutableList.of(data));
+ when(data.getProgramInfo()).thenReturn(BROADCAST_NAME_1);
list.add(connected);
- when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(list);
+ when(mMetadata.getBroadcastId()).thenReturn(ALREADY_CONNECTED_BROADCAST_ID);
+ when(mMetadata.getBroadcastName()).thenReturn(BROADCAST_NAME_2);
+ when(mLeBroadcastAssistant.getSourceMetadata(any(), anyInt())).thenReturn(mMetadata);
+ when(mAudioStreamsHelper.getAllSourcesByDevice()).thenReturn(Map.of(mSourceDevice, list));
// Handle already connected source in onStart
mController.displayPreference(mScreen);
@@ -382,6 +388,7 @@
assertThat(preference.getValue()).isNotNull();
assertThat(preference.getValue().getAudioStreamBroadcastId())
.isEqualTo(ALREADY_CONNECTED_BROADCAST_ID);
+ assertThat(preference.getValue().getTitle()).isEqualTo(BROADCAST_NAME_2);
assertThat(state.getValue()).isEqualTo(SOURCE_ADDED);
}
@@ -409,7 +416,8 @@
var data = mock(BluetoothLeAudioContentMetadata.class);
when(connected.getSubgroupMetadata()).thenReturn(ImmutableList.of(data));
when(data.getProgramInfo()).thenReturn(BROADCAST_NAME_1);
- when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(ImmutableList.of(connected));
+ when(mAudioStreamsHelper.getAllSourcesByDevice())
+ .thenReturn(Map.of(mSourceDevice, ImmutableList.of(connected)));
// Handle both source from qr code and already connected source in onStart
mController.displayPreference(mScreen);
@@ -578,8 +586,8 @@
// Setup source already connected
BluetoothLeBroadcastReceiveState connected =
createConnectedMock(ALREADY_CONNECTED_BROADCAST_ID);
- when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(ImmutableList.of(connected));
-
+ when(mAudioStreamsHelper.getAllSourcesByDevice())
+ .thenReturn(Map.of(mSourceDevice, List.of(connected)));
// Handle source already connected in onStart
mController.displayPreference(mScreen);
mController.onStart(mLifecycleOwner);
@@ -687,7 +695,8 @@
// Setup already connected source
BluetoothLeBroadcastReceiveState connected =
createConnectedMock(ALREADY_CONNECTED_BROADCAST_ID);
- when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(ImmutableList.of(connected));
+ when(mAudioStreamsHelper.getAllSourcesByDevice())
+ .thenReturn(Map.of(mSourceDevice, List.of(connected)));
// Handle connected source in onStart
mController.displayPreference(mScreen);
@@ -695,7 +704,7 @@
shadowOf(Looper.getMainLooper()).idle();
// The connect source is no longer connected
- when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(emptyList());
+ when(mAudioStreamsHelper.getAllSourcesByDevice()).thenReturn(emptyMap());
mController.handleSourceRemoved();
shadowOf(Looper.getMainLooper()).idle();
@@ -728,7 +737,8 @@
// Setup a connected source
BluetoothLeBroadcastReceiveState connected =
createConnectedMock(ALREADY_CONNECTED_BROADCAST_ID);
- when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(ImmutableList.of(connected));
+ when(mAudioStreamsHelper.getAllSourcesByDevice())
+ .thenReturn(Map.of(mSourceDevice, List.of(connected)));
// Handle connected source in onStart
mController.displayPreference(mScreen);
@@ -834,7 +844,7 @@
when(receiveState.getBisSyncState()).thenReturn(bisSyncState);
// The new found source is identified as failed to connect
- mController.handleSourcePresent(receiveState);
+ mController.handleSourcePresent(mSourceDevice, receiveState);
shadowOf(Looper.getMainLooper()).idle();
ArgumentCaptor<AudioStreamPreference> preference =
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/testshadows/ShadowAudioStreamsHelper.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/testshadows/ShadowAudioStreamsHelper.java
index c7d0c60..e5e51fc 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/testshadows/ShadowAudioStreamsHelper.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/testshadows/ShadowAudioStreamsHelper.java
@@ -16,6 +16,7 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams.testshadows;
+import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothLeBroadcastReceiveState;
@@ -31,6 +32,7 @@
import org.robolectric.annotation.Resetter;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
@Implements(value = AudioStreamsHelper.class, callThroughByDefault = true)
@@ -60,6 +62,11 @@
}
@Implementation
+ public Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> getAllSourcesByDevice() {
+ return sMockHelper.getAllSourcesByDevice();
+ }
+
+ @Implementation
public List<BluetoothLeBroadcastReceiveState> getAllPresentSources() {
return sMockHelper.getAllPresentSources();
}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/display/DisplayTopologyPreferenceTest.kt b/tests/robotests/src/com/android/settings/connecteddevice/display/DisplayTopologyPreferenceTest.kt
new file mode 100644
index 0000000..ad633cc
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/connecteddevice/display/DisplayTopologyPreferenceTest.kt
@@ -0,0 +1,120 @@
+/*
+ * 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.display
+
+import android.hardware.display.DisplayTopology.TreeNode.POSITION_LEFT
+
+import android.content.Context
+import android.graphics.Color
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.ColorDrawable
+import android.hardware.display.DisplayTopology
+import android.view.View
+import android.widget.FrameLayout
+import androidx.preference.PreferenceViewHolder
+import androidx.test.core.app.ApplicationProvider
+
+import com.android.settings.R
+import com.google.common.truth.Truth.assertThat
+
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+class DisplayTopologyPreferenceTest {
+ val context = ApplicationProvider.getApplicationContext<Context>()
+ val preference = DisplayTopologyPreference(context)
+ val injector = TestInjector()
+ val rootView = View.inflate(context, preference.layoutResource, /*parent=*/ null)
+ val holder = PreferenceViewHolder.createInstanceForTests(rootView)
+ val wallpaper = ColorDrawable(Color.MAGENTA)
+
+ init {
+ preference.injector = injector
+ injector.systemWallpaper = wallpaper
+ preference.onBindViewHolder(holder)
+ }
+
+ class TestInjector : DisplayTopologyPreference.Injector() {
+ var topology : DisplayTopology? = null
+ var systemWallpaper : Drawable? = null
+
+ override fun displayTopology(context : Context) : DisplayTopology? { return topology }
+
+ override fun wallpaper(context : Context) : Drawable { return systemWallpaper!! }
+ }
+
+ @Test
+ fun disabledTopology() {
+ preference.onAttached()
+ preference.onGlobalLayout()
+
+ assertThat(preference.mPaneContent.childCount).isEqualTo(0)
+ // TODO(b/352648432): update test when we show the main display even when
+ // a topology is not active.
+ assertThat(preference.mTopologyHint.text).isEqualTo("")
+ }
+
+ @Test
+ fun twoDisplaysGenerateBlocks() {
+ val child = DisplayTopology.TreeNode(
+ /* displayId= */ 42, /* width= */ 100f, /* height= */ 80f,
+ POSITION_LEFT, /* offset= */ 42f)
+ val root = DisplayTopology.TreeNode(
+ /* displayId= */ 0, /* width= */ 200f, /* height= */ 160f,
+ POSITION_LEFT, /* offset= */ 0f)
+ root.addChild(child)
+ injector.topology = DisplayTopology(root, /*primaryDisplayId=*/ 0)
+
+ // This layoutParams needs to be non-null for the global layout handler.
+ preference.mPaneHolder.layoutParams = FrameLayout.LayoutParams(
+ /* width= */ 640, /* height= */ 480)
+
+ // Force pane width to have a reasonable value (hundreds of dp) so the TopologyScale is
+ // calculated reasonably.
+ preference.mPaneContent.left = 0
+ preference.mPaneContent.right = 640
+
+ preference.onAttached()
+ preference.onGlobalLayout()
+
+ assertThat(preference.mPaneContent.childCount).isEqualTo(2)
+ val block0 = preference.mPaneContent.getChildAt(0)
+ val block1 = preference.mPaneContent.getChildAt(1)
+
+ // Block of child display is on the left.
+ val (childBlock, rootBlock) = if (block0.x < block1.x)
+ listOf(block0, block1)
+ else
+ listOf(block1, block0)
+
+ // After accounting for padding, child should be half the length of root in each dimension.
+ assertThat(childBlock.layoutParams.width + BLOCK_PADDING)
+ .isEqualTo(rootBlock.layoutParams.width / 2)
+ assertThat(childBlock.layoutParams.height + BLOCK_PADDING)
+ .isEqualTo(rootBlock.layoutParams.height / 2)
+ assertThat(childBlock.y).isGreaterThan(rootBlock.y)
+ assertThat(block0.background).isEqualTo(wallpaper)
+ assertThat(block1.background).isEqualTo(wallpaper)
+ assertThat(rootBlock.x - BLOCK_PADDING * 2)
+ .isEqualTo(childBlock.x + childBlock.layoutParams.width)
+
+ assertThat(preference.mTopologyHint.text)
+ .isEqualTo(context.getString(R.string.external_display_topology_hint))
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/display/TopologyScaleTest.kt b/tests/robotests/src/com/android/settings/connecteddevice/display/TopologyScaleTest.kt
index 0784362..76f9bfc 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/display/TopologyScaleTest.kt
+++ b/tests/robotests/src/com/android/settings/connecteddevice/display/TopologyScaleTest.kt
@@ -35,6 +35,7 @@
fun oneDisplay4to3Aspect() {
val scale = TopologyScale(
/* paneWidth= */ 640,
+ minEdgeLength = 48, maxBlockRatio = 0.05f,
listOf(RectF(0f, 0f, 640f, 480f)))
// blockRatio is higher than 0.05 in order to make the smallest display edge (480 dp) 48dp
@@ -51,6 +52,7 @@
fun twoUnalignedDisplays() {
val scale = TopologyScale(
/* paneWidth= */ 300,
+ minEdgeLength = 48, maxBlockRatio = 0.05f,
listOf(RectF(0f, 0f, 1920f, 1200f), RectF(1920f, -300f, 3840f, 900f)))
assertEquals(
@@ -65,6 +67,7 @@
fun twoDisplaysBlockRatioBumpedForGarSizeMinimumHorizontal() {
val scale = TopologyScale(
/* paneWidth= */ 192,
+ minEdgeLength = 48, maxBlockRatio = 0.05f,
listOf(RectF(0f, 0f, 240f, 320f), RectF(-240f, -320f, 0f, 0f)))
// blockRatio is higher than 0.05 in order to make the smallest display edge (240 dp) 48dp
@@ -81,6 +84,7 @@
fun paneVerticalPaddingLimitedByTallestDisplay() {
val scale = TopologyScale(
/* paneWidth= */ 300,
+ minEdgeLength = 48, maxBlockRatio = 0.05f,
listOf(
RectF(0f, 0f, 640f, 480f),
RectF(0f, 480f, 640f, 960f),
@@ -94,4 +98,35 @@
assertEquals(Point(150, 48), scale.displayToPaneCoor(PointF(320f, 0f)))
assertPointF(-180f, 2880f, 0.001f, scale.paneToDisplayCoor(Point(100, 336)))
}
+
+ @Test
+ fun limitedByCustomMaxBlockRatio() {
+ val scale = TopologyScale(
+ /* paneWidth= */ 300,
+ minEdgeLength = 24, maxBlockRatio = 0.12f,
+ listOf(
+ RectF(0f, 0f, 640f, 480f),
+ RectF(0f, 480f, 640f, 960f)))
+
+ assertEquals(
+ "{TopoScale blockRatio=0.120000 originPaneXY=111,57 paneHeight=230}", "" + scale)
+ assertEquals(Point(149, 57), scale.displayToPaneCoor(PointF(320f, 0f)))
+ assertPointF(-91.6667f, 2325f, 0.001f, scale.paneToDisplayCoor(Point(100, 336)))
+ }
+
+ @Test
+ fun largeCustomMinEdgeLength() {
+ // minBlockEdgeLength/minDisplayEdgeLength = 80/480 = 1/6, so the block ratio will be 1/6
+ val scale = TopologyScale(
+ /* paneWidth= */ 300,
+ minEdgeLength = 80, maxBlockRatio = 0.05f,
+ listOf(
+ RectF(0f, 0f, 640f, 480f),
+ RectF(0f, 480f, 640f, 960f)))
+
+ assertEquals(
+ "{TopoScale blockRatio=0.166667 originPaneXY=96,80 paneHeight=320}", "" + scale)
+ assertEquals(Point(149, 80), scale.displayToPaneCoor(PointF(320f, 0f)))
+ assertPointF(24f, 1536f, 0.001f, scale.paneToDisplayCoor(Point(100, 336)))
+ }
}
diff --git a/tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java b/tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java
index 4b8c9de..ce33401 100644
--- a/tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java
+++ b/tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java
@@ -28,11 +28,13 @@
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
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;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -42,6 +44,7 @@
import android.os.Process;
import android.telephony.SubscriptionManager;
import android.util.ArraySet;
+import android.util.FeatureFlagUtils;
import androidx.fragment.app.FragmentActivity;
import androidx.preference.PreferenceManager;
@@ -51,22 +54,20 @@
import com.android.settings.applications.AppInfoBase;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.shadow.ShadowDataUsageUtils;
-import com.android.settings.testutils.shadow.ShadowEntityHeaderController;
import com.android.settings.testutils.shadow.ShadowFragment;
import com.android.settings.testutils.shadow.ShadowRestrictedLockUtilsInternal;
-import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.AppItem;
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import com.android.settingslib.RestrictedSwitchPreference;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.net.UidDetail;
import com.android.settingslib.net.UidDetailProvider;
+import com.android.settingslib.widget.IntroPreference;
-import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Answers;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric;
@@ -79,27 +80,25 @@
import java.util.List;
@RunWith(RobolectricTestRunner.class)
-@Config(shadows = {ShadowEntityHeaderController.class, ShadowRestrictedLockUtilsInternal.class})
+@Config(shadows = {ShadowRestrictedLockUtilsInternal.class})
public class AppDataUsageTest {
- @Mock(answer = Answers.RETURNS_DEEP_STUBS)
- private EntityHeaderController mHeaderController;
@Mock
private PackageManager mPackageManager;
+ private IntroPreference mIntroPreference;
+
private AppDataUsage mFragment;
+ private Context mContext;
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- ShadowEntityHeaderController.setUseMock(mHeaderController);
- when(mHeaderController.setUid(anyInt())).thenReturn(mHeaderController);
- }
-
- @After
- public void tearDown() {
- ShadowEntityHeaderController.reset();
+ mContext = spy(RuntimeEnvironment.application);
+ mIntroPreference = new IntroPreference(mContext);
+ FeatureFlagUtils.setEnabled(mContext, FeatureFlagUtils.SETTINGS_ENABLE_SPA, true);
}
@Test
@@ -161,6 +160,7 @@
}
@Test
+ @Config(shadows = ShadowFragment.class)
public void bindAppHeader_allWorkApps_shouldNotShowAppInfoLink() {
mFragment = spy(new TestFragment());
@@ -169,12 +169,20 @@
doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen();
ReflectionHelpers.setField(mFragment, "mAppItem", mock(AppItem.class));
- mFragment.addEntityHeader();
+ when(mFragment.getPreferenceScreen().findPreference(AppDataUsage.ARG_APP_HEADER))
+ .thenReturn(mIntroPreference);
+ when(mFragment.getContext()).thenReturn(mContext);
+ doNothing().when(mContext).startActivity(any());
- verify(mHeaderController).setHasAppInfoLink(false);
+ mFragment.setupIntroPreference();
+ mFragment.onPreferenceTreeClick(mIntroPreference);
+
+ verify(mFragment, never()).getActivity();
+ verify(mContext, never()).startActivity(any(Intent.class));
}
@Test
+ @Config(shadows = ShadowFragment.class)
public void bindAppHeader_workApp_shouldSetWorkAppUid()
throws PackageManager.NameNotFoundException {
final int fakeUserId = 100;
@@ -188,19 +196,21 @@
ReflectionHelpers.setField(mFragment, "mAppItem", appItem);
ReflectionHelpers.setField(mFragment, "mPackages", packages);
- when(mPackageManager.getPackageUidAsUser(anyString(), anyInt()))
- .thenReturn(fakeUserId);
-
- when(mHeaderController.setHasAppInfoLink(anyBoolean())).thenReturn(mHeaderController);
-
+ when(mPackageManager.getPackageUidAsUser(anyString(), anyInt())).thenReturn(fakeUserId);
when(mFragment.getPreferenceManager())
.thenReturn(mock(PreferenceManager.class, RETURNS_DEEP_STUBS));
doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen();
- mFragment.addEntityHeader();
+ when(mFragment.getPreferenceScreen().findPreference(AppDataUsage.ARG_APP_HEADER))
+ .thenReturn(mIntroPreference);
+ when(mFragment.getContext()).thenReturn(mContext);
+ doNothing().when(mContext).startActivity(any());
- verify(mHeaderController).setHasAppInfoLink(true);
- verify(mHeaderController).setUid(fakeUserId);
+ mFragment.setupIntroPreference();
+ mFragment.onPreferenceTreeClick(mIntroPreference);
+
+ ArgumentCaptor<Intent> argumentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mContext).startActivity(argumentCaptor.capture());
}
@Test
diff --git a/tests/robotests/src/com/android/settings/datetime/AutoTimeFormatPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/datetime/AutoTimeFormatPreferenceControllerTest.java
deleted file mode 100644
index f9b566e..0000000
--- a/tests/robotests/src/com/android/settings/datetime/AutoTimeFormatPreferenceControllerTest.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2017 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.datetime;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.Context;
-import android.content.Intent;
-import android.provider.Settings;
-import android.text.format.DateFormat;
-
-import androidx.preference.SwitchPreference;
-
-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.RuntimeEnvironment;
-import org.robolectric.shadows.ShadowApplication;
-
-import java.util.List;
-import java.util.Locale;
-
-@RunWith(RobolectricTestRunner.class)
-public class AutoTimeFormatPreferenceControllerTest {
-
- @Mock
- private UpdateTimeAndDateCallback mCallback;
-
- private ShadowApplication mApplication;
- private Context mContext;
- private SwitchPreference mPreference;
- private TestAutoTimeFormatPreferenceController mController;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mApplication = ShadowApplication.getInstance();
- mContext = RuntimeEnvironment.application;
- mController = new TestAutoTimeFormatPreferenceController(mContext, "test_key");
- mPreference = new SwitchPreference(mContext);
- mPreference.setKey("test_key");
- }
-
- @Test
- public void updateState_24HourSet_shouldCheckPreference() {
- Settings.System.putString(mContext.getContentResolver(), Settings.System.TIME_12_24,
- TimeFormatPreferenceController.HOURS_24);
-
- mController.updateState(mPreference);
-
- assertThat(mPreference.isChecked()).isFalse();
- }
-
- @Test
- public void updateState_12HourSet_shouldCheckPreference() {
- Settings.System.putString(mContext.getContentResolver(), Settings.System.TIME_12_24,
- TimeFormatPreferenceController.HOURS_12);
-
- mController.updateState(mPreference);
-
- assertThat(mPreference.isChecked()).isFalse();
- }
-
- @Test
- public void updateState_autoSet_shouldNotCheckPreference() {
- Settings.System.putString(mContext.getContentResolver(), Settings.System.TIME_12_24, null);
-
- mController.updateState(mPreference);
-
- assertThat(mPreference.isChecked()).isTrue();
- }
-
- @Test
- public void updatePreference_autoSet_shouldSendIntent_12HourLocale() {
- mController.setChecked(false);
-
- List<Intent> intentsFired = mApplication.getBroadcastIntents();
- assertThat(intentsFired.size()).isEqualTo(1);
- Intent intentFired = intentsFired.get(0);
- assertThat(intentFired.getAction()).isEqualTo(Intent.ACTION_TIME_CHANGED);
- assertThat(intentFired.getIntExtra(Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT, -1))
- .isEqualTo(Intent.EXTRA_TIME_PREF_VALUE_USE_12_HOUR);
- }
-
- @Test
- public void updatePreference_autoSet_shouldSendIntent_24HourLocale() {
- mController.setIs24HourLocale(true);
-
- mController.setChecked(false);
-
- List<Intent> intentsFired = mApplication.getBroadcastIntents();
- assertThat(intentsFired.size()).isEqualTo(1);
- Intent intentFired = intentsFired.get(0);
- assertThat(intentFired.getAction()).isEqualTo(Intent.ACTION_TIME_CHANGED);
- assertThat(intentFired.getIntExtra(Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT, -1))
- .isEqualTo(Intent.EXTRA_TIME_PREF_VALUE_USE_24_HOUR);
- }
-
- @Test
- public void updatePreference_24HourSet_shouldSendIntent() {
- mController.setIs24HourLocale(false);
-
- mController.setChecked(true);
-
- List<Intent> intentsFired = mApplication.getBroadcastIntents();
- assertThat(intentsFired.size()).isEqualTo(1);
- Intent intentFired = intentsFired.get(0);
- assertThat(intentFired.getAction()).isEqualTo(Intent.ACTION_TIME_CHANGED);
- assertThat(intentFired.getIntExtra(Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT, -1))
- .isEqualTo(Intent.EXTRA_TIME_PREF_VALUE_USE_LOCALE_DEFAULT);
- }
-
- /**
- * Extend class under test to change {@link #is24HourLocale} to not call
- * {@link DateFormat#is24HourLocale(Locale)} because that's not available in roboelectric.
- */
- private static class TestAutoTimeFormatPreferenceController
- extends AutoTimeFormatPreferenceController {
-
- private boolean is24HourLocale = false;
-
- TestAutoTimeFormatPreferenceController(Context context, String preferenceKey) {
- super(context, preferenceKey);
- }
-
- void setIs24HourLocale(boolean value) {
- is24HourLocale = value;
- }
-
- @Override
- boolean is24HourLocale(Locale locale) {
- return is24HourLocale;
- }
- }
-}
diff --git a/tests/robotests/src/com/android/settings/datetime/AutoTimeZonePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/datetime/AutoTimeZonePreferenceControllerTest.java
index 7bf8d52..2961935 100644
--- a/tests/robotests/src/com/android/settings/datetime/AutoTimeZonePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/datetime/AutoTimeZonePreferenceControllerTest.java
@@ -40,17 +40,12 @@
import android.app.time.TimeZoneDetectorStatus;
import android.content.Context;
import android.os.UserHandle;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
import androidx.preference.Preference;
import com.android.settings.R;
-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;
@@ -62,9 +57,6 @@
@RunWith(RobolectricTestRunner.class)
public class AutoTimeZonePreferenceControllerTest {
- @Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
-
@Mock
private UpdateTimeAndDateCallback mCallback;
private Context mContext;
@@ -247,7 +239,6 @@
}
@Test
- @EnableFlags({Flags.FLAG_REVAMP_TOGGLES})
public void toggleOff_revampFlagOn_shouldToggleOffUseLocation() {
TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = createCapabilitiesAndConfig(
/* autoSupported= */ true,
@@ -266,25 +257,6 @@
verify(mTimeManager).updateTimeZoneConfiguration(configuration);
}
- @Test
- @DisableFlags({Flags.FLAG_REVAMP_TOGGLES})
- public void toggleOff_revampFlagOff_shouldToggleOffUseLocation() {
- TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = createCapabilitiesAndConfig(
- /* autoSupported= */ true,
- /* autoEnabled= */ true,
- /* telephonySupported= */ true,
- /* locationSupported= */ true);
- when(mTimeManager.getTimeZoneCapabilitiesAndConfig()).thenReturn(capabilitiesAndConfig);
-
- mController.setChecked(false);
-
- TimeZoneConfiguration configuration = new TimeZoneConfiguration.Builder()
- .setAutoDetectionEnabled(false)
- .build();
-
- verify(mTimeManager).updateTimeZoneConfiguration(configuration);
- }
-
private static TimeZoneCapabilitiesAndConfig createCapabilitiesAndConfig(
boolean autoSupported, boolean autoEnabled, boolean telephonySupported) {
return createCapabilitiesAndConfig(autoSupported, autoEnabled, telephonySupported, false);
diff --git a/tests/robotests/src/com/android/settings/datetime/LocationTimeZoneDetectionPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/datetime/LocationTimeZoneDetectionPreferenceControllerTest.java
index 40794d2..2f23257 100644
--- a/tests/robotests/src/com/android/settings/datetime/LocationTimeZoneDetectionPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/datetime/LocationTimeZoneDetectionPreferenceControllerTest.java
@@ -45,17 +45,13 @@
import android.app.time.TimeZoneDetectorStatus;
import android.content.Context;
import android.os.UserHandle;
-import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
import androidx.preference.SwitchPreference;
import com.android.settings.R;
import com.android.settings.core.InstrumentedPreferenceFragment;
-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.Answers;
@@ -71,9 +67,6 @@
})
public class LocationTimeZoneDetectionPreferenceControllerTest {
- @Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
-
@Mock
private TimeManager mTimeManager;
private Context mContext;
@@ -131,8 +124,7 @@
}
@Test
- @EnableFlags({Flags.FLAG_REVAMP_TOGGLES})
- public void flagRevampTogglesOn_toggleOff_automaticTimeZone_disablesLocationToggle() {
+ public void toggleOff_automaticTimeZone_disablesLocationToggle() {
TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
createTimeZoneCapabilitiesAndConfig(/* useLocationEnabled= */ true,
CAPABILITY_POSSESSED, /* setAutoDetectionEnabled= */ false);
diff --git a/tests/robotests/src/com/android/settings/datetime/TimeFormatPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/datetime/TimeFormatPreferenceControllerTest.java
index c5aac84..a341922 100644
--- a/tests/robotests/src/com/android/settings/datetime/TimeFormatPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/datetime/TimeFormatPreferenceControllerTest.java
@@ -23,16 +23,11 @@
import android.content.Context;
import android.content.Intent;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
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;
@@ -46,9 +41,6 @@
@RunWith(RobolectricTestRunner.class)
public class TimeFormatPreferenceControllerTest {
- @Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
-
@Mock
private UpdateTimeAndDateCallback mCallback;
@@ -105,16 +97,6 @@
}
@Test
- @DisableFlags({Flags.FLAG_REVAMP_TOGGLES})
- public void updateState_autoSet_shouldNotEnablePreference() {
- Settings.System.putString(mContext.getContentResolver(), Settings.System.TIME_12_24, null);
-
- mController.updateState(mPreference);
-
- assertThat(mPreference.isEnabled()).isFalse();
- }
-
- @Test
public void updatePreference_12HourSet_shouldSendIntent() {
mController.setChecked(false);
diff --git a/tests/robotests/src/com/android/settings/gestures/OneHandedSettingsTest.java b/tests/robotests/src/com/android/settings/gestures/OneHandedSettingsTest.java
index a03ca61..1ce6c3f 100644
--- a/tests/robotests/src/com/android/settings/gestures/OneHandedSettingsTest.java
+++ b/tests/robotests/src/com/android/settings/gestures/OneHandedSettingsTest.java
@@ -21,7 +21,6 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
import android.content.Context;
import android.os.SystemProperties;
@@ -32,8 +31,6 @@
import androidx.test.core.app.ApplicationProvider;
-import com.android.settings.R;
-import com.android.settings.accessibility.AccessibilityUtil.QuickSettingsTooltipType;
import com.android.settingslib.search.SearchIndexableRaw;
import org.junit.Before;
@@ -63,21 +60,6 @@
}
@Test
- public void getTileTooltipContent_returnsExpectedValues() {
- // Simulate to call getTileTooltipContent after onDetach
- assertThat(mSettings.getTileTooltipContent(QuickSettingsTooltipType.GUIDE_TO_EDIT))
- .isNull();
- // Simulate to call getTileTooltipContent after onAttach
- when(mSettings.getContext()).thenReturn(mContext);
- assertThat(mSettings.getTileTooltipContent(QuickSettingsTooltipType.GUIDE_TO_EDIT))
- .isEqualTo(mContext.getText(
- R.string.accessibility_one_handed_mode_qs_tooltip_content));
- assertThat(mSettings.getTileTooltipContent(QuickSettingsTooltipType.GUIDE_TO_DIRECT_USE))
- .isEqualTo(mContext.getText(
- R.string.accessibility_one_handed_mode_auto_added_qs_tooltip_content));
- }
-
- @Test
public void getLogTag_returnsCorrectTag() {
assertThat(mSettings.getLogTag()).isEqualTo("OneHandedSettings");
}
diff --git a/tests/robotests/src/com/android/settings/inputmethod/TouchpadAndMouseSettingsControllerTest.java b/tests/robotests/src/com/android/settings/inputmethod/TouchpadAndMouseSettingsControllerTest.java
index 9d3cc5f..f3fe93d 100644
--- a/tests/robotests/src/com/android/settings/inputmethod/TouchpadAndMouseSettingsControllerTest.java
+++ b/tests/robotests/src/com/android/settings/inputmethod/TouchpadAndMouseSettingsControllerTest.java
@@ -23,12 +23,15 @@
import static org.junit.Assume.assumeTrue;
import android.content.Context;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.view.InputDevice;
import androidx.preference.Preference;
import androidx.test.core.app.ApplicationProvider;
import com.android.settings.core.BasePreferenceController;
+import com.android.settings.keyboard.Flags;
import com.android.settings.testutils.shadow.ShadowInputDevice;
import org.junit.Before;
@@ -48,6 +51,8 @@
})
public class TouchpadAndMouseSettingsControllerTest {
@Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Rule
public MockitoRule rule = MockitoJUnit.rule();
private static final String PREFERENCE_KEY = "trackpad_settings";
@@ -80,6 +85,7 @@
}
@Test
+ @DisableFlags(Flags.FLAG_KEYBOARD_AND_TOUCHPAD_A11Y_NEW_PAGE_ENABLED)
public void getAvailabilityStatus_isTouchpadAvailable() {
int deviceId = 1;
ShadowInputDevice.sDeviceIds = new int[]{deviceId};
@@ -92,6 +98,7 @@
}
@Test
+ @DisableFlags(Flags.FLAG_KEYBOARD_AND_TOUCHPAD_A11Y_NEW_PAGE_ENABLED)
public void getAvailabilityStatus_isMouseAvailable() {
assumeTrue(enableVectorCursorA11ySettings());
diff --git a/tests/robotests/src/com/android/settings/service/PreferenceServiceRequestTransformerTest.kt b/tests/robotests/src/com/android/settings/service/PreferenceServiceRequestTransformerTest.kt
index f064b22..7631a00 100644
--- a/tests/robotests/src/com/android/settings/service/PreferenceServiceRequestTransformerTest.kt
+++ b/tests/robotests/src/com/android/settings/service/PreferenceServiceRequestTransformerTest.kt
@@ -16,7 +16,6 @@
package com.android.settings.service
-import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.platform.test.annotations.RequiresFlagsEnabled
@@ -24,6 +23,7 @@
import android.platform.test.flag.junit.DeviceFlagsValueProvider
import android.service.settings.preferences.GetValueRequest
import android.service.settings.preferences.GetValueResult
+import android.service.settings.preferences.MetadataResult
import android.service.settings.preferences.SetValueRequest
import android.service.settings.preferences.SetValueResult
import android.service.settings.preferences.SettingsPreferenceMetadata
@@ -37,9 +37,15 @@
import com.android.settingslib.graph.PreferenceGetterFlags
import com.android.settingslib.graph.PreferenceGetterResponse
import com.android.settingslib.graph.PreferenceSetterResult
+import com.android.settingslib.graph.preferenceGroupProto
+import com.android.settingslib.graph.preferenceOrGroupProto
+import com.android.settingslib.graph.preferenceProto
+import com.android.settingslib.graph.preferenceScreenProto
+import com.android.settingslib.graph.proto.PreferenceGraphProto
import com.android.settingslib.graph.proto.PreferenceProto
import com.android.settingslib.graph.proto.PreferenceValueProto
import com.android.settingslib.graph.proto.TextProto
+import com.android.settingslib.graph.textProto
import com.android.settingslib.graph.toProto
import com.android.settingslib.metadata.SensitivityLevel
import com.google.common.truth.Truth.assertThat
@@ -55,6 +61,73 @@
val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
@Test
+ fun transformCatalystGetMetadataResponse_emptyGraph_returnsFrameworkResponseWithError() {
+ val context: Context = ApplicationProvider.getApplicationContext()
+ val graphProto = PreferenceGraphProto.newBuilder().build()
+ val fResult = transformCatalystGetMetadataResponse(context, graphProto)
+ with(fResult) {
+ assertThat(resultCode).isEqualTo(MetadataResult.RESULT_UNSUPPORTED)
+ assertThat(metadataList).isEmpty()
+ }
+ }
+
+ @Test
+ fun transformCatalystGetMetadataResponse_populatedGraph_returnsFrameworkResponseWithSuccess() {
+ val context: Context = ApplicationProvider.getApplicationContext()
+ val screen = preferenceScreenProto {
+ root = preferenceGroupProto {
+ addAllPreferences(
+ listOf(
+ preferenceOrGroupProto {
+ group = preferenceGroupProto {
+ addPreferences(
+ preferenceOrGroupProto {
+ preference = preferenceProto {
+ key = "key1"
+ title = textProto { string = "title1" }
+ enabled = true
+ }
+ }
+ )
+ }
+ },
+ preferenceOrGroupProto {
+ preference = preferenceProto {
+ key = "key2"
+ title = textProto { string = "title2" }
+ enabled = false
+ }
+ }
+ )
+ )
+ }
+ }
+ val graphProto = PreferenceGraphProto.newBuilder().putScreens("screen", screen).build()
+
+ val fResult = transformCatalystGetMetadataResponse(context, graphProto)
+ with(fResult) {
+ assertThat(resultCode).isEqualTo(MetadataResult.RESULT_OK)
+ assertThat(metadataList.size).isEqualTo(2)
+ }
+ assertThat(
+ fResult.metadataList.any {
+ it.key == "key1" &&
+ it.screenKey == "screen" &&
+ it.title == "title1" &&
+ it.isEnabled == true
+ }
+ ).isTrue()
+ assertThat(
+ fResult.metadataList.any {
+ it.key == "key2" &&
+ it.screenKey == "screen" &&
+ it.title == "title2" &&
+ it.isEnabled == false
+ }
+ ).isTrue()
+ }
+
+ @Test
fun transformFrameworkGetValueRequest_returnsValidCatalystRequest() {
val fRequest = GetValueRequest.Builder("screen", "pref").build()
val cRequest = transformFrameworkGetValueRequest(fRequest)
diff --git a/tests/spa_unit/src/com/android/settings/spa/wifi/dpp/WifiDppUtilsTest.kt b/tests/spa_unit/src/com/android/settings/spa/wifi/dpp/WifiDppUtilsTest.kt
new file mode 100644
index 0000000..31ee9e6
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/spa/wifi/dpp/WifiDppUtilsTest.kt
@@ -0,0 +1,118 @@
+/*
+ * 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.spa.wifi.dpp
+
+import android.app.KeyguardManager
+import android.content.Context
+import android.hardware.biometrics.BiometricPrompt
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.settings.wifi.dpp.WifiDppUtils
+import java.security.InvalidKeyException
+import java.security.Key
+import javax.crypto.Cipher
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoSession
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.doThrow
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.stub
+import org.mockito.kotlin.whenever
+import org.mockito.quality.Strictness
+
+@RunWith(AndroidJUnit4::class)
+class WifiDppUtilsTest {
+ private lateinit var mockSession: MockitoSession
+
+ private val runnable = mock<Runnable>()
+ private val cipher = mock<Cipher>()
+ private var mockKeyguardManager = mock<KeyguardManager>()
+ private var context: Context =
+ spy(ApplicationProvider.getApplicationContext()) {
+ on { getSystemService(KeyguardManager::class.java) } doReturn mockKeyguardManager
+ }
+
+ @Before
+ fun setUp() {
+ mockSession =
+ ExtendedMockito.mockitoSession()
+ .initMocks(this)
+ .mockStatic(Cipher::class.java)
+ .mockStatic(BiometricPrompt::class.java)
+ .mockStatic(BiometricPrompt.Builder::class.java)
+ .strictness(Strictness.LENIENT)
+ .startMocking()
+ whenever(context.applicationContext).thenReturn(context)
+ }
+
+ @After
+ fun tearDown() {
+ mockSession.finishMocking()
+ }
+
+ @Test
+ fun showLockScreen_notKeyguardSecure_runRunnable() {
+ mockKeyguardManager.stub { on { isKeyguardSecure } doReturn false }
+
+ WifiDppUtils.showLockScreen(context, runnable)
+
+ verify(runnable).run()
+ }
+
+ @Test
+ fun showLockScreen_isKeyguardSecure_doNotRunRunnable() {
+ mockKeyguardManager.stub { on { isKeyguardSecure } doReturn true }
+
+ try {
+ WifiDppUtils.showLockScreen(context, runnable)
+ } catch (_: Exception) {}
+
+ verify(runnable, never()).run()
+ }
+
+ @Test
+ fun showLockScreenForWifiSharing_deviceUnlockedRecently_runRunnable() {
+ mockKeyguardManager.stub { on { isKeyguardSecure } doReturn true }
+ whenever(Cipher.getInstance(WifiDppUtils.AES_CBC_PKCS7_PADDING)).thenReturn(cipher)
+
+ WifiDppUtils.showLockScreenForWifiSharing(context, runnable)
+
+ verify(runnable).run()
+ }
+
+ @Test
+ fun showLockScreenForWifiSharing_deviceNotUnlockedRecently_doNotRunRunnable() {
+ mockKeyguardManager.stub { on { isKeyguardSecure } doReturn true }
+ whenever(Cipher.getInstance(WifiDppUtils.AES_CBC_PKCS7_PADDING)).thenReturn(cipher)
+ doThrow(InvalidKeyException()).whenever(cipher).init(anyInt(), any<Key>())
+
+ try {
+ WifiDppUtils.showLockScreenForWifiSharing(context, runnable)
+ } catch (_: Exception) {}
+
+ verify(runnable, never()).run()
+ }
+}
diff --git a/tests/unit/src/com/android/settings/accessibility/ReduceBrightColorsIntensityPreferenceControllerTest.java b/tests/unit/src/com/android/settings/accessibility/ReduceBrightColorsIntensityPreferenceControllerTest.java
index c3ccf34..96d9583 100644
--- a/tests/unit/src/com/android/settings/accessibility/ReduceBrightColorsIntensityPreferenceControllerTest.java
+++ b/tests/unit/src/com/android/settings/accessibility/ReduceBrightColorsIntensityPreferenceControllerTest.java
@@ -24,20 +24,29 @@
import android.content.Context;
import android.content.res.Resources;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.internal.R;
+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;
+/** Tests for {@link ReduceBrightColorsIntensityPreferenceController} */
@RunWith(AndroidJUnit4.class)
public class ReduceBrightColorsIntensityPreferenceControllerTest {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private Context mContext;
private Resources mResources;
private ReduceBrightColorsIntensityPreferenceController mPreferenceController;
@@ -52,27 +61,119 @@
}
@Test
- public void isAvailable_configuredRbcAvailable_enabledRbc_shouldReturnTrue() {
+ @DisableFlags(Flags.FLAG_EVEN_DIMMER)
+ public void isAvailable_whenEvenDimmerOffAndDisabled_RbcOnAndAvailable_returnTrue() {
+ doReturn(false).when(mResources).getBoolean(
+ com.android.internal.R.bool.config_evenDimmerEnabled);
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 1);
doReturn(true).when(mResources).getBoolean(
R.bool.config_reduceBrightColorsAvailable);
+
assertThat(mPreferenceController.isAvailable()).isTrue();
}
+
@Test
- public void isAvailable_configuredRbcAvailable_disabledRbc_shouldReturnTrue() {
+ @DisableFlags(Flags.FLAG_EVEN_DIMMER)
+ public void isAvailable_whenEvenDimmerOffAndDisabled_RbcOffAndAvailable_returnTrue() {
+ doReturn(false).when(mResources).getBoolean(
+ com.android.internal.R.bool.config_evenDimmerEnabled);
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 0);
doReturn(true).when(mResources).getBoolean(
R.bool.config_reduceBrightColorsAvailable);
+
assertThat(mPreferenceController.isAvailable()).isTrue();
}
+
@Test
- public void isAvailable_configuredRbcUnavailable_enabledRbc_shouldReturnFalse() {
+ @DisableFlags(Flags.FLAG_EVEN_DIMMER)
+ public void isAvailable_whenEvenDimmerOffAndDisabled_RbcOnAndUnavailable_returnFalse() {
+ doReturn(false).when(mResources).getBoolean(
+ com.android.internal.R.bool.config_evenDimmerEnabled);
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 1);
doReturn(false).when(mResources).getBoolean(
R.bool.config_reduceBrightColorsAvailable);
+
+ assertThat(mPreferenceController.isAvailable()).isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_EVEN_DIMMER)
+ public void isAvailable_whenEvenDimmerOnAndDisabled_RbcOnAndAvailable_returnTrue() {
+ doReturn(false).when(mResources).getBoolean(
+ com.android.internal.R.bool.config_evenDimmerEnabled);
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 1);
+ doReturn(true).when(mResources).getBoolean(
+ R.bool.config_reduceBrightColorsAvailable);
+
+ assertThat(mPreferenceController.isAvailable()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_EVEN_DIMMER)
+ public void isAvailable_whenEvenDimmerOnAndDisabled_RbcOffAndAvailable_returnTrue() {
+ doReturn(false).when(mResources).getBoolean(
+ com.android.internal.R.bool.config_evenDimmerEnabled);
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 0);
+ doReturn(true).when(mResources).getBoolean(
+ R.bool.config_reduceBrightColorsAvailable);
+
+ assertThat(mPreferenceController.isAvailable()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_EVEN_DIMMER)
+ public void isAvailable_whenEvenDimmerOnAndDisabled_RbcOnAndUnavailable_returnFalse() {
+ doReturn(false).when(mResources).getBoolean(
+ com.android.internal.R.bool.config_evenDimmerEnabled);
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 1);
+ doReturn(false).when(mResources).getBoolean(
+ R.bool.config_reduceBrightColorsAvailable);
+
+ assertThat(mPreferenceController.isAvailable()).isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_EVEN_DIMMER)
+ public void isAvailable_whenEvenDimmerOnAndEnabled_RbcOnAndAvailable_returnFalse() {
+ doReturn(true).when(mResources).getBoolean(
+ com.android.internal.R.bool.config_evenDimmerEnabled);
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 1);
+ doReturn(true).when(mResources).getBoolean(
+ R.bool.config_reduceBrightColorsAvailable);
+
+ assertThat(mPreferenceController.isAvailable()).isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_EVEN_DIMMER)
+ public void isAvailable_whenEvenDimmerOnAndEnabled_RbcOffAndAvailable_returnFalse() {
+ doReturn(true).when(mResources).getBoolean(
+ com.android.internal.R.bool.config_evenDimmerEnabled);
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 0);
+ doReturn(true).when(mResources).getBoolean(
+ R.bool.config_reduceBrightColorsAvailable);
+
+ assertThat(mPreferenceController.isAvailable()).isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_EVEN_DIMMER)
+ public void isAvailable_whenEvenDimmerOnAndEnabled_RbcOnAndUnavailable_returnFalse() {
+ doReturn(true).when(mResources).getBoolean(
+ com.android.internal.R.bool.config_evenDimmerEnabled);
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 1);
+ doReturn(false).when(mResources).getBoolean(
+ R.bool.config_reduceBrightColorsAvailable);
+
assertThat(mPreferenceController.isAvailable()).isFalse();
}
diff --git a/tests/unit/src/com/android/settings/language/LanguagePreferenceControllerTest.java b/tests/unit/src/com/android/settings/language/LanguagePreferenceControllerTest.java
index 656fa27..2209355 100644
--- a/tests/unit/src/com/android/settings/language/LanguagePreferenceControllerTest.java
+++ b/tests/unit/src/com/android/settings/language/LanguagePreferenceControllerTest.java
@@ -21,18 +21,24 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.core.app.ApplicationProvider;
import com.android.settings.Settings;
+import com.android.settings.flags.Flags;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
public class LanguagePreferenceControllerTest {
private Context mContext;
private LanguagePreferenceController mController;
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Before
public void setup() {
mContext = ApplicationProvider.getApplicationContext();
@@ -40,6 +46,7 @@
}
@Test
+ @DisableFlags(Flags.FLAG_REGIONAL_PREFERENCES_API_ENABLED)
public void getAvailabilityStatus_featureFlagOff_LanguageSettingsActivitydisabled() {
mController.getAvailabilityStatus();
diff --git a/tests/unit/src/com/android/settings/network/telephony/CellInfoUtilTest.kt b/tests/unit/src/com/android/settings/network/telephony/CellInfoUtilTest.kt
index 27b5c38..c6974ab 100644
--- a/tests/unit/src/com/android/settings/network/telephony/CellInfoUtilTest.kt
+++ b/tests/unit/src/com/android/settings/network/telephony/CellInfoUtilTest.kt
@@ -24,6 +24,7 @@
import com.android.settings.network.telephony.CellInfoUtil.getNetworkTitle
import com.android.settings.network.telephony.CellInfoUtil.getOperatorNumeric
import com.google.common.truth.Truth.assertThat
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
@@ -95,6 +96,7 @@
}
@Test
+ @Ignore("b/383858953")
fun cellInfoListToString() {
val cellInfoList =
listOf(