Snap for 12014005 from 40b6a49287 to 24Q4-release
Change-Id: Ifb17e0327c732dd9e67fb90eb498896b02d1e295
This commit is contained in:
@@ -2891,17 +2891,20 @@
|
|||||||
<!-- Note this must not be exported since it returns the password in the intent -->
|
<!-- Note this must not be exported since it returns the password in the intent -->
|
||||||
<activity android:name=".password.ConfirmLockPattern$InternalActivity"
|
<activity android:name=".password.ConfirmLockPattern$InternalActivity"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
|
android:enableOnBackInvokedCallback="false"
|
||||||
android:theme="@style/GlifTheme.Light"/>
|
android:theme="@style/GlifTheme.Light"/>
|
||||||
|
|
||||||
<!-- Note this must not be exported since it returns the password in the intent -->
|
<!-- Note this must not be exported since it returns the password in the intent -->
|
||||||
<activity android:name=".password.ConfirmLockPassword$InternalActivity"
|
<activity android:name=".password.ConfirmLockPassword$InternalActivity"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:windowSoftInputMode="adjustResize"
|
android:windowSoftInputMode="adjustResize"
|
||||||
|
android:enableOnBackInvokedCallback="false"
|
||||||
android:theme="@style/GlifTheme.Light"/>
|
android:theme="@style/GlifTheme.Light"/>
|
||||||
|
|
||||||
<activity android:name=".password.SetupChooseLockGeneric"
|
<activity android:name=".password.SetupChooseLockGeneric"
|
||||||
android:theme="@style/GlifTheme.Light"
|
android:theme="@style/GlifTheme.Light"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
android:enableOnBackInvokedCallback="false"
|
||||||
android:label="@string/lock_settings_picker_title">
|
android:label="@string/lock_settings_picker_title">
|
||||||
<intent-filter android:priority="1">
|
<intent-filter android:priority="1">
|
||||||
<action android:name="com.android.settings.SETUP_LOCK_SCREEN" />
|
<action android:name="com.android.settings.SETUP_LOCK_SCREEN" />
|
||||||
@@ -2911,16 +2914,19 @@
|
|||||||
|
|
||||||
<activity android:name=".password.SetupChooseLockGeneric$InternalActivity"
|
<activity android:name=".password.SetupChooseLockGeneric$InternalActivity"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
|
android:enableOnBackInvokedCallback="false"
|
||||||
android:excludeFromRecents="true" />
|
android:excludeFromRecents="true" />
|
||||||
|
|
||||||
<activity android:name=".password.ChooseLockGeneric"
|
<activity android:name=".password.ChooseLockGeneric"
|
||||||
android:label="@string/lockpassword_choose_lock_generic_header"
|
android:label="@string/lockpassword_choose_lock_generic_header"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
|
android:enableOnBackInvokedCallback="false"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<activity android:name=".password.SetNewPasswordActivity"
|
<activity android:name=".password.SetNewPasswordActivity"
|
||||||
android:theme="@android:style/Theme.NoDisplay"
|
android:theme="@android:style/Theme.NoDisplay"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
android:enableOnBackInvokedCallback="false"
|
||||||
android:excludeFromRecents="true" >
|
android:excludeFromRecents="true" >
|
||||||
<intent-filter android:priority="1">
|
<intent-filter android:priority="1">
|
||||||
<action android:name="android.app.action.SET_NEW_PASSWORD" />
|
<action android:name="android.app.action.SET_NEW_PASSWORD" />
|
||||||
@@ -2966,24 +2972,29 @@
|
|||||||
<activity android:name=".password.ChooseLockGeneric$InternalActivity"
|
<activity android:name=".password.ChooseLockGeneric$InternalActivity"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:label="@string/lockpassword_choose_lock_generic_header"
|
android:label="@string/lockpassword_choose_lock_generic_header"
|
||||||
|
android:enableOnBackInvokedCallback="false"
|
||||||
android:excludeFromRecents="true" />
|
android:excludeFromRecents="true" />
|
||||||
|
|
||||||
<activity android:name=".password.SetupChooseLockPattern"
|
<activity android:name=".password.SetupChooseLockPattern"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
|
android:enableOnBackInvokedCallback="false"
|
||||||
android:theme="@style/GlifTheme.Light" />
|
android:theme="@style/GlifTheme.Light" />
|
||||||
|
|
||||||
<activity android:name=".password.ChooseLockPattern"
|
<activity android:name=".password.ChooseLockPattern"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
|
android:enableOnBackInvokedCallback="false"
|
||||||
android:theme="@style/GlifTheme.Light" />
|
android:theme="@style/GlifTheme.Light" />
|
||||||
|
|
||||||
<activity android:name=".password.SetupChooseLockPassword"
|
<activity android:name=".password.SetupChooseLockPassword"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:theme="@style/GlifTheme.Light"
|
android:theme="@style/GlifTheme.Light"
|
||||||
|
android:enableOnBackInvokedCallback="false"
|
||||||
android:windowSoftInputMode="stateVisible|adjustResize" />
|
android:windowSoftInputMode="stateVisible|adjustResize" />
|
||||||
|
|
||||||
<activity android:name=".password.ChooseLockPassword"
|
<activity android:name=".password.ChooseLockPassword"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:theme="@style/GlifTheme.Light"
|
android:theme="@style/GlifTheme.Light"
|
||||||
|
android:enableOnBackInvokedCallback="false"
|
||||||
android:windowSoftInputMode="stateVisible|adjustResize"/>
|
android:windowSoftInputMode="stateVisible|adjustResize"/>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
|
|||||||
@@ -8,6 +8,20 @@ flag {
|
|||||||
bug: "299405720"
|
bug: "299405720"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
flag {
|
||||||
|
name: "rotation_connected_display_setting"
|
||||||
|
namespace: "display_manager"
|
||||||
|
description: "Allow changing rotation of the connected display."
|
||||||
|
bug: "294015706"
|
||||||
|
}
|
||||||
|
|
||||||
|
flag {
|
||||||
|
name: "resolution_and_enable_connected_display_setting"
|
||||||
|
namespace: "display_manager"
|
||||||
|
description: "Allow enabling/disabling and changing resolution of the connected display."
|
||||||
|
bug: "253296253"
|
||||||
|
}
|
||||||
|
|
||||||
flag {
|
flag {
|
||||||
name: "enable_auth_challenge_for_usb_preferences"
|
name: "enable_auth_challenge_for_usb_preferences"
|
||||||
namespace: "safety_center"
|
namespace: "safety_center"
|
||||||
@@ -15,7 +29,6 @@ flag {
|
|||||||
bug: "317367746"
|
bug: "317367746"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
flag {
|
flag {
|
||||||
name: "enable_bonded_bluetooth_device_searchable"
|
name: "enable_bonded_bluetooth_device_searchable"
|
||||||
namespace: "pixel_cross_device_control"
|
namespace: "pixel_cross_device_control"
|
||||||
|
|||||||
55
res/drawable/external_display_mirror_landscape.xml
Normal file
55
res/drawable/external_display_mirror_landscape.xml
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<?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.
|
||||||
|
-->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:height="232.02106dp"
|
||||||
|
android:viewportHeight="214"
|
||||||
|
android:viewportWidth="380"
|
||||||
|
android:width="412dp">
|
||||||
|
<path
|
||||||
|
android:pathData="M16,0L364,0A16,16 0,0 1,380 16L380,198A16,16 0,0 1,364 214L16,214A16,16 0,0 1,0 198L0,16A16,16 0,0 1,16 0z"
|
||||||
|
android:fillColor="#00000000"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M150.5,38L327.5,38A5.5,5.5 0,0 1,333 43.5L333,138.5A5.5,5.5 0,0 1,327.5 144L150.5,144A5.5,5.5 0,0 1,145 138.5L145,43.5A5.5,5.5 0,0 1,150.5 38z"
|
||||||
|
android:fillColor="#80868B"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M150.58,39L327.42,39A4.58,4.58 0,0 1,332 43.58L332,138.42A4.58,4.58 0,0 1,327.42 143L150.58,143A4.58,4.58 0,0 1,146 138.42L146,43.58A4.58,4.58 0,0 1,150.58 39z"
|
||||||
|
android:fillColor="#000000"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M254.25,144H223.75L221.52,173.34C221.48,173.82 221.08,174.18 220.6,174.18H211.37C211.25,174.18 211.12,174.21 211.01,174.26C210.11,174.65 210.39,176 211.37,176H266.63C267.61,176 267.89,174.65 266.99,174.26C266.88,174.21 266.75,174.18 266.63,174.18H257.4C256.92,174.18 256.52,173.82 256.48,173.34L254.25,144Z"
|
||||||
|
android:fillColor="#5F6368"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M330,53L330,129A3,3 0,0 1,327 132L151,132A3,3 0,0 1,148 129L148,53A3,3 0,0 1,151 50L327,50A3,3 0,0 1,330 53z"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#E0E994"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M113,91.08V86.55C113,86.25 112.88,85.96 112.67,85.75C112.45,85.54 112.17,85.42 111.86,85.41V61.64C111.84,60.15 111.24,58.72 110.17,57.66C109.1,56.61 107.66,56.01 106.16,56H53.71C52.2,56.01 50.75,56.61 49.68,57.67C48.62,58.73 48.01,60.17 48,61.66V170.34C48.01,171.83 48.62,173.27 49.68,174.33C50.75,175.39 52.2,175.99 53.71,176H106.16C107.67,175.99 109.11,175.39 110.18,174.33C111.25,173.27 111.85,171.83 111.86,170.34V114.86C112.16,114.86 112.45,114.74 112.67,114.52C112.88,114.31 113,114.03 113,113.73V102.4C113,102.1 112.88,101.82 112.67,101.6C112.45,101.39 112.17,101.27 111.86,101.27V92.21C112.16,92.21 112.45,92.09 112.67,91.88C112.88,91.67 113,91.38 113,91.08ZM110.72,170.34C110.72,171.54 110.24,172.69 109.38,173.54C108.53,174.39 107.37,174.87 106.16,174.87H53.71C52.5,174.87 51.34,174.39 50.48,173.54C49.62,172.69 49.14,171.54 49.14,170.34V61.64C49.14,60.44 49.62,59.29 50.48,58.44C51.34,57.59 52.5,57.11 53.71,57.11H106.16C107.37,57.11 108.53,57.59 109.38,58.44C110.24,59.29 110.72,60.44 110.72,61.64V170.34Z"
|
||||||
|
android:fillColor="#80868B"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M54,59L106,59A3,3 0,0 1,109 62L109,170A3,3 0,0 1,106 173L54,173A3,3 0,0 1,51 170L51,62A3,3 0,0 1,54 59z"
|
||||||
|
android:strokeColor="#E0E994"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:fillColor="#000000"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M80,184.72V189.7C80,193.73 83.27,197 87.3,197H164.7C168.73,197 172,193.73 172,189.7V144"
|
||||||
|
android:strokeColor="#5F6368"
|
||||||
|
android:strokeWidth="0.684"
|
||||||
|
android:fillColor="#00000000"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M77,176H83V184.09C83,184.59 82.59,185 82.09,185H77.91C77.41,185 77,184.59 77,184.09V176Z"
|
||||||
|
android:fillColor="#5F6368"/>
|
||||||
|
</vector>
|
||||||
55
res/drawable/external_display_mirror_portrait.xml
Normal file
55
res/drawable/external_display_mirror_portrait.xml
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<?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.
|
||||||
|
-->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:height="232.02106dp"
|
||||||
|
android:viewportHeight="214"
|
||||||
|
android:viewportWidth="380"
|
||||||
|
android:width="412dp" >
|
||||||
|
<path
|
||||||
|
android:pathData="M16,0L364,0A16,16 0,0 1,380 16L380,198A16,16 0,0 1,364 214L16,214A16,16 0,0 1,0 198L0,16A16,16 0,0 1,16 0z"
|
||||||
|
android:fillColor="#00000000"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M150.5,38L327.5,38A5.5,5.5 0,0 1,333 43.5L333,138.5A5.5,5.5 0,0 1,327.5 144L150.5,144A5.5,5.5 0,0 1,145 138.5L145,43.5A5.5,5.5 0,0 1,150.5 38z"
|
||||||
|
android:fillColor="#80868B"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M150.58,39L327.42,39A4.58,4.58 0,0 1,332 43.58L332,138.42A4.58,4.58 0,0 1,327.42 143L150.58,143A4.58,4.58 0,0 1,146 138.42L146,43.58A4.58,4.58 0,0 1,150.58 39z"
|
||||||
|
android:fillColor="#000000"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M254.25,144H223.75L221.52,173.34C221.48,173.82 221.08,174.18 220.6,174.18H211.37C211.25,174.18 211.12,174.21 211.01,174.26C210.11,174.65 210.39,176 211.37,176H266.63C267.61,176 267.89,174.65 266.99,174.26C266.88,174.21 266.75,174.18 266.63,174.18H257.4C256.92,174.18 256.52,173.82 256.48,173.34L254.25,144Z"
|
||||||
|
android:fillColor="#5F6368"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M216,41L262,41A3,3 0,0 1,265 44L265,138A3,3 0,0 1,262 141L216,141A3,3 0,0 1,213 138L213,44A3,3 0,0 1,216 41z"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#E0E994"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M113,91.08V86.55C113,86.25 112.88,85.96 112.67,85.75C112.45,85.54 112.17,85.42 111.86,85.41V61.64C111.84,60.15 111.24,58.72 110.17,57.66C109.1,56.61 107.66,56.01 106.16,56H53.71C52.2,56.01 50.75,56.61 49.68,57.67C48.62,58.73 48.01,60.17 48,61.66V170.34C48.01,171.83 48.62,173.27 49.68,174.33C50.75,175.39 52.2,175.99 53.71,176H106.16C107.67,175.99 109.11,175.39 110.18,174.33C111.25,173.27 111.85,171.83 111.86,170.34V114.86C112.16,114.86 112.45,114.74 112.67,114.52C112.88,114.31 113,114.03 113,113.73V102.4C113,102.1 112.88,101.82 112.67,101.6C112.45,101.39 112.17,101.27 111.86,101.27V92.21C112.16,92.21 112.45,92.09 112.67,91.88C112.88,91.67 113,91.38 113,91.08ZM110.72,170.34C110.72,171.54 110.24,172.69 109.38,173.54C108.53,174.39 107.37,174.87 106.16,174.87H53.71C52.5,174.87 51.34,174.39 50.48,173.54C49.62,172.69 49.14,171.54 49.14,170.34V61.64C49.14,60.44 49.62,59.29 50.48,58.44C51.34,57.59 52.5,57.11 53.71,57.11H106.16C107.37,57.11 108.53,57.59 109.38,58.44C110.24,59.29 110.72,60.44 110.72,61.64V170.34Z"
|
||||||
|
android:fillColor="#80868B"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M54,59L106,59A3,3 0,0 1,109 62L109,170A3,3 0,0 1,106 173L54,173A3,3 0,0 1,51 170L51,62A3,3 0,0 1,54 59z"
|
||||||
|
android:strokeColor="#E0E994"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:fillColor="#000000"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M80,184.72V189.7C80,193.73 83.27,197 87.3,197H164.7C168.73,197 172,193.73 172,189.7V144"
|
||||||
|
android:strokeColor="#5F6368"
|
||||||
|
android:strokeWidth="0.684"
|
||||||
|
android:fillColor="#00000000"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M77,176H83V184.09C83,184.59 82.59,185 82.09,185H77.91C77.41,185 77,184.59 77,184.09V176Z"
|
||||||
|
android:fillColor="#5F6368"/>
|
||||||
|
</vector>
|
||||||
32
res/drawable/ic_external_display_32dp.xml
Normal file
32
res/drawable/ic_external_display_32dp.xml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?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.
|
||||||
|
-->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="32dp"
|
||||||
|
android:height="32dp"
|
||||||
|
android:viewportWidth="32"
|
||||||
|
android:viewportHeight="32">
|
||||||
|
<path
|
||||||
|
android:pathData="M16,16m-16,0a16,16 0,1 1,32 0a16,16 0,1 1,-32 0"
|
||||||
|
android:fillColor="#FAFBD8"/>
|
||||||
|
<group>
|
||||||
|
<clip-path
|
||||||
|
android:pathData="M5.333,5.332h21.333v21.333h-21.333z"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M12.689,23.288V21.643H14.333V19.976H9C8.555,19.976 8.17,19.813 7.844,19.488C7.518,19.162 7.355,18.769 7.355,18.31V9.665C7.355,9.206 7.518,8.814 7.844,8.488C8.17,8.162 8.555,7.999 9,7.999H23C23.444,7.999 23.829,8.162 24.155,8.488C24.481,8.814 24.644,9.206 24.644,9.665V18.31C24.644,18.769 24.481,19.162 24.155,19.488C23.829,19.813 23.444,19.976 23,19.976H17.666V21.643H19.311V23.288H12.689ZM9,18.31H23V9.665H9V18.31ZM9,18.31V9.665V18.31Z"
|
||||||
|
android:fillColor="#8E964B"/>
|
||||||
|
</group>
|
||||||
|
</vector>
|
||||||
@@ -179,6 +179,9 @@
|
|||||||
<dimen name="pointer_fill_style_circle_padding">8dp</dimen>
|
<dimen name="pointer_fill_style_circle_padding">8dp</dimen>
|
||||||
<dimen name="pointer_fill_style_shape_default_stroke">1dp</dimen>
|
<dimen name="pointer_fill_style_shape_default_stroke">1dp</dimen>
|
||||||
<dimen name="pointer_fill_style_shape_hovered_stroke">3dp</dimen>
|
<dimen name="pointer_fill_style_shape_hovered_stroke">3dp</dimen>
|
||||||
|
<dimen name="pointer_scale_padding">8dp</dimen>
|
||||||
|
<item name="pointer_scale_size_start" format="float" type="dimen">1.0</item>
|
||||||
|
<item name="pointer_scale_size_end" format="float" type="dimen">2.5</item>
|
||||||
|
|
||||||
<!-- RemoteAuth-->
|
<!-- RemoteAuth-->
|
||||||
<dimen name="remoteauth_fragment_padding_horizontal">40dp</dimen>
|
<dimen name="remoteauth_fragment_padding_horizontal">40dp</dimen>
|
||||||
|
|||||||
@@ -36,4 +36,8 @@
|
|||||||
<integer name="enrollment_progress_minimum_time_display">0</integer>
|
<integer name="enrollment_progress_minimum_time_display">0</integer>
|
||||||
<!-- The time (in millis) to wait to collect messages in fingerprint enrollment before displaying it. -->
|
<!-- The time (in millis) to wait to collect messages in fingerprint enrollment before displaying it. -->
|
||||||
<integer name="enrollment_collect_time">0</integer>
|
<integer name="enrollment_collect_time">0</integer>
|
||||||
|
|
||||||
|
<!-- PointerIcon Settings -->
|
||||||
|
<integer name="pointer_scale_seek_bar_start">0</integer>
|
||||||
|
<integer name="pointer_scale_seek_bar_end">3</integer>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1893,6 +1893,37 @@
|
|||||||
<!-- Nfc developer settings: The confirm button of the popup dialog. [CHAR_LIMIT=60] -->
|
<!-- Nfc developer settings: The confirm button of the popup dialog. [CHAR_LIMIT=60] -->
|
||||||
<string name="nfc_reboot_dialog_confirm">Restart</string>
|
<string name="nfc_reboot_dialog_confirm">Restart</string>
|
||||||
|
|
||||||
|
<!-- External Display settings. The keywords for searching. [CHAR LIMIT=40] -->
|
||||||
|
<string name="keywords_external_display">mirror, external display, connected display, usb display, resolution, rotation</string>
|
||||||
|
<!-- External Display settings. When external display is enabled. [CHAR LIMIT=40] -->
|
||||||
|
<string name="external_display_on">On</string>
|
||||||
|
<!-- External Display settings. When external display is disabled. [CHAR LIMIT=40] -->
|
||||||
|
<string name="external_display_off">Off</string>
|
||||||
|
<!-- External Display settings. The title of the screen. [CHAR LIMIT=40] -->
|
||||||
|
<string name="external_display_settings_title">External Display</string>
|
||||||
|
<!-- External Display use. The title of the use preference. [CHAR LIMIT=40] -->
|
||||||
|
<string name="external_display_use_title">Use external display</string>
|
||||||
|
<!-- External Display resolution settings. The title of the screen. [CHAR LIMIT=40] -->
|
||||||
|
<string name="external_display_resolution_settings_title">Display resolution</string>
|
||||||
|
<!-- External Display settings. Text that appears when scanning for devices is finished and no nearby device was found [CHAR LIMIT=40]-->
|
||||||
|
<string name="external_display_not_found">External display is disconnected</string>
|
||||||
|
<!-- External Display settings. Rotation of the external display -->
|
||||||
|
<string name="external_display_rotation">Rotation</string>
|
||||||
|
<!-- External Display settings. Standard rotation of the external display -->
|
||||||
|
<string name="external_display_standard_rotation">Standard</string>
|
||||||
|
<!-- External Display settings. 90 rotation of the external display -->
|
||||||
|
<string name="external_display_rotation_90">90°</string>
|
||||||
|
<!-- External Display settings. 180 rotation of the external display -->
|
||||||
|
<string name="external_display_rotation_180">180°</string>
|
||||||
|
<!-- External Display settings. 180 rotation of the external display -->
|
||||||
|
<string name="external_display_rotation_270">270°</string>
|
||||||
|
<!-- External Display settings. Footer title -->
|
||||||
|
<string name="external_display_change_resolution_footer_title">Changing rotation or resolution may stop any apps that are currently running</string>
|
||||||
|
<!-- External Display settings. No Displays footer title -->
|
||||||
|
<string name="external_display_not_found_footer_title">Your device must be connected to an external display to mirror your screen</string>
|
||||||
|
<!-- External Display settings. More resolution options -->
|
||||||
|
<string name="external_display_more_options_title">More options</string>
|
||||||
|
|
||||||
<!-- Wifi Display settings. The title of the screen. [CHAR LIMIT=40] -->
|
<!-- Wifi Display settings. The title of the screen. [CHAR LIMIT=40] -->
|
||||||
<string name="wifi_display_settings_title">Cast</string>
|
<string name="wifi_display_settings_title">Cast</string>
|
||||||
<!-- Wifi Display settings. The keywords of the setting. [CHAR LIMIT=NONE] -->
|
<!-- Wifi Display settings. The keywords of the setting. [CHAR LIMIT=NONE] -->
|
||||||
@@ -4571,6 +4602,12 @@
|
|||||||
|
|
||||||
<!-- On Languages & input settings screen, setting summary. Setting for mouse pointer speed. [CHAR LIMIT=35] -->
|
<!-- On Languages & input settings screen, setting summary. Setting for mouse pointer speed. [CHAR LIMIT=35] -->
|
||||||
<string name="pointer_speed">Pointer speed</string>
|
<string name="pointer_speed">Pointer speed</string>
|
||||||
|
<!-- Setting for mouse pointer scale. [CHAR LIMIT=35] -->
|
||||||
|
<string name="pointer_scale">Pointer scale</string>
|
||||||
|
<!-- Content description for decreasing pointer scale. [CHAR LIMIT=35] -->
|
||||||
|
<string name="pointer_scale_decrease_content_description">Decrease pointer scale</string>
|
||||||
|
<!-- Setting for mouse pointer scale. [CHAR LIMIT=35] -->
|
||||||
|
<string name="pointer_scale_increase_content_description">Increase pointer scale</string>
|
||||||
|
|
||||||
<!-- On Languages & input settings screen, heading. Inside the "Languages & input settings" screen, this is the header for settings that relate to game controller devices. [CHAR LIMIT=40] -->
|
<!-- On Languages & input settings screen, heading. Inside the "Languages & input settings" screen, this is the header for settings that relate to game controller devices. [CHAR LIMIT=40] -->
|
||||||
<string name="game_controller_settings_category">Game Controller</string>
|
<string name="game_controller_settings_category">Game Controller</string>
|
||||||
@@ -7268,6 +7305,8 @@
|
|||||||
<string name="help_url_install_certificate" translatable="false"></string>
|
<string name="help_url_install_certificate" translatable="false"></string>
|
||||||
<!-- Help URL, Tap & pay [DO NOT TRANSLATE] -->
|
<!-- Help URL, Tap & pay [DO NOT TRANSLATE] -->
|
||||||
<string name="help_url_nfc_payment" translatable="false"></string>
|
<string name="help_url_nfc_payment" translatable="false"></string>
|
||||||
|
<!-- Help URL, External display [DO NOT TRANSLATE] -->
|
||||||
|
<string name="help_url_external_display" translatable="false"></string>
|
||||||
<!-- Help URL, Remote display [DO NOT TRANSLATE] -->
|
<!-- Help URL, Remote display [DO NOT TRANSLATE] -->
|
||||||
<string name="help_url_remote_display" translatable="false"></string>
|
<string name="help_url_remote_display" translatable="false"></string>
|
||||||
<!-- Help URL, Face [DO NOT TRANSLATE] -->
|
<!-- Help URL, Face [DO NOT TRANSLATE] -->
|
||||||
@@ -7946,11 +7985,11 @@
|
|||||||
<!-- Priority Modes: Format string for the "current state + trigger description summary for rules in the list. [CHAR_LIMIT=10] -->
|
<!-- Priority Modes: Format string for the "current state + trigger description summary for rules in the list. [CHAR_LIMIT=10] -->
|
||||||
<string name="zen_mode_format_status_and_trigger" translatable="false"><xliff:g id="current_status" example="ON">%1$s</xliff:g> • <xliff:g id="trigger_description" example="Mon-Fri, 23:00-7:00">%2$s</xliff:g></string>
|
<string name="zen_mode_format_status_and_trigger" translatable="false"><xliff:g id="current_status" example="ON">%1$s</xliff:g> • <xliff:g id="trigger_description" example="Mon-Fri, 23:00-7:00">%2$s</xliff:g></string>
|
||||||
|
|
||||||
<!-- Priority Modes: Call to action for a mode that is disabled and needs to be configured. [CHAR_LIMIT=40] -->
|
<!-- Priority Modes: Indicates that a mode is disabled and needs to be configured. [CHAR_LIMIT=40] -->
|
||||||
<string name="zen_mode_disabled_tap_to_set_up">Tap to set up</string>
|
<string name="zen_mode_disabled_needs_setup">Not set</string>
|
||||||
|
|
||||||
<!-- Priority Modes: Indicates that a mode is disabled by the user. [CHAR_LIMIT=40] -->
|
<!-- Priority Modes: Indicates that a mode is disabled by the user. [CHAR_LIMIT=40] -->
|
||||||
<string name="zen_mode_disabled_by_user">Paused</string>
|
<string name="zen_mode_disabled_by_user">Disabled</string>
|
||||||
|
|
||||||
<!-- Subtitle for the Do not Disturb slice. [CHAR LIMIT=50]-->
|
<!-- Subtitle for the Do not Disturb slice. [CHAR LIMIT=50]-->
|
||||||
<string name="zen_mode_slice_subtitle">Limit interruptions</string>
|
<string name="zen_mode_slice_subtitle">Limit interruptions</string>
|
||||||
@@ -7967,9 +8006,15 @@
|
|||||||
<!-- Do not disturb: Title for dialog that allows users to delete DND rules/schedules[CHAR LIMIT=40] -->
|
<!-- Do not disturb: Title for dialog that allows users to delete DND rules/schedules[CHAR LIMIT=40] -->
|
||||||
<string name="zen_mode_delete_automatic_rules">Delete schedules</string>
|
<string name="zen_mode_delete_automatic_rules">Delete schedules</string>
|
||||||
|
|
||||||
<!-- Do not disturb: Delete text button presented in a dialog to confirm the user would like to delete the selected DND rules. [CHAR LIMIT=30] -->
|
<!-- Do not disturb: Delete text button presented in a dialog to confirm the user would like to delete the selected DND rules. [CHAR LIMIT=30] -->
|
||||||
<string name="zen_mode_schedule_delete">Delete</string>
|
<string name="zen_mode_schedule_delete">Delete</string>
|
||||||
|
|
||||||
|
<!-- Do not disturb: Menu option for deleting a mode on its configuration page [CHAR LIMIT=40] -->
|
||||||
|
<string name="zen_mode_menu_delete_mode">Delete mode</string>
|
||||||
|
|
||||||
|
<!-- Do not disturb: Confirmation dialog asking the user whether they would like to delete the named mode [CHAR LIMIT: 40] -->
|
||||||
|
<string name="zen_mode_delete_mode_confirmation">Delete \"<xliff:g id="mode" example="My Schedule">%1$s</xliff:g>\" mode?</string>
|
||||||
|
|
||||||
<!-- Do not disturb: Edit label for button that allows user to edit the dnd schedule name. [CHAR LIMIT=30] -->
|
<!-- Do not disturb: Edit label for button that allows user to edit the dnd schedule name. [CHAR LIMIT=30] -->
|
||||||
<string name="zen_mode_rule_name_edit">Edit</string>
|
<string name="zen_mode_rule_name_edit">Edit</string>
|
||||||
|
|
||||||
@@ -12655,7 +12700,7 @@
|
|||||||
<string name="default_print_service_main_switch_title">Use print service</string>
|
<string name="default_print_service_main_switch_title">Use print service</string>
|
||||||
|
|
||||||
<!-- Title for multiple users main switch. [CHAR LIMIT=50] -->
|
<!-- Title for multiple users main switch. [CHAR LIMIT=50] -->
|
||||||
<string name="multiple_users_main_switch_title">Allow multiple users</string>
|
<string name="multiple_users_main_switch_title">Allow user switch</string>
|
||||||
<!-- Search keywords for the "Allow Multiple Users" section in Multiple Users Screen. [CHAR LIMIT=NONE] -->
|
<!-- Search keywords for the "Allow Multiple Users" section in Multiple Users Screen. [CHAR LIMIT=NONE] -->
|
||||||
<string name="multiple_users_main_switch_keywords">allow, multiple, user, permit, many</string>
|
<string name="multiple_users_main_switch_keywords">allow, multiple, user, permit, many</string>
|
||||||
<!-- Search keywords for the Users Screen. [CHAR LIMIT=NONE] -->
|
<!-- Search keywords for the Users Screen. [CHAR LIMIT=NONE] -->
|
||||||
|
|||||||
20
res/xml/external_display_resolution_settings.xml
Normal file
20
res/xml/external_display_resolution_settings.xml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?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"
|
||||||
|
android:title="@string/external_display_resolution_settings_title">
|
||||||
|
</PreferenceScreen>
|
||||||
22
res/xml/external_display_settings.xml
Normal file
22
res/xml/external_display_settings.xml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?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"
|
||||||
|
settings:keywords="@string/keywords_external_display"
|
||||||
|
android:title="@string/external_display_settings_title">
|
||||||
|
</PreferenceScreen>
|
||||||
@@ -66,9 +66,19 @@
|
|||||||
android:key="pointer_fill_style"
|
android:key="pointer_fill_style"
|
||||||
android:title="@string/pointer_fill_style"
|
android:title="@string/pointer_fill_style"
|
||||||
android:order="50"
|
android:order="50"
|
||||||
android:dialogTitle="@string/pointer_fill_style"
|
|
||||||
settings:controller="com.android.settings.inputmethod.PointerFillStylePreferenceController"/>
|
settings:controller="com.android.settings.inputmethod.PointerFillStylePreferenceController"/>
|
||||||
|
|
||||||
|
<com.android.settings.widget.LabeledSeekBarPreference
|
||||||
|
android:key="pointer_scale"
|
||||||
|
android:title="@string/pointer_scale"
|
||||||
|
android:order="70"
|
||||||
|
android:max="@integer/pointer_scale_seek_bar_end"
|
||||||
|
settings:iconStart="@drawable/ic_remove_24dp"
|
||||||
|
settings:iconStartContentDescription="@string/pointer_scale_decrease_content_description"
|
||||||
|
settings:iconEnd="@drawable/ic_add_24dp"
|
||||||
|
settings:iconEndContentDescription="@string/pointer_scale_increase_content_description"
|
||||||
|
settings:controller="com.android.settings.inputmethod.PointerScaleSeekBarController" />
|
||||||
|
|
||||||
<com.android.settingslib.widget.ButtonPreference
|
<com.android.settingslib.widget.ButtonPreference
|
||||||
android:key="trackpad_touch_gesture"
|
android:key="trackpad_touch_gesture"
|
||||||
android:title="@string/trackpad_touch_gesture"
|
android:title="@string/trackpad_touch_gesture"
|
||||||
|
|||||||
94
src/com/android/settings/SettingsPreferenceFragmentBase.java
Normal file
94
src/com/android/settings/SettingsPreferenceFragmentBase.java
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.android.settingslib.search.Indexable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for fragment suitable for unit testing.
|
||||||
|
*/
|
||||||
|
public abstract class SettingsPreferenceFragmentBase extends SettingsPreferenceFragment
|
||||||
|
implements Indexable {
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings({"RequiresNullabilityAnnotation"})
|
||||||
|
public void onCreate(final Bundle icicle) {
|
||||||
|
super.onCreate(icicle);
|
||||||
|
onCreateCallback(icicle);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings({"RequiresNullabilityAnnotation"})
|
||||||
|
public void onActivityCreated(final Bundle savedInstanceState) {
|
||||||
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
onActivityCreatedCallback(savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(@NonNull final Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
onSaveInstanceStateCallback(outState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
onStartCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
onStopCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Activity getCurrentActivity() {
|
||||||
|
return getActivity();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback called from {@link #onCreate}
|
||||||
|
*/
|
||||||
|
public abstract void onCreateCallback(@Nullable Bundle icicle);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback called from {@link #onActivityCreated}
|
||||||
|
*/
|
||||||
|
public abstract void onActivityCreatedCallback(@Nullable Bundle savedInstanceState);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback called from {@link #onStart}
|
||||||
|
*/
|
||||||
|
public abstract void onStartCallback();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback called from {@link #onStop}
|
||||||
|
*/
|
||||||
|
public abstract void onStopCallback();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback called from {@link #onSaveInstanceState}
|
||||||
|
*/
|
||||||
|
public void onSaveInstanceStateCallback(@NonNull final Bundle outState) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -142,13 +142,23 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
|
@Nullable
|
||||||
CachedBluetoothDevice getCachedDevice(String deviceAddress) {
|
CachedBluetoothDevice getCachedDevice(String deviceAddress) {
|
||||||
if (sTestDataFactory != null) {
|
if (sTestDataFactory != null) {
|
||||||
return sTestDataFactory.getDevice(deviceAddress);
|
return sTestDataFactory.getDevice(deviceAddress);
|
||||||
}
|
}
|
||||||
BluetoothDevice remoteDevice =
|
BluetoothDevice remoteDevice =
|
||||||
mManager.getBluetoothAdapter().getRemoteDevice(deviceAddress);
|
mManager.getBluetoothAdapter().getRemoteDevice(deviceAddress);
|
||||||
return mManager.getCachedDeviceManager().findDevice(remoteDevice);
|
if (remoteDevice == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
CachedBluetoothDevice cachedDevice =
|
||||||
|
mManager.getCachedDeviceManager().findDevice(remoteDevice);
|
||||||
|
if (cachedDevice != null) {
|
||||||
|
return cachedDevice;
|
||||||
|
}
|
||||||
|
Log.i(TAG, "Add device to cached device manager: " + remoteDevice.getAnonymizedAddress());
|
||||||
|
return mManager.getCachedDeviceManager().addDevice(remoteDevice);
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
|
|||||||
@@ -15,6 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.android.settings.connecteddevice;
|
package com.android.settings.connecteddevice;
|
||||||
|
|
||||||
|
import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.isExternalDisplaySettingsPageEnabled;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.hardware.input.InputManager;
|
import android.hardware.input.InputManager;
|
||||||
@@ -22,6 +24,8 @@ import android.util.FeatureFlagUtils;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.InputDevice;
|
import android.view.InputDevice;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
import androidx.preference.PreferenceGroup;
|
import androidx.preference.PreferenceGroup;
|
||||||
@@ -31,12 +35,15 @@ import com.android.settings.R;
|
|||||||
import com.android.settings.bluetooth.BluetoothDeviceUpdater;
|
import com.android.settings.bluetooth.BluetoothDeviceUpdater;
|
||||||
import com.android.settings.bluetooth.ConnectedBluetoothDeviceUpdater;
|
import com.android.settings.bluetooth.ConnectedBluetoothDeviceUpdater;
|
||||||
import com.android.settings.bluetooth.Utils;
|
import com.android.settings.bluetooth.Utils;
|
||||||
|
import com.android.settings.connecteddevice.display.ExternalDisplayUpdater;
|
||||||
import com.android.settings.connecteddevice.dock.DockUpdater;
|
import com.android.settings.connecteddevice.dock.DockUpdater;
|
||||||
import com.android.settings.connecteddevice.stylus.StylusDeviceUpdater;
|
import com.android.settings.connecteddevice.stylus.StylusDeviceUpdater;
|
||||||
import com.android.settings.connecteddevice.usb.ConnectedUsbDeviceUpdater;
|
import com.android.settings.connecteddevice.usb.ConnectedUsbDeviceUpdater;
|
||||||
import com.android.settings.core.BasePreferenceController;
|
import com.android.settings.core.BasePreferenceController;
|
||||||
import com.android.settings.core.PreferenceControllerMixin;
|
import com.android.settings.core.PreferenceControllerMixin;
|
||||||
import com.android.settings.dashboard.DashboardFragment;
|
import com.android.settings.dashboard.DashboardFragment;
|
||||||
|
import com.android.settings.flags.FeatureFlags;
|
||||||
|
import com.android.settings.flags.FeatureFlagsImpl;
|
||||||
import com.android.settings.flags.Flags;
|
import com.android.settings.flags.Flags;
|
||||||
import com.android.settings.overlay.DockUpdaterFeatureProvider;
|
import com.android.settings.overlay.DockUpdaterFeatureProvider;
|
||||||
import com.android.settings.overlay.FeatureFactory;
|
import com.android.settings.overlay.FeatureFactory;
|
||||||
@@ -64,6 +71,8 @@ public class ConnectedDeviceGroupController extends BasePreferenceController
|
|||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
PreferenceGroup mPreferenceGroup;
|
PreferenceGroup mPreferenceGroup;
|
||||||
|
@Nullable
|
||||||
|
private ExternalDisplayUpdater mExternalDisplayUpdater;
|
||||||
private BluetoothDeviceUpdater mBluetoothDeviceUpdater;
|
private BluetoothDeviceUpdater mBluetoothDeviceUpdater;
|
||||||
private ConnectedUsbDeviceUpdater mConnectedUsbDeviceUpdater;
|
private ConnectedUsbDeviceUpdater mConnectedUsbDeviceUpdater;
|
||||||
private DockUpdater mConnectedDockUpdater;
|
private DockUpdater mConnectedDockUpdater;
|
||||||
@@ -71,6 +80,8 @@ public class ConnectedDeviceGroupController extends BasePreferenceController
|
|||||||
private final PackageManager mPackageManager;
|
private final PackageManager mPackageManager;
|
||||||
private final InputManager mInputManager;
|
private final InputManager mInputManager;
|
||||||
private final LocalBluetoothManager mLocalBluetoothManager;
|
private final LocalBluetoothManager mLocalBluetoothManager;
|
||||||
|
@NonNull
|
||||||
|
private final FeatureFlags mFeatureFlags = new FeatureFlagsImpl();
|
||||||
|
|
||||||
public ConnectedDeviceGroupController(Context context) {
|
public ConnectedDeviceGroupController(Context context) {
|
||||||
super(context, KEY);
|
super(context, KEY);
|
||||||
@@ -81,6 +92,10 @@ public class ConnectedDeviceGroupController extends BasePreferenceController
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStart() {
|
public void onStart() {
|
||||||
|
if (mExternalDisplayUpdater != null) {
|
||||||
|
mExternalDisplayUpdater.registerCallback();
|
||||||
|
}
|
||||||
|
|
||||||
if (mBluetoothDeviceUpdater != null) {
|
if (mBluetoothDeviceUpdater != null) {
|
||||||
mBluetoothDeviceUpdater.registerCallback();
|
mBluetoothDeviceUpdater.registerCallback();
|
||||||
mBluetoothDeviceUpdater.refreshPreference();
|
mBluetoothDeviceUpdater.refreshPreference();
|
||||||
@@ -101,6 +116,10 @@ public class ConnectedDeviceGroupController extends BasePreferenceController
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStop() {
|
public void onStop() {
|
||||||
|
if (mExternalDisplayUpdater != null) {
|
||||||
|
mExternalDisplayUpdater.unregisterCallback();
|
||||||
|
}
|
||||||
|
|
||||||
if (mBluetoothDeviceUpdater != null) {
|
if (mBluetoothDeviceUpdater != null) {
|
||||||
mBluetoothDeviceUpdater.unregisterCallback();
|
mBluetoothDeviceUpdater.unregisterCallback();
|
||||||
}
|
}
|
||||||
@@ -127,6 +146,10 @@ public class ConnectedDeviceGroupController extends BasePreferenceController
|
|||||||
|
|
||||||
if (isAvailable()) {
|
if (isAvailable()) {
|
||||||
final Context context = screen.getContext();
|
final Context context = screen.getContext();
|
||||||
|
if (mExternalDisplayUpdater != null) {
|
||||||
|
mExternalDisplayUpdater.initPreference(context);
|
||||||
|
}
|
||||||
|
|
||||||
if (mBluetoothDeviceUpdater != null) {
|
if (mBluetoothDeviceUpdater != null) {
|
||||||
mBluetoothDeviceUpdater.setPrefContext(context);
|
mBluetoothDeviceUpdater.setPrefContext(context);
|
||||||
mBluetoothDeviceUpdater.forceUpdate();
|
mBluetoothDeviceUpdater.forceUpdate();
|
||||||
@@ -150,7 +173,8 @@ public class ConnectedDeviceGroupController extends BasePreferenceController
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getAvailabilityStatus() {
|
public int getAvailabilityStatus() {
|
||||||
return (hasBluetoothFeature()
|
return (hasExternalDisplayFeature()
|
||||||
|
|| hasBluetoothFeature()
|
||||||
|| hasUsbFeature()
|
|| hasUsbFeature()
|
||||||
|| hasUsiStylusFeature()
|
|| hasUsiStylusFeature()
|
||||||
|| mConnectedDockUpdater != null)
|
|| mConnectedDockUpdater != null)
|
||||||
@@ -180,11 +204,13 @@ public class ConnectedDeviceGroupController extends BasePreferenceController
|
|||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
void init(BluetoothDeviceUpdater bluetoothDeviceUpdater,
|
void init(@Nullable ExternalDisplayUpdater externalDisplayUpdater,
|
||||||
|
BluetoothDeviceUpdater bluetoothDeviceUpdater,
|
||||||
ConnectedUsbDeviceUpdater connectedUsbDeviceUpdater,
|
ConnectedUsbDeviceUpdater connectedUsbDeviceUpdater,
|
||||||
DockUpdater connectedDockUpdater,
|
DockUpdater connectedDockUpdater,
|
||||||
StylusDeviceUpdater connectedStylusDeviceUpdater) {
|
StylusDeviceUpdater connectedStylusDeviceUpdater) {
|
||||||
|
|
||||||
|
mExternalDisplayUpdater = externalDisplayUpdater;
|
||||||
mBluetoothDeviceUpdater = bluetoothDeviceUpdater;
|
mBluetoothDeviceUpdater = bluetoothDeviceUpdater;
|
||||||
mConnectedUsbDeviceUpdater = connectedUsbDeviceUpdater;
|
mConnectedUsbDeviceUpdater = connectedUsbDeviceUpdater;
|
||||||
mConnectedDockUpdater = connectedDockUpdater;
|
mConnectedDockUpdater = connectedDockUpdater;
|
||||||
@@ -197,7 +223,10 @@ public class ConnectedDeviceGroupController extends BasePreferenceController
|
|||||||
FeatureFactory.getFeatureFactory().getDockUpdaterFeatureProvider();
|
FeatureFactory.getFeatureFactory().getDockUpdaterFeatureProvider();
|
||||||
final DockUpdater connectedDockUpdater =
|
final DockUpdater connectedDockUpdater =
|
||||||
dockUpdaterFeatureProvider.getConnectedDockUpdater(context, this);
|
dockUpdaterFeatureProvider.getConnectedDockUpdater(context, this);
|
||||||
init(hasBluetoothFeature()
|
init(hasExternalDisplayFeature()
|
||||||
|
? new ExternalDisplayUpdater(this, fragment.getMetricsCategory())
|
||||||
|
: null,
|
||||||
|
hasBluetoothFeature()
|
||||||
? new ConnectedBluetoothDeviceUpdater(context, this,
|
? new ConnectedBluetoothDeviceUpdater(context, this,
|
||||||
fragment.getMetricsCategory())
|
fragment.getMetricsCategory())
|
||||||
: null,
|
: null,
|
||||||
@@ -210,6 +239,19 @@ public class ConnectedDeviceGroupController extends BasePreferenceController
|
|||||||
: null);
|
: null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return trunk stable feature flags.
|
||||||
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
|
@NonNull
|
||||||
|
public FeatureFlags getFeatureFlags() {
|
||||||
|
return mFeatureFlags;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasExternalDisplayFeature() {
|
||||||
|
return isExternalDisplaySettingsPageEnabled(getFeatureFlags());
|
||||||
|
}
|
||||||
|
|
||||||
private boolean hasBluetoothFeature() {
|
private boolean hasBluetoothFeature() {
|
||||||
return mPackageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);
|
return mPackageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,544 @@
|
|||||||
|
/*
|
||||||
|
* 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 static android.view.Display.INVALID_DISPLAY;
|
||||||
|
|
||||||
|
import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.EXTERNAL_DISPLAY_HELP_URL;
|
||||||
|
import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.DISPLAY_ID_ARG;
|
||||||
|
import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.EXTERNAL_DISPLAY_NOT_FOUND_RESOURCE;
|
||||||
|
import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.isDisplayAllowed;
|
||||||
|
import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.isUseDisplaySettingEnabled;
|
||||||
|
import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.isResolutionSettingEnabled;
|
||||||
|
import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.isRotationSettingEnabled;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.settings.SettingsEnums;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.Display;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.preference.ListPreference;
|
||||||
|
import androidx.preference.Preference;
|
||||||
|
import androidx.preference.PreferenceCategory;
|
||||||
|
import androidx.preference.PreferenceScreen;
|
||||||
|
|
||||||
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
|
import com.android.settings.R;
|
||||||
|
import com.android.settings.SettingsPreferenceFragmentBase;
|
||||||
|
import com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.DisplayListener;
|
||||||
|
import com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.Injector;
|
||||||
|
import com.android.settings.core.SubSettingLauncher;
|
||||||
|
import com.android.settings.search.BaseSearchIndexProvider;
|
||||||
|
import com.android.settingslib.search.Indexable;
|
||||||
|
import com.android.settingslib.search.SearchIndexable;
|
||||||
|
import com.android.settingslib.widget.FooterPreference;
|
||||||
|
import com.android.settingslib.widget.IllustrationPreference;
|
||||||
|
import com.android.settingslib.widget.MainSwitchPreference;
|
||||||
|
import com.android.settingslib.widget.TwoTargetPreference;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Settings screen for External Displays configuration and connection management.
|
||||||
|
*/
|
||||||
|
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
|
||||||
|
public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmentBase
|
||||||
|
implements Indexable {
|
||||||
|
static final int EXTERNAL_DISPLAY_SETTINGS_RESOURCE = R.xml.external_display_settings;
|
||||||
|
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
|
||||||
|
new BaseSearchIndexProvider(EXTERNAL_DISPLAY_SETTINGS_RESOURCE);
|
||||||
|
static final String DISPLAYS_LIST_PREFERENCE_KEY = "displays_list_preference";
|
||||||
|
static final String EXTERNAL_DISPLAY_USE_PREFERENCE_KEY = "external_display_use_preference";
|
||||||
|
static final String EXTERNAL_DISPLAY_ROTATION_KEY = "external_display_rotation";
|
||||||
|
static final String EXTERNAL_DISPLAY_RESOLUTION_PREFERENCE_KEY = "external_display_resolution";
|
||||||
|
static final int EXTERNAL_DISPLAY_CHANGE_RESOLUTION_FOOTER_RESOURCE =
|
||||||
|
R.string.external_display_change_resolution_footer_title;
|
||||||
|
static final int EXTERNAL_DISPLAY_LANDSCAPE_DRAWABLE =
|
||||||
|
R.drawable.external_display_mirror_landscape;
|
||||||
|
static final int EXTERANAL_DISPLAY_TITLE_RESOURCE =
|
||||||
|
R.string.external_display_settings_title;
|
||||||
|
static final int EXTERNAL_DISPLAY_USE_TITLE_RESOURCE =
|
||||||
|
R.string.external_display_use_title;
|
||||||
|
static final int EXTERNAL_DISPLAY_NOT_FOUND_FOOTER_RESOURCE =
|
||||||
|
R.string.external_display_not_found_footer_title;
|
||||||
|
static final int EXTERNAL_DISPLAY_PORTRAIT_DRAWABLE =
|
||||||
|
R.drawable.external_display_mirror_portrait;
|
||||||
|
static final int EXTERNAL_DISPLAY_ROTATION_TITLE_RESOURCE =
|
||||||
|
R.string.external_display_rotation;
|
||||||
|
static final int EXTERNAL_DISPLAY_RESOLUTION_TITLE_RESOURCE =
|
||||||
|
R.string.external_display_resolution_settings_title;
|
||||||
|
@VisibleForTesting
|
||||||
|
static final String PREVIOUSLY_SHOWN_LIST_KEY = "mPreviouslyShownListOfDisplays";
|
||||||
|
private boolean mStarted;
|
||||||
|
@Nullable
|
||||||
|
private MainSwitchPreference mUseDisplayPref;
|
||||||
|
@Nullable
|
||||||
|
private IllustrationPreference mImagePreference;
|
||||||
|
@Nullable
|
||||||
|
private Preference mResolutionPreference;
|
||||||
|
@Nullable
|
||||||
|
private ListPreference mRotationPref;
|
||||||
|
@Nullable
|
||||||
|
private FooterPreference mFooterPreference;
|
||||||
|
@Nullable
|
||||||
|
private PreferenceCategory mDisplaysPreference;
|
||||||
|
@Nullable
|
||||||
|
private Injector mInjector;
|
||||||
|
@Nullable
|
||||||
|
private String[] mRotationEntries;
|
||||||
|
@Nullable
|
||||||
|
private String[] mRotationEntriesValues;
|
||||||
|
@NonNull
|
||||||
|
private final Runnable mUpdateRunnable = this::update;
|
||||||
|
private final DisplayListener mListener = new DisplayListener() {
|
||||||
|
@Override
|
||||||
|
public void update(int displayId) {
|
||||||
|
scheduleUpdate();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private boolean mPreviouslyShownListOfDisplays;
|
||||||
|
|
||||||
|
public ExternalDisplayPreferenceFragment() {}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
ExternalDisplayPreferenceFragment(@NonNull Injector injector) {
|
||||||
|
mInjector = injector;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMetricsCategory() {
|
||||||
|
return SettingsEnums.SETTINGS_CONNECTED_DEVICE_CATEGORY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getHelpResource() {
|
||||||
|
return EXTERNAL_DISPLAY_HELP_URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceStateCallback(@NonNull Bundle outState) {
|
||||||
|
outState.putSerializable(PREVIOUSLY_SHOWN_LIST_KEY,
|
||||||
|
(Serializable) mPreviouslyShownListOfDisplays);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateCallback(@Nullable Bundle icicle) {
|
||||||
|
if (mInjector == null) {
|
||||||
|
mInjector = new Injector(getPrefContext());
|
||||||
|
}
|
||||||
|
addPreferencesFromResource(EXTERNAL_DISPLAY_SETTINGS_RESOURCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreatedCallback(@Nullable Bundle savedInstanceState) {
|
||||||
|
restoreState(savedInstanceState);
|
||||||
|
View view = getView();
|
||||||
|
TextView emptyView = null;
|
||||||
|
if (view != null) {
|
||||||
|
emptyView = (TextView) view.findViewById(android.R.id.empty);
|
||||||
|
}
|
||||||
|
if (emptyView != null) {
|
||||||
|
emptyView.setText(EXTERNAL_DISPLAY_NOT_FOUND_RESOURCE);
|
||||||
|
setEmptyView(emptyView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStartCallback() {
|
||||||
|
mStarted = true;
|
||||||
|
if (mInjector == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mInjector.registerDisplayListener(mListener);
|
||||||
|
scheduleUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStopCallback() {
|
||||||
|
mStarted = false;
|
||||||
|
if (mInjector == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mInjector.unregisterDisplayListener(mListener);
|
||||||
|
unscheduleUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return id of the preference.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected int getPreferenceScreenResId() {
|
||||||
|
return EXTERNAL_DISPLAY_SETTINGS_RESOURCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
protected void launchResolutionSelector(@NonNull final Context context, final int displayId) {
|
||||||
|
final Bundle args = new Bundle();
|
||||||
|
args.putInt(DISPLAY_ID_ARG, displayId);
|
||||||
|
new SubSettingLauncher(context)
|
||||||
|
.setDestination(ResolutionPreferenceFragment.class.getName())
|
||||||
|
.setArguments(args)
|
||||||
|
.setSourceMetricsCategory(getMetricsCategory()).launch();
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
protected void launchDisplaySettings(final int displayId) {
|
||||||
|
final Bundle args = new Bundle();
|
||||||
|
var context = getPrefContext();
|
||||||
|
args.putInt(DISPLAY_ID_ARG, displayId);
|
||||||
|
new SubSettingLauncher(context)
|
||||||
|
.setDestination(this.getClass().getName())
|
||||||
|
.setArguments(args)
|
||||||
|
.setSourceMetricsCategory(getMetricsCategory()).launch();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the preference for the footer.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
@VisibleForTesting
|
||||||
|
FooterPreference getFooterPreference(@NonNull Context context) {
|
||||||
|
if (mFooterPreference == null) {
|
||||||
|
mFooterPreference = new FooterPreference(context);
|
||||||
|
mFooterPreference.setPersistent(false);
|
||||||
|
}
|
||||||
|
return mFooterPreference;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@VisibleForTesting
|
||||||
|
ListPreference getRotationPreference(@NonNull Context context) {
|
||||||
|
if (mRotationPref == null) {
|
||||||
|
mRotationPref = new ListPreference(context);
|
||||||
|
mRotationPref.setPersistent(false);
|
||||||
|
}
|
||||||
|
return mRotationPref;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@VisibleForTesting
|
||||||
|
Preference getResolutionPreference(@NonNull Context context) {
|
||||||
|
if (mResolutionPreference == null) {
|
||||||
|
mResolutionPreference = new Preference(context);
|
||||||
|
mResolutionPreference.setPersistent(false);
|
||||||
|
}
|
||||||
|
return mResolutionPreference;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@VisibleForTesting
|
||||||
|
MainSwitchPreference getUseDisplayPreference(@NonNull Context context) {
|
||||||
|
if (mUseDisplayPref == null) {
|
||||||
|
mUseDisplayPref = new MainSwitchPreference(context);
|
||||||
|
mUseDisplayPref.setPersistent(false);
|
||||||
|
}
|
||||||
|
return mUseDisplayPref;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@VisibleForTesting
|
||||||
|
IllustrationPreference getIllustrationPreference(@NonNull Context context) {
|
||||||
|
if (mImagePreference == null) {
|
||||||
|
mImagePreference = new IllustrationPreference(context);
|
||||||
|
mImagePreference.setPersistent(false);
|
||||||
|
}
|
||||||
|
return mImagePreference;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return return display id argument of this settings page.
|
||||||
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
|
protected int getDisplayIdArg() {
|
||||||
|
var args = getArguments();
|
||||||
|
return args != null ? args.getInt(DISPLAY_ID_ARG, INVALID_DISPLAY) : INVALID_DISPLAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private PreferenceCategory getDisplaysListPreference(@NonNull Context context) {
|
||||||
|
if (mDisplaysPreference == null) {
|
||||||
|
mDisplaysPreference = new PreferenceCategory(context);
|
||||||
|
mDisplaysPreference.setPersistent(false);
|
||||||
|
}
|
||||||
|
return mDisplaysPreference;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void restoreState(@Nullable Bundle savedInstanceState) {
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mPreviouslyShownListOfDisplays = Boolean.TRUE.equals(savedInstanceState.getSerializable(
|
||||||
|
PREVIOUSLY_SHOWN_LIST_KEY, Boolean.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void update() {
|
||||||
|
final var screen = getPreferenceScreen();
|
||||||
|
if (screen == null || mInjector == null || mInjector.getContext() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
screen.removeAll();
|
||||||
|
updateScreenForDisplayId(getDisplayIdArg(), screen, mInjector.getContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateScreenForDisplayId(final int displayId,
|
||||||
|
@NonNull final PreferenceScreen screen, @NonNull Context context) {
|
||||||
|
final var displaysToShow = getDisplaysToShow(displayId);
|
||||||
|
if (displaysToShow.isEmpty() && displayId == INVALID_DISPLAY) {
|
||||||
|
showTextWhenNoDisplaysToShow(screen, context);
|
||||||
|
} else if (displaysToShow.size() == 1
|
||||||
|
&& ((displayId == INVALID_DISPLAY && !mPreviouslyShownListOfDisplays)
|
||||||
|
|| displaysToShow.get(0).getDisplayId() == displayId)) {
|
||||||
|
showDisplaySettings(displaysToShow.get(0), screen, context);
|
||||||
|
} else if (displayId == INVALID_DISPLAY) {
|
||||||
|
// If ever shown a list of displays - keep showing it for consistency after
|
||||||
|
// disconnecting one of the displays, and only one display is left.
|
||||||
|
mPreviouslyShownListOfDisplays = true;
|
||||||
|
showDisplaysList(displaysToShow, screen, context);
|
||||||
|
}
|
||||||
|
updateSettingsTitle(displaysToShow, displayId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSettingsTitle(@NonNull final List<Display> displaysToShow, int displayId) {
|
||||||
|
final Activity activity = getCurrentActivity();
|
||||||
|
if (activity == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (displaysToShow.size() == 1 && displaysToShow.get(0).getDisplayId() == displayId) {
|
||||||
|
var displayName = displaysToShow.get(0).getName();
|
||||||
|
if (!displayName.isEmpty()) {
|
||||||
|
activity.setTitle(displayName.substring(0, Math.min(displayName.length(), 40)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
activity.setTitle(EXTERANAL_DISPLAY_TITLE_RESOURCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showTextWhenNoDisplaysToShow(@NonNull final PreferenceScreen screen,
|
||||||
|
@NonNull Context context) {
|
||||||
|
if (isUseDisplaySettingEnabled(mInjector)) {
|
||||||
|
screen.addPreference(updateUseDisplayPreferenceNoDisplaysFound(context));
|
||||||
|
}
|
||||||
|
screen.addPreference(updateFooterPreference(context,
|
||||||
|
EXTERNAL_DISPLAY_NOT_FOUND_FOOTER_RESOURCE));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showDisplaySettings(@NonNull Display display, @NonNull PreferenceScreen screen,
|
||||||
|
@NonNull Context context) {
|
||||||
|
final var isEnabled = mInjector != null && mInjector.isDisplayEnabled(display);
|
||||||
|
if (isUseDisplaySettingEnabled(mInjector)) {
|
||||||
|
screen.addPreference(updateUseDisplayPreference(context, display, isEnabled));
|
||||||
|
}
|
||||||
|
if (!isEnabled) {
|
||||||
|
// Skip all other settings
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final var displayRotation = getDisplayRotation(display.getDisplayId());
|
||||||
|
screen.addPreference(updateIllustrationImage(context, displayRotation));
|
||||||
|
screen.addPreference(updateResolutionPreference(context, display));
|
||||||
|
screen.addPreference(updateRotationPreference(context, display, displayRotation));
|
||||||
|
if (isResolutionSettingEnabled(mInjector)) {
|
||||||
|
screen.addPreference(updateFooterPreference(context,
|
||||||
|
EXTERNAL_DISPLAY_CHANGE_RESOLUTION_FOOTER_RESOURCE));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showDisplaysList(@NonNull List<Display> displaysToShow,
|
||||||
|
@NonNull PreferenceScreen screen, @NonNull Context context) {
|
||||||
|
var pref = getDisplaysListPreference(context);
|
||||||
|
pref.setKey(DISPLAYS_LIST_PREFERENCE_KEY);
|
||||||
|
pref.removeAll();
|
||||||
|
if (!displaysToShow.isEmpty()) {
|
||||||
|
screen.addPreference(pref);
|
||||||
|
}
|
||||||
|
for (var display : displaysToShow) {
|
||||||
|
pref.addPreference(new DisplayPreference(context, display));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Display> getDisplaysToShow(int displayIdToShow) {
|
||||||
|
if (mInjector == null) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
if (displayIdToShow != INVALID_DISPLAY) {
|
||||||
|
var display = mInjector.getDisplay(displayIdToShow);
|
||||||
|
if (display != null && isDisplayAllowed(display, mInjector)) {
|
||||||
|
return List.of(display);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var displaysToShow = new ArrayList<Display>();
|
||||||
|
for (var display : mInjector.getAllDisplays()) {
|
||||||
|
if (display != null && isDisplayAllowed(display, mInjector)) {
|
||||||
|
displaysToShow.add(display);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return displaysToShow;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Preference updateUseDisplayPreferenceNoDisplaysFound(@NonNull Context context) {
|
||||||
|
final var pref = getUseDisplayPreference(context);
|
||||||
|
pref.setKey(EXTERNAL_DISPLAY_USE_PREFERENCE_KEY);
|
||||||
|
pref.setTitle(EXTERNAL_DISPLAY_USE_TITLE_RESOURCE);
|
||||||
|
pref.setChecked(false);
|
||||||
|
pref.setEnabled(false);
|
||||||
|
pref.setOnPreferenceChangeListener(null);
|
||||||
|
return pref;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Preference updateUseDisplayPreference(@NonNull final Context context,
|
||||||
|
@NonNull final Display display, boolean isEnabled) {
|
||||||
|
final var pref = getUseDisplayPreference(context);
|
||||||
|
pref.setKey(EXTERNAL_DISPLAY_USE_PREFERENCE_KEY);
|
||||||
|
pref.setTitle(EXTERNAL_DISPLAY_USE_TITLE_RESOURCE);
|
||||||
|
pref.setChecked(isEnabled);
|
||||||
|
pref.setEnabled(true);
|
||||||
|
pref.setOnPreferenceChangeListener((p, newValue) -> {
|
||||||
|
writePreferenceClickMetric(p);
|
||||||
|
final boolean result;
|
||||||
|
if (mInjector == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ((Boolean) newValue) {
|
||||||
|
result = mInjector.enableConnectedDisplay(display.getDisplayId());
|
||||||
|
} else {
|
||||||
|
result = mInjector.disableConnectedDisplay(display.getDisplayId());
|
||||||
|
}
|
||||||
|
if (result) {
|
||||||
|
pref.setChecked((Boolean) newValue);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
return pref;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Preference updateIllustrationImage(@NonNull final Context context,
|
||||||
|
final int displayRotation) {
|
||||||
|
var pref = getIllustrationPreference(context);
|
||||||
|
if (displayRotation % 2 == 0) {
|
||||||
|
pref.setLottieAnimationResId(EXTERNAL_DISPLAY_PORTRAIT_DRAWABLE);
|
||||||
|
} else {
|
||||||
|
pref.setLottieAnimationResId(EXTERNAL_DISPLAY_LANDSCAPE_DRAWABLE);
|
||||||
|
}
|
||||||
|
return pref;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Preference updateFooterPreference(@NonNull final Context context, final int title) {
|
||||||
|
var pref = getFooterPreference(context);
|
||||||
|
pref.setTitle(title);
|
||||||
|
return pref;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Preference updateRotationPreference(@NonNull final Context context,
|
||||||
|
@NonNull final Display display, final int displayRotation) {
|
||||||
|
var pref = getRotationPreference(context);
|
||||||
|
pref.setKey(EXTERNAL_DISPLAY_ROTATION_KEY);
|
||||||
|
pref.setTitle(EXTERNAL_DISPLAY_ROTATION_TITLE_RESOURCE);
|
||||||
|
if (mRotationEntries == null || mRotationEntriesValues == null) {
|
||||||
|
mRotationEntries = new String[] {
|
||||||
|
context.getString(R.string.external_display_standard_rotation),
|
||||||
|
context.getString(R.string.external_display_rotation_90),
|
||||||
|
context.getString(R.string.external_display_rotation_180),
|
||||||
|
context.getString(R.string.external_display_rotation_270)};
|
||||||
|
mRotationEntriesValues = new String[] {"0", "1", "2", "3"};
|
||||||
|
}
|
||||||
|
pref.setEntries(mRotationEntries);
|
||||||
|
pref.setEntryValues(mRotationEntriesValues);
|
||||||
|
pref.setValueIndex(displayRotation);
|
||||||
|
pref.setSummary(mRotationEntries[displayRotation]);
|
||||||
|
pref.setOnPreferenceChangeListener((p, newValue) -> {
|
||||||
|
writePreferenceClickMetric(p);
|
||||||
|
var rotation = Integer.parseInt((String) newValue);
|
||||||
|
var displayId = display.getDisplayId();
|
||||||
|
if (mInjector == null || !mInjector.freezeDisplayRotation(displayId, rotation)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
pref.setValueIndex(rotation);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
pref.setEnabled(isRotationSettingEnabled(mInjector));
|
||||||
|
return pref;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Preference updateResolutionPreference(@NonNull final Context context,
|
||||||
|
@NonNull final Display display) {
|
||||||
|
var pref = getResolutionPreference(context);
|
||||||
|
pref.setKey(EXTERNAL_DISPLAY_RESOLUTION_PREFERENCE_KEY);
|
||||||
|
pref.setTitle(EXTERNAL_DISPLAY_RESOLUTION_TITLE_RESOURCE);
|
||||||
|
pref.setSummary(display.getMode().getPhysicalWidth() + " x "
|
||||||
|
+ display.getMode().getPhysicalHeight());
|
||||||
|
pref.setOnPreferenceClickListener((Preference p) -> {
|
||||||
|
writePreferenceClickMetric(p);
|
||||||
|
launchResolutionSelector(context, display.getDisplayId());
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
pref.setEnabled(isResolutionSettingEnabled(mInjector));
|
||||||
|
return pref;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getDisplayRotation(int displayId) {
|
||||||
|
if (mInjector == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return Math.min(3, Math.max(0, mInjector.getDisplayUserRotation(displayId)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scheduleUpdate() {
|
||||||
|
if (mInjector == null || !mStarted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
unscheduleUpdate();
|
||||||
|
mInjector.getHandler().post(mUpdateRunnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unscheduleUpdate() {
|
||||||
|
if (mInjector == null || !mStarted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mInjector.getHandler().removeCallbacks(mUpdateRunnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
class DisplayPreference extends TwoTargetPreference
|
||||||
|
implements Preference.OnPreferenceClickListener {
|
||||||
|
private final int mDisplayId;
|
||||||
|
|
||||||
|
DisplayPreference(@NonNull final Context context, @NonNull final Display display) {
|
||||||
|
super(context);
|
||||||
|
mDisplayId = display.getDisplayId();
|
||||||
|
setPersistent(false);
|
||||||
|
setKey("display_id_" + mDisplayId);
|
||||||
|
setTitle(display.getName());
|
||||||
|
setSummary(display.getMode().getPhysicalWidth() + " x "
|
||||||
|
+ display.getMode().getPhysicalHeight());
|
||||||
|
setOnPreferenceClickListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onPreferenceClick(@NonNull Preference preference) {
|
||||||
|
launchDisplaySettings(mDisplayId);
|
||||||
|
writePreferenceClickMetric(preference);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,341 @@
|
|||||||
|
/*
|
||||||
|
* 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 static android.content.Context.DISPLAY_SERVICE;
|
||||||
|
import static android.hardware.display.DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED;
|
||||||
|
import static android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_ADDED;
|
||||||
|
import static android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_CHANGED;
|
||||||
|
import static android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED;
|
||||||
|
import static android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
|
||||||
|
import static android.view.Display.INVALID_DISPLAY;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.hardware.display.DisplayManager;
|
||||||
|
import android.hardware.display.DisplayManagerGlobal;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import android.os.SystemProperties;
|
||||||
|
import android.view.Display;
|
||||||
|
import android.view.Display.Mode;
|
||||||
|
import android.view.IWindowManager;
|
||||||
|
import android.view.WindowManagerGlobal;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.android.settings.R;
|
||||||
|
import com.android.settings.flags.FeatureFlags;
|
||||||
|
import com.android.settings.flags.FeatureFlagsImpl;
|
||||||
|
|
||||||
|
public class ExternalDisplaySettingsConfiguration {
|
||||||
|
static final String VIRTUAL_DISPLAY_PACKAGE_NAME_SYSTEM_PROPERTY =
|
||||||
|
"persist.demo.userrotation.package_name";
|
||||||
|
static final String DISPLAY_ID_ARG = "display_id";
|
||||||
|
static final int EXTERNAL_DISPLAY_NOT_FOUND_RESOURCE = R.string.external_display_not_found;
|
||||||
|
static final int EXTERNAL_DISPLAY_HELP_URL = R.string.help_url_external_display;
|
||||||
|
|
||||||
|
public static class SystemServicesProvider {
|
||||||
|
@Nullable
|
||||||
|
private IWindowManager mWindowManager;
|
||||||
|
@Nullable
|
||||||
|
private DisplayManager mDisplayManager;
|
||||||
|
@Nullable
|
||||||
|
protected Context mContext;
|
||||||
|
/**
|
||||||
|
* @param name of a system property.
|
||||||
|
* @return the value of the system property.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public String getSystemProperty(@NonNull String name) {
|
||||||
|
return SystemProperties.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return return public Display manager.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public DisplayManager getDisplayManager() {
|
||||||
|
if (mDisplayManager == null && getContext() != null) {
|
||||||
|
mDisplayManager = (DisplayManager) getContext().getSystemService(DISPLAY_SERVICE);
|
||||||
|
}
|
||||||
|
return mDisplayManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return internal IWindowManager
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public IWindowManager getWindowManager() {
|
||||||
|
if (mWindowManager == null) {
|
||||||
|
mWindowManager = WindowManagerGlobal.getWindowManagerService();
|
||||||
|
}
|
||||||
|
return mWindowManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return context.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public Context getContext() {
|
||||||
|
return mContext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Injector extends SystemServicesProvider {
|
||||||
|
@NonNull
|
||||||
|
private final FeatureFlags mFlags;
|
||||||
|
@NonNull
|
||||||
|
private final Handler mHandler;
|
||||||
|
|
||||||
|
Injector(@Nullable Context context) {
|
||||||
|
this(context, new FeatureFlagsImpl(), new Handler(Looper.getMainLooper()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Injector(@Nullable Context context, @NonNull FeatureFlags flags, @NonNull Handler handler) {
|
||||||
|
mContext = context;
|
||||||
|
mFlags = flags;
|
||||||
|
mHandler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return all displays including disabled.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public Display[] getAllDisplays() {
|
||||||
|
var dm = getDisplayManager();
|
||||||
|
if (dm == null) {
|
||||||
|
return new Display[0];
|
||||||
|
}
|
||||||
|
return dm.getDisplays(DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return enabled displays only.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public Display[] getEnabledDisplays() {
|
||||||
|
var dm = getDisplayManager();
|
||||||
|
if (dm == null) {
|
||||||
|
return new Display[0];
|
||||||
|
}
|
||||||
|
return dm.getDisplays();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if the display is enabled
|
||||||
|
*/
|
||||||
|
public boolean isDisplayEnabled(@NonNull Display display) {
|
||||||
|
for (var enabledDisplay : getEnabledDisplays()) {
|
||||||
|
if (enabledDisplay.getDisplayId() == display.getDisplayId()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register display listener.
|
||||||
|
*/
|
||||||
|
public void registerDisplayListener(@NonNull DisplayManager.DisplayListener listener) {
|
||||||
|
var dm = getDisplayManager();
|
||||||
|
if (dm == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dm.registerDisplayListener(listener, mHandler, EVENT_FLAG_DISPLAY_ADDED
|
||||||
|
| EVENT_FLAG_DISPLAY_CHANGED | EVENT_FLAG_DISPLAY_REMOVED
|
||||||
|
| EVENT_FLAG_DISPLAY_CONNECTION_CHANGED);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregister display listener.
|
||||||
|
*/
|
||||||
|
public void unregisterDisplayListener(@NonNull DisplayManager.DisplayListener listener) {
|
||||||
|
var dm = getDisplayManager();
|
||||||
|
if (dm == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dm.unregisterDisplayListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return feature flags.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public FeatureFlags getFlags() {
|
||||||
|
return mFlags;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable connected display.
|
||||||
|
*/
|
||||||
|
public boolean enableConnectedDisplay(int displayId) {
|
||||||
|
var dm = getDisplayManager();
|
||||||
|
if (dm == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
dm.enableConnectedDisplay(displayId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable connected display.
|
||||||
|
*/
|
||||||
|
public boolean disableConnectedDisplay(int displayId) {
|
||||||
|
var dm = getDisplayManager();
|
||||||
|
if (dm == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
dm.disableConnectedDisplay(displayId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param displayId which must be returned
|
||||||
|
* @return display object for the displayId
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public Display getDisplay(int displayId) {
|
||||||
|
if (displayId == INVALID_DISPLAY) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var dm = getDisplayManager();
|
||||||
|
if (dm == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return dm.getDisplay(displayId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return handler
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public Handler getHandler() {
|
||||||
|
return mHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get display rotation
|
||||||
|
* @param displayId display identifier
|
||||||
|
* @return rotation
|
||||||
|
*/
|
||||||
|
public int getDisplayUserRotation(int displayId) {
|
||||||
|
var wm = getWindowManager();
|
||||||
|
if (wm == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return wm.getDisplayUserRotation(displayId);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Freeze rotation of the display in the specified rotation.
|
||||||
|
* @param displayId display identifier
|
||||||
|
* @param rotation [0, 1, 2, 3]
|
||||||
|
* @return true if successful
|
||||||
|
*/
|
||||||
|
public boolean freezeDisplayRotation(int displayId, int rotation) {
|
||||||
|
var wm = getWindowManager();
|
||||||
|
if (wm == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
wm.freezeDisplayRotation(displayId, rotation,
|
||||||
|
"ExternalDisplayPreferenceFragment");
|
||||||
|
return true;
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enforce display mode on the given display.
|
||||||
|
*/
|
||||||
|
public void setUserPreferredDisplayMode(int displayId, @NonNull Mode mode) {
|
||||||
|
DisplayManagerGlobal.getInstance().setUserPreferredDisplayMode(displayId, mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract static class DisplayListener implements DisplayManager.DisplayListener {
|
||||||
|
@Override
|
||||||
|
public void onDisplayAdded(int displayId) {
|
||||||
|
update(displayId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisplayRemoved(int displayId) {
|
||||||
|
update(displayId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisplayChanged(int displayId) {
|
||||||
|
update(displayId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisplayConnected(int displayId) {
|
||||||
|
update(displayId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisplayDisconnected(int displayId) {
|
||||||
|
update(displayId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called from other listener methods to trigger update of the settings page.
|
||||||
|
*/
|
||||||
|
public abstract void update(int displayId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return whether the settings page is enabled or not.
|
||||||
|
*/
|
||||||
|
public static boolean isExternalDisplaySettingsPageEnabled(@NonNull FeatureFlags flags) {
|
||||||
|
return flags.rotationConnectedDisplaySetting()
|
||||||
|
|| flags.resolutionAndEnableConnectedDisplaySetting();
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean isDisplayAllowed(@NonNull Display display,
|
||||||
|
@NonNull SystemServicesProvider props) {
|
||||||
|
return display.getType() == Display.TYPE_EXTERNAL
|
||||||
|
|| display.getType() == Display.TYPE_OVERLAY
|
||||||
|
|| isVirtualDisplayAllowed(display, props);
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean isVirtualDisplayAllowed(@NonNull Display display,
|
||||||
|
@NonNull SystemServicesProvider properties) {
|
||||||
|
var sysProp = properties.getSystemProperty(VIRTUAL_DISPLAY_PACKAGE_NAME_SYSTEM_PROPERTY);
|
||||||
|
return !sysProp.isEmpty() && display.getType() == Display.TYPE_VIRTUAL
|
||||||
|
&& sysProp.equals(display.getOwnerPackageName());
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean isUseDisplaySettingEnabled(@Nullable Injector injector) {
|
||||||
|
return injector != null && injector.getFlags().resolutionAndEnableConnectedDisplaySetting();
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean isResolutionSettingEnabled(@Nullable Injector injector) {
|
||||||
|
return injector != null && injector.getFlags().resolutionAndEnableConnectedDisplaySetting();
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean isRotationSettingEnabled(@Nullable Injector injector) {
|
||||||
|
return injector != null && injector.getFlags().rotationConnectedDisplaySetting();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
/*
|
||||||
|
* 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 static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.isDisplayAllowed;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.os.UserHandle;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
import androidx.preference.Preference;
|
||||||
|
|
||||||
|
import com.android.settings.R;
|
||||||
|
import com.android.settings.connecteddevice.DevicePreferenceCallback;
|
||||||
|
import com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.DisplayListener;
|
||||||
|
import com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.Injector;
|
||||||
|
import com.android.settings.core.SubSettingLauncher;
|
||||||
|
import com.android.settings.overlay.FeatureFactory;
|
||||||
|
import com.android.settingslib.RestrictedLockUtils;
|
||||||
|
import com.android.settingslib.RestrictedLockUtilsInternal;
|
||||||
|
import com.android.settingslib.RestrictedPreference;
|
||||||
|
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
|
||||||
|
|
||||||
|
public class ExternalDisplayUpdater {
|
||||||
|
|
||||||
|
private static final String PREF_KEY = "external_display_settings";
|
||||||
|
private final int mMetricsCategory;
|
||||||
|
@NonNull
|
||||||
|
private final MetricsFeatureProvider mMetricsFeatureProvider;
|
||||||
|
@NonNull
|
||||||
|
private final Runnable mUpdateRunnable = this::update;
|
||||||
|
@NonNull
|
||||||
|
private final DevicePreferenceCallback mDevicePreferenceCallback;
|
||||||
|
@Nullable
|
||||||
|
private RestrictedPreference mPreference;
|
||||||
|
@Nullable
|
||||||
|
private Injector mInjector;
|
||||||
|
private final DisplayListener mListener = new DisplayListener() {
|
||||||
|
@Override
|
||||||
|
public void update(int displayId) {
|
||||||
|
scheduleUpdate();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public ExternalDisplayUpdater(@NonNull DevicePreferenceCallback callback, int metricsCategory) {
|
||||||
|
mDevicePreferenceCallback = callback;
|
||||||
|
mMetricsCategory = metricsCategory;
|
||||||
|
mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the context to generate the {@link Preference}, so it could get the correct theme.
|
||||||
|
*/
|
||||||
|
public void initPreference(@NonNull Context context) {
|
||||||
|
initPreference(context, new Injector(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
void initPreference(@NonNull Context context, Injector injector) {
|
||||||
|
mInjector = injector;
|
||||||
|
mPreference = new RestrictedPreference(context, null /* AttributeSet */);
|
||||||
|
mPreference.setTitle(R.string.external_display_settings_title);
|
||||||
|
mPreference.setSummary(getSummary());
|
||||||
|
mPreference.setIcon(getDrawable(context));
|
||||||
|
mPreference.setKey(PREF_KEY);
|
||||||
|
mPreference.setDisabledByAdmin(checkIfUsbDataSignalingIsDisabled(context));
|
||||||
|
mPreference.setOnPreferenceClickListener((Preference p) -> {
|
||||||
|
mMetricsFeatureProvider.logClickedPreference(p, mMetricsCategory);
|
||||||
|
// New version - uses a separate screen.
|
||||||
|
new SubSettingLauncher(context)
|
||||||
|
.setDestination(ExternalDisplayPreferenceFragment.class.getName())
|
||||||
|
.setTitleRes(R.string.external_display_settings_title)
|
||||||
|
.setSourceMetricsCategory(mMetricsCategory)
|
||||||
|
.launch();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
scheduleUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregister the display listener.
|
||||||
|
*/
|
||||||
|
public void unregisterCallback() {
|
||||||
|
if (mInjector != null) {
|
||||||
|
mInjector.unregisterDisplayListener(mListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the display listener.
|
||||||
|
*/
|
||||||
|
public void registerCallback() {
|
||||||
|
if (mInjector != null) {
|
||||||
|
mInjector.registerDisplayListener(mListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
@Nullable
|
||||||
|
protected RestrictedLockUtils.EnforcedAdmin checkIfUsbDataSignalingIsDisabled(Context context) {
|
||||||
|
return RestrictedLockUtilsInternal.checkIfUsbDataSignalingIsDisabled(context,
|
||||||
|
UserHandle.myUserId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
@Nullable
|
||||||
|
protected Drawable getDrawable(Context context) {
|
||||||
|
return context.getDrawable(R.drawable.ic_external_display_32dp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
protected CharSequence getSummary() {
|
||||||
|
if (mInjector == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var context = mInjector.getContext();
|
||||||
|
if (context == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var display : mInjector.getEnabledDisplays()) {
|
||||||
|
if (display != null && isDisplayAllowed(display, mInjector)) {
|
||||||
|
return context.getString(R.string.external_display_on);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var display : mInjector.getAllDisplays()) {
|
||||||
|
if (display != null && isDisplayAllowed(display, mInjector)) {
|
||||||
|
return context.getString(R.string.external_display_off);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scheduleUpdate() {
|
||||||
|
if (mInjector == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
unscheduleUpdate();
|
||||||
|
mInjector.getHandler().post(mUpdateRunnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unscheduleUpdate() {
|
||||||
|
if (mInjector == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mInjector.getHandler().removeCallbacks(mUpdateRunnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void update() {
|
||||||
|
var summary = getSummary();
|
||||||
|
if (mPreference == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mPreference.setSummary(summary);
|
||||||
|
if (summary != null) {
|
||||||
|
mDevicePreferenceCallback.onDeviceAdded(mPreference);
|
||||||
|
} else {
|
||||||
|
mDevicePreferenceCallback.onDeviceRemoved(mPreference);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/com/android/settings/connecteddevice/display/OWNERS
Normal file
7
src/com/android/settings/connecteddevice/display/OWNERS
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# Default reviewers for this and subdirectories.
|
||||||
|
santoscordon@google.com
|
||||||
|
petsjonkin@google.com
|
||||||
|
flc@google.com
|
||||||
|
wilczynskip@google.com
|
||||||
|
brup@google.com
|
||||||
|
olb@google.com
|
||||||
@@ -0,0 +1,343 @@
|
|||||||
|
/*
|
||||||
|
* 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 static android.view.Display.INVALID_DISPLAY;
|
||||||
|
|
||||||
|
import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.DISPLAY_ID_ARG;
|
||||||
|
import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.EXTERNAL_DISPLAY_HELP_URL;
|
||||||
|
import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.EXTERNAL_DISPLAY_NOT_FOUND_RESOURCE;
|
||||||
|
import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.isDisplayAllowed;
|
||||||
|
|
||||||
|
import android.app.settings.SettingsEnums;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.Pair;
|
||||||
|
import android.view.Display;
|
||||||
|
import android.view.Display.Mode;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
import androidx.preference.PreferenceCategory;
|
||||||
|
import androidx.preference.PreferenceGroup;
|
||||||
|
import androidx.preference.PreferenceScreen;
|
||||||
|
|
||||||
|
import com.android.internal.util.ToBooleanFunction;
|
||||||
|
import com.android.settings.R;
|
||||||
|
import com.android.settings.SettingsPreferenceFragmentBase;
|
||||||
|
import com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.DisplayListener;
|
||||||
|
import com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.Injector;
|
||||||
|
import com.android.settingslib.widget.SelectorWithWidgetPreference;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
|
||||||
|
public class ResolutionPreferenceFragment extends SettingsPreferenceFragmentBase {
|
||||||
|
private static final String TAG = "ResolutionPreferenceFragment";
|
||||||
|
static final int DEFAULT_LOW_REFRESH_RATE = 60;
|
||||||
|
static final String MORE_OPTIONS_KEY = "more_options";
|
||||||
|
static final String TOP_OPTIONS_KEY = "top_options";
|
||||||
|
static final int MORE_OPTIONS_TITLE_RESOURCE =
|
||||||
|
R.string.external_display_more_options_title;
|
||||||
|
static final int EXTERNAL_DISPLAY_RESOLUTION_SETTINGS_RESOURCE =
|
||||||
|
R.xml.external_display_resolution_settings;
|
||||||
|
@Nullable
|
||||||
|
private Injector mInjector;
|
||||||
|
@Nullable
|
||||||
|
private PreferenceCategory mTopOptionsPreference;
|
||||||
|
@Nullable
|
||||||
|
private PreferenceCategory mMoreOptionsPreference;
|
||||||
|
private boolean mStarted;
|
||||||
|
private final HashSet<String> mResolutionPreferences = new HashSet<>();
|
||||||
|
private int mExternalDisplayPeakWidth;
|
||||||
|
private int mExternalDisplayPeakHeight;
|
||||||
|
private int mExternalDisplayPeakRefreshRate;
|
||||||
|
private boolean mRefreshRateSynchronizationEnabled;
|
||||||
|
private boolean mMoreOptionsExpanded;
|
||||||
|
private final Runnable mUpdateRunnable = this::update;
|
||||||
|
private final DisplayListener mListener = new DisplayListener() {
|
||||||
|
@Override
|
||||||
|
public void update(int displayId) {
|
||||||
|
scheduleUpdate();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMetricsCategory() {
|
||||||
|
return SettingsEnums.SETTINGS_CONNECTED_DEVICE_CATEGORY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getHelpResource() {
|
||||||
|
return EXTERNAL_DISPLAY_HELP_URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateCallback(@Nullable Bundle icicle) {
|
||||||
|
if (mInjector == null) {
|
||||||
|
mInjector = new Injector(getPrefContext());
|
||||||
|
}
|
||||||
|
addPreferencesFromResource(EXTERNAL_DISPLAY_RESOLUTION_SETTINGS_RESOURCE);
|
||||||
|
updateDisplayModeLimits(mInjector.getContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreatedCallback(@Nullable Bundle savedInstanceState) {
|
||||||
|
View view = getView();
|
||||||
|
TextView emptyView = null;
|
||||||
|
if (view != null) {
|
||||||
|
emptyView = (TextView) view.findViewById(android.R.id.empty);
|
||||||
|
}
|
||||||
|
if (emptyView != null) {
|
||||||
|
emptyView.setText(EXTERNAL_DISPLAY_NOT_FOUND_RESOURCE);
|
||||||
|
setEmptyView(emptyView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStartCallback() {
|
||||||
|
mStarted = true;
|
||||||
|
if (mInjector == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mInjector.registerDisplayListener(mListener);
|
||||||
|
scheduleUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStopCallback() {
|
||||||
|
mStarted = false;
|
||||||
|
if (mInjector == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mInjector.unregisterDisplayListener(mListener);
|
||||||
|
unscheduleUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResolutionPreferenceFragment() {}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
ResolutionPreferenceFragment(@NonNull Injector injector) {
|
||||||
|
mInjector = injector;
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
protected int getDisplayIdArg() {
|
||||||
|
var args = getArguments();
|
||||||
|
return args != null ? args.getInt(DISPLAY_ID_ARG, INVALID_DISPLAY) : INVALID_DISPLAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
@NonNull
|
||||||
|
protected Resources getResources(@NonNull Context context) {
|
||||||
|
return context.getResources();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void update() {
|
||||||
|
final PreferenceScreen screen = getPreferenceScreen();
|
||||||
|
if (screen == null || mInjector == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var context = mInjector.getContext();
|
||||||
|
if (context == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var display = mInjector.getDisplay(getDisplayIdArg());
|
||||||
|
if (display == null || !isDisplayAllowed(display, mInjector)) {
|
||||||
|
screen.removeAll();
|
||||||
|
mTopOptionsPreference = null;
|
||||||
|
mMoreOptionsPreference = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mResolutionPreferences.clear();
|
||||||
|
var remainingModes = addModePreferences(context,
|
||||||
|
getTopPreference(context, screen),
|
||||||
|
display.getSupportedModes(), this::isTopMode, display);
|
||||||
|
addRemainingPreferences(context,
|
||||||
|
getMorePreference(context, screen),
|
||||||
|
display, remainingModes.first, remainingModes.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PreferenceCategory getTopPreference(@NonNull Context context,
|
||||||
|
@NonNull PreferenceScreen screen) {
|
||||||
|
if (mTopOptionsPreference == null) {
|
||||||
|
mTopOptionsPreference = new PreferenceCategory(context);
|
||||||
|
mTopOptionsPreference.setPersistent(false);
|
||||||
|
mTopOptionsPreference.setKey(TOP_OPTIONS_KEY);
|
||||||
|
screen.addPreference(mTopOptionsPreference);
|
||||||
|
} else {
|
||||||
|
mTopOptionsPreference.removeAll();
|
||||||
|
}
|
||||||
|
return mTopOptionsPreference;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PreferenceCategory getMorePreference(@NonNull Context context,
|
||||||
|
@NonNull PreferenceScreen screen) {
|
||||||
|
if (mMoreOptionsPreference == null) {
|
||||||
|
mMoreOptionsPreference = new PreferenceCategory(context);
|
||||||
|
mMoreOptionsPreference.setPersistent(false);
|
||||||
|
mMoreOptionsPreference.setTitle(MORE_OPTIONS_TITLE_RESOURCE);
|
||||||
|
mMoreOptionsPreference.setOnExpandButtonClickListener(() -> {
|
||||||
|
mMoreOptionsExpanded = true;
|
||||||
|
});
|
||||||
|
mMoreOptionsPreference.setKey(MORE_OPTIONS_KEY);
|
||||||
|
screen.addPreference(mMoreOptionsPreference);
|
||||||
|
} else {
|
||||||
|
mMoreOptionsPreference.removeAll();
|
||||||
|
}
|
||||||
|
return mMoreOptionsPreference;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addRemainingPreferences(@NonNull Context context,
|
||||||
|
@NonNull PreferenceCategory group, @NonNull Display display,
|
||||||
|
boolean isSelectedModeFound, @NonNull Mode[] moreModes) {
|
||||||
|
if (moreModes.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mMoreOptionsExpanded |= !isSelectedModeFound;
|
||||||
|
group.setInitialExpandedChildrenCount(mMoreOptionsExpanded ? Integer.MAX_VALUE : 0);
|
||||||
|
addModePreferences(context, group, moreModes, /*checkMode=*/ null, display);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Pair<Boolean, Mode[]> addModePreferences(@NonNull Context context,
|
||||||
|
@NonNull PreferenceGroup group,
|
||||||
|
@NonNull Mode[] modes,
|
||||||
|
@Nullable ToBooleanFunction<Mode> checkMode,
|
||||||
|
@NonNull Display display) {
|
||||||
|
Display.Mode curMode = display.getMode();
|
||||||
|
var currentResolution = curMode.getPhysicalWidth() + "x" + curMode.getPhysicalHeight();
|
||||||
|
var rotatedResolution = curMode.getPhysicalHeight() + "x" + curMode.getPhysicalWidth();
|
||||||
|
var skippedModes = new ArrayList<Mode>();
|
||||||
|
var isAnyOfModesSelected = false;
|
||||||
|
for (var mode : modes) {
|
||||||
|
var modeStr = mode.getPhysicalWidth() + "x" + mode.getPhysicalHeight();
|
||||||
|
SelectorWithWidgetPreference pref = group.findPreference(modeStr);
|
||||||
|
if (pref != null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (checkMode != null && !checkMode.apply(mode)) {
|
||||||
|
skippedModes.add(mode);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var isCurrentMode =
|
||||||
|
currentResolution.equals(modeStr) || rotatedResolution.equals(modeStr);
|
||||||
|
if (!isCurrentMode && !isAllowedMode(mode)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (mResolutionPreferences.contains(modeStr)) {
|
||||||
|
// Added to "Top modes" already.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
mResolutionPreferences.add(modeStr);
|
||||||
|
pref = new SelectorWithWidgetPreference(context);
|
||||||
|
pref.setPersistent(false);
|
||||||
|
pref.setKey(modeStr);
|
||||||
|
pref.setTitle(mode.getPhysicalWidth() + " x " + mode.getPhysicalHeight());
|
||||||
|
pref.setSingleLineTitle(true);
|
||||||
|
pref.setOnClickListener(preference -> onDisplayModeClicked(preference, display));
|
||||||
|
pref.setChecked(isCurrentMode);
|
||||||
|
isAnyOfModesSelected |= isCurrentMode;
|
||||||
|
group.addPreference(pref);
|
||||||
|
}
|
||||||
|
return new Pair<>(isAnyOfModesSelected, skippedModes.toArray(Mode.EMPTY_ARRAY));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isTopMode(@NonNull Mode mode) {
|
||||||
|
return mTopOptionsPreference != null
|
||||||
|
&& mTopOptionsPreference.getPreferenceCount() < 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isAllowedMode(@NonNull Mode mode) {
|
||||||
|
if (mRefreshRateSynchronizationEnabled
|
||||||
|
&& (mode.getRefreshRate() < DEFAULT_LOW_REFRESH_RATE - 1
|
||||||
|
|| mode.getRefreshRate() > DEFAULT_LOW_REFRESH_RATE + 1)) {
|
||||||
|
Log.d(TAG, mode + " refresh rate is out of synchronization range");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (mExternalDisplayPeakHeight > 0
|
||||||
|
&& mode.getPhysicalHeight() > mExternalDisplayPeakHeight) {
|
||||||
|
Log.d(TAG, mode + " height is above the allowed limit");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (mExternalDisplayPeakWidth > 0
|
||||||
|
&& mode.getPhysicalWidth() > mExternalDisplayPeakWidth) {
|
||||||
|
Log.d(TAG, mode + " width is above the allowed limit");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (mExternalDisplayPeakRefreshRate > 0
|
||||||
|
&& mode.getRefreshRate() > mExternalDisplayPeakRefreshRate) {
|
||||||
|
Log.d(TAG, mode + " refresh rate is above the allowed limit");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scheduleUpdate() {
|
||||||
|
if (mInjector == null || !mStarted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
unscheduleUpdate();
|
||||||
|
mInjector.getHandler().post(mUpdateRunnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unscheduleUpdate() {
|
||||||
|
if (mInjector == null || !mStarted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mInjector.getHandler().removeCallbacks(mUpdateRunnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onDisplayModeClicked(@NonNull SelectorWithWidgetPreference preference,
|
||||||
|
@NonNull Display display) {
|
||||||
|
if (mInjector == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String[] modeResolution = preference.getKey().split("x");
|
||||||
|
int width = Integer.parseInt(modeResolution[0]);
|
||||||
|
int height = Integer.parseInt(modeResolution[1]);
|
||||||
|
for (var mode : display.getSupportedModes()) {
|
||||||
|
if (mode.getPhysicalWidth() == width && mode.getPhysicalHeight() == height
|
||||||
|
&& isAllowedMode(mode)) {
|
||||||
|
mInjector.setUserPreferredDisplayMode(display.getDisplayId(), mode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateDisplayModeLimits(@Nullable Context context) {
|
||||||
|
if (context == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mExternalDisplayPeakRefreshRate = getResources(context).getInteger(
|
||||||
|
com.android.internal.R.integer.config_externalDisplayPeakRefreshRate);
|
||||||
|
mExternalDisplayPeakWidth = getResources(context).getInteger(
|
||||||
|
com.android.internal.R.integer.config_externalDisplayPeakWidth);
|
||||||
|
mExternalDisplayPeakHeight = getResources(context).getInteger(
|
||||||
|
com.android.internal.R.integer.config_externalDisplayPeakHeight);
|
||||||
|
mRefreshRateSynchronizationEnabled = getResources(context).getBoolean(
|
||||||
|
com.android.internal.R.bool.config_refreshRateSynchronizationEnabled);
|
||||||
|
Log.d(TAG, "mExternalDisplayPeakRefreshRate=" + mExternalDisplayPeakRefreshRate);
|
||||||
|
Log.d(TAG, "mExternalDisplayPeakWidth=" + mExternalDisplayPeakWidth);
|
||||||
|
Log.d(TAG, "mExternalDisplayPeakHeight=" + mExternalDisplayPeakHeight);
|
||||||
|
Log.d(TAG, "mRefreshRateSynchronizationEnabled=" + mRefreshRateSynchronizationEnabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 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.inputmethod;
|
||||||
|
|
||||||
|
import static android.view.PointerIcon.DEFAULT_POINTER_SCALE;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.os.UserHandle;
|
||||||
|
import android.provider.Settings;
|
||||||
|
import android.widget.SeekBar;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.preference.PreferenceScreen;
|
||||||
|
|
||||||
|
import com.android.settings.R;
|
||||||
|
import com.android.settings.core.BasePreferenceController;
|
||||||
|
import com.android.settings.widget.LabeledSeekBarPreference;
|
||||||
|
|
||||||
|
public class PointerScaleSeekBarController extends BasePreferenceController {
|
||||||
|
|
||||||
|
private final int mProgressMin;
|
||||||
|
private final int mProgressMax;
|
||||||
|
private final float mScaleMin;
|
||||||
|
private final float mScaleMax;
|
||||||
|
|
||||||
|
public PointerScaleSeekBarController(@NonNull Context context, @NonNull String key) {
|
||||||
|
super(context, key);
|
||||||
|
|
||||||
|
Resources res = context.getResources();
|
||||||
|
mProgressMin = res.getInteger(R.integer.pointer_scale_seek_bar_start);
|
||||||
|
mProgressMax = res.getInteger(R.integer.pointer_scale_seek_bar_end);
|
||||||
|
mScaleMin = res.getFloat(R.dimen.pointer_scale_size_start);
|
||||||
|
mScaleMax = res.getFloat(R.dimen.pointer_scale_size_end);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AvailabilityStatus
|
||||||
|
public int getAvailabilityStatus() {
|
||||||
|
return android.view.flags.Flags.enableVectorCursorA11ySettings() ? AVAILABLE
|
||||||
|
: CONDITIONALLY_UNAVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void displayPreference(@NonNull PreferenceScreen screen) {
|
||||||
|
super.displayPreference(screen);
|
||||||
|
|
||||||
|
LabeledSeekBarPreference seekBarPreference = screen.findPreference(getPreferenceKey());
|
||||||
|
seekBarPreference.setMax(mProgressMax);
|
||||||
|
seekBarPreference.setContinuousUpdates(/* continuousUpdates= */ true);
|
||||||
|
seekBarPreference.setProgress(scaleToProgress(
|
||||||
|
Settings.System.getFloatForUser(mContext.getContentResolver(),
|
||||||
|
Settings.System.POINTER_SCALE, DEFAULT_POINTER_SCALE,
|
||||||
|
UserHandle.USER_CURRENT)));
|
||||||
|
seekBarPreference.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onProgressChanged(@NonNull SeekBar seekBar, int progress,
|
||||||
|
boolean fromUser) {
|
||||||
|
Settings.System.putFloatForUser(mContext.getContentResolver(),
|
||||||
|
Settings.System.POINTER_SCALE, progressToScale(progress),
|
||||||
|
UserHandle.USER_CURRENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStartTrackingTouch(@NonNull SeekBar seekBar) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStopTrackingTouch(@NonNull SeekBar seekBar) {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private float progressToScale(int progress) {
|
||||||
|
return (((progress - mProgressMin) * (mScaleMax - mScaleMin)) / (mProgressMax
|
||||||
|
- mProgressMin)) + mScaleMin;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int scaleToProgress(float scale) {
|
||||||
|
return (int) (
|
||||||
|
(((scale - mScaleMin) * (mProgressMax - mProgressMin)) / (mScaleMax - mScaleMin))
|
||||||
|
+ mProgressMin);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -85,7 +85,6 @@ public class MobileNetworkRepository extends SubscriptionManager.OnSubscriptions
|
|||||||
private List<SubscriptionInfoEntity> mActiveSubInfoEntityList = new ArrayList<>();
|
private List<SubscriptionInfoEntity> mActiveSubInfoEntityList = new ArrayList<>();
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
private AirplaneModeObserver mAirplaneModeObserver;
|
private AirplaneModeObserver mAirplaneModeObserver;
|
||||||
private DataRoamingObserver mDataRoamingObserver;
|
|
||||||
private MetricsFeatureProvider mMetricsFeatureProvider;
|
private MetricsFeatureProvider mMetricsFeatureProvider;
|
||||||
private int mPhysicalSlotIndex = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
|
private int mPhysicalSlotIndex = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
|
||||||
private int mLogicalSlotIndex = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
|
private int mLogicalSlotIndex = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
|
||||||
@@ -122,7 +121,6 @@ public class MobileNetworkRepository extends SubscriptionManager.OnSubscriptions
|
|||||||
mSubscriptionInfoDao = mMobileNetworkDatabase.mSubscriptionInfoDao();
|
mSubscriptionInfoDao = mMobileNetworkDatabase.mSubscriptionInfoDao();
|
||||||
mMobileNetworkInfoDao = mMobileNetworkDatabase.mMobileNetworkInfoDao();
|
mMobileNetworkInfoDao = mMobileNetworkDatabase.mMobileNetworkInfoDao();
|
||||||
mAirplaneModeObserver = new AirplaneModeObserver(new Handler(Looper.getMainLooper()));
|
mAirplaneModeObserver = new AirplaneModeObserver(new Handler(Looper.getMainLooper()));
|
||||||
mDataRoamingObserver = new DataRoamingObserver(new Handler(Looper.getMainLooper()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class AirplaneModeObserver extends ContentObserver {
|
private class AirplaneModeObserver extends ContentObserver {
|
||||||
@@ -153,47 +151,6 @@ public class MobileNetworkRepository extends SubscriptionManager.OnSubscriptions
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DataRoamingObserver extends ContentObserver {
|
|
||||||
private int mRegSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
|
|
||||||
private String mBaseField = Settings.Global.DATA_ROAMING;
|
|
||||||
|
|
||||||
DataRoamingObserver(Handler handler) {
|
|
||||||
super(handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void register(Context context, int subId) {
|
|
||||||
mRegSubId = subId;
|
|
||||||
String lastField = mBaseField;
|
|
||||||
createTelephonyManagerBySubId(subId);
|
|
||||||
TelephonyManager tm = mTelephonyManagerMap.get(subId);
|
|
||||||
if (tm.getSimCount() != 1) {
|
|
||||||
lastField += subId;
|
|
||||||
}
|
|
||||||
context.getContentResolver().registerContentObserver(
|
|
||||||
Settings.Global.getUriFor(lastField), false, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void unRegister(Context context) {
|
|
||||||
context.getContentResolver().unregisterContentObserver(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onChange(boolean selfChange, Uri uri) {
|
|
||||||
TelephonyManager tm = mTelephonyManagerMap.get(mRegSubId);
|
|
||||||
if (tm == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sExecutor.execute(() -> {
|
|
||||||
Log.d(TAG, "DataRoamingObserver changed");
|
|
||||||
insertMobileNetworkInfo(mContext, mRegSubId, tm);
|
|
||||||
});
|
|
||||||
boolean isDataRoamingEnabled = tm.isDataRoamingEnabled();
|
|
||||||
for (MobileNetworkCallback callback : sCallbacks) {
|
|
||||||
callback.onDataRoamingChanged(mRegSubId, isDataRoamingEnabled);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register all callbacks and listener.
|
* Register all callbacks and listener.
|
||||||
*
|
*
|
||||||
@@ -219,7 +176,6 @@ public class MobileNetworkRepository extends SubscriptionManager.OnSubscriptions
|
|||||||
observeAllMobileNetworkInfo(lifecycleOwner);
|
observeAllMobileNetworkInfo(lifecycleOwner);
|
||||||
if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
|
if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
|
||||||
createTelephonyManagerBySubId(subId);
|
createTelephonyManagerBySubId(subId);
|
||||||
mDataRoamingObserver.register(mContext, subId);
|
|
||||||
}
|
}
|
||||||
// When one client registers callback first time, convey the cached results to the client
|
// When one client registers callback first time, convey the cached results to the client
|
||||||
// so that the client is aware of the content therein.
|
// so that the client is aware of the content therein.
|
||||||
@@ -283,7 +239,6 @@ public class MobileNetworkRepository extends SubscriptionManager.OnSubscriptions
|
|||||||
if (sCallbacks.isEmpty()) {
|
if (sCallbacks.isEmpty()) {
|
||||||
mSubscriptionManager.removeOnSubscriptionsChangedListener(this);
|
mSubscriptionManager.removeOnSubscriptionsChangedListener(this);
|
||||||
mAirplaneModeObserver.unRegister(mContext);
|
mAirplaneModeObserver.unRegister(mContext);
|
||||||
mDataRoamingObserver.unRegister(mContext);
|
|
||||||
|
|
||||||
mTelephonyManagerMap.forEach((id, manager) -> {
|
mTelephonyManagerMap.forEach((id, manager) -> {
|
||||||
TelephonyCallback callback = mTelephonyCallbackMap.get(id);
|
TelephonyCallback callback = mTelephonyCallbackMap.get(id);
|
||||||
@@ -588,10 +543,8 @@ public class MobileNetworkRepository extends SubscriptionManager.OnSubscriptions
|
|||||||
private MobileNetworkInfoEntity convertToMobileNetworkInfoEntity(Context context, int subId,
|
private MobileNetworkInfoEntity convertToMobileNetworkInfoEntity(Context context, int subId,
|
||||||
TelephonyManager telephonyManager) {
|
TelephonyManager telephonyManager) {
|
||||||
boolean isDataEnabled = false;
|
boolean isDataEnabled = false;
|
||||||
boolean isDataRoamingEnabled = false;
|
|
||||||
if (telephonyManager != null) {
|
if (telephonyManager != null) {
|
||||||
isDataEnabled = telephonyManager.isDataEnabled();
|
isDataEnabled = telephonyManager.isDataEnabled();
|
||||||
isDataRoamingEnabled = telephonyManager.isDataRoamingEnabled();
|
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, "TelephonyManager is null, subId = " + subId);
|
Log.d(TAG, "TelephonyManager is null, subId = " + subId);
|
||||||
}
|
}
|
||||||
@@ -607,7 +560,7 @@ public class MobileNetworkRepository extends SubscriptionManager.OnSubscriptions
|
|||||||
MobileNetworkUtils.isTdscdmaSupported(context, subId),
|
MobileNetworkUtils.isTdscdmaSupported(context, subId),
|
||||||
MobileNetworkUtils.activeNetworkIsCellular(context),
|
MobileNetworkUtils.activeNetworkIsCellular(context),
|
||||||
SubscriptionUtil.showToggleForPhysicalSim(mSubscriptionManager),
|
SubscriptionUtil.showToggleForPhysicalSim(mSubscriptionManager),
|
||||||
isDataRoamingEnabled
|
/* deprecated isDataRoamingEnabled = */ false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -754,12 +707,6 @@ public class MobileNetworkRepository extends SubscriptionManager.OnSubscriptions
|
|||||||
|
|
||||||
default void onAirplaneModeChanged(boolean enabled) {
|
default void onAirplaneModeChanged(boolean enabled) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Notify clients data roaming changed of subscription.
|
|
||||||
*/
|
|
||||||
default void onDataRoamingChanged(int subId, boolean enabled) {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void dump(IndentingPrintWriter printwriter) {
|
public void dump(IndentingPrintWriter printwriter) {
|
||||||
|
|||||||
@@ -85,10 +85,11 @@ public class ApnPreference extends Preference
|
|||||||
final RelativeLayout textArea = (RelativeLayout) view.findViewById(R.id.text_layout);
|
final RelativeLayout textArea = (RelativeLayout) view.findViewById(R.id.text_layout);
|
||||||
textArea.setOnClickListener(this);
|
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);
|
final RadioButton rb = view.itemView.requireViewById(R.id.apn_radiobutton);
|
||||||
mRadioButton = rb;
|
mRadioButton = rb;
|
||||||
if (mDefaultSelectable) {
|
if (mDefaultSelectable) {
|
||||||
view.itemView.requireViewById(R.id.apn_radio_button_frame).setOnClickListener((v) -> {
|
radioButtonFrame.setOnClickListener((v) -> {
|
||||||
rb.performClick();
|
rb.performClick();
|
||||||
});
|
});
|
||||||
rb.setOnCheckedChangeListener(this);
|
rb.setOnCheckedChangeListener(this);
|
||||||
@@ -96,9 +97,9 @@ public class ApnPreference extends Preference
|
|||||||
mProtectFromCheckedChange = true;
|
mProtectFromCheckedChange = true;
|
||||||
rb.setChecked(mIsChecked);
|
rb.setChecked(mIsChecked);
|
||||||
mProtectFromCheckedChange = false;
|
mProtectFromCheckedChange = false;
|
||||||
rb.setVisibility(View.VISIBLE);
|
radioButtonFrame.setVisibility(View.VISIBLE);
|
||||||
} else {
|
} else {
|
||||||
rb.setVisibility(View.GONE);
|
radioButtonFrame.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,11 +20,14 @@ import static com.android.settings.network.MobileNetworkListFragment.collectAirp
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.settings.SettingsEnums;
|
import android.app.settings.SettingsEnums;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.UserManager;
|
import android.os.UserManager;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
|
import android.telephony.CarrierConfigManager;
|
||||||
import android.telephony.SubscriptionInfo;
|
import android.telephony.SubscriptionInfo;
|
||||||
import android.telephony.SubscriptionManager;
|
import android.telephony.SubscriptionManager;
|
||||||
import android.telephony.TelephonyManager;
|
import android.telephony.TelephonyManager;
|
||||||
@@ -106,6 +109,15 @@ public class MobileNetworkSettings extends AbstractMobileNetworkSettings impleme
|
|||||||
private SubscriptionInfoEntity mSubscriptionInfoEntity;
|
private SubscriptionInfoEntity mSubscriptionInfoEntity;
|
||||||
private MobileNetworkInfoEntity mMobileNetworkInfoEntity;
|
private MobileNetworkInfoEntity mMobileNetworkInfoEntity;
|
||||||
|
|
||||||
|
private BroadcastReceiver mBrocastReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
if (intent.getAction().equals(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) {
|
||||||
|
redrawPreferenceControllers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public MobileNetworkSettings() {
|
public MobileNetworkSettings() {
|
||||||
super(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS);
|
super(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS);
|
||||||
}
|
}
|
||||||
@@ -351,6 +363,10 @@ public class MobileNetworkSettings extends AbstractMobileNetworkSettings impleme
|
|||||||
mMobileNetworkRepository.updateEntity();
|
mMobileNetworkRepository.updateEntity();
|
||||||
// TODO: remove log after fixing b/182326102
|
// TODO: remove log after fixing b/182326102
|
||||||
Log.d(LOG_TAG, "onResume() subId=" + mSubId);
|
Log.d(LOG_TAG, "onResume() subId=" + mSubId);
|
||||||
|
|
||||||
|
IntentFilter intentFilter = new IntentFilter();
|
||||||
|
intentFilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
|
||||||
|
getContext().registerReceiver(mBrocastReceiver, intentFilter, Context.RECEIVER_EXPORTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onSubscriptionDetailChanged() {
|
private void onSubscriptionDetailChanged() {
|
||||||
@@ -370,6 +386,7 @@ public class MobileNetworkSettings extends AbstractMobileNetworkSettings impleme
|
|||||||
@Override
|
@Override
|
||||||
public void onPause() {
|
public void onPause() {
|
||||||
mMobileNetworkRepository.removeRegister(this);
|
mMobileNetworkRepository.removeRegister(this);
|
||||||
|
getContext().unregisterReceiver(mBrocastReceiver);
|
||||||
super.onPause();
|
super.onPause();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,10 +16,14 @@
|
|||||||
|
|
||||||
package com.android.settings.notification.modes;
|
package com.android.settings.notification.modes;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
import android.app.AutomaticZenRule;
|
import android.app.AutomaticZenRule;
|
||||||
import android.app.settings.SettingsEnums;
|
import android.app.settings.SettingsEnums;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
|
||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
import com.android.settingslib.applications.ApplicationsState;
|
import com.android.settingslib.applications.ApplicationsState;
|
||||||
@@ -31,6 +35,9 @@ import java.util.List;
|
|||||||
|
|
||||||
public class ZenModeFragment extends ZenModeFragmentBase {
|
public class ZenModeFragment extends ZenModeFragmentBase {
|
||||||
|
|
||||||
|
// for mode deletion menu
|
||||||
|
private static final int DELETE_MODE = 1;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int getPreferenceScreenResId() {
|
protected int getPreferenceScreenResId() {
|
||||||
return R.xml.modes_rule_settings;
|
return R.xml.modes_rule_settings;
|
||||||
@@ -77,4 +84,43 @@ public class ZenModeFragment extends ZenModeFragmentBase {
|
|||||||
// TODO: b/332937635 - make this the correct metrics category
|
// TODO: b/332937635 - make this the correct metrics category
|
||||||
return SettingsEnums.NOTIFICATION_ZEN_MODE_AUTOMATION;
|
return SettingsEnums.NOTIFICATION_ZEN_MODE_AUTOMATION;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
|
menu.add(Menu.NONE, DELETE_MODE, Menu.NONE, R.string.zen_mode_menu_delete_mode);
|
||||||
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean onOptionsItemSelected(MenuItem item, ZenMode zenMode) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case DELETE_MODE:
|
||||||
|
new AlertDialog.Builder(mContext)
|
||||||
|
.setTitle(mContext.getString(R.string.zen_mode_delete_mode_confirmation,
|
||||||
|
zenMode.getRule().getName()))
|
||||||
|
.setPositiveButton(R.string.zen_mode_schedule_delete,
|
||||||
|
(dialog, which) -> {
|
||||||
|
// start finishing before calling removeMode() so that we don't
|
||||||
|
// try to update this activity with a nonexistent mode when the
|
||||||
|
// zen mode config is updated
|
||||||
|
finish();
|
||||||
|
mBackend.removeMode(zenMode);
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.cancel, null)
|
||||||
|
.show();
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void updateZenModeState() {
|
||||||
|
// Because this fragment may be asked to finish by the delete menu but not be done doing
|
||||||
|
// so yet, ignore any attempts to update info in that case.
|
||||||
|
if (getActivity() != null && getActivity().isFinishing()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.updateZenModeState();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import android.app.AutomaticZenRule;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.view.MenuItem;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -114,6 +115,18 @@ abstract class ZenModeFragmentBase extends ZenModesFragmentBase {
|
|||||||
updateControllers();
|
updateControllers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
if (mZenMode != null) {
|
||||||
|
return onOptionsItemSelected(item, mZenMode);
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean onOptionsItemSelected(MenuItem item, @NonNull ZenMode zenMode) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private void updateControllers() {
|
private void updateControllers() {
|
||||||
if (getPreferenceControllers() == null || mZenMode == null) {
|
if (getPreferenceControllers() == null || mZenMode == null) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ class ZenModesListItemPreference extends RestrictedPreference {
|
|||||||
mZenMode.getRule().getTriggerDescription());
|
mZenMode.getRule().getTriggerDescription());
|
||||||
case ENABLED -> mZenMode.getRule().getTriggerDescription();
|
case ENABLED -> mZenMode.getRule().getTriggerDescription();
|
||||||
case DISABLED_BY_USER -> mContext.getString(R.string.zen_mode_disabled_by_user);
|
case DISABLED_BY_USER -> mContext.getString(R.string.zen_mode_disabled_by_user);
|
||||||
case DISABLED_BY_OTHER -> mContext.getString(R.string.zen_mode_disabled_tap_to_set_up);
|
case DISABLED_BY_OTHER -> mContext.getString(R.string.zen_mode_disabled_needs_setup);
|
||||||
};
|
};
|
||||||
setSummary(statusText);
|
setSummary(statusText);
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
package com.android.settings.system;
|
package com.android.settings.system;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
|
import android.annotation.RequiresPermission;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageInfo;
|
import android.content.pm.PackageInfo;
|
||||||
@@ -37,8 +38,8 @@ public class FactoryResetPreferenceController extends BasePreferenceController {
|
|||||||
|
|
||||||
private static final String TAG = "FactoryResetPreference";
|
private static final String TAG = "FactoryResetPreference";
|
||||||
|
|
||||||
@VisibleForTesting
|
@RequiresPermission(Manifest.permission.PREPARE_FACTORY_RESET)
|
||||||
static final String ACTION_PREPARE_FACTORY_RESET =
|
public static final String ACTION_PREPARE_FACTORY_RESET =
|
||||||
"com.android.settings.ACTION_PREPARE_FACTORY_RESET";
|
"com.android.settings.ACTION_PREPARE_FACTORY_RESET";
|
||||||
|
|
||||||
private final UserManager mUm;
|
private final UserManager mUm;
|
||||||
|
|||||||
@@ -42,9 +42,18 @@ public class AddUserWhenLockedPreferenceController extends TogglePreferenceContr
|
|||||||
if (!isAvailable()) {
|
if (!isAvailable()) {
|
||||||
restrictedSwitchPreference.setVisible(false);
|
restrictedSwitchPreference.setVisible(false);
|
||||||
} else {
|
} else {
|
||||||
restrictedSwitchPreference.setDisabledByAdmin(
|
if (android.multiuser.Flags.newMultiuserSettingsUx()) {
|
||||||
mUserCaps.disallowAddUser() ? mUserCaps.getEnforcedAdmin() : null);
|
restrictedSwitchPreference.setVisible(true);
|
||||||
restrictedSwitchPreference.setVisible(mUserCaps.mUserSwitcherEnabled);
|
if (mUserCaps.mDisallowAddUserSetByAdmin) {
|
||||||
|
restrictedSwitchPreference.setDisabledByAdmin(mUserCaps.mEnforcedAdmin);
|
||||||
|
} else if (mUserCaps.mDisallowAddUser) {
|
||||||
|
restrictedSwitchPreference.setVisible(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
restrictedSwitchPreference.setDisabledByAdmin(
|
||||||
|
mUserCaps.disallowAddUser() ? mUserCaps.getEnforcedAdmin() : null);
|
||||||
|
restrictedSwitchPreference.setVisible(mUserCaps.mUserSwitcherEnabled);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,6 +61,8 @@ public class AddUserWhenLockedPreferenceController extends TogglePreferenceContr
|
|||||||
public int getAvailabilityStatus() {
|
public int getAvailabilityStatus() {
|
||||||
if (!mUserCaps.isAdmin()) {
|
if (!mUserCaps.isAdmin()) {
|
||||||
return DISABLED_FOR_USER;
|
return DISABLED_FOR_USER;
|
||||||
|
} else if (android.multiuser.Flags.newMultiuserSettingsUx()) {
|
||||||
|
return AVAILABLE;
|
||||||
} else if (mUserCaps.disallowAddUser() || mUserCaps.disallowAddUserSetByAdmin()) {
|
} else if (mUserCaps.disallowAddUser() || mUserCaps.disallowAddUserSetByAdmin()) {
|
||||||
return DISABLED_FOR_USER;
|
return DISABLED_FOR_USER;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -19,12 +19,16 @@ package com.android.settings.users;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.UserHandle;
|
||||||
import android.os.UserManager;
|
import android.os.UserManager;
|
||||||
|
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
|
|
||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
import com.android.settings.core.TogglePreferenceController;
|
import com.android.settings.core.TogglePreferenceController;
|
||||||
|
import com.android.settingslib.RestrictedLockUtils;
|
||||||
|
import com.android.settingslib.RestrictedLockUtilsInternal;
|
||||||
|
import com.android.settingslib.RestrictedSwitchPreference;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Controls the preference on the user settings screen which determines whether the guest user
|
* Controls the preference on the user settings screen which determines whether the guest user
|
||||||
@@ -43,10 +47,21 @@ public class GuestTelephonyPreferenceController extends TogglePreferenceControll
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getAvailabilityStatus() {
|
public int getAvailabilityStatus() {
|
||||||
if (!mUserCaps.isAdmin() || !mUserCaps.mCanAddGuest) {
|
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
|
||||||
|
|| UserManager.isHeadlessSystemUserMode()) {
|
||||||
return DISABLED_FOR_USER;
|
return DISABLED_FOR_USER;
|
||||||
|
}
|
||||||
|
if (android.multiuser.Flags.newMultiuserSettingsUx()) {
|
||||||
|
if (!mUserCaps.isAdmin()) {
|
||||||
|
return DISABLED_FOR_USER;
|
||||||
|
}
|
||||||
|
return AVAILABLE;
|
||||||
} else {
|
} else {
|
||||||
return mUserCaps.mUserSwitcherEnabled ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
|
if (!mUserCaps.isAdmin() || !mUserCaps.mCanAddGuest) {
|
||||||
|
return DISABLED_FOR_USER;
|
||||||
|
} else {
|
||||||
|
return mUserCaps.mUserSwitcherEnabled ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,8 +89,31 @@ public class GuestTelephonyPreferenceController extends TogglePreferenceControll
|
|||||||
public void updateState(Preference preference) {
|
public void updateState(Preference preference) {
|
||||||
super.updateState(preference);
|
super.updateState(preference);
|
||||||
mUserCaps.updateAddUserCapabilities(mContext);
|
mUserCaps.updateAddUserCapabilities(mContext);
|
||||||
preference.setVisible(isAvailable() && mUserCaps.mUserSwitcherEnabled
|
final RestrictedSwitchPreference restrictedSwitchPreference =
|
||||||
&& mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
|
(RestrictedSwitchPreference) preference;
|
||||||
&& !UserManager.isHeadlessSystemUserMode());
|
restrictedSwitchPreference.setChecked(isChecked());
|
||||||
|
if (!isAvailable()) {
|
||||||
|
restrictedSwitchPreference.setVisible(false);
|
||||||
|
} else {
|
||||||
|
if (android.multiuser.Flags.newMultiuserSettingsUx()) {
|
||||||
|
restrictedSwitchPreference.setVisible(true);
|
||||||
|
final RestrictedLockUtils.EnforcedAdmin disallowRemoveUserAdmin =
|
||||||
|
RestrictedLockUtilsInternal.checkIfRestrictionEnforced(mContext,
|
||||||
|
UserManager.DISALLOW_REMOVE_USER, UserHandle.myUserId());
|
||||||
|
if (disallowRemoveUserAdmin != null) {
|
||||||
|
restrictedSwitchPreference.setDisabledByAdmin(disallowRemoveUserAdmin);
|
||||||
|
} else if (mUserCaps.mDisallowAddUserSetByAdmin) {
|
||||||
|
restrictedSwitchPreference.setDisabledByAdmin(mUserCaps.mEnforcedAdmin);
|
||||||
|
} else if (mUserCaps.mDisallowAddUser) {
|
||||||
|
// Adding user is restricted by system
|
||||||
|
restrictedSwitchPreference.setVisible(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
restrictedSwitchPreference.setDisabledByAdmin(
|
||||||
|
mUserCaps.disallowAddUser() ? mUserCaps.getEnforcedAdmin() : null);
|
||||||
|
restrictedSwitchPreference.setVisible(mUserCaps.mUserSwitcherEnabled
|
||||||
|
&& isAvailable());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,11 +57,6 @@ public class MultiUserSwitchBarController implements SwitchWidgetController.OnSw
|
|||||||
mSwitchBar.setDisabledByAdmin(RestrictedLockUtilsInternal
|
mSwitchBar.setDisabledByAdmin(RestrictedLockUtilsInternal
|
||||||
.checkIfRestrictionEnforced(mContext, UserManager.DISALLOW_USER_SWITCH,
|
.checkIfRestrictionEnforced(mContext, UserManager.DISALLOW_USER_SWITCH,
|
||||||
UserHandle.myUserId()));
|
UserHandle.myUserId()));
|
||||||
|
|
||||||
} else if (mUserCapabilities.mDisallowAddUser) {
|
|
||||||
mSwitchBar.setDisabledByAdmin(RestrictedLockUtilsInternal
|
|
||||||
.checkIfRestrictionEnforced(mContext, UserManager.DISALLOW_ADD_USER,
|
|
||||||
UserHandle.myUserId()));
|
|
||||||
} else {
|
} else {
|
||||||
mSwitchBar.setEnabled(mUserCapabilities.mIsMain);
|
mSwitchBar.setEnabled(mUserCapabilities.mIsMain);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import android.content.DialogInterface;
|
|||||||
import android.content.pm.UserInfo;
|
import android.content.pm.UserInfo;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import android.os.UserHandle;
|
||||||
import android.os.UserManager;
|
import android.os.UserManager;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
@@ -33,6 +34,8 @@ import androidx.preference.Preference;
|
|||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
import com.android.settings.core.BasePreferenceController;
|
import com.android.settings.core.BasePreferenceController;
|
||||||
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
|
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
|
||||||
|
import com.android.settingslib.RestrictedLockUtils;
|
||||||
|
import com.android.settingslib.RestrictedLockUtilsInternal;
|
||||||
import com.android.settingslib.RestrictedSwitchPreference;
|
import com.android.settingslib.RestrictedSwitchPreference;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -70,9 +73,24 @@ public class RemoveGuestOnExitPreferenceController extends BasePreferenceControl
|
|||||||
if (!isAvailable()) {
|
if (!isAvailable()) {
|
||||||
restrictedSwitchPreference.setVisible(false);
|
restrictedSwitchPreference.setVisible(false);
|
||||||
} else {
|
} else {
|
||||||
restrictedSwitchPreference.setDisabledByAdmin(
|
if (android.multiuser.Flags.newMultiuserSettingsUx()) {
|
||||||
mUserCaps.disallowAddUser() ? mUserCaps.getEnforcedAdmin() : null);
|
restrictedSwitchPreference.setVisible(true);
|
||||||
restrictedSwitchPreference.setVisible(mUserCaps.mUserSwitcherEnabled);
|
final RestrictedLockUtils.EnforcedAdmin disallowRemoveUserAdmin =
|
||||||
|
RestrictedLockUtilsInternal.checkIfRestrictionEnforced(mContext,
|
||||||
|
UserManager.DISALLOW_REMOVE_USER, UserHandle.myUserId());
|
||||||
|
if (disallowRemoveUserAdmin != null) {
|
||||||
|
restrictedSwitchPreference.setDisabledByAdmin(disallowRemoveUserAdmin);
|
||||||
|
} else if (mUserCaps.mDisallowAddUserSetByAdmin) {
|
||||||
|
restrictedSwitchPreference.setDisabledByAdmin(mUserCaps.mEnforcedAdmin);
|
||||||
|
} else if (mUserCaps.mDisallowAddUser) {
|
||||||
|
// Adding user is restricted by system
|
||||||
|
restrictedSwitchPreference.setVisible(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
restrictedSwitchPreference.setDisabledByAdmin(
|
||||||
|
mUserCaps.disallowAddUser() ? mUserCaps.getEnforcedAdmin() : null);
|
||||||
|
restrictedSwitchPreference.setVisible(mUserCaps.mUserSwitcherEnabled);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,14 +100,24 @@ public class RemoveGuestOnExitPreferenceController extends BasePreferenceControl
|
|||||||
// then disable this controller
|
// then disable this controller
|
||||||
// also disable this controller for non-admin users
|
// also disable this controller for non-admin users
|
||||||
// also disable when config_guestUserAllowEphemeralStateChange is false
|
// also disable when config_guestUserAllowEphemeralStateChange is false
|
||||||
if (mUserManager.isGuestUserAlwaysEphemeral()
|
if (android.multiuser.Flags.newMultiuserSettingsUx()) {
|
||||||
|| !UserManager.isGuestUserAllowEphemeralStateChange()
|
if (mUserManager.isGuestUserAlwaysEphemeral()
|
||||||
|| !mUserCaps.isAdmin()
|
|| !UserManager.isGuestUserAllowEphemeralStateChange()
|
||||||
|| mUserCaps.disallowAddUser()
|
|| !mUserCaps.isAdmin()) {
|
||||||
|| mUserCaps.disallowAddUserSetByAdmin()) {
|
return DISABLED_FOR_USER;
|
||||||
return DISABLED_FOR_USER;
|
} else {
|
||||||
|
return AVAILABLE;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return mUserCaps.mUserSwitcherEnabled ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
|
if (mUserManager.isGuestUserAlwaysEphemeral()
|
||||||
|
|| !UserManager.isGuestUserAllowEphemeralStateChange()
|
||||||
|
|| !mUserCaps.isAdmin()
|
||||||
|
|| mUserCaps.disallowAddUser()
|
||||||
|
|| mUserCaps.disallowAddUserSetByAdmin()) {
|
||||||
|
return DISABLED_FOR_USER;
|
||||||
|
} else {
|
||||||
|
return mUserCaps.mUserSwitcherEnabled ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -126,7 +126,11 @@ public class UserDetailsSettings extends SettingsPreferenceFragment
|
|||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
mSwitchUserPref.setEnabled(canSwitchUserNow());
|
if (android.multiuser.Flags.newMultiuserSettingsUx()) {
|
||||||
|
mSwitchUserPref.setEnabled(canSwitchUserNow() && mUserCaps.mUserSwitcherEnabled);
|
||||||
|
} else {
|
||||||
|
mSwitchUserPref.setEnabled(canSwitchUserNow());
|
||||||
|
}
|
||||||
if (mUserInfo.isGuest() && mGuestUserAutoCreated) {
|
if (mUserInfo.isGuest() && mGuestUserAutoCreated) {
|
||||||
mRemoveUserPref.setEnabled((mUserInfo.flags & UserInfo.FLAG_INITIALIZED) != 0);
|
mRemoveUserPref.setEnabled((mUserInfo.flags & UserInfo.FLAG_INITIALIZED) != 0);
|
||||||
}
|
}
|
||||||
@@ -358,7 +362,12 @@ public class UserDetailsSettings extends SettingsPreferenceFragment
|
|||||||
mSwitchUserPref.setDisabledByAdmin(RestrictedLockUtilsInternal.getDeviceOwner(context));
|
mSwitchUserPref.setDisabledByAdmin(RestrictedLockUtilsInternal.getDeviceOwner(context));
|
||||||
} else {
|
} else {
|
||||||
mSwitchUserPref.setDisabledByAdmin(null);
|
mSwitchUserPref.setDisabledByAdmin(null);
|
||||||
mSwitchUserPref.setSelectable(true);
|
if (android.multiuser.Flags.newMultiuserSettingsUx()) {
|
||||||
|
mSwitchUserPref.setEnabled(mUserCaps.mUserSwitcherEnabled);
|
||||||
|
mSwitchUserPref.setSelectable(mUserCaps.mUserSwitcherEnabled);
|
||||||
|
} else {
|
||||||
|
mSwitchUserPref.setSelectable(true);
|
||||||
|
}
|
||||||
mSwitchUserPref.setOnPreferenceClickListener(this);
|
mSwitchUserPref.setOnPreferenceClickListener(this);
|
||||||
}
|
}
|
||||||
if (mUserInfo.isMain() || mUserInfo.isGuest() || !UserManager.isMultipleAdminEnabled()
|
if (mUserInfo.isMain() || mUserInfo.isGuest() || !UserManager.isMultipleAdminEnabled()
|
||||||
|
|||||||
@@ -463,7 +463,8 @@ public class UserSettings extends SettingsPreferenceFragment
|
|||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
int pos = 0;
|
int pos = 0;
|
||||||
if (!isCurrentUserAdmin() && canSwitchUserNow() && !isCurrentUserGuest()) {
|
if (!isCurrentUserAdmin() && (canSwitchUserNow() || Flags.newMultiuserSettingsUx())
|
||||||
|
&& !isCurrentUserGuest()) {
|
||||||
String nickname = mUserManager.getUserName();
|
String nickname = mUserManager.getUserName();
|
||||||
MenuItem removeThisUser = menu.add(0, MENU_REMOVE_USER, pos++,
|
MenuItem removeThisUser = menu.add(0, MENU_REMOVE_USER, pos++,
|
||||||
getResources().getString(R.string.user_remove_user_menu, nickname));
|
getResources().getString(R.string.user_remove_user_menu, nickname));
|
||||||
@@ -1198,15 +1199,23 @@ public class UserSettings extends SettingsPreferenceFragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<UserInfo> users;
|
List<UserInfo> users;
|
||||||
if (mUserCaps.mUserSwitcherEnabled) {
|
if (Flags.newMultiuserSettingsUx()) {
|
||||||
// Only users that can be switched to should show up here.
|
// Only users that can be switched to should show up here.
|
||||||
// e.g. Managed profiles appear under Accounts Settings instead
|
// e.g. Managed profiles appear under Accounts Settings instead
|
||||||
users = mUserManager.getAliveUsers().stream()
|
users = mUserManager.getAliveUsers().stream()
|
||||||
.filter(UserInfo::supportsSwitchToByUser)
|
.filter(UserInfo::supportsSwitchToByUser)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
} else {
|
} else {
|
||||||
// Only current user will be displayed in case of multi-user switch is disabled
|
if (mUserCaps.mUserSwitcherEnabled) {
|
||||||
users = List.of(mUserManager.getUserInfo(context.getUserId()));
|
// Only users that can be switched to should show up here.
|
||||||
|
// e.g. Managed profiles appear under Accounts Settings instead
|
||||||
|
users = mUserManager.getAliveUsers().stream()
|
||||||
|
.filter(UserInfo::supportsSwitchToByUser)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
} else {
|
||||||
|
// Only current user will be displayed in case of multi-user switch is disabled
|
||||||
|
users = List.of(mUserManager.getUserInfo(context.getUserId()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final ArrayList<Integer> missingIcons = new ArrayList<>();
|
final ArrayList<Integer> missingIcons = new ArrayList<>();
|
||||||
@@ -1257,7 +1266,10 @@ public class UserSettings extends SettingsPreferenceFragment
|
|||||||
pref.setSummary(R.string.user_summary_not_set_up);
|
pref.setSummary(R.string.user_summary_not_set_up);
|
||||||
// Disallow setting up user which results in user switching when the
|
// Disallow setting up user which results in user switching when the
|
||||||
// restriction is set.
|
// restriction is set.
|
||||||
pref.setEnabled(!mUserCaps.mDisallowSwitchUser && canSwitchUserNow());
|
// If newMultiuserSettingsUx flag is enabled, allow opening user details page
|
||||||
|
// since switch to user will be disabled
|
||||||
|
pref.setEnabled((!mUserCaps.mDisallowSwitchUser && canSwitchUserNow())
|
||||||
|
|| Flags.newMultiuserSettingsUx());
|
||||||
}
|
}
|
||||||
} else if (user.isRestricted()) {
|
} else if (user.isRestricted()) {
|
||||||
pref.setSummary(R.string.user_summary_restricted_profile);
|
pref.setSummary(R.string.user_summary_restricted_profile);
|
||||||
@@ -1417,16 +1429,22 @@ public class UserSettings extends SettingsPreferenceFragment
|
|||||||
getContext().getResources(), icon)));
|
getContext().getResources(), icon)));
|
||||||
pref.setKey(KEY_USER_GUEST);
|
pref.setKey(KEY_USER_GUEST);
|
||||||
pref.setOrder(Preference.DEFAULT_ORDER);
|
pref.setOrder(Preference.DEFAULT_ORDER);
|
||||||
if (mUserCaps.mDisallowSwitchUser) {
|
if (mUserCaps.mDisallowSwitchUser && !Flags.newMultiuserSettingsUx()) {
|
||||||
pref.setDisabledByAdmin(
|
pref.setDisabledByAdmin(
|
||||||
RestrictedLockUtilsInternal.getDeviceOwner(context));
|
RestrictedLockUtilsInternal.getDeviceOwner(context));
|
||||||
} else {
|
} else {
|
||||||
pref.setDisabledByAdmin(null);
|
pref.setDisabledByAdmin(null);
|
||||||
}
|
}
|
||||||
if (mUserCaps.mUserSwitcherEnabled) {
|
if (Flags.newMultiuserSettingsUx()) {
|
||||||
mGuestUserCategory.addPreference(pref);
|
mGuestUserCategory.addPreference(pref);
|
||||||
// guest user preference is shown hence also make guest category visible
|
// guest user preference is shown hence also make guest category visible
|
||||||
mGuestUserCategory.setVisible(true);
|
mGuestUserCategory.setVisible(true);
|
||||||
|
} else {
|
||||||
|
if (mUserCaps.mUserSwitcherEnabled) {
|
||||||
|
mGuestUserCategory.addPreference(pref);
|
||||||
|
// guest user preference is shown hence also make guest category visible
|
||||||
|
mGuestUserCategory.setVisible(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
isGuestAlreadyCreated = true;
|
isGuestAlreadyCreated = true;
|
||||||
}
|
}
|
||||||
@@ -1450,10 +1468,11 @@ public class UserSettings extends SettingsPreferenceFragment
|
|||||||
|
|
||||||
private boolean updateAddGuestPreference(Context context, boolean isGuestAlreadyCreated) {
|
private boolean updateAddGuestPreference(Context context, boolean isGuestAlreadyCreated) {
|
||||||
boolean isVisible = false;
|
boolean isVisible = false;
|
||||||
if (!isGuestAlreadyCreated && mUserCaps.mCanAddGuest
|
if (!isGuestAlreadyCreated && (mUserCaps.mCanAddGuest
|
||||||
|
|| (Flags.newMultiuserSettingsUx() && mUserCaps.mDisallowAddUser))
|
||||||
&& mUserManager.canAddMoreUsers(UserManager.USER_TYPE_FULL_GUEST)
|
&& mUserManager.canAddMoreUsers(UserManager.USER_TYPE_FULL_GUEST)
|
||||||
&& WizardManagerHelper.isDeviceProvisioned(context)
|
&& WizardManagerHelper.isDeviceProvisioned(context)
|
||||||
&& mUserCaps.mUserSwitcherEnabled) {
|
&& (mUserCaps.mUserSwitcherEnabled || Flags.newMultiuserSettingsUx())) {
|
||||||
Drawable icon = context.getDrawable(
|
Drawable icon = context.getDrawable(
|
||||||
com.android.settingslib.R.drawable.ic_account_circle);
|
com.android.settingslib.R.drawable.ic_account_circle);
|
||||||
mAddGuest.setIcon(centerAndTint(icon));
|
mAddGuest.setIcon(centerAndTint(icon));
|
||||||
@@ -1466,7 +1485,25 @@ public class UserSettings extends SettingsPreferenceFragment
|
|||||||
mAddGuest.setEnabled(false);
|
mAddGuest.setEnabled(false);
|
||||||
} else {
|
} else {
|
||||||
mAddGuest.setTitle(com.android.settingslib.R.string.guest_new_guest);
|
mAddGuest.setTitle(com.android.settingslib.R.string.guest_new_guest);
|
||||||
mAddGuest.setEnabled(canSwitchUserNow());
|
if (Flags.newMultiuserSettingsUx()
|
||||||
|
&& mUserCaps.mDisallowAddUserSetByAdmin) {
|
||||||
|
mAddGuest.setDisabledByAdmin(mUserCaps.mEnforcedAdmin);
|
||||||
|
} else if (Flags.newMultiuserSettingsUx() && mUserCaps.mDisallowAddUser) {
|
||||||
|
final List<UserManager.EnforcingUser> enforcingUsers =
|
||||||
|
mUserManager.getUserRestrictionSources(UserManager.DISALLOW_ADD_USER,
|
||||||
|
UserHandle.of(UserHandle.myUserId()));
|
||||||
|
if (!enforcingUsers.isEmpty()) {
|
||||||
|
final UserManager.EnforcingUser enforcingUser = enforcingUsers.get(0);
|
||||||
|
final int restrictionSource = enforcingUser.getUserRestrictionSource();
|
||||||
|
if (restrictionSource == UserManager.RESTRICTION_SOURCE_SYSTEM) {
|
||||||
|
mAddGuest.setEnabled(false);
|
||||||
|
} else {
|
||||||
|
mAddGuest.setVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mAddGuest.setEnabled(canSwitchUserNow() || Flags.newMultiuserSettingsUx());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
mAddGuest.setVisible(false);
|
mAddGuest.setVisible(false);
|
||||||
@@ -1494,16 +1531,18 @@ public class UserSettings extends SettingsPreferenceFragment
|
|||||||
|
|
||||||
private void updateAddUserCommon(Context context, RestrictedPreference addUser,
|
private void updateAddUserCommon(Context context, RestrictedPreference addUser,
|
||||||
boolean canAddRestrictedProfile) {
|
boolean canAddRestrictedProfile) {
|
||||||
if ((mUserCaps.mCanAddUser && !mUserCaps.mDisallowAddUserSetByAdmin)
|
if ((mUserCaps.mCanAddUser
|
||||||
|
&& !(mUserCaps.mDisallowAddUserSetByAdmin && Flags.newMultiuserSettingsUx()))
|
||||||
&& WizardManagerHelper.isDeviceProvisioned(context)
|
&& WizardManagerHelper.isDeviceProvisioned(context)
|
||||||
&& mUserCaps.mUserSwitcherEnabled) {
|
&& (mUserCaps.mUserSwitcherEnabled || Flags.newMultiuserSettingsUx())) {
|
||||||
addUser.setVisible(true);
|
addUser.setVisible(true);
|
||||||
addUser.setSelectable(true);
|
addUser.setSelectable(true);
|
||||||
final boolean canAddMoreUsers =
|
final boolean canAddMoreUsers =
|
||||||
mUserManager.canAddMoreUsers(UserManager.USER_TYPE_FULL_SECONDARY)
|
mUserManager.canAddMoreUsers(UserManager.USER_TYPE_FULL_SECONDARY)
|
||||||
|| (canAddRestrictedProfile
|
|| (canAddRestrictedProfile
|
||||||
&& mUserManager.canAddMoreUsers(UserManager.USER_TYPE_FULL_RESTRICTED));
|
&& mUserManager.canAddMoreUsers(UserManager.USER_TYPE_FULL_RESTRICTED));
|
||||||
addUser.setEnabled(canAddMoreUsers && !mAddingUser && canSwitchUserNow());
|
addUser.setEnabled(canAddMoreUsers && !mAddingUser
|
||||||
|
&& (canSwitchUserNow() || Flags.newMultiuserSettingsUx()));
|
||||||
|
|
||||||
if (!canAddMoreUsers) {
|
if (!canAddMoreUsers) {
|
||||||
addUser.setSummary(getString(R.string.user_add_max_count));
|
addUser.setSummary(getString(R.string.user_add_max_count));
|
||||||
@@ -1514,6 +1553,23 @@ public class UserSettings extends SettingsPreferenceFragment
|
|||||||
addUser.setDisabledByAdmin(
|
addUser.setDisabledByAdmin(
|
||||||
mUserCaps.mDisallowAddUser ? mUserCaps.mEnforcedAdmin : null);
|
mUserCaps.mDisallowAddUser ? mUserCaps.mEnforcedAdmin : null);
|
||||||
}
|
}
|
||||||
|
} else if (Flags.newMultiuserSettingsUx() && mUserCaps.mDisallowAddUserSetByAdmin) {
|
||||||
|
addUser.setVisible(true);
|
||||||
|
addUser.setDisabledByAdmin(mUserCaps.mEnforcedAdmin);
|
||||||
|
} else if (Flags.newMultiuserSettingsUx() && mUserCaps.mDisallowAddUser) {
|
||||||
|
final List<UserManager.EnforcingUser> enforcingUsers =
|
||||||
|
mUserManager.getUserRestrictionSources(UserManager.DISALLOW_ADD_USER,
|
||||||
|
UserHandle.of(UserHandle.myUserId()));
|
||||||
|
if (!enforcingUsers.isEmpty()) {
|
||||||
|
final UserManager.EnforcingUser enforcingUser = enforcingUsers.get(0);
|
||||||
|
final int restrictionSource = enforcingUser.getUserRestrictionSource();
|
||||||
|
if (restrictionSource == UserManager.RESTRICTION_SOURCE_SYSTEM) {
|
||||||
|
addUser.setVisible(true);
|
||||||
|
addUser.setEnabled(false);
|
||||||
|
} else {
|
||||||
|
addUser.setVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
addUser.setVisible(false);
|
addUser.setVisible(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ android_test {
|
|||||||
],
|
],
|
||||||
platform_apis: true,
|
platform_apis: true,
|
||||||
certificate: "platform",
|
certificate: "platform",
|
||||||
test_suites: ["device-tests"],
|
|
||||||
libs: [
|
libs: [
|
||||||
"android.test.runner",
|
"android.test.runner",
|
||||||
"android.test.base",
|
"android.test.base",
|
||||||
@@ -57,6 +56,6 @@ java_test_host {
|
|||||||
data: [
|
data: [
|
||||||
":test_16kb_app",
|
":test_16kb_app",
|
||||||
],
|
],
|
||||||
test_suites: ["device-tests"],
|
test_suites: ["general-tests"],
|
||||||
test_config: "AndroidTest.xml",
|
test_config: "AndroidTest.xml",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ package com.android.settings.connecteddevice;
|
|||||||
|
|
||||||
import static com.android.settings.core.BasePreferenceController.AVAILABLE_UNSEARCHABLE;
|
import static com.android.settings.core.BasePreferenceController.AVAILABLE_UNSEARCHABLE;
|
||||||
import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE;
|
import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE;
|
||||||
|
import static com.android.settings.flags.Flags.FLAG_RESOLUTION_AND_ENABLE_CONNECTED_DISPLAY_SETTING;
|
||||||
|
import static com.android.settings.flags.Flags.FLAG_ROTATION_CONNECTED_DISPLAY_SETTING;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
@@ -30,6 +32,7 @@ import static org.mockito.Mockito.when;
|
|||||||
import android.bluetooth.BluetoothDevice;
|
import android.bluetooth.BluetoothDevice;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.res.Resources;
|
||||||
import android.hardware.input.InputManager;
|
import android.hardware.input.InputManager;
|
||||||
import android.platform.test.annotations.EnableFlags;
|
import android.platform.test.annotations.EnableFlags;
|
||||||
import android.platform.test.flag.junit.SetFlagsRule;
|
import android.platform.test.flag.junit.SetFlagsRule;
|
||||||
@@ -40,13 +43,16 @@ import androidx.preference.Preference;
|
|||||||
import androidx.preference.PreferenceGroup;
|
import androidx.preference.PreferenceGroup;
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
import androidx.preference.PreferenceScreen;
|
import androidx.preference.PreferenceScreen;
|
||||||
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
|
||||||
import com.android.settings.bluetooth.ConnectedBluetoothDeviceUpdater;
|
import com.android.settings.bluetooth.ConnectedBluetoothDeviceUpdater;
|
||||||
import com.android.settings.bluetooth.Utils;
|
import com.android.settings.bluetooth.Utils;
|
||||||
|
import com.android.settings.connecteddevice.display.ExternalDisplayUpdater;
|
||||||
import com.android.settings.connecteddevice.dock.DockUpdater;
|
import com.android.settings.connecteddevice.dock.DockUpdater;
|
||||||
import com.android.settings.connecteddevice.stylus.StylusDeviceUpdater;
|
import com.android.settings.connecteddevice.stylus.StylusDeviceUpdater;
|
||||||
import com.android.settings.connecteddevice.usb.ConnectedUsbDeviceUpdater;
|
import com.android.settings.connecteddevice.usb.ConnectedUsbDeviceUpdater;
|
||||||
import com.android.settings.dashboard.DashboardFragment;
|
import com.android.settings.dashboard.DashboardFragment;
|
||||||
|
import com.android.settings.flags.FakeFeatureFlagsImpl;
|
||||||
import com.android.settings.flags.Flags;
|
import com.android.settings.flags.Flags;
|
||||||
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
|
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
|
||||||
import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
|
import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
|
||||||
@@ -65,7 +71,6 @@ import org.mockito.Answers;
|
|||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.MockitoAnnotations;
|
||||||
import org.robolectric.RobolectricTestRunner;
|
import org.robolectric.RobolectricTestRunner;
|
||||||
import org.robolectric.RuntimeEnvironment;
|
|
||||||
import org.robolectric.Shadows;
|
import org.robolectric.Shadows;
|
||||||
import org.robolectric.annotation.Config;
|
import org.robolectric.annotation.Config;
|
||||||
import org.robolectric.shadows.ShadowApplicationPackageManager;
|
import org.robolectric.shadows.ShadowApplicationPackageManager;
|
||||||
@@ -84,6 +89,8 @@ public class ConnectedDeviceGroupControllerTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private DashboardFragment mDashboardFragment;
|
private DashboardFragment mDashboardFragment;
|
||||||
@Mock
|
@Mock
|
||||||
|
private ExternalDisplayUpdater mExternalDisplayUpdater;
|
||||||
|
@Mock
|
||||||
private ConnectedBluetoothDeviceUpdater mConnectedBluetoothDeviceUpdater;
|
private ConnectedBluetoothDeviceUpdater mConnectedBluetoothDeviceUpdater;
|
||||||
@Mock
|
@Mock
|
||||||
private ConnectedUsbDeviceUpdater mConnectedUsbDeviceUpdater;
|
private ConnectedUsbDeviceUpdater mConnectedUsbDeviceUpdater;
|
||||||
@@ -105,6 +112,9 @@ public class ConnectedDeviceGroupControllerTest {
|
|||||||
private CachedBluetoothDevice mCachedDevice;
|
private CachedBluetoothDevice mCachedDevice;
|
||||||
@Mock
|
@Mock
|
||||||
private BluetoothDevice mDevice;
|
private BluetoothDevice mDevice;
|
||||||
|
@Mock
|
||||||
|
private Resources mResources;
|
||||||
|
private final FakeFeatureFlagsImpl mFakeFeatureFlags = new FakeFeatureFlagsImpl();
|
||||||
|
|
||||||
private ShadowApplicationPackageManager mPackageManager;
|
private ShadowApplicationPackageManager mPackageManager;
|
||||||
private PreferenceGroup mPreferenceGroup;
|
private PreferenceGroup mPreferenceGroup;
|
||||||
@@ -118,8 +128,10 @@ public class ConnectedDeviceGroupControllerTest {
|
|||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
|
mFakeFeatureFlags.setFlag(FLAG_ROTATION_CONNECTED_DISPLAY_SETTING, true);
|
||||||
|
mFakeFeatureFlags.setFlag(FLAG_RESOLUTION_AND_ENABLE_CONNECTED_DISPLAY_SETTING, true);
|
||||||
|
|
||||||
mContext = spy(RuntimeEnvironment.application);
|
mContext = spy(ApplicationProvider.getApplicationContext());
|
||||||
mPreference = new Preference(mContext);
|
mPreference = new Preference(mContext);
|
||||||
mPreference.setKey(PREFERENCE_KEY_1);
|
mPreference.setKey(PREFERENCE_KEY_1);
|
||||||
mPackageManager = (ShadowApplicationPackageManager) Shadows.shadowOf(
|
mPackageManager = (ShadowApplicationPackageManager) Shadows.shadowOf(
|
||||||
@@ -129,15 +141,19 @@ public class ConnectedDeviceGroupControllerTest {
|
|||||||
doReturn(mContext).when(mDashboardFragment).getContext();
|
doReturn(mContext).when(mDashboardFragment).getContext();
|
||||||
mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, true);
|
mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, true);
|
||||||
when(mContext.getSystemService(InputManager.class)).thenReturn(mInputManager);
|
when(mContext.getSystemService(InputManager.class)).thenReturn(mInputManager);
|
||||||
|
when(mContext.getResources()).thenReturn(mResources);
|
||||||
when(mInputManager.getInputDeviceIds()).thenReturn(new int[]{});
|
when(mInputManager.getInputDeviceIds()).thenReturn(new int[]{});
|
||||||
|
|
||||||
ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager;
|
ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager;
|
||||||
mLocalBluetoothManager = Utils.getLocalBtManager(mContext);
|
mLocalBluetoothManager = Utils.getLocalBtManager(mContext);
|
||||||
when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager);
|
when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager);
|
||||||
|
|
||||||
mConnectedDeviceGroupController = new ConnectedDeviceGroupController(mContext);
|
mConnectedDeviceGroupController = spy(new ConnectedDeviceGroupController(mContext));
|
||||||
mConnectedDeviceGroupController.init(mConnectedBluetoothDeviceUpdater,
|
when(mConnectedDeviceGroupController.getFeatureFlags()).thenReturn(mFakeFeatureFlags);
|
||||||
mConnectedUsbDeviceUpdater, mConnectedDockUpdater, mStylusDeviceUpdater);
|
|
||||||
|
mConnectedDeviceGroupController.init(mExternalDisplayUpdater,
|
||||||
|
mConnectedBluetoothDeviceUpdater, mConnectedUsbDeviceUpdater, mConnectedDockUpdater,
|
||||||
|
mStylusDeviceUpdater);
|
||||||
mConnectedDeviceGroupController.mPreferenceGroup = mPreferenceGroup;
|
mConnectedDeviceGroupController.mPreferenceGroup = mPreferenceGroup;
|
||||||
|
|
||||||
when(mCachedDevice.getName()).thenReturn(DEVICE_NAME);
|
when(mCachedDevice.getName()).thenReturn(DEVICE_NAME);
|
||||||
@@ -147,6 +163,7 @@ public class ConnectedDeviceGroupControllerTest {
|
|||||||
|
|
||||||
FeatureFlagUtils.setEnabled(mContext, FeatureFlagUtils.SETTINGS_SHOW_STYLUS_PREFERENCES,
|
FeatureFlagUtils.setEnabled(mContext, FeatureFlagUtils.SETTINGS_SHOW_STYLUS_PREFERENCES,
|
||||||
true);
|
true);
|
||||||
|
when(mPreferenceScreen.getContext()).thenReturn(mContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -193,6 +210,7 @@ public class ConnectedDeviceGroupControllerTest {
|
|||||||
// register the callback in onStart()
|
// register the callback in onStart()
|
||||||
mConnectedDeviceGroupController.onStart();
|
mConnectedDeviceGroupController.onStart();
|
||||||
|
|
||||||
|
verify(mExternalDisplayUpdater).registerCallback();
|
||||||
verify(mConnectedBluetoothDeviceUpdater).registerCallback();
|
verify(mConnectedBluetoothDeviceUpdater).registerCallback();
|
||||||
verify(mConnectedUsbDeviceUpdater).registerCallback();
|
verify(mConnectedUsbDeviceUpdater).registerCallback();
|
||||||
verify(mConnectedDockUpdater).registerCallback();
|
verify(mConnectedDockUpdater).registerCallback();
|
||||||
@@ -204,6 +222,7 @@ public class ConnectedDeviceGroupControllerTest {
|
|||||||
public void onStop_shouldUnregisterUpdaters() {
|
public void onStop_shouldUnregisterUpdaters() {
|
||||||
// unregister the callback in onStop()
|
// unregister the callback in onStop()
|
||||||
mConnectedDeviceGroupController.onStop();
|
mConnectedDeviceGroupController.onStop();
|
||||||
|
verify(mExternalDisplayUpdater).unregisterCallback();
|
||||||
verify(mConnectedBluetoothDeviceUpdater).unregisterCallback();
|
verify(mConnectedBluetoothDeviceUpdater).unregisterCallback();
|
||||||
verify(mConnectedUsbDeviceUpdater).unregisterCallback();
|
verify(mConnectedUsbDeviceUpdater).unregisterCallback();
|
||||||
verify(mConnectedDockUpdater).unregisterCallback();
|
verify(mConnectedDockUpdater).unregisterCallback();
|
||||||
@@ -212,22 +231,36 @@ public class ConnectedDeviceGroupControllerTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getAvailabilityStatus_noBluetoothUsbDockFeature_returnUnSupported() {
|
public void getAvailabilityStatus_noBluetoothUsbDockFeature_returnUnSupported() {
|
||||||
|
mFakeFeatureFlags.setFlag(FLAG_ROTATION_CONNECTED_DISPLAY_SETTING, false);
|
||||||
|
mFakeFeatureFlags.setFlag(FLAG_RESOLUTION_AND_ENABLE_CONNECTED_DISPLAY_SETTING, false);
|
||||||
mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, false);
|
mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, false);
|
||||||
mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_ACCESSORY, false);
|
mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_ACCESSORY, false);
|
||||||
mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_HOST, false);
|
mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_HOST, false);
|
||||||
mConnectedDeviceGroupController.init(mConnectedBluetoothDeviceUpdater,
|
mConnectedDeviceGroupController.init(null, mConnectedBluetoothDeviceUpdater,
|
||||||
mConnectedUsbDeviceUpdater, null, null);
|
mConnectedUsbDeviceUpdater, null, null);
|
||||||
|
|
||||||
assertThat(mConnectedDeviceGroupController.getAvailabilityStatus()).isEqualTo(
|
assertThat(mConnectedDeviceGroupController.getAvailabilityStatus()).isEqualTo(
|
||||||
UNSUPPORTED_ON_DEVICE);
|
UNSUPPORTED_ON_DEVICE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getAvailabilityStatus_connectedDisplay_returnSupported() {
|
||||||
|
mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, false);
|
||||||
|
mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_ACCESSORY, false);
|
||||||
|
mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_HOST, false);
|
||||||
|
mConnectedDeviceGroupController.init(null, mConnectedBluetoothDeviceUpdater,
|
||||||
|
mConnectedUsbDeviceUpdater, null, null);
|
||||||
|
|
||||||
|
assertThat(mConnectedDeviceGroupController.getAvailabilityStatus()).isEqualTo(
|
||||||
|
AVAILABLE_UNSEARCHABLE);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getAvailabilityStatus_BluetoothFeature_returnSupported() {
|
public void getAvailabilityStatus_BluetoothFeature_returnSupported() {
|
||||||
mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, true);
|
mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, true);
|
||||||
mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_ACCESSORY, false);
|
mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_ACCESSORY, false);
|
||||||
mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_HOST, false);
|
mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_HOST, false);
|
||||||
mConnectedDeviceGroupController.init(mConnectedBluetoothDeviceUpdater,
|
mConnectedDeviceGroupController.init(null, mConnectedBluetoothDeviceUpdater,
|
||||||
mConnectedUsbDeviceUpdater, null, null);
|
mConnectedUsbDeviceUpdater, null, null);
|
||||||
|
|
||||||
assertThat(mConnectedDeviceGroupController.getAvailabilityStatus()).isEqualTo(
|
assertThat(mConnectedDeviceGroupController.getAvailabilityStatus()).isEqualTo(
|
||||||
@@ -239,7 +272,7 @@ public class ConnectedDeviceGroupControllerTest {
|
|||||||
mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, false);
|
mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, false);
|
||||||
mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_ACCESSORY, false);
|
mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_ACCESSORY, false);
|
||||||
mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_HOST, true);
|
mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_HOST, true);
|
||||||
mConnectedDeviceGroupController.init(mConnectedBluetoothDeviceUpdater,
|
mConnectedDeviceGroupController.init(null, mConnectedBluetoothDeviceUpdater,
|
||||||
mConnectedUsbDeviceUpdater, null, null);
|
mConnectedUsbDeviceUpdater, null, null);
|
||||||
|
|
||||||
assertThat(mConnectedDeviceGroupController.getAvailabilityStatus()).isEqualTo(
|
assertThat(mConnectedDeviceGroupController.getAvailabilityStatus()).isEqualTo(
|
||||||
@@ -251,7 +284,7 @@ public class ConnectedDeviceGroupControllerTest {
|
|||||||
mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, false);
|
mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, false);
|
||||||
mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_ACCESSORY, false);
|
mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_ACCESSORY, false);
|
||||||
mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_HOST, false);
|
mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_HOST, false);
|
||||||
mConnectedDeviceGroupController.init(mConnectedBluetoothDeviceUpdater,
|
mConnectedDeviceGroupController.init(null, mConnectedBluetoothDeviceUpdater,
|
||||||
mConnectedUsbDeviceUpdater, mConnectedDockUpdater, null);
|
mConnectedUsbDeviceUpdater, mConnectedDockUpdater, null);
|
||||||
|
|
||||||
assertThat(mConnectedDeviceGroupController.getAvailabilityStatus()).isEqualTo(
|
assertThat(mConnectedDeviceGroupController.getAvailabilityStatus()).isEqualTo(
|
||||||
@@ -261,6 +294,8 @@ public class ConnectedDeviceGroupControllerTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getAvailabilityStatus_noUsiStylusFeature_returnUnSupported() {
|
public void getAvailabilityStatus_noUsiStylusFeature_returnUnSupported() {
|
||||||
|
mFakeFeatureFlags.setFlag(FLAG_ROTATION_CONNECTED_DISPLAY_SETTING, false);
|
||||||
|
mFakeFeatureFlags.setFlag(FLAG_RESOLUTION_AND_ENABLE_CONNECTED_DISPLAY_SETTING, false);
|
||||||
mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, false);
|
mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, false);
|
||||||
mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_ACCESSORY, false);
|
mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_ACCESSORY, false);
|
||||||
mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_HOST, false);
|
mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_HOST, false);
|
||||||
@@ -268,7 +303,7 @@ public class ConnectedDeviceGroupControllerTest {
|
|||||||
when(mInputManager.getInputDevice(0)).thenReturn(new InputDevice.Builder().setSources(
|
when(mInputManager.getInputDevice(0)).thenReturn(new InputDevice.Builder().setSources(
|
||||||
InputDevice.SOURCE_DPAD).setExternal(false).build());
|
InputDevice.SOURCE_DPAD).setExternal(false).build());
|
||||||
|
|
||||||
mConnectedDeviceGroupController.init(mConnectedBluetoothDeviceUpdater,
|
mConnectedDeviceGroupController.init(null, mConnectedBluetoothDeviceUpdater,
|
||||||
mConnectedUsbDeviceUpdater, null, mStylusDeviceUpdater);
|
mConnectedUsbDeviceUpdater, null, mStylusDeviceUpdater);
|
||||||
|
|
||||||
assertThat(mConnectedDeviceGroupController.getAvailabilityStatus()).isEqualTo(
|
assertThat(mConnectedDeviceGroupController.getAvailabilityStatus()).isEqualTo(
|
||||||
@@ -284,7 +319,7 @@ public class ConnectedDeviceGroupControllerTest {
|
|||||||
when(mInputManager.getInputDevice(0)).thenReturn(new InputDevice.Builder().setSources(
|
when(mInputManager.getInputDevice(0)).thenReturn(new InputDevice.Builder().setSources(
|
||||||
InputDevice.SOURCE_STYLUS).setExternal(false).build());
|
InputDevice.SOURCE_STYLUS).setExternal(false).build());
|
||||||
|
|
||||||
mConnectedDeviceGroupController.init(mConnectedBluetoothDeviceUpdater,
|
mConnectedDeviceGroupController.init(null, mConnectedBluetoothDeviceUpdater,
|
||||||
mConnectedUsbDeviceUpdater, mConnectedDockUpdater, mStylusDeviceUpdater);
|
mConnectedUsbDeviceUpdater, mConnectedDockUpdater, mStylusDeviceUpdater);
|
||||||
|
|
||||||
assertThat(mConnectedDeviceGroupController.getAvailabilityStatus()).isEqualTo(
|
assertThat(mConnectedDeviceGroupController.getAvailabilityStatus()).isEqualTo(
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ public class AddSourceBadCodeStateTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetInstance() {
|
public void testGetInstance() {
|
||||||
|
mInstance = AddSourceBadCodeState.getInstance();
|
||||||
assertThat(mInstance).isNotNull();
|
assertThat(mInstance).isNotNull();
|
||||||
assertThat(mInstance).isInstanceOf(SyncedState.class);
|
assertThat(mInstance).isInstanceOf(SyncedState.class);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ public class AddSourceFailedStateTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetInstance() {
|
public void testGetInstance() {
|
||||||
|
mInstance = AddSourceFailedState.getInstance();
|
||||||
assertThat(mInstance).isNotNull();
|
assertThat(mInstance).isNotNull();
|
||||||
assertThat(mInstance).isInstanceOf(SyncedState.class);
|
assertThat(mInstance).isInstanceOf(SyncedState.class);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ public class AddSourceWaitForResponseStateTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetInstance() {
|
public void testGetInstance() {
|
||||||
|
mInstance = AddSourceWaitForResponseState.getInstance();
|
||||||
assertThat(mInstance).isNotNull();
|
assertThat(mInstance).isNotNull();
|
||||||
assertThat(mInstance).isInstanceOf(AudioStreamStateHandler.class);
|
assertThat(mInstance).isInstanceOf(AudioStreamStateHandler.class);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ public class SourceAddedStateTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetInstance() {
|
public void testGetInstance() {
|
||||||
|
mInstance = SourceAddedState.getInstance();
|
||||||
assertThat(mInstance).isNotNull();
|
assertThat(mInstance).isNotNull();
|
||||||
assertThat(mInstance).isInstanceOf(SourceAddedState.class);
|
assertThat(mInstance).isInstanceOf(SourceAddedState.class);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ public class WaitForSyncStateTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetInstance() {
|
public void testGetInstance() {
|
||||||
|
mInstance = WaitForSyncState.getInstance();
|
||||||
assertThat(mInstance).isNotNull();
|
assertThat(mInstance).isNotNull();
|
||||||
assertThat(mInstance).isInstanceOf(AudioStreamStateHandler.class);
|
assertThat(mInstance).isInstanceOf(AudioStreamStateHandler.class);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.settings.inputmethod;
|
||||||
|
|
||||||
|
import static android.view.flags.Flags.enableVectorCursorA11ySettings;
|
||||||
|
|
||||||
|
import static com.android.settings.core.BasePreferenceController.AVAILABLE;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assume.assumeTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.UserHandle;
|
||||||
|
import android.provider.Settings;
|
||||||
|
import android.widget.SeekBar;
|
||||||
|
|
||||||
|
import androidx.preference.PreferenceScreen;
|
||||||
|
|
||||||
|
import com.android.settings.testutils.shadow.ShadowSystemSettings;
|
||||||
|
import com.android.settings.widget.LabeledSeekBarPreference;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.MockitoJUnit;
|
||||||
|
import org.mockito.junit.MockitoRule;
|
||||||
|
import org.robolectric.RobolectricTestRunner;
|
||||||
|
import org.robolectric.RuntimeEnvironment;
|
||||||
|
import org.robolectric.annotation.Config;
|
||||||
|
|
||||||
|
/** Tests for {@link PointerScaleSeekBarController} */
|
||||||
|
@RunWith(RobolectricTestRunner.class)
|
||||||
|
@Config(shadows = {
|
||||||
|
ShadowSystemSettings.class,
|
||||||
|
})
|
||||||
|
public class PointerScaleSeekBarControllerTest {
|
||||||
|
|
||||||
|
private static final String PREFERENCE_KEY = "pointer_scale";
|
||||||
|
|
||||||
|
@Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||||
|
|
||||||
|
@Mock private PreferenceScreen mPreferenceScreen;
|
||||||
|
|
||||||
|
private Context mContext;
|
||||||
|
private LabeledSeekBarPreference mPreference;
|
||||||
|
private PointerScaleSeekBarController mController;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
mContext = RuntimeEnvironment.application;
|
||||||
|
mPreference = new LabeledSeekBarPreference(mContext, null);
|
||||||
|
mController = new PointerScaleSeekBarController(mContext, PREFERENCE_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getAvailabilityStatus_flagEnabled() {
|
||||||
|
assumeTrue(enableVectorCursorA11ySettings());
|
||||||
|
|
||||||
|
assertEquals(mController.getAvailabilityStatus(), AVAILABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onProgressChanged_changeListenerUpdatesSetting() {
|
||||||
|
when(mPreferenceScreen.findPreference(anyString())).thenReturn(mPreference);
|
||||||
|
mController.displayPreference(mPreferenceScreen);
|
||||||
|
SeekBar seekBar = mPreference.getSeekbar();
|
||||||
|
int sliderValue = 1;
|
||||||
|
|
||||||
|
mPreference.onProgressChanged(seekBar, sliderValue, false);
|
||||||
|
|
||||||
|
float expectedScale = 1.5f;
|
||||||
|
float currentScale = Settings.System.getFloatForUser(mContext.getContentResolver(),
|
||||||
|
Settings.System.POINTER_SCALE, -1, UserHandle.USER_CURRENT);
|
||||||
|
assertEquals(expectedScale, currentScale, /* delta= */ 0.001f);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -100,7 +100,7 @@ public class ZenModesListItemPreferenceTest {
|
|||||||
ShadowLooper.idleMainLooper();
|
ShadowLooper.idleMainLooper();
|
||||||
|
|
||||||
assertThat(preference.getTitle()).isEqualTo("Mode disabled by app");
|
assertThat(preference.getTitle()).isEqualTo("Mode disabled by app");
|
||||||
assertThat(preference.getSummary()).isEqualTo("Tap to set up");
|
assertThat(preference.getSummary()).isEqualTo("Not set");
|
||||||
assertThat(preference.getIcon()).isNotNull();
|
assertThat(preference.getIcon()).isNotNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,7 +120,7 @@ public class ZenModesListItemPreferenceTest {
|
|||||||
ShadowLooper.idleMainLooper();
|
ShadowLooper.idleMainLooper();
|
||||||
|
|
||||||
assertThat(preference.getTitle()).isEqualTo("Mode disabled by user");
|
assertThat(preference.getTitle()).isEqualTo("Mode disabled by user");
|
||||||
assertThat(preference.getSummary()).isEqualTo("Paused");
|
assertThat(preference.getSummary()).isEqualTo("Disabled");
|
||||||
assertThat(preference.getIcon()).isNotNull();
|
assertThat(preference.getIcon()).isNotNull();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,9 +42,13 @@ import android.content.ComponentName;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.UserInfo;
|
import android.content.pm.UserInfo;
|
||||||
|
import android.multiuser.Flags;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
import android.os.UserManager;
|
import android.os.UserManager;
|
||||||
|
import android.platform.test.annotations.RequiresFlagsEnabled;
|
||||||
|
import android.platform.test.flag.junit.CheckFlagsRule;
|
||||||
|
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
|
||||||
import android.telephony.TelephonyManager;
|
import android.telephony.TelephonyManager;
|
||||||
|
|
||||||
import androidx.fragment.app.FragmentActivity;
|
import androidx.fragment.app.FragmentActivity;
|
||||||
@@ -63,6 +67,7 @@ import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
|
|||||||
|
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
@@ -123,6 +128,8 @@ public class UserDetailsSettingsTest {
|
|||||||
private Bundle mArguments;
|
private Bundle mArguments;
|
||||||
private UserInfo mUserInfo;
|
private UserInfo mUserInfo;
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
@@ -244,6 +251,19 @@ public class UserDetailsSettingsTest {
|
|||||||
verify(mSwitchUserPref).setEnabled(false);
|
verify(mSwitchUserPref).setEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@RequiresFlagsEnabled({Flags.FLAG_NEW_MULTIUSER_SETTINGS_UX})
|
||||||
|
public void onResume_UserSwitcherDisabled_shouldDisableSwitchPref() {
|
||||||
|
setupSelectedUser();
|
||||||
|
mUserCapabilities.mUserSwitcherEnabled = false;
|
||||||
|
mFragment.mSwitchUserPref = mSwitchUserPref;
|
||||||
|
mFragment.onAttach(mContext);
|
||||||
|
|
||||||
|
mFragment.onResume();
|
||||||
|
|
||||||
|
verify(mSwitchUserPref).setEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void onResume_switchDisallowed_shouldDisableSwitchPref() {
|
public void onResume_switchDisallowed_shouldDisableSwitchPref() {
|
||||||
setupSelectedUser();
|
setupSelectedUser();
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import static org.mockito.Mockito.never;
|
|||||||
import static org.mockito.Mockito.spy;
|
import static org.mockito.Mockito.spy;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
import static org.robolectric.Shadows.shadowOf;
|
import static org.robolectric.Shadows.shadowOf;
|
||||||
|
|
||||||
import android.app.settings.SettingsEnums;
|
import android.app.settings.SettingsEnums;
|
||||||
@@ -46,10 +47,15 @@ import android.content.pm.ResolveInfo;
|
|||||||
import android.content.pm.UserInfo;
|
import android.content.pm.UserInfo;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.multiuser.Flags;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
import android.os.UserManager;
|
import android.os.UserManager;
|
||||||
|
import android.platform.test.annotations.RequiresFlagsDisabled;
|
||||||
|
import android.platform.test.annotations.RequiresFlagsEnabled;
|
||||||
|
import android.platform.test.flag.junit.CheckFlagsRule;
|
||||||
|
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
@@ -75,6 +81,7 @@ import com.android.settingslib.search.SearchIndexableRaw;
|
|||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Ignore;
|
import org.junit.Ignore;
|
||||||
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.mockito.AdditionalMatchers;
|
import org.mockito.AdditionalMatchers;
|
||||||
@@ -142,6 +149,9 @@ public class UserSettingsTest {
|
|||||||
private UserSettings mFragment;
|
private UserSettings mFragment;
|
||||||
private UserCapabilities mUserCapabilities;
|
private UserCapabilities mUserCapabilities;
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
@@ -359,6 +369,7 @@ public class UserSettingsTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@RequiresFlagsDisabled({Flags.FLAG_NEW_MULTIUSER_SETTINGS_UX})
|
||||||
public void updateUserList_cannotSwitchUser_shouldDisableAddUser() {
|
public void updateUserList_cannotSwitchUser_shouldDisableAddUser() {
|
||||||
mUserCapabilities.mCanAddUser = true;
|
mUserCapabilities.mCanAddUser = true;
|
||||||
doReturn(true).when(mUserManager).canAddMoreUsers(anyString());
|
doReturn(true).when(mUserManager).canAddMoreUsers(anyString());
|
||||||
@@ -374,6 +385,20 @@ public class UserSettingsTest {
|
|||||||
verify(mAddUserPreference).setSelectable(true);
|
verify(mAddUserPreference).setSelectable(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@RequiresFlagsEnabled({Flags.FLAG_NEW_MULTIUSER_SETTINGS_UX})
|
||||||
|
public void updateUserList_disallowAddUser_shouldDisableAddUserAndAddGuest() {
|
||||||
|
mUserCapabilities.mDisallowAddUserSetByAdmin = true;
|
||||||
|
doReturn(true).when(mUserManager).canAddMoreUsers(anyString());
|
||||||
|
doReturn(SWITCHABILITY_STATUS_OK)
|
||||||
|
.when(mUserManager).getUserSwitchability();
|
||||||
|
|
||||||
|
mFragment.updateUserList();
|
||||||
|
|
||||||
|
verify(mAddUserPreference).setVisible(true);
|
||||||
|
verify(mAddUserPreference).setDisabledByAdmin(any());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void updateUserList_canNotAddMoreUsers_shouldDisableAddUserWithSummary() {
|
public void updateUserList_canNotAddMoreUsers_shouldDisableAddUserWithSummary() {
|
||||||
mUserCapabilities.mCanAddUser = true;
|
mUserCapabilities.mCanAddUser = true;
|
||||||
@@ -392,6 +417,7 @@ public class UserSettingsTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@RequiresFlagsDisabled({Flags.FLAG_NEW_MULTIUSER_SETTINGS_UX})
|
||||||
public void updateUserList_cannotSwitchUser_shouldDisableAddGuest() {
|
public void updateUserList_cannotSwitchUser_shouldDisableAddGuest() {
|
||||||
mUserCapabilities.mCanAddGuest = true;
|
mUserCapabilities.mCanAddGuest = true;
|
||||||
doReturn(true)
|
doReturn(true)
|
||||||
@@ -406,6 +432,54 @@ public class UserSettingsTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@RequiresFlagsEnabled({Flags.FLAG_NEW_MULTIUSER_SETTINGS_UX})
|
||||||
|
public void updateUserList_cannotSwitchUser_shouldKeepPreferencesVisibleAndEnabled() {
|
||||||
|
givenUsers(getAdminUser(true));
|
||||||
|
mUserCapabilities.mCanAddGuest = true;
|
||||||
|
mUserCapabilities.mCanAddUser = true;
|
||||||
|
mUserCapabilities.mDisallowSwitchUser = true;
|
||||||
|
doReturn(true)
|
||||||
|
.when(mUserManager).canAddMoreUsers(eq(UserManager.USER_TYPE_FULL_GUEST));
|
||||||
|
doReturn(true)
|
||||||
|
.when(mUserManager).canAddMoreUsers(eq(UserManager.USER_TYPE_FULL_SECONDARY));
|
||||||
|
|
||||||
|
mFragment.updateUserList();
|
||||||
|
|
||||||
|
verify(mAddGuestPreference).setVisible(true);
|
||||||
|
verify(mAddGuestPreference).setEnabled(true);
|
||||||
|
verify(mAddUserPreference).setVisible(true);
|
||||||
|
verify(mAddUserPreference).setEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@RequiresFlagsEnabled({Flags.FLAG_NEW_MULTIUSER_SETTINGS_UX})
|
||||||
|
public void updateUserList_disallowAddUser_shouldShowButDisableAddActions() {
|
||||||
|
givenUsers(getAdminUser(true));
|
||||||
|
mUserCapabilities.mCanAddGuest = true;
|
||||||
|
mUserCapabilities.mCanAddUser = false;
|
||||||
|
mUserCapabilities.mDisallowAddUser = true;
|
||||||
|
mUserCapabilities.mDisallowAddUserSetByAdmin = false;
|
||||||
|
List<UserManager.EnforcingUser> enforcingUsers = new ArrayList<>();
|
||||||
|
enforcingUsers.add(new UserManager.EnforcingUser(UserHandle.myUserId(),
|
||||||
|
UserManager.RESTRICTION_SOURCE_SYSTEM));
|
||||||
|
when(mUserManager.getUserRestrictionSources(UserManager.DISALLOW_ADD_USER,
|
||||||
|
UserHandle.of(UserHandle.myUserId()))).thenReturn(enforcingUsers);
|
||||||
|
|
||||||
|
doReturn(true)
|
||||||
|
.when(mUserManager).canAddMoreUsers(eq(UserManager.USER_TYPE_FULL_GUEST));
|
||||||
|
doReturn(true)
|
||||||
|
.when(mUserManager).canAddMoreUsers(eq(UserManager.USER_TYPE_FULL_SECONDARY));
|
||||||
|
|
||||||
|
mFragment.updateUserList();
|
||||||
|
|
||||||
|
verify(mAddGuestPreference).setVisible(true);
|
||||||
|
verify(mAddGuestPreference).setEnabled(false);
|
||||||
|
verify(mAddUserPreference).setVisible(true);
|
||||||
|
verify(mAddUserPreference).setEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@RequiresFlagsDisabled({Flags.FLAG_NEW_MULTIUSER_SETTINGS_UX})
|
||||||
public void updateUserList_addUserDisallowedByAdmin_shouldNotShowAddUser() {
|
public void updateUserList_addUserDisallowedByAdmin_shouldNotShowAddUser() {
|
||||||
RestrictedLockUtils.EnforcedAdmin enforcedAdmin = mock(
|
RestrictedLockUtils.EnforcedAdmin enforcedAdmin = mock(
|
||||||
RestrictedLockUtils.EnforcedAdmin.class);
|
RestrictedLockUtils.EnforcedAdmin.class);
|
||||||
@@ -420,6 +494,22 @@ public class UserSettingsTest {
|
|||||||
verify(mAddUserPreference).setVisible(false);
|
verify(mAddUserPreference).setVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@RequiresFlagsEnabled({Flags.FLAG_NEW_MULTIUSER_SETTINGS_UX})
|
||||||
|
public void updateUserList_addUserDisallowedByAdmin_shouldShowPrefDisabledByAdmin() {
|
||||||
|
RestrictedLockUtils.EnforcedAdmin enforcedAdmin = mock(
|
||||||
|
RestrictedLockUtils.EnforcedAdmin.class);
|
||||||
|
|
||||||
|
mUserCapabilities.mEnforcedAdmin = enforcedAdmin;
|
||||||
|
mUserCapabilities.mCanAddUser = false;
|
||||||
|
mUserCapabilities.mDisallowAddUser = true;
|
||||||
|
mUserCapabilities.mDisallowAddUserSetByAdmin = true;
|
||||||
|
doReturn(true).when(mAddUserPreference).isEnabled();
|
||||||
|
|
||||||
|
mFragment.updateUserList();
|
||||||
|
|
||||||
|
verify(mAddUserPreference).setDisabledByAdmin(enforcedAdmin);
|
||||||
|
}
|
||||||
@Test
|
@Test
|
||||||
public void updateUserList_cannotAddUserButCanSwitchUser_shouldNotShowAddUser() {
|
public void updateUserList_cannotAddUserButCanSwitchUser_shouldNotShowAddUser() {
|
||||||
mUserCapabilities.mCanAddUser = false;
|
mUserCapabilities.mCanAddUser = false;
|
||||||
@@ -461,18 +551,31 @@ public class UserSettingsTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void updateUserList_userSwitcherDisabled_shouldNotShowAddUser() {
|
public void updateUserList_userSwitcherDisabled_shouldShowAddUser() {
|
||||||
givenUsers(getAdminUser(true));
|
givenUsers(getAdminUser(true));
|
||||||
mUserCapabilities.mCanAddUser = true;
|
mUserCapabilities.mCanAddUser = true;
|
||||||
mUserCapabilities.mUserSwitcherEnabled = false;
|
mUserCapabilities.mUserSwitcherEnabled = false;
|
||||||
|
|
||||||
mFragment.updateUserList();
|
mFragment.updateUserList();
|
||||||
|
|
||||||
verify(mAddUserPreference).setVisible(false);
|
verify(mAddUserPreference).setVisible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void updateUserList_userSwitcherDisabled_shouldNotShowAddGuest() {
|
public void updateUserList_userSwitcherDisabled_shouldShowAddGuest() {
|
||||||
|
givenUsers(getAdminUser(true));
|
||||||
|
mUserCapabilities.mCanAddGuest = true;
|
||||||
|
mUserCapabilities.mUserSwitcherEnabled = false;
|
||||||
|
doReturn(true)
|
||||||
|
.when(mUserManager).canAddMoreUsers(eq(UserManager.USER_TYPE_FULL_GUEST));
|
||||||
|
|
||||||
|
mFragment.updateUserList();
|
||||||
|
|
||||||
|
verify(mAddGuestPreference).setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void updateUserList_userSwitcherDisabledCannotAddMoreGuests_shouldNotShowAddGuest() {
|
||||||
givenUsers(getAdminUser(true));
|
givenUsers(getAdminUser(true));
|
||||||
mUserCapabilities.mCanAddGuest = true;
|
mUserCapabilities.mCanAddGuest = true;
|
||||||
mUserCapabilities.mUserSwitcherEnabled = false;
|
mUserCapabilities.mUserSwitcherEnabled = false;
|
||||||
@@ -533,18 +636,18 @@ public class UserSettingsTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void updateUserList_existingSecondaryUser_shouldAddOnlyCurrUser_MultiUserIsDisabled() {
|
public void updateUserList_existingSecondaryUser_shouldAddAllUsers_MultiUserIsDisabled() {
|
||||||
givenUsers(getAdminUser(true), getSecondaryUser(false));
|
givenUsers(getAdminUser(true), getSecondaryUser(false));
|
||||||
mUserCapabilities.mUserSwitcherEnabled = false;
|
mUserCapabilities.mUserSwitcherEnabled = false;
|
||||||
|
|
||||||
mFragment.updateUserList();
|
mFragment.updateUserList();
|
||||||
|
|
||||||
ArgumentCaptor<UserPreference> captor = ArgumentCaptor.forClass(UserPreference.class);
|
ArgumentCaptor<UserPreference> captor = ArgumentCaptor.forClass(UserPreference.class);
|
||||||
verify(mFragment.mUserListCategory, times(1))
|
verify(mFragment.mUserListCategory, times(2))
|
||||||
.addPreference(captor.capture());
|
.addPreference(captor.capture());
|
||||||
|
|
||||||
List<UserPreference> userPrefs = captor.getAllValues();
|
List<UserPreference> userPrefs = captor.getAllValues();
|
||||||
assertThat(userPrefs.size()).isEqualTo(1);
|
assertThat(userPrefs.size()).isEqualTo(2);
|
||||||
assertThat(userPrefs.get(0)).isSameInstanceAs(mMePreference);
|
assertThat(userPrefs.get(0)).isSameInstanceAs(mMePreference);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -631,6 +734,7 @@ public class UserSettingsTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@RequiresFlagsDisabled({Flags.FLAG_NEW_MULTIUSER_SETTINGS_UX})
|
||||||
public void updateUserList_uninitializedUserAndCanNotSwitchUser_shouldDisablePref() {
|
public void updateUserList_uninitializedUserAndCanNotSwitchUser_shouldDisablePref() {
|
||||||
UserInfo uninitializedUser = getSecondaryUser(false);
|
UserInfo uninitializedUser = getSecondaryUser(false);
|
||||||
removeFlag(uninitializedUser, UserInfo.FLAG_INITIALIZED);
|
removeFlag(uninitializedUser, UserInfo.FLAG_INITIALIZED);
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ android_test {
|
|||||||
"kotlinx_coroutines_test",
|
"kotlinx_coroutines_test",
|
||||||
"Settings-testutils2",
|
"Settings-testutils2",
|
||||||
"MediaDrmSettingsFlagsLib",
|
"MediaDrmSettingsFlagsLib",
|
||||||
|
"servicestests-utils",
|
||||||
// Don't add SettingsLib libraries here - you can use them directly as they are in the
|
// Don't add SettingsLib libraries here - you can use them directly as they are in the
|
||||||
// instrumented Settings app.
|
// instrumented Settings app.
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -0,0 +1,409 @@
|
|||||||
|
/*
|
||||||
|
* 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 static android.view.Display.INVALID_DISPLAY;
|
||||||
|
|
||||||
|
import static com.android.settings.connecteddevice.display.ExternalDisplayPreferenceFragment.PREVIOUSLY_SHOWN_LIST_KEY;
|
||||||
|
import static com.android.settings.connecteddevice.display.ExternalDisplayPreferenceFragment.DISPLAYS_LIST_PREFERENCE_KEY;
|
||||||
|
import static com.android.settings.connecteddevice.display.ExternalDisplayPreferenceFragment.EXTERNAL_DISPLAY_CHANGE_RESOLUTION_FOOTER_RESOURCE;
|
||||||
|
import static com.android.settings.connecteddevice.display.ExternalDisplayPreferenceFragment.EXTERNAL_DISPLAY_NOT_FOUND_FOOTER_RESOURCE;
|
||||||
|
import static com.android.settings.connecteddevice.display.ExternalDisplayPreferenceFragment.EXTERNAL_DISPLAY_RESOLUTION_PREFERENCE_KEY;
|
||||||
|
import static com.android.settings.connecteddevice.display.ExternalDisplayPreferenceFragment.EXTERNAL_DISPLAY_RESOLUTION_TITLE_RESOURCE;
|
||||||
|
import static com.android.settings.connecteddevice.display.ExternalDisplayPreferenceFragment.EXTERNAL_DISPLAY_ROTATION_KEY;
|
||||||
|
import static com.android.settings.connecteddevice.display.ExternalDisplayPreferenceFragment.EXTERNAL_DISPLAY_ROTATION_TITLE_RESOURCE;
|
||||||
|
import static com.android.settings.connecteddevice.display.ExternalDisplayPreferenceFragment.EXTERNAL_DISPLAY_SETTINGS_RESOURCE;
|
||||||
|
import static com.android.settings.connecteddevice.display.ExternalDisplayPreferenceFragment.EXTERNAL_DISPLAY_USE_PREFERENCE_KEY;
|
||||||
|
import static com.android.settings.connecteddevice.display.ExternalDisplayPreferenceFragment.EXTERNAL_DISPLAY_USE_TITLE_RESOURCE;
|
||||||
|
import static com.android.settingslib.widget.FooterPreference.KEY_FOOTER;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.Display;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.preference.Preference;
|
||||||
|
import androidx.preference.PreferenceCategory;
|
||||||
|
import androidx.preference.PreferenceScreen;
|
||||||
|
import androidx.test.annotation.UiThreadTest;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
|
||||||
|
import com.android.settings.connecteddevice.display.ExternalDisplayPreferenceFragment.DisplayPreference;
|
||||||
|
import com.android.settingslib.widget.FooterPreference;
|
||||||
|
import com.android.settingslib.widget.MainSwitchPreference;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
|
||||||
|
/** Unit tests for {@link ExternalDisplayPreferenceFragment}. */
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class ExternalDisplayPreferenceFragmentTest extends ExternalDisplayTestBase {
|
||||||
|
@Nullable
|
||||||
|
private ExternalDisplayPreferenceFragment mFragment;
|
||||||
|
private int mPreferenceIdFromResource;
|
||||||
|
private int mDisplayIdArg = INVALID_DISPLAY;
|
||||||
|
private int mResolutionSelectorDisplayId = INVALID_DISPLAY;
|
||||||
|
@Mock
|
||||||
|
private MetricsLogger mMockedMetricsLogger;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@UiThreadTest
|
||||||
|
public void testCreateAndStart() {
|
||||||
|
initFragment();
|
||||||
|
assertThat(mPreferenceIdFromResource).isEqualTo(EXTERNAL_DISPLAY_SETTINGS_RESOURCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@UiThreadTest
|
||||||
|
public void testShowDisplayList() {
|
||||||
|
var fragment = initFragment();
|
||||||
|
var outState = new Bundle();
|
||||||
|
fragment.onSaveInstanceStateCallback(outState);
|
||||||
|
assertThat(outState.getBoolean(PREVIOUSLY_SHOWN_LIST_KEY)).isFalse();
|
||||||
|
assertThat(mHandler.getPendingMessages().size()).isEqualTo(1);
|
||||||
|
PreferenceCategory pref = mPreferenceScreen.findPreference(DISPLAYS_LIST_PREFERENCE_KEY);
|
||||||
|
assertThat(pref).isNull();
|
||||||
|
verify(mMockedInjector, never()).getAllDisplays();
|
||||||
|
mHandler.flush();
|
||||||
|
assertThat(mHandler.getPendingMessages().size()).isEqualTo(0);
|
||||||
|
verify(mMockedInjector).getAllDisplays();
|
||||||
|
pref = mPreferenceScreen.findPreference(DISPLAYS_LIST_PREFERENCE_KEY);
|
||||||
|
assertThat(pref).isNotNull();
|
||||||
|
assertThat(pref.getPreferenceCount()).isEqualTo(2);
|
||||||
|
fragment.onSaveInstanceStateCallback(outState);
|
||||||
|
assertThat(outState.getBoolean(PREVIOUSLY_SHOWN_LIST_KEY)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@UiThreadTest
|
||||||
|
public void testLaunchDisplaySettingFromList() {
|
||||||
|
initFragment();
|
||||||
|
mHandler.flush();
|
||||||
|
PreferenceCategory pref = mPreferenceScreen.findPreference(DISPLAYS_LIST_PREFERENCE_KEY);
|
||||||
|
assertThat(pref).isNotNull();
|
||||||
|
DisplayPreference display1Pref = (DisplayPreference) pref.getPreference(0);
|
||||||
|
DisplayPreference display2Pref = (DisplayPreference) pref.getPreference(1);
|
||||||
|
assertThat(display1Pref.getKey()).isEqualTo("display_id_" + 1);
|
||||||
|
assertThat("" + display1Pref.getTitle()).isEqualTo("HDMI");
|
||||||
|
assertThat("" + display1Pref.getSummary()).isEqualTo("1920 x 1080");
|
||||||
|
display1Pref.onPreferenceClick(display1Pref);
|
||||||
|
assertThat(mDisplayIdArg).isEqualTo(1);
|
||||||
|
verify(mMockedMetricsLogger).writePreferenceClickMetric(display1Pref);
|
||||||
|
assertThat(display2Pref.getKey()).isEqualTo("display_id_" + 2);
|
||||||
|
assertThat("" + display2Pref.getTitle()).isEqualTo("Overlay #1");
|
||||||
|
assertThat("" + display2Pref.getSummary()).isEqualTo("1240 x 780");
|
||||||
|
display2Pref.onPreferenceClick(display2Pref);
|
||||||
|
assertThat(mDisplayIdArg).isEqualTo(2);
|
||||||
|
verify(mMockedMetricsLogger).writePreferenceClickMetric(display2Pref);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@UiThreadTest
|
||||||
|
public void testShowDisplayListForOnlyOneDisplay_PreviouslyShownList() {
|
||||||
|
var fragment = initFragment();
|
||||||
|
// Previously shown list of displays
|
||||||
|
fragment.onActivityCreatedCallback(createBundleForPreviouslyShownList());
|
||||||
|
// Only one display available
|
||||||
|
doReturn(new Display[] {mDisplays[1]}).when(mMockedInjector).getAllDisplays();
|
||||||
|
mHandler.flush();
|
||||||
|
PreferenceCategory pref = mPreferenceScreen.findPreference(DISPLAYS_LIST_PREFERENCE_KEY);
|
||||||
|
assertThat(pref).isNotNull();
|
||||||
|
assertThat(pref.getPreferenceCount()).isEqualTo(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@UiThreadTest
|
||||||
|
public void testShowEnabledDisplay_OnlyOneDisplayAvailable() {
|
||||||
|
doReturn(true).when(mMockedInjector).isDisplayEnabled(any());
|
||||||
|
// Only one display available
|
||||||
|
doReturn(new Display[] {mDisplays[1]}).when(mMockedInjector).getAllDisplays();
|
||||||
|
// Init
|
||||||
|
initFragment();
|
||||||
|
mHandler.flush();
|
||||||
|
PreferenceCategory list = mPreferenceScreen.findPreference(DISPLAYS_LIST_PREFERENCE_KEY);
|
||||||
|
assertThat(list).isNull();
|
||||||
|
var pref = mPreferenceScreen.findPreference(EXTERNAL_DISPLAY_RESOLUTION_PREFERENCE_KEY);
|
||||||
|
assertThat(pref).isNotNull();
|
||||||
|
pref = mPreferenceScreen.findPreference(EXTERNAL_DISPLAY_ROTATION_KEY);
|
||||||
|
assertThat(pref).isNotNull();
|
||||||
|
var footerPref = (FooterPreference) mPreferenceScreen.findPreference(KEY_FOOTER);
|
||||||
|
assertThat(footerPref).isNotNull();
|
||||||
|
verify(footerPref).setTitle(EXTERNAL_DISPLAY_CHANGE_RESOLUTION_FOOTER_RESOURCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@UiThreadTest
|
||||||
|
public void testShowOneEnabledDisplay_FewAvailable() {
|
||||||
|
mDisplayIdArg = 1;
|
||||||
|
doReturn(true).when(mMockedInjector).isDisplayEnabled(any());
|
||||||
|
initFragment();
|
||||||
|
verify(mMockedInjector, never()).getDisplay(anyInt());
|
||||||
|
mHandler.flush();
|
||||||
|
verify(mMockedInjector).getDisplay(mDisplayIdArg);
|
||||||
|
var pref = mPreferenceScreen.findPreference(EXTERNAL_DISPLAY_RESOLUTION_PREFERENCE_KEY);
|
||||||
|
assertThat(pref).isNotNull();
|
||||||
|
pref = mPreferenceScreen.findPreference(EXTERNAL_DISPLAY_ROTATION_KEY);
|
||||||
|
assertThat(pref).isNotNull();
|
||||||
|
var footerPref = (FooterPreference) mPreferenceScreen.findPreference(KEY_FOOTER);
|
||||||
|
assertThat(footerPref).isNotNull();
|
||||||
|
verify(footerPref).setTitle(EXTERNAL_DISPLAY_CHANGE_RESOLUTION_FOOTER_RESOURCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@UiThreadTest
|
||||||
|
public void testShowDisabledDisplay() {
|
||||||
|
mDisplayIdArg = 1;
|
||||||
|
initFragment();
|
||||||
|
verify(mMockedInjector, never()).getDisplay(anyInt());
|
||||||
|
mHandler.flush();
|
||||||
|
verify(mMockedInjector).getDisplay(mDisplayIdArg);
|
||||||
|
var mainPref = (MainSwitchPreference) mPreferenceScreen.findPreference(
|
||||||
|
EXTERNAL_DISPLAY_USE_PREFERENCE_KEY);
|
||||||
|
assertThat(mainPref).isNotNull();
|
||||||
|
assertThat("" + mainPref.getTitle()).isEqualTo(
|
||||||
|
getText(EXTERNAL_DISPLAY_USE_TITLE_RESOURCE));
|
||||||
|
assertThat(mainPref.isChecked()).isFalse();
|
||||||
|
assertThat(mainPref.isEnabled()).isTrue();
|
||||||
|
assertThat(mainPref.getOnPreferenceChangeListener()).isNotNull();
|
||||||
|
var pref = mPreferenceScreen.findPreference(EXTERNAL_DISPLAY_RESOLUTION_PREFERENCE_KEY);
|
||||||
|
assertThat(pref).isNull();
|
||||||
|
pref = mPreferenceScreen.findPreference(EXTERNAL_DISPLAY_ROTATION_KEY);
|
||||||
|
assertThat(pref).isNull();
|
||||||
|
var footerPref = (FooterPreference) mPreferenceScreen.findPreference(KEY_FOOTER);
|
||||||
|
assertThat(footerPref).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@UiThreadTest
|
||||||
|
public void testNoDisplays() {
|
||||||
|
doReturn(new Display[0]).when(mMockedInjector).getAllDisplays();
|
||||||
|
initFragment();
|
||||||
|
mHandler.flush();
|
||||||
|
var mainPref = (MainSwitchPreference) mPreferenceScreen.findPreference(
|
||||||
|
EXTERNAL_DISPLAY_USE_PREFERENCE_KEY);
|
||||||
|
assertThat(mainPref).isNotNull();
|
||||||
|
assertThat("" + mainPref.getTitle()).isEqualTo(
|
||||||
|
getText(EXTERNAL_DISPLAY_USE_TITLE_RESOURCE));
|
||||||
|
assertThat(mainPref.isChecked()).isFalse();
|
||||||
|
assertThat(mainPref.isEnabled()).isFalse();
|
||||||
|
assertThat(mainPref.getOnPreferenceChangeListener()).isNull();
|
||||||
|
var footerPref = (FooterPreference) mPreferenceScreen.findPreference(KEY_FOOTER);
|
||||||
|
assertThat(footerPref).isNotNull();
|
||||||
|
verify(footerPref).setTitle(EXTERNAL_DISPLAY_NOT_FOUND_FOOTER_RESOURCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@UiThreadTest
|
||||||
|
public void testDisplayRotationPreference() {
|
||||||
|
mDisplayIdArg = 1;
|
||||||
|
doReturn(true).when(mMockedInjector).isDisplayEnabled(any());
|
||||||
|
var fragment = initFragment();
|
||||||
|
mHandler.flush();
|
||||||
|
var pref = fragment.getRotationPreference(mContext);
|
||||||
|
assertThat(pref.getKey()).isEqualTo(EXTERNAL_DISPLAY_ROTATION_KEY);
|
||||||
|
assertThat("" + pref.getTitle()).isEqualTo(
|
||||||
|
getText(EXTERNAL_DISPLAY_ROTATION_TITLE_RESOURCE));
|
||||||
|
assertThat(pref.getEntries().length).isEqualTo(4);
|
||||||
|
assertThat(pref.getEntryValues().length).isEqualTo(4);
|
||||||
|
assertThat(pref.getEntryValues()[0].toString()).isEqualTo("0");
|
||||||
|
assertThat(pref.getEntryValues()[1].toString()).isEqualTo("1");
|
||||||
|
assertThat(pref.getEntryValues()[2].toString()).isEqualTo("2");
|
||||||
|
assertThat(pref.getEntryValues()[3].toString()).isEqualTo("3");
|
||||||
|
assertThat(pref.getEntries()[0].length()).isGreaterThan(0);
|
||||||
|
assertThat(pref.getEntries()[1].length()).isGreaterThan(0);
|
||||||
|
assertThat("" + pref.getSummary()).isEqualTo(pref.getEntries()[0].toString());
|
||||||
|
assertThat(pref.getValue()).isEqualTo("0");
|
||||||
|
assertThat(pref.getOnPreferenceChangeListener()).isNotNull();
|
||||||
|
assertThat(pref.isEnabled()).isTrue();
|
||||||
|
var rotation = 1;
|
||||||
|
doReturn(true).when(mMockedInjector).freezeDisplayRotation(mDisplayIdArg, rotation);
|
||||||
|
assertThat(pref.getOnPreferenceChangeListener().onPreferenceChange(pref, rotation + ""))
|
||||||
|
.isTrue();
|
||||||
|
verify(mMockedInjector).freezeDisplayRotation(mDisplayIdArg, rotation);
|
||||||
|
assertThat(pref.getValue()).isEqualTo(rotation + "");
|
||||||
|
verify(mMockedMetricsLogger).writePreferenceClickMetric(pref);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@UiThreadTest
|
||||||
|
public void testDisplayResolutionPreference() {
|
||||||
|
mDisplayIdArg = 1;
|
||||||
|
doReturn(true).when(mMockedInjector).isDisplayEnabled(any());
|
||||||
|
var fragment = initFragment();
|
||||||
|
mHandler.flush();
|
||||||
|
var pref = fragment.getResolutionPreference(mContext);
|
||||||
|
assertThat(pref.getKey()).isEqualTo(EXTERNAL_DISPLAY_RESOLUTION_PREFERENCE_KEY);
|
||||||
|
assertThat("" + pref.getTitle()).isEqualTo(
|
||||||
|
getText(EXTERNAL_DISPLAY_RESOLUTION_TITLE_RESOURCE));
|
||||||
|
assertThat("" + pref.getSummary()).isEqualTo("1920 x 1080");
|
||||||
|
assertThat(pref.isEnabled()).isTrue();
|
||||||
|
assertThat(pref.getOnPreferenceClickListener()).isNotNull();
|
||||||
|
assertThat(pref.getOnPreferenceClickListener().onPreferenceClick(pref)).isTrue();
|
||||||
|
assertThat(mResolutionSelectorDisplayId).isEqualTo(mDisplayIdArg);
|
||||||
|
verify(mMockedMetricsLogger).writePreferenceClickMetric(pref);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@UiThreadTest
|
||||||
|
public void testUseDisplayPreference_EnabledDisplay() {
|
||||||
|
mDisplayIdArg = 1;
|
||||||
|
doReturn(true).when(mMockedInjector).isDisplayEnabled(any());
|
||||||
|
doReturn(true).when(mMockedInjector).enableConnectedDisplay(mDisplayIdArg);
|
||||||
|
doReturn(true).when(mMockedInjector).disableConnectedDisplay(mDisplayIdArg);
|
||||||
|
var fragment = initFragment();
|
||||||
|
mHandler.flush();
|
||||||
|
var pref = fragment.getUseDisplayPreference(mContext);
|
||||||
|
assertThat(pref.getKey()).isEqualTo(EXTERNAL_DISPLAY_USE_PREFERENCE_KEY);
|
||||||
|
assertThat("" + pref.getTitle()).isEqualTo(getText(EXTERNAL_DISPLAY_USE_TITLE_RESOURCE));
|
||||||
|
assertThat(pref.isEnabled()).isTrue();
|
||||||
|
assertThat(pref.isChecked()).isTrue();
|
||||||
|
assertThat(pref.getOnPreferenceChangeListener()).isNotNull();
|
||||||
|
assertThat(pref.getOnPreferenceChangeListener().onPreferenceChange(pref, false)).isTrue();
|
||||||
|
verify(mMockedInjector).disableConnectedDisplay(mDisplayIdArg);
|
||||||
|
assertThat(pref.isChecked()).isFalse();
|
||||||
|
assertThat(pref.getOnPreferenceChangeListener().onPreferenceChange(pref, true)).isTrue();
|
||||||
|
verify(mMockedInjector).enableConnectedDisplay(mDisplayIdArg);
|
||||||
|
assertThat(pref.isChecked()).isTrue();
|
||||||
|
verify(mMockedMetricsLogger, times(2)).writePreferenceClickMetric(pref);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private ExternalDisplayPreferenceFragment initFragment() {
|
||||||
|
if (mFragment != null) {
|
||||||
|
return mFragment;
|
||||||
|
}
|
||||||
|
mFragment = new TestableExternalDisplayPreferenceFragment();
|
||||||
|
mFragment.onCreateCallback(null);
|
||||||
|
mFragment.onActivityCreatedCallback(null);
|
||||||
|
mFragment.onStartCallback();
|
||||||
|
return mFragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private Bundle createBundleForPreviouslyShownList() {
|
||||||
|
var state = new Bundle();
|
||||||
|
state.putBoolean(PREVIOUSLY_SHOWN_LIST_KEY, true);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private String getText(int id) {
|
||||||
|
return mContext.getResources().getText(id).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestableExternalDisplayPreferenceFragment extends
|
||||||
|
ExternalDisplayPreferenceFragment {
|
||||||
|
private final View mMockedRootView;
|
||||||
|
private final TextView mEmptyView;
|
||||||
|
private final Activity mMockedActivity;
|
||||||
|
private final FooterPreference mMockedFooterPreference;
|
||||||
|
private final MetricsLogger mLogger;
|
||||||
|
|
||||||
|
TestableExternalDisplayPreferenceFragment() {
|
||||||
|
super(mMockedInjector);
|
||||||
|
mMockedActivity = mock(Activity.class);
|
||||||
|
mMockedRootView = mock(View.class);
|
||||||
|
mMockedFooterPreference = mock(FooterPreference.class);
|
||||||
|
doReturn(KEY_FOOTER).when(mMockedFooterPreference).getKey();
|
||||||
|
mEmptyView = new TextView(mContext);
|
||||||
|
doReturn(mEmptyView).when(mMockedRootView).findViewById(android.R.id.empty);
|
||||||
|
mLogger = mMockedMetricsLogger;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PreferenceScreen getPreferenceScreen() {
|
||||||
|
return mPreferenceScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Activity getCurrentActivity() {
|
||||||
|
return mMockedActivity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView() {
|
||||||
|
return mMockedRootView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEmptyView(View view) {
|
||||||
|
assertThat(view).isEqualTo(mEmptyView);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getEmptyView() {
|
||||||
|
return mEmptyView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addPreferencesFromResource(int resource) {
|
||||||
|
mPreferenceIdFromResource = resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
FooterPreference getFooterPreference(@NonNull Context context) {
|
||||||
|
return mMockedFooterPreference;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getDisplayIdArg() {
|
||||||
|
return mDisplayIdArg;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void launchResolutionSelector(@NonNull Context context, int displayId) {
|
||||||
|
mResolutionSelectorDisplayId = displayId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void launchDisplaySettings(final int displayId) {
|
||||||
|
mDisplayIdArg = displayId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void writePreferenceClickMetric(Preference preference) {
|
||||||
|
mLogger.writePreferenceClickMetric(preference);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface allowing to mock and spy on log events.
|
||||||
|
*/
|
||||||
|
public interface MetricsLogger {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On preference click metric
|
||||||
|
*/
|
||||||
|
void writePreferenceClickMetric(Preference preference);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
/*
|
||||||
|
* 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 static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.VIRTUAL_DISPLAY_PACKAGE_NAME_SYSTEM_PROPERTY;
|
||||||
|
import static com.android.settings.flags.Flags.FLAG_ROTATION_CONNECTED_DISPLAY_SETTING;
|
||||||
|
import static com.android.settings.flags.Flags.FLAG_RESOLUTION_AND_ENABLE_CONNECTED_DISPLAY_SETTING;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
|
import static org.mockito.Mockito.doAnswer;
|
||||||
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
import static org.mockito.Mockito.spy;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.hardware.display.DisplayManagerGlobal;
|
||||||
|
import android.hardware.display.IDisplayManager;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import android.view.Display;
|
||||||
|
import android.view.DisplayAdjustments;
|
||||||
|
import android.view.DisplayInfo;
|
||||||
|
|
||||||
|
import androidx.preference.PreferenceManager;
|
||||||
|
import androidx.preference.PreferenceScreen;
|
||||||
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
|
||||||
|
import com.android.server.testutils.TestHandler;
|
||||||
|
import com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.DisplayListener;
|
||||||
|
import com.android.settings.flags.FakeFeatureFlagsImpl;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
|
public class ExternalDisplayTestBase {
|
||||||
|
@Mock
|
||||||
|
ExternalDisplaySettingsConfiguration.Injector mMockedInjector;
|
||||||
|
@Mock
|
||||||
|
IDisplayManager mMockedIDisplayManager;
|
||||||
|
Resources mResources;
|
||||||
|
DisplayManagerGlobal mDisplayManagerGlobal;
|
||||||
|
FakeFeatureFlagsImpl mFlags = new FakeFeatureFlagsImpl();
|
||||||
|
Context mContext;
|
||||||
|
DisplayListener mListener;
|
||||||
|
TestHandler mHandler = new TestHandler(null);
|
||||||
|
PreferenceManager mPreferenceManager;
|
||||||
|
PreferenceScreen mPreferenceScreen;
|
||||||
|
Display[] mDisplays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup.
|
||||||
|
*/
|
||||||
|
@Before
|
||||||
|
public void setUp() throws RemoteException {
|
||||||
|
MockitoAnnotations.initMocks(this);
|
||||||
|
mContext = spy(ApplicationProvider.getApplicationContext());
|
||||||
|
mResources = spy(mContext.getResources());
|
||||||
|
doReturn(mResources).when(mContext).getResources();
|
||||||
|
mPreferenceManager = new PreferenceManager(mContext);
|
||||||
|
mPreferenceScreen = mPreferenceManager.createPreferenceScreen(mContext);
|
||||||
|
doReturn(0).when(mMockedIDisplayManager).getPreferredWideGamutColorSpaceId();
|
||||||
|
mDisplayManagerGlobal = new DisplayManagerGlobal(mMockedIDisplayManager);
|
||||||
|
mFlags.setFlag(FLAG_ROTATION_CONNECTED_DISPLAY_SETTING, true);
|
||||||
|
mFlags.setFlag(FLAG_RESOLUTION_AND_ENABLE_CONNECTED_DISPLAY_SETTING, true);
|
||||||
|
mDisplays = new Display[] {
|
||||||
|
createDefaultDisplay(), createExternalDisplay(), createOverlayDisplay()};
|
||||||
|
doReturn(mDisplays).when(mMockedInjector).getAllDisplays();
|
||||||
|
doReturn(mDisplays).when(mMockedInjector).getEnabledDisplays();
|
||||||
|
for (var display : mDisplays) {
|
||||||
|
doReturn(display).when(mMockedInjector).getDisplay(display.getDisplayId());
|
||||||
|
}
|
||||||
|
doReturn(mFlags).when(mMockedInjector).getFlags();
|
||||||
|
doReturn(mHandler).when(mMockedInjector).getHandler();
|
||||||
|
doReturn("").when(mMockedInjector).getSystemProperty(
|
||||||
|
VIRTUAL_DISPLAY_PACKAGE_NAME_SYSTEM_PROPERTY);
|
||||||
|
doAnswer((arg) -> {
|
||||||
|
mListener = arg.getArgument(0);
|
||||||
|
return null;
|
||||||
|
}).when(mMockedInjector).registerDisplayListener(any());
|
||||||
|
doReturn(0).when(mMockedInjector).getDisplayUserRotation(anyInt());
|
||||||
|
doReturn(mContext).when(mMockedInjector).getContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
Display createDefaultDisplay() throws RemoteException {
|
||||||
|
int displayId = 0;
|
||||||
|
var displayInfo = new DisplayInfo();
|
||||||
|
doReturn(displayInfo).when(mMockedIDisplayManager).getDisplayInfo(displayId);
|
||||||
|
displayInfo.displayId = displayId;
|
||||||
|
displayInfo.name = "Built-in";
|
||||||
|
displayInfo.type = Display.TYPE_INTERNAL;
|
||||||
|
displayInfo.supportedModes = new Display.Mode[]{
|
||||||
|
new Display.Mode(0, 2048, 1024, 60, 60, new float[0],
|
||||||
|
new int[0])};
|
||||||
|
displayInfo.appsSupportedModes = displayInfo.supportedModes;
|
||||||
|
return createDisplay(displayInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
Display createExternalDisplay() throws RemoteException {
|
||||||
|
int displayId = 1;
|
||||||
|
var displayInfo = new DisplayInfo();
|
||||||
|
doReturn(displayInfo).when(mMockedIDisplayManager).getDisplayInfo(displayId);
|
||||||
|
displayInfo.displayId = displayId;
|
||||||
|
displayInfo.name = "HDMI";
|
||||||
|
displayInfo.type = Display.TYPE_EXTERNAL;
|
||||||
|
displayInfo.supportedModes = new Display.Mode[]{
|
||||||
|
new Display.Mode(0, 1920, 1080, 60, 60, new float[0], new int[0]),
|
||||||
|
new Display.Mode(1, 800, 600, 60, 60, new float[0], new int[0]),
|
||||||
|
new Display.Mode(2, 320, 240, 70, 70, new float[0], new int[0]),
|
||||||
|
new Display.Mode(3, 640, 480, 60, 60, new float[0], new int[0]),
|
||||||
|
new Display.Mode(4, 640, 480, 50, 60, new float[0], new int[0]),
|
||||||
|
new Display.Mode(5, 2048, 1024, 60, 60, new float[0], new int[0]),
|
||||||
|
new Display.Mode(6, 720, 480, 60, 60, new float[0], new int[0])};
|
||||||
|
displayInfo.appsSupportedModes = displayInfo.supportedModes;
|
||||||
|
return createDisplay(displayInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
Display createOverlayDisplay() throws RemoteException {
|
||||||
|
int displayId = 2;
|
||||||
|
var displayInfo = new DisplayInfo();
|
||||||
|
doReturn(displayInfo).when(mMockedIDisplayManager).getDisplayInfo(displayId);
|
||||||
|
displayInfo.displayId = displayId;
|
||||||
|
displayInfo.name = "Overlay #1";
|
||||||
|
displayInfo.type = Display.TYPE_OVERLAY;
|
||||||
|
displayInfo.supportedModes = new Display.Mode[]{
|
||||||
|
new Display.Mode(0, 1240, 780, 60, 60, new float[0],
|
||||||
|
new int[0])};
|
||||||
|
displayInfo.appsSupportedModes = displayInfo.supportedModes;
|
||||||
|
return createDisplay(displayInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
Display createDisplay(DisplayInfo displayInfo) {
|
||||||
|
return new Display(mDisplayManagerGlobal, displayInfo.displayId, displayInfo,
|
||||||
|
(DisplayAdjustments) null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
* 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 static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.doAnswer;
|
||||||
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import android.view.Display;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
|
||||||
|
import com.android.settings.connecteddevice.DevicePreferenceCallback;
|
||||||
|
import com.android.settingslib.RestrictedLockUtils;
|
||||||
|
import com.android.settingslib.RestrictedPreference;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
|
||||||
|
/** Unit tests for {@link ExternalDisplayUpdater}. */
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class ExternalDisplayUpdaterTest extends ExternalDisplayTestBase {
|
||||||
|
|
||||||
|
private ExternalDisplayUpdater mUpdater;
|
||||||
|
@Mock
|
||||||
|
private DevicePreferenceCallback mMockedCallback;
|
||||||
|
@Mock
|
||||||
|
private Drawable mMockedDrawable;
|
||||||
|
private RestrictedPreference mPreferenceAdded;
|
||||||
|
private RestrictedPreference mPreferenceRemoved;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws RemoteException {
|
||||||
|
super.setUp();
|
||||||
|
mUpdater = new TestableExternalDisplayUpdater(mMockedCallback, /*metricsCategory=*/ 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPreferenceAdded() {
|
||||||
|
doAnswer((v) -> {
|
||||||
|
mPreferenceAdded = v.getArgument(0);
|
||||||
|
return null;
|
||||||
|
}).when(mMockedCallback).onDeviceAdded(any());
|
||||||
|
mUpdater.initPreference(mContext, mMockedInjector);
|
||||||
|
mUpdater.registerCallback();
|
||||||
|
mHandler.flush();
|
||||||
|
assertThat(mPreferenceAdded).isNotNull();
|
||||||
|
var summary = mPreferenceAdded.getSummary();
|
||||||
|
assertThat(summary).isNotNull();
|
||||||
|
assertThat(summary.length()).isGreaterThan(0);
|
||||||
|
var title = mPreferenceAdded.getTitle();
|
||||||
|
assertThat(title).isNotNull();
|
||||||
|
assertThat(title.length()).isGreaterThan(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPreferenceRemoved() {
|
||||||
|
doAnswer((v) -> {
|
||||||
|
mPreferenceAdded = v.getArgument(0);
|
||||||
|
return null;
|
||||||
|
}).when(mMockedCallback).onDeviceAdded(any());
|
||||||
|
doAnswer((v) -> {
|
||||||
|
mPreferenceRemoved = v.getArgument(0);
|
||||||
|
return null;
|
||||||
|
}).when(mMockedCallback).onDeviceRemoved(any());
|
||||||
|
mUpdater.initPreference(mContext, mMockedInjector);
|
||||||
|
mUpdater.registerCallback();
|
||||||
|
mHandler.flush();
|
||||||
|
assertThat(mPreferenceAdded).isNotNull();
|
||||||
|
assertThat(mPreferenceRemoved).isNull();
|
||||||
|
// Remove display
|
||||||
|
doReturn(new Display[0]).when(mMockedInjector).getAllDisplays();
|
||||||
|
doReturn(new Display[0]).when(mMockedInjector).getEnabledDisplays();
|
||||||
|
mListener.onDisplayRemoved(1);
|
||||||
|
mHandler.flush();
|
||||||
|
assertThat(mPreferenceRemoved).isEqualTo(mPreferenceAdded);
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestableExternalDisplayUpdater extends ExternalDisplayUpdater {
|
||||||
|
TestableExternalDisplayUpdater(
|
||||||
|
DevicePreferenceCallback callback,
|
||||||
|
int metricsCategory) {
|
||||||
|
super(callback, metricsCategory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
protected RestrictedLockUtils.EnforcedAdmin checkIfUsbDataSignalingIsDisabled(
|
||||||
|
Context context) {
|
||||||
|
// if null is returned - usb signalling is enabled
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
protected Drawable getDrawable(Context context) {
|
||||||
|
return mMockedDrawable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
/*
|
||||||
|
* 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 static android.view.Display.INVALID_DISPLAY;
|
||||||
|
|
||||||
|
import static com.android.settings.connecteddevice.display.ResolutionPreferenceFragment.EXTERNAL_DISPLAY_RESOLUTION_SETTINGS_RESOURCE;
|
||||||
|
import static com.android.settings.connecteddevice.display.ResolutionPreferenceFragment.MORE_OPTIONS_KEY;
|
||||||
|
import static com.android.settings.connecteddevice.display.ResolutionPreferenceFragment.TOP_OPTIONS_KEY;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.preference.Preference;
|
||||||
|
import androidx.preference.PreferenceCategory;
|
||||||
|
import androidx.preference.PreferenceScreen;
|
||||||
|
import androidx.test.annotation.UiThreadTest;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
|
||||||
|
import com.android.settingslib.widget.SelectorWithWidgetPreference;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
|
||||||
|
/** Unit tests for {@link ResolutionPreferenceFragment}. */
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class ResolutionPreferenceFragmentTest extends ExternalDisplayTestBase {
|
||||||
|
@Nullable
|
||||||
|
private ResolutionPreferenceFragment mFragment;
|
||||||
|
private int mPreferenceIdFromResource;
|
||||||
|
private int mDisplayIdArg = INVALID_DISPLAY;
|
||||||
|
@Mock
|
||||||
|
private MetricsLogger mMockedMetricsLogger;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@UiThreadTest
|
||||||
|
public void testCreateAndStart() {
|
||||||
|
initFragment();
|
||||||
|
mHandler.flush();
|
||||||
|
assertThat(mPreferenceIdFromResource).isEqualTo(
|
||||||
|
EXTERNAL_DISPLAY_RESOLUTION_SETTINGS_RESOURCE);
|
||||||
|
var pref = mPreferenceScreen.findPreference(TOP_OPTIONS_KEY);
|
||||||
|
assertThat(pref).isNull();
|
||||||
|
pref = mPreferenceScreen.findPreference(MORE_OPTIONS_KEY);
|
||||||
|
assertThat(pref).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@UiThreadTest
|
||||||
|
public void testCreateAndStartDefaultDisplayNotAllowed() {
|
||||||
|
mDisplayIdArg = 0;
|
||||||
|
initFragment();
|
||||||
|
mHandler.flush();
|
||||||
|
var pref = mPreferenceScreen.findPreference(TOP_OPTIONS_KEY);
|
||||||
|
assertThat(pref).isNull();
|
||||||
|
pref = mPreferenceScreen.findPreference(MORE_OPTIONS_KEY);
|
||||||
|
assertThat(pref).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@UiThreadTest
|
||||||
|
public void testModePreferences() {
|
||||||
|
mDisplayIdArg = 1;
|
||||||
|
initFragment();
|
||||||
|
mHandler.flush();
|
||||||
|
PreferenceCategory topPref = mPreferenceScreen.findPreference(TOP_OPTIONS_KEY);
|
||||||
|
assertThat(topPref).isNotNull();
|
||||||
|
PreferenceCategory morePref = mPreferenceScreen.findPreference(MORE_OPTIONS_KEY);
|
||||||
|
assertThat(morePref).isNotNull();
|
||||||
|
assertThat(topPref.getPreferenceCount()).isEqualTo(3);
|
||||||
|
assertThat(morePref.getPreferenceCount()).isEqualTo(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@UiThreadTest
|
||||||
|
public void testModeChange() {
|
||||||
|
mDisplayIdArg = 1;
|
||||||
|
initFragment();
|
||||||
|
mHandler.flush();
|
||||||
|
PreferenceCategory topPref = mPreferenceScreen.findPreference(TOP_OPTIONS_KEY);
|
||||||
|
assertThat(topPref).isNotNull();
|
||||||
|
var modePref = (SelectorWithWidgetPreference) topPref.getPreference(1);
|
||||||
|
modePref.onClick();
|
||||||
|
var mode = mDisplays[mDisplayIdArg].getSupportedModes()[1];
|
||||||
|
verify(mMockedInjector).setUserPreferredDisplayMode(mDisplayIdArg, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initFragment() {
|
||||||
|
if (mFragment != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mFragment = new TestableResolutionPreferenceFragment();
|
||||||
|
mFragment.onCreateCallback(null);
|
||||||
|
mFragment.onActivityCreatedCallback(null);
|
||||||
|
mFragment.onStartCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestableResolutionPreferenceFragment extends ResolutionPreferenceFragment {
|
||||||
|
private final View mMockedRootView;
|
||||||
|
private final TextView mEmptyView;
|
||||||
|
private final Resources mMockedResources;
|
||||||
|
private final MetricsLogger mLogger;
|
||||||
|
TestableResolutionPreferenceFragment() {
|
||||||
|
super(mMockedInjector);
|
||||||
|
mMockedResources = mock(Resources.class);
|
||||||
|
doReturn(61).when(mMockedResources).getInteger(
|
||||||
|
com.android.internal.R.integer.config_externalDisplayPeakRefreshRate);
|
||||||
|
doReturn(1920).when(mMockedResources).getInteger(
|
||||||
|
com.android.internal.R.integer.config_externalDisplayPeakWidth);
|
||||||
|
doReturn(1080).when(mMockedResources).getInteger(
|
||||||
|
com.android.internal.R.integer.config_externalDisplayPeakHeight);
|
||||||
|
doReturn(true).when(mMockedResources).getBoolean(
|
||||||
|
com.android.internal.R.bool.config_refreshRateSynchronizationEnabled);
|
||||||
|
mMockedRootView = mock(View.class);
|
||||||
|
mEmptyView = new TextView(mContext);
|
||||||
|
doReturn(mEmptyView).when(mMockedRootView).findViewById(android.R.id.empty);
|
||||||
|
mLogger = mMockedMetricsLogger;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PreferenceScreen getPreferenceScreen() {
|
||||||
|
return mPreferenceScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView() {
|
||||||
|
return mMockedRootView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEmptyView(View view) {
|
||||||
|
assertThat(view).isEqualTo(mEmptyView);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getEmptyView() {
|
||||||
|
return mEmptyView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addPreferencesFromResource(int resource) {
|
||||||
|
mPreferenceIdFromResource = resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getDisplayIdArg() {
|
||||||
|
return mDisplayIdArg;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void writePreferenceClickMetric(Preference preference) {
|
||||||
|
mLogger.writePreferenceClickMetric(preference);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
protected Resources getResources(@NonNull Context context) {
|
||||||
|
return mMockedResources;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface allowing to mock and spy on log events.
|
||||||
|
*/
|
||||||
|
public interface MetricsLogger {
|
||||||
|
/**
|
||||||
|
* On preference click metric
|
||||||
|
*/
|
||||||
|
void writePreferenceClickMetric(Preference preference);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user