Snap for 12199973 from 59f11d9377 to 24Q4-release
Change-Id: I812d0e411f1a3e23d4c949b68dd665cefa0692c2
This commit is contained in:
@@ -1369,6 +1369,12 @@
|
||||
android:value="true" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".notification.modes.SetupInterstitialActivity"
|
||||
android:exported="false"
|
||||
android:theme="@style/Theme.Settings.NoActionBar">
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".notification.zen.ZenSuggestionActivity"
|
||||
android:label="@string/zen_mode_settings_title"
|
||||
|
||||
@@ -15,48 +15,48 @@
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.core.widget.NestedScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal"
|
||||
android:padding="25dp"
|
||||
android:baselineAligned="false">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@android:id/summary"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="start"
|
||||
android:textSize="15sp"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal"
|
||||
android:padding="25dp"
|
||||
android:gravity="center">
|
||||
|
||||
<TextView
|
||||
android:id="@android:id/summary"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="start"
|
||||
android:textSize="15sp"
|
||||
android:textColor="?android:attr/textColorPrimary"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/qrcode_view"
|
||||
android:layout_width="@dimen/qrcode_size"
|
||||
android:layout_height="@dimen/qrcode_size"
|
||||
android:contentDescription="@string/qr_code_content_description"
|
||||
android:focusable="true" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/password"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="15sp"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:gravity="center" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/qrcode_view"
|
||||
android:layout_width="@dimen/qrcode_size"
|
||||
android:layout_height="@dimen/qrcode_size"
|
||||
android:contentDescription="@string/qr_code_content_description"
|
||||
android:focusable="true" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/password"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="15sp"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
126
res/layout-land/mode_interstitial_layout.xml
Normal file
126
res/layout-land/mode_interstitial_layout.xml
Normal file
@@ -0,0 +1,126 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2024 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<LinearLayout
|
||||
android:id="@+id/interstitial_page"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
android:transitionGroup="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<Toolbar
|
||||
android:id="@+id/action_bar"
|
||||
style="?android:attr/actionBarStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="?android:attr/actionBarTheme"
|
||||
android:elevation="0dp"
|
||||
android:background="@android:color/transparent"/>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingBottom="24dp"
|
||||
android:paddingHorizontal="24dp"
|
||||
android:clipChildren="true">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constrainedHeight="true"
|
||||
app:layout_constraintBottom_toTopOf="@+id/enable_mode_button"
|
||||
app:layout_constraintEnd_toStartOf="@+id/guideline"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingStart="12dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/mode_name_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="false"
|
||||
android:paddingVertical="12dp"
|
||||
android:textSize="36sp"
|
||||
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/mode_name_subtitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="false"
|
||||
android:paddingBottom="12dp"
|
||||
android:text="@string/zen_mode_setup_page_summary"
|
||||
android:textSize="18sp"
|
||||
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Subhead" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<Button
|
||||
android:id="@+id/enable_mode_button"
|
||||
style="@style/ActionPrimaryButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/guideline"
|
||||
android:paddingEnd="12dp" />
|
||||
|
||||
<!-- guideline to have text/button side & image side take up half the page each -->
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/guideline"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_percent="0.5" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/image_frame"
|
||||
android:layout_height="0dp"
|
||||
android:layout_width="0dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/guideline"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:paddingHorizontal="12dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/image"
|
||||
android:clickable="false"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="centerCrop"
|
||||
android:layout_gravity="center" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -15,60 +15,45 @@
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<LinearLayout
|
||||
<androidx.core.widget.NestedScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
<TextView
|
||||
android:id="@android:id/summary"
|
||||
style="@style/QrCodeScanner"
|
||||
android:gravity="center"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top"
|
||||
android:gravity="center"
|
||||
android:clipChildren="true"
|
||||
android:contentDescription="@string/audio_streams_qr_code_scanner_label"
|
||||
android:focusable="true">
|
||||
<TextureView
|
||||
android:id="@+id/preview_view"
|
||||
android:layout_marginStart="@dimen/qrcode_preview_margin"
|
||||
android:layout_marginEnd="@dimen/qrcode_preview_margin"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/qrcode_preview_size"/>
|
||||
</FrameLayout>
|
||||
android:orientation="horizontal"
|
||||
android:paddingTop="@dimen/audio_streams_qrcode_scanner_fragment_padding">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/error_message"
|
||||
style="@style/TextAppearance.ErrorText"
|
||||
android:layout_width="wrap_content"
|
||||
android:id="@android:id/summary"
|
||||
style="@style/QrCodeScanner"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:gravity="center"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="invisible"/>
|
||||
android:orientation="vertical"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center">
|
||||
<TextureView
|
||||
android:id="@+id/preview_view"
|
||||
android:layout_width="@dimen/audio_streams_qrcode_preview_size"
|
||||
android:layout_height="@dimen/audio_streams_qrcode_preview_size"
|
||||
android:contentDescription="@string/audio_streams_qr_code_scanner_label"
|
||||
android:focusable="true" />
|
||||
<TextView
|
||||
android:id="@+id/error_message"
|
||||
style="@style/TextAppearance.ErrorText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"/>
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<LinearLayout
|
||||
<androidx.core.widget.NestedScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
@@ -23,10 +23,9 @@
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:paddingLeft="25dp"
|
||||
android:paddingRight="25dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
@@ -35,30 +34,24 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="start"
|
||||
android:textSize="15sp"
|
||||
android:textColor="?android:attr/textColorPrimary"/>
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
<ImageView
|
||||
android:id="@+id/qrcode_view"
|
||||
android:layout_width="@dimen/qrcode_size"
|
||||
android:layout_height="@dimen/qrcode_size"
|
||||
android:contentDescription="@string/qr_code_content_description"
|
||||
android:focusable="true"
|
||||
android:paddingTop="70dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/password"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="70dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/qrcode_view"
|
||||
android:layout_width="@dimen/qrcode_size"
|
||||
android:layout_height="@dimen/qrcode_size"
|
||||
android:contentDescription="@string/qr_code_content_description"
|
||||
android:focusable="true"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/password"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="15sp"
|
||||
android:textColor="?android:attr/textColorPrimary"/>
|
||||
</LinearLayout>
|
||||
android:textSize="15sp"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:gravity="center" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
96
res/layout/mode_interstitial_layout.xml
Normal file
96
res/layout/mode_interstitial_layout.xml
Normal file
@@ -0,0 +1,96 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2024 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<LinearLayout
|
||||
android:id="@+id/interstitial_page"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
android:transitionGroup="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<Toolbar
|
||||
android:id="@+id/action_bar"
|
||||
style="?android:attr/actionBarStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="?android:attr/actionBarTheme"
|
||||
android:elevation="0dp"
|
||||
android:background="@android:color/transparent" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingBottom="64dp"
|
||||
android:paddingLeft="24dp"
|
||||
android:paddingRight="24dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<!-- image goes here -->
|
||||
<FrameLayout
|
||||
android:id="@+id/image_frame"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/mode_name_title"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/image"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:adjustViewBounds="true"
|
||||
android:clickable="false"
|
||||
android:scaleType="centerCrop"
|
||||
android:layout_gravity="center" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/mode_name_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="false"
|
||||
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Title"
|
||||
app:layout_constraintBottom_toTopOf="@+id/mode_name_subtitle"
|
||||
android:textSize="36sp"
|
||||
android:paddingVertical="12dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/mode_name_subtitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="false"
|
||||
android:text="@string/zen_mode_setup_page_summary"
|
||||
android:textSize="18sp"
|
||||
android:paddingBottom="12dp"
|
||||
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body1"
|
||||
app:layout_constraintBottom_toTopOf="@+id/enable_mode_button" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/enable_mode_button"
|
||||
style="@style/ActionPrimaryButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingVertical="12dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintVertical_bias="1" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -17,6 +17,7 @@
|
||||
-->
|
||||
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
@@ -64,7 +65,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textColor="?androidprv:attr/materialColorOnPrimaryContainer"
|
||||
android:ellipsize="marquee"
|
||||
android:fadingEdge="horizontal"
|
||||
android:hyphenationFrequency="normalFast"
|
||||
@@ -77,7 +78,7 @@
|
||||
android:layout_alignStart="@android:id/title"
|
||||
android:layout_alignLeft="@android:id/title"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textColor="?androidprv:attr/materialColorOnPrimaryContainer"
|
||||
android:hyphenationFrequency="normalFast"
|
||||
android:lineBreakWordStyle="phrase"
|
||||
android:maxLines="4"
|
||||
|
||||
@@ -17,10 +17,11 @@
|
||||
|
||||
<ImageView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:contentDescription="@null"
|
||||
android:scaleType="center"
|
||||
android:tint="?android:attr/textColorPrimary"
|
||||
android:tint="?androidprv:attr/materialColorOnPrimaryContainer"
|
||||
android:src="@drawable/ic_arrow_forward" />
|
||||
@@ -15,69 +15,41 @@
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<LinearLayout
|
||||
<androidx.core.widget.NestedScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="3"
|
||||
android:layout_marginBottom="55dp">
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:layout_margin="20dp">
|
||||
|
||||
<TextView
|
||||
android:id="@android:id/summary"
|
||||
style="@style/QrCodeScanner"
|
||||
android:gravity="center"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="40dp"
|
||||
android:paddingEnd="40dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
<TextView
|
||||
android:id="@android:id/summary"
|
||||
style="@style/QrCodeScanner"
|
||||
android:gravity="center"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
android:layout_margin="40dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="7"
|
||||
android:orientation="vertical">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top"
|
||||
android:gravity="center"
|
||||
android:clipChildren="true"
|
||||
<TextureView
|
||||
android:id="@+id/preview_view"
|
||||
android:layout_width="@dimen/audio_streams_qrcode_preview_size"
|
||||
android:layout_height="@dimen/audio_streams_qrcode_preview_size"
|
||||
android:contentDescription="@string/audio_streams_qr_code_scanner_label"
|
||||
android:focusable="true">
|
||||
<TextureView
|
||||
android:id="@+id/preview_view"
|
||||
android:layout_marginStart="@dimen/qrcode_preview_margin"
|
||||
android:layout_marginEnd="@dimen/qrcode_preview_margin"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/qrcode_preview_size"/>
|
||||
</FrameLayout>
|
||||
android:focusable="true" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/error_message"
|
||||
style="@style/TextAppearance.ErrorText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:gravity="center"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="invisible"/>
|
||||
|
||||
android:visibility="gone"/>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
|
||||
@@ -1555,26 +1555,26 @@
|
||||
<!-- Descriptions of the icons in zen_mode_icon_options. Should describe the associated image
|
||||
[CHAR LIMIT=NONE] -->
|
||||
<string-array name="zen_mode_icon_options_descriptions">
|
||||
<item>Bedtime</item>
|
||||
<item>Driving</item>
|
||||
<item>Immersive</item>
|
||||
<item>Half-moon</item>
|
||||
<item>Car</item>
|
||||
<item>Person\'s mind</item>
|
||||
<item>Calendar</item>
|
||||
<item>Time</item>
|
||||
<item>Beach</item>
|
||||
<item>Camping</item>
|
||||
<item>Theater</item>
|
||||
<item>Gaming</item>
|
||||
<item>Gym</item>
|
||||
<item>Ball sports</item>
|
||||
<item>Martial arts</item>
|
||||
<item>Clock</item>
|
||||
<item>Beach umbrella</item>
|
||||
<item>Tent</item>
|
||||
<item>Film reel</item>
|
||||
<item>Game controller</item>
|
||||
<item>Gym dumbbell</item>
|
||||
<item>Person throwing ball</item>
|
||||
<item>Person kicking</item>
|
||||
<item>Swimming</item>
|
||||
<item>Hiking</item>
|
||||
<item>Person hiking</item>
|
||||
<item>Golf</item>
|
||||
<item>Workshop</item>
|
||||
<item>Work</item>
|
||||
<item>Workshop tools</item>
|
||||
<item>Briefcase</item>
|
||||
<item>Star</item>
|
||||
<item>Zen</item>
|
||||
<item>Managed</item>
|
||||
<item>Lotus flower</item>
|
||||
<item>Supervisor</item>
|
||||
</string-array>
|
||||
|
||||
<!-- Packages that will not show Display over other apps permission -->
|
||||
|
||||
@@ -500,7 +500,9 @@
|
||||
<dimen name="contrast_button_horizontal_spacing">16dp</dimen>
|
||||
|
||||
<dimen name="audio_streams_qrcode_size">264dp</dimen>
|
||||
<dimen name="audio_streams_qrcode_preview_size">300dp</dimen>
|
||||
<dimen name="audio_streams_qrcode_preview_radius">30dp</dimen>
|
||||
<dimen name="audio_streams_qrcode_scanner_fragment_padding">16dp</dimen>
|
||||
|
||||
<!-- Zen Modes -->
|
||||
<dimen name="zen_mode_header_size">136dp</dimen>
|
||||
@@ -517,4 +519,6 @@
|
||||
<dimen name="zen_mode_circular_icon_margin_between">4dp</dimen>
|
||||
<dimen name="zen_mode_circular_icon_margin_vertical">8dp</dimen>
|
||||
<dimen name="zen_mode_circular_icon_text_size">18dp</dimen>
|
||||
<!-- For images in SetupInterstitialActivity -->
|
||||
<dimen name="zen_mode_interstitial_corner_radius">30dp</dimen>
|
||||
</resources>
|
||||
|
||||
@@ -40,6 +40,4 @@
|
||||
<!-- PointerIcon Settings -->
|
||||
<integer name="pointer_scale_seek_bar_start">0</integer>
|
||||
<integer name="pointer_scale_seek_bar_end">3</integer>
|
||||
|
||||
<integer name="max_integer">2147483647</integer>
|
||||
</resources>
|
||||
|
||||
@@ -409,7 +409,7 @@
|
||||
<string name="desc_app_locale_selection_supported">Set the language for each app.</string>
|
||||
|
||||
<!-- Description for the introduction to language picker activity. [CHAR LIMIT=NONE]-->
|
||||
<string name="desc_introduction_of_language_picker">Your system, apps, and websites use the first supported language from your preferred languages.</string>
|
||||
<string name="desc_introduction_of_language_picker">Your device, apps, and websites use the first supported language from your preferred language list.\n\nMany apps will also use the region from your preferred language to format dates, numbers, and units. To change your region, add a language, then choose your preferred region.</string>
|
||||
|
||||
<!-- Description for the notice of language picker. [CHAR LIMIT=NONE]-->
|
||||
<string name="desc_notice_of_language_picker">To select a language for each app, go to app language settings.</string>
|
||||
@@ -7984,6 +7984,26 @@
|
||||
<!-- Priority Modes: Option to choose a calendar-events-based schedule for a mode. [CHAR_LIMIT=40] -->
|
||||
<string name="zen_mode_select_schedule_calendar">Calendar events</string>
|
||||
|
||||
<!-- Priority Modes: Summary for the modes segment, when at least one mode is active. [CHAR LIMIT=NONE]-->
|
||||
<string name="zen_modes_summary_some_active">
|
||||
{count, plural, offset:2
|
||||
=0 {}
|
||||
=1 {{mode_1} is active}
|
||||
=2 {{mode_1} and {mode_2} are active}
|
||||
=3 {{mode_1}, {mode_2}, and {mode_3} are active}
|
||||
other {{mode_1}, {mode_2}, and # more are active}
|
||||
}
|
||||
</string>
|
||||
|
||||
<!-- Priority Modes: Summary for the modes segment, when no modes are active. [CHAR LIMIT=NONE]-->
|
||||
<string name="zen_modes_summary_none_active">
|
||||
{count, plural,
|
||||
=0 {}
|
||||
=1 {1 mode can turn on automatically}
|
||||
other {# modes can turn on automatically}
|
||||
}
|
||||
</string>
|
||||
|
||||
<!-- Priority Modes: Short text that indicates that a mode is currently on (active). [CHAR_LIMIT=10] -->
|
||||
<string name="zen_mode_active_text">ON</string>
|
||||
|
||||
@@ -8014,6 +8034,12 @@
|
||||
<!-- Subtitle for the Do not Disturb slice. [CHAR LIMIT=50]-->
|
||||
<string name="zen_mode_slice_subtitle">Limit interruptions</string>
|
||||
|
||||
<!-- Priority Modes: Summary on a page prompting the user to set up/enable a mode [CHAR_LIMIT=NONE] -->
|
||||
<string name="zen_mode_setup_page_summary">Block interruptions and distractions</string>
|
||||
|
||||
<!-- Priority Modes: Label on a button prompting the user to set up the mode with the given name. [CHAR_LIMIT=40] -->
|
||||
<string name="zen_mode_setup_button_label">Set up <xliff:g id="mode" example="My Mode">%1$s</xliff:g></string>
|
||||
|
||||
<!-- Do not disturb: Title for the Do not Disturb dialog to turn on Do not disturb. [CHAR LIMIT=50]-->
|
||||
<string name="zen_mode_settings_turn_on_dialog_title">Turn on Do Not Disturb</string>
|
||||
|
||||
@@ -13454,7 +13480,7 @@
|
||||
<!-- Label of the color option show in the summary of screen flash preference. [CHAR LIMIT=60] -->
|
||||
<string name="screen_flash_color_violet">Violet</string>
|
||||
<!-- Button to save the selection in screen flash color selection dialog. [CHAR LIMIT=20] -->
|
||||
<string name="color_selector_dialog_done">Done</string>
|
||||
<string name="color_selector_dialog_save">Save</string>
|
||||
<!-- Button to close the dialog without saving in screen flash color selection dialog. [CHAR LIMIT=20] -->
|
||||
<string name="color_selector_dialog_cancel">Cancel</string>
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
android:persistent="false"
|
||||
android:summary="@string/daltonizer_mode_deuteranomaly_summary"
|
||||
android:title="@string/daltonizer_mode_deuteranomaly_title"
|
||||
settings:titleMaxLines="@integer/max_integer"
|
||||
settings:controller="com.android.settings.accessibility.DaltonizerRadioButtonPreferenceController" />
|
||||
|
||||
<com.android.settingslib.widget.SelectorWithWidgetPreference
|
||||
@@ -41,7 +40,6 @@
|
||||
android:persistent="false"
|
||||
android:summary="@string/daltonizer_mode_protanomaly_summary"
|
||||
android:title="@string/daltonizer_mode_protanomaly_title"
|
||||
settings:titleMaxLines="@integer/max_integer"
|
||||
settings:controller="com.android.settings.accessibility.DaltonizerRadioButtonPreferenceController" />
|
||||
|
||||
<com.android.settingslib.widget.SelectorWithWidgetPreference
|
||||
@@ -49,14 +47,12 @@
|
||||
android:persistent="false"
|
||||
android:summary="@string/daltonizer_mode_tritanomaly_summary"
|
||||
android:title="@string/daltonizer_mode_tritanomaly_title"
|
||||
settings:titleMaxLines="@integer/max_integer"
|
||||
settings:controller="com.android.settings.accessibility.DaltonizerRadioButtonPreferenceController" />
|
||||
|
||||
<com.android.settingslib.widget.SelectorWithWidgetPreference
|
||||
android:key="daltonizer_mode_grayscale"
|
||||
android:persistent="false"
|
||||
android:title="@string/daltonizer_mode_grayscale_title"
|
||||
settings:titleMaxLines="@integer/max_integer"
|
||||
settings:controller="com.android.settings.accessibility.DaltonizerRadioButtonPreferenceController" />
|
||||
|
||||
<com.android.settings.widget.SeekBarPreference
|
||||
|
||||
@@ -105,6 +105,14 @@
|
||||
settings:keywords="@string/keywords_sounds_and_notifications_interruptions"
|
||||
settings:controller="com.android.settings.notification.zen.ZenModePreferenceController"/>
|
||||
|
||||
<com.android.settingslib.RestrictedPreference
|
||||
android:key="modes_notifications"
|
||||
android:order="-130"
|
||||
android:title="@string/zen_modes_list_title"
|
||||
settings:useAdminDisabledSummary="true"
|
||||
android:fragment="com.android.settings.notification.modes.ZenModesListFragment"
|
||||
settings:controller="com.android.settings.notification.modes.ZenModesLinkPreferenceController"/>
|
||||
|
||||
<!-- Phone ringtone -->
|
||||
<com.android.settings.DefaultRingtonePreference
|
||||
android:key="phone_ringtone"
|
||||
|
||||
@@ -110,7 +110,7 @@ public class FlashNotificationsPreviewPreferenceController extends
|
||||
if (preference == null) {
|
||||
return;
|
||||
}
|
||||
preference.setEnabled(FlashNotificationsUtil.getFlashNotificationsState(mContext)
|
||||
preference.setVisible(FlashNotificationsUtil.getFlashNotificationsState(mContext)
|
||||
!= FlashNotificationsUtil.State.OFF);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,8 +65,7 @@ public class ScreenFlashNotificationColorDialogFragment extends DialogFragment i
|
||||
final ScreenFlashNotificationColorDialogFragment result =
|
||||
new ScreenFlashNotificationColorDialogFragment();
|
||||
result.mCurrentColor = initialColor;
|
||||
result.mConsumer = colorConsumer != null ? colorConsumer : i -> {
|
||||
};
|
||||
result.mConsumer = colorConsumer != null ? colorConsumer : i -> {};
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -89,7 +88,7 @@ public class ScreenFlashNotificationColorDialogFragment extends DialogFragment i
|
||||
.setNeutralButton(R.string.flash_notifications_preview, null)
|
||||
.setNegativeButton(R.string.color_selector_dialog_cancel, (dialog, which) -> {
|
||||
})
|
||||
.setPositiveButton(R.string.color_selector_dialog_done, (dialog, which) -> {
|
||||
.setPositiveButton(R.string.color_selector_dialog_save, (dialog, which) -> {
|
||||
mCurrentColor = colorSelectorLayout.getCheckedColor(DEFAULT_SCREEN_FLASH_COLOR);
|
||||
mConsumer.accept(mCurrentColor);
|
||||
})
|
||||
|
||||
@@ -47,7 +47,7 @@ public class BluetoothDetailsHeaderController extends BluetoothDetailsController
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
boolean hasLeAudio = mCachedDevice.getConnectableProfiles()
|
||||
boolean hasLeAudio = mCachedDevice.getUiAccessibleProfiles()
|
||||
.stream()
|
||||
.anyMatch(profile -> profile.getProfileId() == BluetoothProfile.LE_AUDIO);
|
||||
return !BluetoothUtils.isAdvancedDetailsHeader(mCachedDevice.getDevice()) && !hasLeAudio;
|
||||
|
||||
@@ -314,7 +314,7 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll
|
||||
return result;
|
||||
}
|
||||
for (CachedBluetoothDevice cachedItem : mAllOfCachedDevices) {
|
||||
List<LocalBluetoothProfile> tmpResult = cachedItem.getConnectableProfiles();
|
||||
List<LocalBluetoothProfile> tmpResult = cachedItem.getUiAccessibleProfiles();
|
||||
for (LocalBluetoothProfile profile : tmpResult) {
|
||||
if (mProfileDeviceMap.containsKey(profile.toString())) {
|
||||
mProfileDeviceMap.get(profile.toString()).add(cachedItem);
|
||||
|
||||
@@ -107,7 +107,7 @@ public class LeAudioBluetoothDetailsHeaderController extends BasePreferenceContr
|
||||
if (mCachedDevice == null || mProfileManager == null) {
|
||||
return CONDITIONALLY_UNAVAILABLE;
|
||||
}
|
||||
boolean hasLeAudio = mCachedDevice.getConnectableProfiles()
|
||||
boolean hasLeAudio = mCachedDevice.getUiAccessibleProfiles()
|
||||
.stream()
|
||||
.anyMatch(profile -> profile.getProfileId() == BluetoothProfile.LE_AUDIO);
|
||||
|
||||
|
||||
@@ -437,7 +437,7 @@ public class AudioSharingDevicePreferenceController extends BasePreferenceContro
|
||||
}
|
||||
|
||||
private boolean isMediaDevice(CachedBluetoothDevice cachedDevice) {
|
||||
return cachedDevice.getConnectableProfiles().stream()
|
||||
return cachedDevice.getUiAccessibleProfiles().stream()
|
||||
.anyMatch(
|
||||
profile ->
|
||||
profile instanceof A2dpProfile
|
||||
|
||||
@@ -16,13 +16,9 @@
|
||||
|
||||
package com.android.settings.connecteddevice.audiosharing;
|
||||
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.bluetooth.BluetoothCsipSetCoordinator;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.content.Context;
|
||||
import android.media.AudioManager;
|
||||
import android.util.Log;
|
||||
import android.widget.SeekBar;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
@@ -40,7 +36,7 @@ import com.android.settingslib.bluetooth.VolumeControlProfile;
|
||||
public class AudioSharingDeviceVolumeControlUpdater extends BluetoothDeviceUpdater
|
||||
implements Preference.OnPreferenceClickListener {
|
||||
|
||||
private static final String TAG = "AudioSharingDeviceVolumeControlUpdater";
|
||||
private static final String TAG = "AudioSharingVolUpdater";
|
||||
|
||||
@VisibleForTesting
|
||||
static final String PREF_KEY_PREFIX = "audio_sharing_volume_control_";
|
||||
@@ -91,36 +87,9 @@ public class AudioSharingDeviceVolumeControlUpdater extends BluetoothDeviceUpdat
|
||||
if (cachedDevice == null) return;
|
||||
final BluetoothDevice device = cachedDevice.getDevice();
|
||||
if (!mPreferenceMap.containsKey(device)) {
|
||||
SeekBar.OnSeekBarChangeListener listener =
|
||||
new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(
|
||||
SeekBar seekBar, int progress, boolean fromUser) {}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
int progress = seekBar.getProgress();
|
||||
int groupId = BluetoothUtils.getGroupId(cachedDevice);
|
||||
if (groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID
|
||||
&& groupId
|
||||
== BluetoothUtils.getPrimaryGroupIdForBroadcast(
|
||||
mContext.getContentResolver())) {
|
||||
// Set media stream volume for primary buds, audio manager will
|
||||
// update all buds volume in the audio sharing.
|
||||
setAudioManagerStreamVolume(progress);
|
||||
} else {
|
||||
// Set buds volume for other buds.
|
||||
setDeviceVolume(cachedDevice, progress);
|
||||
}
|
||||
}
|
||||
};
|
||||
AudioSharingDeviceVolumePreference vPreference =
|
||||
new AudioSharingDeviceVolumePreference(mPrefContext, cachedDevice);
|
||||
vPreference.initialize();
|
||||
vPreference.setOnSeekBarChangeListener(listener);
|
||||
vPreference.setKey(getPreferenceKeyPrefix() + cachedDevice.hashCode());
|
||||
vPreference.setIcon(com.android.settingslib.R.drawable.ic_bt_untethered_earbuds);
|
||||
vPreference.setTitle(cachedDevice.getName());
|
||||
@@ -154,35 +123,4 @@ public class AudioSharingDeviceVolumeControlUpdater extends BluetoothDeviceUpdat
|
||||
|
||||
@Override
|
||||
public void refreshPreference() {}
|
||||
|
||||
private void setDeviceVolume(CachedBluetoothDevice cachedDevice, int progress) {
|
||||
if (mVolumeControl != null) {
|
||||
mVolumeControl.setDeviceVolume(
|
||||
cachedDevice.getDevice(), progress, /* isGroupOp= */ true);
|
||||
mMetricsFeatureProvider.action(
|
||||
mContext,
|
||||
SettingsEnums.ACTION_AUDIO_SHARING_CHANGE_MEDIA_DEVICE_VOLUME,
|
||||
/* isPrimary= */ false);
|
||||
}
|
||||
}
|
||||
|
||||
private void setAudioManagerStreamVolume(int progress) {
|
||||
int seekbarRange =
|
||||
AudioSharingDeviceVolumePreference.MAX_VOLUME
|
||||
- AudioSharingDeviceVolumePreference.MIN_VOLUME;
|
||||
try {
|
||||
AudioManager audioManager = mContext.getSystemService(AudioManager.class);
|
||||
int streamVolumeRange =
|
||||
audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
|
||||
- audioManager.getStreamMinVolume(AudioManager.STREAM_MUSIC);
|
||||
int volume = Math.round((float) progress * streamVolumeRange / seekbarRange);
|
||||
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
|
||||
mMetricsFeatureProvider.action(
|
||||
mContext,
|
||||
SettingsEnums.ACTION_AUDIO_SHARING_CHANGE_MEDIA_DEVICE_VOLUME,
|
||||
/* isPrimary= */ true);
|
||||
} catch (RuntimeException e) {
|
||||
Log.e(TAG, "Fail to setAudioManagerStreamVolumeForFallbackDevice, error = " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class AudioSharingDeviceVolumeGroupController extends AudioSharingBasePreferenceController
|
||||
implements DevicePreferenceCallback {
|
||||
private static final String TAG = "AudioSharingDeviceVolumeGroupController";
|
||||
private static final String TAG = "AudioSharingVolCtlr";
|
||||
private static final String KEY = "audio_sharing_device_volume_group";
|
||||
|
||||
@Nullable private final LocalBluetoothManager mBtManager;
|
||||
|
||||
@@ -16,27 +16,46 @@
|
||||
|
||||
package com.android.settings.connecteddevice.audiosharing;
|
||||
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.bluetooth.BluetoothCsipSetCoordinator;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.content.Context;
|
||||
import android.media.AudioManager;
|
||||
import android.util.Log;
|
||||
import android.widget.SeekBar;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.bluetooth.Utils;
|
||||
import com.android.settings.overlay.FeatureFactory;
|
||||
import com.android.settings.widget.SeekBarPreference;
|
||||
import com.android.settingslib.bluetooth.BluetoothUtils;
|
||||
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
|
||||
import com.android.settingslib.bluetooth.LocalBluetoothManager;
|
||||
import com.android.settingslib.bluetooth.VolumeControlProfile;
|
||||
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
|
||||
import com.android.settingslib.utils.ThreadUtils;
|
||||
|
||||
public class AudioSharingDeviceVolumePreference extends SeekBarPreference {
|
||||
private static final String TAG = "AudioSharingVolPref";
|
||||
|
||||
public static final int MIN_VOLUME = 0;
|
||||
public static final int MAX_VOLUME = 255;
|
||||
|
||||
private final Context mContext;
|
||||
private final CachedBluetoothDevice mCachedDevice;
|
||||
@Nullable protected SeekBar mSeekBar;
|
||||
private Boolean mTrackingTouch = false;
|
||||
private MetricsFeatureProvider mMetricsFeatureProvider =
|
||||
FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
|
||||
|
||||
public AudioSharingDeviceVolumePreference(
|
||||
Context context, @NonNull CachedBluetoothDevice device) {
|
||||
super(context);
|
||||
setLayoutResource(R.layout.preference_volume_slider);
|
||||
mContext = context;
|
||||
mCachedDevice = device;
|
||||
}
|
||||
|
||||
@@ -54,4 +73,95 @@ public class AudioSharingDeviceVolumePreference extends SeekBarPreference {
|
||||
setMax(MAX_VOLUME);
|
||||
setMin(MIN_VOLUME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
super.onProgressChanged(seekBar, progress, fromUser);
|
||||
// When user use talk back swipe up/down or use Switch Access to change the volume bar
|
||||
// progress, there is no onStopTrackingTouch triggered. So we need to check this scenario
|
||||
// and update the device volume here.
|
||||
if (fromUser && !mTrackingTouch) {
|
||||
Log.d(TAG, "onProgressChanged from user and not in touch, handleProgressChange.");
|
||||
handleProgressChange(progress);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
mTrackingTouch = true;
|
||||
super.onStartTrackingTouch(seekBar);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
mTrackingTouch = false;
|
||||
super.onStopTrackingTouch(seekBar);
|
||||
// When user touch the volume bar to change volume, we only update the device volume when
|
||||
// user stop touching the bar.
|
||||
Log.d(TAG, "onStopTrackingTouch, handleProgressChange.");
|
||||
handleProgressChange(seekBar.getProgress());
|
||||
}
|
||||
|
||||
private void handleProgressChange(int progress) {
|
||||
var unused =
|
||||
ThreadUtils.postOnBackgroundThread(
|
||||
() -> {
|
||||
int groupId = BluetoothUtils.getGroupId(mCachedDevice);
|
||||
if (groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID
|
||||
&& groupId
|
||||
== BluetoothUtils.getPrimaryGroupIdForBroadcast(
|
||||
mContext.getContentResolver())) {
|
||||
// Set media stream volume for primary buds, audio manager will
|
||||
// update all buds volume in the audio sharing.
|
||||
setAudioManagerStreamVolume(progress);
|
||||
} else {
|
||||
// Set buds volume for other buds.
|
||||
setDeviceVolume(mCachedDevice.getDevice(), progress);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setDeviceVolume(@Nullable BluetoothDevice device, int progress) {
|
||||
if (device == null) {
|
||||
Log.d(TAG, "Skip set device volume, device is null");
|
||||
return;
|
||||
}
|
||||
LocalBluetoothManager btManager = Utils.getLocalBtManager(mContext);
|
||||
VolumeControlProfile vc =
|
||||
btManager == null ? null : btManager.getProfileManager().getVolumeControlProfile();
|
||||
if (vc != null) {
|
||||
vc.setDeviceVolume(device, progress, /* isGroupOp= */ true);
|
||||
mMetricsFeatureProvider.action(
|
||||
mContext,
|
||||
SettingsEnums.ACTION_AUDIO_SHARING_CHANGE_MEDIA_DEVICE_VOLUME,
|
||||
/* isPrimary= */ false);
|
||||
Log.d(
|
||||
TAG,
|
||||
"set device volume, device = "
|
||||
+ device.getAnonymizedAddress()
|
||||
+ " volume = "
|
||||
+ progress);
|
||||
}
|
||||
}
|
||||
|
||||
private void setAudioManagerStreamVolume(int progress) {
|
||||
int seekbarRange =
|
||||
AudioSharingDeviceVolumePreference.MAX_VOLUME
|
||||
- AudioSharingDeviceVolumePreference.MIN_VOLUME;
|
||||
try {
|
||||
AudioManager audioManager = mContext.getSystemService(AudioManager.class);
|
||||
int streamVolumeRange =
|
||||
audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
|
||||
- audioManager.getStreamMinVolume(AudioManager.STREAM_MUSIC);
|
||||
int volume = Math.round((float) progress * streamVolumeRange / seekbarRange);
|
||||
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
|
||||
mMetricsFeatureProvider.action(
|
||||
mContext,
|
||||
SettingsEnums.ACTION_AUDIO_SHARING_CHANGE_MEDIA_DEVICE_VOLUME,
|
||||
/* isPrimary= */ true);
|
||||
Log.d(TAG, "set music stream volume, volume = " + progress);
|
||||
} catch (RuntimeException e) {
|
||||
Log.e(TAG, "Fail to setAudioManagerStreamVolumeForFallbackDevice, error = " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,10 +28,13 @@ import android.bluetooth.BluetoothLeBroadcastMetadata;
|
||||
import android.bluetooth.BluetoothLeBroadcastReceiveState;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.connecteddevice.audiosharing.AudioSharingUtils;
|
||||
import com.android.settingslib.bluetooth.BluetoothUtils;
|
||||
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
|
||||
@@ -40,6 +43,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager;
|
||||
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
|
||||
import com.android.settingslib.utils.ThreadUtils;
|
||||
|
||||
import com.google.android.material.appbar.AppBarLayout;
|
||||
import com.google.common.base.Strings;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -346,4 +350,16 @@ public class AudioStreamsHelper {
|
||||
intent.putParcelableArrayListExtra(DEVICES, new ArrayList<>(devices));
|
||||
context.startService(intent);
|
||||
}
|
||||
|
||||
static void configureAppBarByOrientation(@Nullable FragmentActivity activity) {
|
||||
if (activity != null) {
|
||||
AppBarLayout appBarLayout = activity.findViewById(R.id.app_bar);
|
||||
if (appBarLayout != null) {
|
||||
boolean canAppBarExpand =
|
||||
activity.getResources().getConfiguration().orientation
|
||||
== Configuration.ORIENTATION_PORTRAIT;
|
||||
appBarLayout.setExpanded(canAppBarExpand);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,8 @@ public class AudioStreamsQrCodeFragment extends InstrumentedFragment {
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
// Collapse or expand the app bar based on orientation for better display the qr code image.
|
||||
AudioStreamsHelper.configureAppBarByOrientation(getActivity());
|
||||
var unused = ThreadUtils.postOnBackgroundThread(
|
||||
() -> {
|
||||
BluetoothLeBroadcastMetadata broadcastMetadata = getBroadcastMetadata();
|
||||
|
||||
@@ -116,6 +116,8 @@ public class AudioStreamsQrCodeScanFragment extends InstrumentedFragment
|
||||
@Override
|
||||
public final View onCreateView(
|
||||
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
// Collapse or expand the app bar based on orientation for better display the qr camera.
|
||||
AudioStreamsHelper.configureAppBarByOrientation(getActivity());
|
||||
return inflater.inflate(
|
||||
R.layout.qrcode_scanner_fragment, container, /* attachToRoot */ false);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import android.util.SparseArray
|
||||
import android.util.SparseBooleanArray
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.core.util.keyIterator
|
||||
import androidx.core.util.valueIterator
|
||||
import com.android.settings.R
|
||||
import com.android.settings.datausage.lib.NetworkStatsRepository.Companion.Bucket
|
||||
import com.android.settingslib.AppItem
|
||||
@@ -49,33 +50,30 @@ class AppDataUsageRepository(
|
||||
|
||||
@VisibleForTesting
|
||||
fun getAppPercent(carrierId: Int?, buckets: List<Bucket>): List<Pair<AppItem, Int>> {
|
||||
val items = ArrayList<AppItem>()
|
||||
val knownItems = SparseArray<AppItem>()
|
||||
val profiles = context.userManager.userProfiles
|
||||
val userManager : UserManager = context.getSystemService(Context.USER_SERVICE) as UserManager
|
||||
val userIdToIsHiddenMap = profiles.associate { profile ->
|
||||
profile.identifier to shouldSkipProfile(userManager, profile)
|
||||
}
|
||||
bindStats(buckets, userIdToIsHiddenMap, knownItems, items)
|
||||
bindStats(buckets, userIdToIsHiddenMap, knownItems)
|
||||
val restrictedUids = context.getSystemService(NetworkPolicyManager::class.java)!!
|
||||
.getUidsWithPolicy(NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND)
|
||||
for (uid in restrictedUids) {
|
||||
// Only splice in restricted state for current user or managed users
|
||||
if (!profiles.contains(UserHandle.getUserHandleForUid(uid))) {
|
||||
continue
|
||||
}
|
||||
var item = knownItems[uid]
|
||||
if (item == null) {
|
||||
item = AppItem(uid)
|
||||
item.total = 0
|
||||
item.addUid(uid)
|
||||
items.add(item)
|
||||
knownItems.put(item.key, item)
|
||||
}
|
||||
if (UserHandle.getUserHandleForUid(uid) !in profiles) continue
|
||||
val item =
|
||||
knownItems[uid]
|
||||
?: AppItem(uid).apply {
|
||||
category = AppItem.CATEGORY_APP
|
||||
addUid(uid)
|
||||
knownItems.put(uid, this)
|
||||
}
|
||||
item.restricted = true
|
||||
}
|
||||
|
||||
val filteredItems = filterItems(carrierId, items).sorted()
|
||||
val filteredItems =
|
||||
filterItems(carrierId, knownItems.valueIterator().asSequence().toList()).sorted()
|
||||
val largest: Long = filteredItems.maxOfOrNull { it.total } ?: 0
|
||||
return filteredItems.map { item ->
|
||||
val percentTotal = if (largest > 0) (item.total * 100 / largest).toInt() else 0
|
||||
@@ -106,7 +104,6 @@ class AppDataUsageRepository(
|
||||
buckets: List<Bucket>,
|
||||
userIdToIsHiddenMap: Map<Int, Boolean>,
|
||||
knownItems: SparseArray<AppItem>,
|
||||
items: ArrayList<AppItem>,
|
||||
) {
|
||||
for (bucket in buckets) {
|
||||
// Decide how to collapse items together
|
||||
@@ -126,7 +123,6 @@ class AppDataUsageRepository(
|
||||
knownItems = knownItems,
|
||||
bucket = bucket,
|
||||
itemCategory = AppItem.CATEGORY_USER,
|
||||
items = items,
|
||||
)
|
||||
}
|
||||
collapseKey = getAppUid(uid)
|
||||
@@ -157,7 +153,6 @@ class AppDataUsageRepository(
|
||||
knownItems = knownItems,
|
||||
bucket = bucket,
|
||||
itemCategory = category,
|
||||
items = items,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -187,15 +182,13 @@ class AppDataUsageRepository(
|
||||
knownItems: SparseArray<AppItem>,
|
||||
bucket: Bucket,
|
||||
itemCategory: Int,
|
||||
items: ArrayList<AppItem>,
|
||||
) {
|
||||
var item = knownItems[collapseKey]
|
||||
if (item == null) {
|
||||
item = AppItem(collapseKey)
|
||||
item.category = itemCategory
|
||||
items.add(item)
|
||||
knownItems.put(item.key, item)
|
||||
}
|
||||
val item =
|
||||
knownItems[collapseKey]
|
||||
?: AppItem(collapseKey).apply {
|
||||
category = itemCategory
|
||||
knownItems.put(collapseKey, this)
|
||||
}
|
||||
item.addUid(bucket.uid)
|
||||
item.total += bucket.bytes
|
||||
}
|
||||
|
||||
@@ -92,8 +92,7 @@ public class BatteryHeaderPreferenceController extends BasePreferenceController
|
||||
&& mBatterySettingsFeatureProvider.isChargingOptimizationMode(mContext)) {
|
||||
return info.remainingLabel;
|
||||
}
|
||||
if (info.remainingLabel == null
|
||||
|| info.batteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING) {
|
||||
if (info.batteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING) {
|
||||
return info.statusLabel;
|
||||
}
|
||||
if (info.pluggedStatus == BatteryManager.BATTERY_PLUGGED_WIRELESS) {
|
||||
@@ -106,6 +105,9 @@ public class BatteryHeaderPreferenceController extends BasePreferenceController
|
||||
return wirelessChargingLabel;
|
||||
}
|
||||
}
|
||||
if (info.remainingLabel == null) {
|
||||
return info.statusLabel;
|
||||
}
|
||||
if (info.statusLabel != null && !info.discharging) {
|
||||
// Charging state
|
||||
if (com.android.settingslib.fuelgauge.BatteryUtils.isChargingStringV2Enabled()) {
|
||||
|
||||
@@ -0,0 +1,270 @@
|
||||
/*
|
||||
* 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.notification.modes;
|
||||
|
||||
import static android.graphics.drawable.GradientDrawable.LINEAR_GRADIENT;
|
||||
import static android.graphics.drawable.GradientDrawable.Orientation.BL_TR;
|
||||
import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
|
||||
|
||||
import android.app.ActionBar;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.GradientDrawable;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewOutlineProvider;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.widget.Button;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import android.widget.Toolbar;
|
||||
|
||||
import androidx.activity.EdgeToEdge;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.Utils;
|
||||
import com.android.settingslib.notification.modes.ZenMode;
|
||||
import com.android.settingslib.notification.modes.ZenModesBackend;
|
||||
|
||||
/**
|
||||
* Interstitial page for modes that are disabled, but not disabled by the user. This page
|
||||
* provides a button to enable the mode, and then goes to the mode setup page.
|
||||
*/
|
||||
public class SetupInterstitialActivity extends FragmentActivity {
|
||||
private static final String TAG = "ModeSetupInterstitial";
|
||||
private ZenModesBackend mBackend;
|
||||
|
||||
private final ViewOutlineProvider mOutlineProvider = new ViewOutlineProvider() {
|
||||
@Override
|
||||
public void getOutline(View view, Outline outline) {
|
||||
// Provides a rounded rectangle outline whose width & height matches the View.
|
||||
float cornerRadius = getResources().getDimensionPixelSize(
|
||||
R.dimen.zen_mode_interstitial_corner_radius);
|
||||
outline.setRoundRect(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight(),
|
||||
cornerRadius);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an intent leading to this page for the given mode and context.
|
||||
*/
|
||||
public static @NonNull Intent getIntent(@NonNull Context context, @NonNull ZenMode mode) {
|
||||
return new Intent(Intent.ACTION_MAIN)
|
||||
.setClass(context, SetupInterstitialActivity.class)
|
||||
.setPackage(context.getPackageName())
|
||||
.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT)
|
||||
.putExtra(EXTRA_AUTOMATIC_ZEN_RULE_ID, mode.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
EdgeToEdge.enable(this);
|
||||
Utils.setupEdgeToEdge(this);
|
||||
super.onCreate(savedInstanceState);
|
||||
mBackend = ZenModesBackend.getInstance(this);
|
||||
setContentView(R.layout.mode_interstitial_layout);
|
||||
|
||||
// Set up toolbar to only have a back button & no title
|
||||
Toolbar toolbar = findViewById(R.id.action_bar);
|
||||
setActionBar(toolbar);
|
||||
ActionBar actionBar = getActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
actionBar.setHomeButtonEnabled(true);
|
||||
actionBar.setDisplayShowTitleEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onNavigateUp() {
|
||||
// have the home button on the action bar go back
|
||||
getOnBackPressedDispatcher().onBackPressed();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
// See if we have mode data
|
||||
final Intent intent = getIntent();
|
||||
if (intent == null) {
|
||||
Log.w(TAG, "no intent found for modes interstitial");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
String modeId = intent.getStringExtra(EXTRA_AUTOMATIC_ZEN_RULE_ID);
|
||||
if (modeId == null) {
|
||||
Log.w(TAG, "no mode id included in intent: " + intent);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
ZenMode mode = mBackend.getMode(modeId);
|
||||
if (mode == null) {
|
||||
Log.w(TAG, "mode not found for mode id: " + modeId);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
setTitle(mode.getName());
|
||||
|
||||
TextView title = findViewById(R.id.mode_name_title);
|
||||
if (title != null) {
|
||||
title.setText(mode.getName());
|
||||
}
|
||||
|
||||
ImageView img = findViewById(R.id.image);
|
||||
if (img != null) {
|
||||
setImage(img, mode);
|
||||
}
|
||||
|
||||
Button button = findViewById(R.id.enable_mode_button);
|
||||
if (button != null) {
|
||||
setupButton(button, mode);
|
||||
}
|
||||
}
|
||||
|
||||
private void setImage(@NonNull ImageView img, @NonNull ZenMode mode) {
|
||||
img.setImageDrawable(getModeDrawable(mode));
|
||||
img.setClipToOutline(true);
|
||||
img.setOutlineProvider(mOutlineProvider);
|
||||
|
||||
FrameLayout frame = findViewById(R.id.image_frame);
|
||||
if (frame == null) {
|
||||
return;
|
||||
}
|
||||
if (img.getMeasuredWidth() == 0) {
|
||||
// set up to resize after the global layout occurs
|
||||
img.getViewTreeObserver().addOnGlobalLayoutListener(
|
||||
new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
img.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||
sizeImageToFrame(img, frame);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// measured already, resize it now
|
||||
sizeImageToFrame(img, frame);
|
||||
}
|
||||
}
|
||||
|
||||
private Drawable getModeDrawable(@NonNull ZenMode mode) {
|
||||
// TODO: b/332730534 - set actual images depending on mode type (asynchronously?)
|
||||
GradientDrawable placeholder = new GradientDrawable();
|
||||
placeholder.setSize(40, 60); // 4x6 rectangle, slightly taller than wide
|
||||
placeholder.setGradientType(LINEAR_GRADIENT);
|
||||
placeholder.setOrientation(BL_TR);
|
||||
placeholder.setColors(new int[]{Color.BLACK, Color.WHITE});
|
||||
return placeholder;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected void sizeImageToFrame(ImageView img, FrameLayout frame) {
|
||||
// width of the space we have available = overall size of frame - relevant padding
|
||||
int frameHeight =
|
||||
frame.getMeasuredHeight() - frame.getPaddingTop() - frame.getPaddingBottom();
|
||||
int frameWidth =
|
||||
frame.getMeasuredWidth() - frame.getPaddingLeft() - frame.getPaddingRight();
|
||||
|
||||
int imgHeight = img.getDrawable().getIntrinsicHeight();
|
||||
int imgWidth = img.getDrawable().getIntrinsicWidth();
|
||||
|
||||
// if any of these are 0, give up because we won't be able to do the relevant math (and
|
||||
// we probably don't have the relevant data set up)
|
||||
if (frameHeight == 0 || frameWidth == 0 || imgHeight == 0 || imgWidth == 0) {
|
||||
Log.w(TAG, "image or frame has invalid size parameters");
|
||||
return;
|
||||
}
|
||||
float frameHWRatio = ((float) frameHeight) / frameWidth;
|
||||
float imgHWRatio = ((float) imgHeight) / imgWidth;
|
||||
|
||||
// fit horizontal dimension if the frame has a taller ratio (height/width) than the image;
|
||||
// otherwise, fit the vertical direction
|
||||
boolean fitHorizontal = frameHWRatio > imgHWRatio;
|
||||
|
||||
ViewGroup.LayoutParams layoutParams = img.getLayoutParams();
|
||||
if (layoutParams == null) {
|
||||
Log.w(TAG, "image has null LayoutParams");
|
||||
return;
|
||||
}
|
||||
if (fitHorizontal) {
|
||||
layoutParams.width = frameWidth;
|
||||
float scaledHeight = imgHWRatio * frameWidth;
|
||||
layoutParams.height = (int) scaledHeight;
|
||||
} else {
|
||||
layoutParams.height = frameHeight;
|
||||
float scaledWidth = /* w/h ratio */ (1 / imgHWRatio) * frameHeight;
|
||||
layoutParams.width = (int) scaledWidth;
|
||||
}
|
||||
img.setLayoutParams(layoutParams);
|
||||
}
|
||||
|
||||
private void setupButton(Button button, @NonNull ZenMode mode) {
|
||||
button.setText(getString(R.string.zen_mode_setup_button_label, mode.getName()));
|
||||
button.setOnClickListener(enableButtonListener(mode.getId()));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
View.OnClickListener enableButtonListener(String modeId) {
|
||||
return unused -> {
|
||||
// When clicked, we first reload mode info in case it has changed in the interim,
|
||||
// then enable the mode and then send the user to the mode's configuration page.
|
||||
boolean updated = enableMode(modeId);
|
||||
|
||||
// Don't come back to this activity after sending the user to the modes page, if
|
||||
// they happen to go back. Forward the activity result in case we got here (indirectly)
|
||||
// from some app that is waiting for the result.
|
||||
if (updated) {
|
||||
ZenSubSettingLauncher.forMode(this, modeId)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT).launch();
|
||||
}
|
||||
finish();
|
||||
};
|
||||
}
|
||||
|
||||
// Enables the given mode after first refreshing its data from the backend. Returns true if
|
||||
// the update went through, and false if for some reason the mode wasn't found.
|
||||
private boolean enableMode(@NonNull String modeId) {
|
||||
if (mBackend == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ZenMode modeToUpdate = mBackend.getMode(modeId);
|
||||
if (modeToUpdate == null) {
|
||||
// tell the user the mode isn't found, for some reason
|
||||
Toast.makeText(this, R.string.zen_mode_rule_not_found_text, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
return false;
|
||||
}
|
||||
|
||||
modeToUpdate.getRule().setEnabled(true);
|
||||
mBackend.updateMode(modeToUpdate);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.settings.notification.modes;
|
||||
|
||||
import static com.android.settingslib.notification.modes.ZenMode.Status.DISABLED_BY_OTHER;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
@@ -25,6 +27,7 @@ import android.view.MenuItem;
|
||||
|
||||
import androidx.activity.ComponentActivity;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.view.MenuProvider;
|
||||
|
||||
import com.android.settings.R;
|
||||
@@ -41,6 +44,7 @@ public class ZenModeFragment extends ZenModeFragmentBase {
|
||||
private static final int DELETE_MODE = 2;
|
||||
|
||||
private ModeMenuProvider mModeMenuProvider;
|
||||
private boolean mSettingsObserverRegistered = false; // for ManualDurationPreferenceController
|
||||
|
||||
@Override
|
||||
protected int getPreferenceScreenResId() {
|
||||
@@ -82,9 +86,14 @@ public class ZenModeFragment extends ZenModeFragmentBase {
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
ZenMode mode = getMode();
|
||||
|
||||
// Consider redirecting to the interstitial if the mode is disabled (but not by the user).
|
||||
if (maybeRedirectToInterstitial(mode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set title for the entire screen
|
||||
ZenMode mode = getMode();
|
||||
ComponentActivity activity = getActivity();
|
||||
if (mode != null && activity != null) {
|
||||
activity.setTitle(mode.getName());
|
||||
@@ -94,14 +103,27 @@ public class ZenModeFragment extends ZenModeFragmentBase {
|
||||
|
||||
// allow duration preference controller to listen for settings changes
|
||||
use(ManualDurationPreferenceController.class).registerSettingsObserver();
|
||||
mSettingsObserverRegistered = true;
|
||||
}
|
||||
|
||||
private boolean maybeRedirectToInterstitial(@Nullable ZenMode mode) {
|
||||
if (mode == null || mode.getStatus() != DISABLED_BY_OTHER) {
|
||||
return false;
|
||||
}
|
||||
// don't come back here from the interstitial
|
||||
finish();
|
||||
mContext.startActivity(SetupInterstitialActivity.getIntent(mContext, mode));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
if (getActivity() != null) {
|
||||
if (getActivity() != null && mModeMenuProvider != null) {
|
||||
getActivity().removeMenuProvider(mModeMenuProvider);
|
||||
}
|
||||
use(ManualDurationPreferenceController.class).unregisterSettingsObserver();
|
||||
if (mSettingsObserverRegistered) {
|
||||
use(ManualDurationPreferenceController.class).unregisterSettingsObserver();
|
||||
}
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
|
||||
@@ -42,13 +42,10 @@ import static android.service.notification.ZenPolicy.VISUAL_EFFECT_STATUS_BAR;
|
||||
|
||||
import android.content.Context;
|
||||
import android.icu.text.MessageFormat;
|
||||
import android.provider.Settings;
|
||||
import android.service.notification.ZenDeviceEffects;
|
||||
import android.service.notification.ZenModeConfig;
|
||||
import android.service.notification.ZenPolicy;
|
||||
import android.service.notification.ZenPolicy.ConversationSenders;
|
||||
import android.service.notification.ZenPolicy.PeopleType;
|
||||
import android.util.ArrayMap;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -496,32 +493,36 @@ class ZenModeSummaryHelper {
|
||||
return msgFormat.format(args);
|
||||
}
|
||||
|
||||
String getSoundSummary(int zenMode, ZenModeConfig config) {
|
||||
if (zenMode != Settings.Global.ZEN_MODE_OFF) {
|
||||
String description = ZenModeConfig.getDescription(mContext, true, config, false);
|
||||
String getModesSummary(List<ZenMode> modes) {
|
||||
List<ZenMode> activeModes = modes.stream().filter(ZenMode::isActive).toList();
|
||||
|
||||
if (description == null) {
|
||||
return mContext.getString(R.string.zen_mode_sound_summary_on);
|
||||
} else {
|
||||
return mContext.getString(R.string.zen_mode_sound_summary_on_with_info,
|
||||
description);
|
||||
}
|
||||
} else {
|
||||
int count = 0;
|
||||
final ArrayMap<String, ZenModeConfig.ZenRule> ruleMap = config.automaticRules;
|
||||
if (ruleMap != null) {
|
||||
for (ZenModeConfig.ZenRule rule : ruleMap.values()) {
|
||||
if (rule != null && rule.enabled) {
|
||||
count++;
|
||||
}
|
||||
if (!activeModes.isEmpty()) {
|
||||
MessageFormat msgFormat = new MessageFormat(
|
||||
mContext.getString(R.string.zen_modes_summary_some_active),
|
||||
Locale.getDefault());
|
||||
|
||||
Map<String, Object> args = new HashMap<>();
|
||||
args.put("count", activeModes.size());
|
||||
args.put("mode_1", activeModes.get(0).getName());
|
||||
if (activeModes.size() >= 2) {
|
||||
args.put("mode_2", activeModes.get(1).getName());
|
||||
if (activeModes.size() == 3) {
|
||||
args.put("mode_3", activeModes.get(2).getName());
|
||||
}
|
||||
}
|
||||
|
||||
return msgFormat.format(args);
|
||||
} else {
|
||||
int automaticModeCount = (int) modes.stream()
|
||||
.filter(m -> m.isEnabled() && !m.isManualDnd() && !m.isCustomManual())
|
||||
.count();
|
||||
|
||||
MessageFormat msgFormat = new MessageFormat(
|
||||
mContext.getString(R.string.modes_sound_summary_off),
|
||||
mContext.getString(R.string.zen_modes_summary_none_active),
|
||||
Locale.getDefault());
|
||||
Map<String, Object> msgArgs = new HashMap<>();
|
||||
msgArgs.put("count", count);
|
||||
Map<String, Object> msgArgs = Map.of("count", automaticModeCount);
|
||||
return msgFormat.format(msgArgs);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,35 +17,31 @@
|
||||
package com.android.settings.notification.modes;
|
||||
|
||||
import android.app.Flags;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.ContentObserver;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.core.BasePreferenceController;
|
||||
import com.android.settingslib.core.lifecycle.LifecycleObserver;
|
||||
import com.android.settingslib.core.lifecycle.events.OnPause;
|
||||
import com.android.settingslib.core.lifecycle.events.OnResume;
|
||||
import com.android.settingslib.core.lifecycle.events.OnStart;
|
||||
import com.android.settingslib.core.lifecycle.events.OnStop;
|
||||
import com.android.settingslib.notification.modes.ZenModesBackend;
|
||||
|
||||
public class ZenModesLinkPreferenceController extends BasePreferenceController
|
||||
implements LifecycleObserver, OnResume, OnPause {
|
||||
implements LifecycleObserver, OnStart, OnStop {
|
||||
|
||||
private SettingObserver mSettingObserver;
|
||||
private ZenModeSummaryHelper mSummaryBuilder;
|
||||
private NotificationManager mNm;
|
||||
private final ZenModesBackend mBackend;
|
||||
private final ZenSettingsObserver mSettingObserver;
|
||||
private final ZenModeSummaryHelper mSummaryBuilder;
|
||||
|
||||
private Preference mPreference;
|
||||
|
||||
public ZenModesLinkPreferenceController(Context context, String key) {
|
||||
super(context, key);
|
||||
mBackend = ZenModesBackend.getInstance(context);
|
||||
mSummaryBuilder = new ZenModeSummaryHelper(context, ZenHelperBackend.getInstance(context));
|
||||
mNm = mContext.getSystemService(NotificationManager.class);
|
||||
mSettingObserver = new ZenSettingsObserver(context, this::onZenSettingsChanged);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -57,64 +53,31 @@ public class ZenModesLinkPreferenceController extends BasePreferenceController
|
||||
@Override
|
||||
public void displayPreference(PreferenceScreen screen) {
|
||||
super.displayPreference(screen);
|
||||
Preference preference = screen.findPreference(getPreferenceKey());
|
||||
mSettingObserver = new SettingObserver(preference);
|
||||
mPreference = screen.findPreference(getPreferenceKey());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
public void onStart() {
|
||||
if (mSettingObserver != null) {
|
||||
mSettingObserver.register(mContext.getContentResolver());
|
||||
mSettingObserver.register();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
if (mSettingObserver != null) {
|
||||
mSettingObserver.unregister(mContext.getContentResolver());
|
||||
private void onZenSettingsChanged() {
|
||||
if (mPreference != null) {
|
||||
updateState(mPreference);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateState(Preference preference) {
|
||||
preference.setSummary(mSummaryBuilder.getSoundSummary(
|
||||
Settings.Global.getInt(mContext.getContentResolver(),
|
||||
Settings.Global.ZEN_MODE,
|
||||
Settings.Global.ZEN_MODE_OFF),
|
||||
mNm.getZenModeConfig()));
|
||||
preference.setSummary(mSummaryBuilder.getModesSummary(mBackend.getModes()));
|
||||
}
|
||||
|
||||
class SettingObserver extends ContentObserver {
|
||||
private final Uri ZEN_MODE_URI = Settings.Global.getUriFor(Settings.Global.ZEN_MODE);
|
||||
private final Uri ZEN_MODE_CONFIG_ETAG_URI = Settings.Global.getUriFor(
|
||||
Settings.Global.ZEN_MODE_CONFIG_ETAG);
|
||||
|
||||
private final Preference mPreference;
|
||||
|
||||
public SettingObserver(Preference preference) {
|
||||
super(new Handler());
|
||||
mPreference = preference;
|
||||
}
|
||||
|
||||
public void register(ContentResolver cr) {
|
||||
cr.registerContentObserver(ZEN_MODE_URI, false, this, UserHandle.USER_ALL);
|
||||
cr.registerContentObserver(ZEN_MODE_CONFIG_ETAG_URI, false, this, UserHandle.USER_ALL);
|
||||
}
|
||||
|
||||
public void unregister(ContentResolver cr) {
|
||||
cr.unregisterContentObserver(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange(boolean selfChange, Uri uri) {
|
||||
super.onChange(selfChange, uri);
|
||||
if (ZEN_MODE_URI.equals(uri)) {
|
||||
updateState(mPreference);
|
||||
}
|
||||
|
||||
if (ZEN_MODE_CONFIG_ETAG_URI.equals(uri)) {
|
||||
updateState(mPreference);
|
||||
}
|
||||
@Override
|
||||
public void onStop() {
|
||||
if (mSettingObserver != null) {
|
||||
mSettingObserver.unregister();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,17 +67,23 @@ class ZenModesListItemPreference extends RestrictedPreference {
|
||||
}
|
||||
|
||||
public void setZenMode(ZenMode zenMode) {
|
||||
ZenMode previous = mZenMode;
|
||||
mZenMode = zenMode;
|
||||
if (zenMode.equals(previous)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTitle(mZenMode.getName());
|
||||
String dynamicDescription = zenMode.getDynamicDescription(mContext);
|
||||
CharSequence statusText = switch (mZenMode.getStatus()) {
|
||||
case ENABLED_AND_ACTIVE ->
|
||||
Strings.isNullOrEmpty(mZenMode.getTriggerDescription())
|
||||
Strings.isNullOrEmpty(dynamicDescription)
|
||||
? mContext.getString(R.string.zen_mode_active_text)
|
||||
: mContext.getString(
|
||||
R.string.zen_mode_format_status_and_trigger,
|
||||
mContext.getString(R.string.zen_mode_active_text),
|
||||
mZenMode.getRule().getTriggerDescription());
|
||||
case ENABLED -> mZenMode.getRule().getTriggerDescription();
|
||||
dynamicDescription);
|
||||
case ENABLED -> dynamicDescription;
|
||||
case DISABLED_BY_USER -> mContext.getString(R.string.zen_mode_disabled_by_user);
|
||||
case DISABLED_BY_OTHER -> mContext.getString(R.string.zen_mode_disabled_needs_setup);
|
||||
};
|
||||
|
||||
@@ -64,6 +64,7 @@ import com.android.internal.widget.LockPatternUtils;
|
||||
import com.android.internal.widget.LockscreenCredential;
|
||||
import com.android.internal.widget.TextViewInputDisabler;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.SetupRedactionInterstitial;
|
||||
import com.android.settingslib.animation.AppearAnimationUtils;
|
||||
import com.android.settingslib.animation.DisappearAnimationUtils;
|
||||
|
||||
@@ -723,6 +724,7 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
|
||||
Intent result = new Intent();
|
||||
if (mRemoteValidation && containsGatekeeperPasswordHandle(resultData)) {
|
||||
result.putExtra(EXTRA_KEY_GK_PW_HANDLE, getGatekeeperPasswordHandle(resultData));
|
||||
SetupRedactionInterstitial.setEnabled(getContext(), true);
|
||||
}
|
||||
mCredentialCheckResultTracker.setResult(/* matched= */ true, result,
|
||||
/* timeoutMs= */ 0, mEffectiveUserId);
|
||||
|
||||
@@ -54,6 +54,7 @@ import com.android.internal.widget.LockPatternView;
|
||||
import com.android.internal.widget.LockPatternView.Cell;
|
||||
import com.android.internal.widget.LockscreenCredential;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.SetupRedactionInterstitial;
|
||||
import com.android.settingslib.animation.AppearAnimationCreator;
|
||||
import com.android.settingslib.animation.AppearAnimationUtils;
|
||||
import com.android.settingslib.animation.DisappearAnimationUtils;
|
||||
@@ -751,6 +752,7 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
|
||||
Intent result = new Intent();
|
||||
if (mRemoteValidation && containsGatekeeperPasswordHandle(resultData)) {
|
||||
result.putExtra(EXTRA_KEY_GK_PW_HANDLE, getGatekeeperPasswordHandle(resultData));
|
||||
SetupRedactionInterstitial.setEnabled(getContext(), true);
|
||||
}
|
||||
mCredentialCheckResultTracker.setResult(/* matched= */ true, result,
|
||||
/* timeoutMs= */ 0, mEffectiveUserId);
|
||||
|
||||
@@ -67,22 +67,23 @@ public class FlashNotificationsPreviewPreferenceControllerTest {
|
||||
public MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||
@Spy
|
||||
private final Context mContext = ApplicationProvider.getApplicationContext();
|
||||
@Mock
|
||||
private PreferenceScreen mPreferenceScreen;
|
||||
@Mock
|
||||
private Preference mPreference;
|
||||
@Spy
|
||||
private ContentResolver mContentResolver = mContext.getContentResolver();
|
||||
|
||||
@Mock
|
||||
private PreferenceScreen mPreferenceScreen;
|
||||
private Preference mPreference;
|
||||
private FlashNotificationsPreviewPreferenceController mController;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
when(mPreferenceScreen.findPreference(PREFERENCE_KEY)).thenReturn(mPreference);
|
||||
when(mPreference.getKey()).thenReturn(PREFERENCE_KEY);
|
||||
|
||||
when(mContext.getContentResolver()).thenReturn(mContentResolver);
|
||||
|
||||
mPreference = new Preference(mContext);
|
||||
mPreference.setKey(PREFERENCE_KEY);
|
||||
when(mPreferenceScreen.findPreference(PREFERENCE_KEY)).thenReturn(mPreference);
|
||||
|
||||
mController = new FlashNotificationsPreviewPreferenceController(mContext, PREFERENCE_KEY);
|
||||
}
|
||||
|
||||
@@ -97,40 +98,45 @@ public class FlashNotificationsPreviewPreferenceControllerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisplayPreference_torchPresent_cameraOff_screenOff_verifyDisabled() {
|
||||
public void testDisplayPreference_torchPresent_cameraOff_screenOff_notVisible() {
|
||||
setFlashNotificationsState(FlashNotificationsUtil.State.OFF);
|
||||
|
||||
mController.displayPreference(mPreferenceScreen);
|
||||
verify(mPreference).setEnabled(eq(false));
|
||||
|
||||
assertThat(mPreference.isVisible()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisplayPreference_torchPresent_cameraOn_screenOff_verifyEnabled() {
|
||||
public void testDisplayPreference_torchPresent_cameraOn_screenOff_isVisible() {
|
||||
setFlashNotificationsState(FlashNotificationsUtil.State.CAMERA);
|
||||
|
||||
mController.displayPreference(mPreferenceScreen);
|
||||
verify(mPreference).setEnabled(eq(true));
|
||||
|
||||
assertThat(mPreference.isVisible()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisplayPreference_torchPresent_cameraOff_screenOn_verifyEnabled() {
|
||||
public void testDisplayPreference_torchPresent_cameraOff_screenOn_isVisible() {
|
||||
setFlashNotificationsState(FlashNotificationsUtil.State.SCREEN);
|
||||
|
||||
mController.displayPreference(mPreferenceScreen);
|
||||
verify(mPreference).setEnabled(eq(true));
|
||||
|
||||
assertThat(mPreference.isVisible()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisplayPreference_torchPresent_cameraOn_screenOn_verifyEnabled() {
|
||||
public void testDisplayPreference_torchPresent_cameraOn_screenOn_isVisible() {
|
||||
setFlashNotificationsState(FlashNotificationsUtil.State.CAMERA_SCREEN);
|
||||
|
||||
mController.displayPreference(mPreferenceScreen);
|
||||
verify(mPreference).setEnabled(eq(true));
|
||||
|
||||
assertThat(mPreference.isVisible()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandlePreferenceTreeClick_invalidPreference() {
|
||||
mController.handlePreferenceTreeClick(mock(Preference.class));
|
||||
|
||||
verify(mContext, never()).sendBroadcastAsUser(any(), any());
|
||||
}
|
||||
|
||||
@@ -160,6 +166,7 @@ public class FlashNotificationsPreviewPreferenceControllerTest {
|
||||
@Test
|
||||
public void onStateChanged_onResume_cameraUri_verifyRegister() {
|
||||
mController.onStateChanged(mock(LifecycleOwner.class), Lifecycle.Event.ON_RESUME);
|
||||
|
||||
verify(mContentResolver).registerContentObserver(
|
||||
eq(Settings.System.getUriFor(Settings.System.CAMERA_FLASH_NOTIFICATION)),
|
||||
anyBoolean(), eq(mController.mContentObserver));
|
||||
@@ -168,6 +175,7 @@ public class FlashNotificationsPreviewPreferenceControllerTest {
|
||||
@Test
|
||||
public void onStateChanged_onResume_screenUri_verifyRegister() {
|
||||
mController.onStateChanged(mock(LifecycleOwner.class), Lifecycle.Event.ON_RESUME);
|
||||
|
||||
verify(mContentResolver).registerContentObserver(
|
||||
eq(Settings.System.getUriFor(Settings.System.SCREEN_FLASH_NOTIFICATION)),
|
||||
anyBoolean(), eq(mController.mContentObserver));
|
||||
@@ -176,6 +184,7 @@ public class FlashNotificationsPreviewPreferenceControllerTest {
|
||||
@Test
|
||||
public void onStateChanged_onPause_verifyUnregister() {
|
||||
mController.onStateChanged(mock(LifecycleOwner.class), Lifecycle.Event.ON_PAUSE);
|
||||
|
||||
verify(mContentResolver).unregisterContentObserver(eq(mController.mContentObserver));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ public class BluetoothDetailsProfilesControllerTest extends BluetoothDetailsCont
|
||||
setUpMockProfiles();
|
||||
when(mCachedBluetoothDeviceManager.getCachedDevicesCopy())
|
||||
.thenReturn(ImmutableList.of(mCachedDevice));
|
||||
when(mCachedDevice.getConnectableProfiles())
|
||||
when(mCachedDevice.getUiAccessibleProfiles())
|
||||
.thenAnswer(invocation -> new ArrayList<>(mConnectableProfiles));
|
||||
when(mCachedDevice.getProfiles())
|
||||
.thenAnswer(invocation -> ImmutableList.of(mConnectableProfiles));
|
||||
|
||||
@@ -303,7 +303,7 @@ public class AudioSharingDevicePreferenceControllerTest {
|
||||
|
||||
@Test
|
||||
public void onProfileConnectionStateChanged_notMediaDevice_doNothing() {
|
||||
doReturn(ImmutableList.of()).when(mCachedDevice).getConnectableProfiles();
|
||||
doReturn(ImmutableList.of()).when(mCachedDevice).getUiAccessibleProfiles();
|
||||
mController.onProfileConnectionStateChanged(
|
||||
mCachedDevice, BluetoothAdapter.STATE_CONNECTED, BluetoothProfile.HID_DEVICE);
|
||||
verifyNoInteractions(mDialogHandler);
|
||||
@@ -313,7 +313,7 @@ public class AudioSharingDevicePreferenceControllerTest {
|
||||
public void onProfileConnectionStateChanged_leaDeviceDisconnected_closeOpeningDialogsForIt() {
|
||||
// Test when LEA device LE_AUDIO_BROADCAST_ASSISTANT disconnected.
|
||||
when(mDevice.isConnected()).thenReturn(true);
|
||||
doReturn(ImmutableList.of(mLeAudioProfile)).when(mCachedDevice).getConnectableProfiles();
|
||||
doReturn(ImmutableList.of(mLeAudioProfile)).when(mCachedDevice).getUiAccessibleProfiles();
|
||||
doReturn(ImmutableList.of(mLeAudioProfile)).when(mCachedDevice).getProfiles();
|
||||
mController.onProfileConnectionStateChanged(
|
||||
mCachedDevice,
|
||||
@@ -325,7 +325,7 @@ public class AudioSharingDevicePreferenceControllerTest {
|
||||
@Test
|
||||
public void onProfileConnectionStateChanged_assistantProfileConnecting_doNothing() {
|
||||
// Test when LEA device LE_AUDIO_BROADCAST_ASSISTANT connecting
|
||||
doReturn(ImmutableList.of(mLeAudioProfile)).when(mCachedDevice).getConnectableProfiles();
|
||||
doReturn(ImmutableList.of(mLeAudioProfile)).when(mCachedDevice).getUiAccessibleProfiles();
|
||||
doReturn(ImmutableList.of(mLeAudioProfile)).when(mCachedDevice).getProfiles();
|
||||
mController.onProfileConnectionStateChanged(
|
||||
mCachedDevice,
|
||||
@@ -338,7 +338,7 @@ public class AudioSharingDevicePreferenceControllerTest {
|
||||
public void onProfileConnectionStateChanged_otherProfileConnected_doNothing() {
|
||||
// Test when LEA device other profile connected
|
||||
when(mDevice.isConnected()).thenReturn(true);
|
||||
doReturn(ImmutableList.of(mLeAudioProfile)).when(mCachedDevice).getConnectableProfiles();
|
||||
doReturn(ImmutableList.of(mLeAudioProfile)).when(mCachedDevice).getUiAccessibleProfiles();
|
||||
doReturn(ImmutableList.of(mLeAudioProfile)).when(mCachedDevice).getProfiles();
|
||||
mController.onProfileConnectionStateChanged(
|
||||
mCachedDevice, BluetoothAdapter.STATE_CONNECTED, BluetoothProfile.A2DP);
|
||||
@@ -349,7 +349,7 @@ public class AudioSharingDevicePreferenceControllerTest {
|
||||
public void onProfileConnectionStateChanged_otherProfileConnecting_doNothing() {
|
||||
// Test when LEA device other profile connecting
|
||||
when(mDevice.isConnected()).thenReturn(true);
|
||||
doReturn(ImmutableList.of(mLeAudioProfile)).when(mCachedDevice).getConnectableProfiles();
|
||||
doReturn(ImmutableList.of(mLeAudioProfile)).when(mCachedDevice).getUiAccessibleProfiles();
|
||||
doReturn(ImmutableList.of(mLeAudioProfile)).when(mCachedDevice).getProfiles();
|
||||
mController.onProfileConnectionStateChanged(
|
||||
mCachedDevice, BluetoothAdapter.STATE_CONNECTING, BluetoothProfile.A2DP);
|
||||
@@ -360,7 +360,7 @@ public class AudioSharingDevicePreferenceControllerTest {
|
||||
public void onProfileConnectionStateChanged_assistantProfileConnected_handle() {
|
||||
// Test when LEA device LE_AUDIO_BROADCAST_ASSISTANT connected
|
||||
when(mDevice.isConnected()).thenReturn(true);
|
||||
doReturn(ImmutableList.of(mLeAudioProfile)).when(mCachedDevice).getConnectableProfiles();
|
||||
doReturn(ImmutableList.of(mLeAudioProfile)).when(mCachedDevice).getUiAccessibleProfiles();
|
||||
doReturn(ImmutableList.of(mLeAudioProfile)).when(mCachedDevice).getProfiles();
|
||||
mController.onProfileConnectionStateChanged(
|
||||
mCachedDevice,
|
||||
@@ -374,7 +374,7 @@ public class AudioSharingDevicePreferenceControllerTest {
|
||||
onProfileConnectionStateChanged_nonLeaDeviceDisconnected_closeOpeningDialogsForIt() {
|
||||
// Test when non-LEA device totally disconnected
|
||||
when(mLeAudioProfile.isEnabled(mDevice)).thenReturn(false);
|
||||
doReturn(ImmutableList.of(mA2dpProfile)).when(mCachedDevice).getConnectableProfiles();
|
||||
doReturn(ImmutableList.of(mA2dpProfile)).when(mCachedDevice).getUiAccessibleProfiles();
|
||||
doReturn(ImmutableList.of(mLeAudioProfile, mA2dpProfile)).when(mCachedDevice).getProfiles();
|
||||
when(mCachedDevice.isConnected()).thenReturn(false);
|
||||
mController.onProfileConnectionStateChanged(
|
||||
@@ -390,7 +390,7 @@ public class AudioSharingDevicePreferenceControllerTest {
|
||||
.thenReturn(BluetoothAdapter.STATE_CONNECTED);
|
||||
doReturn(ImmutableList.of(mA2dpProfile, mHeadsetProfile))
|
||||
.when(mCachedDevice)
|
||||
.getConnectableProfiles();
|
||||
.getUiAccessibleProfiles();
|
||||
doReturn(ImmutableList.of(mA2dpProfile, mHeadsetProfile)).when(mCachedDevice).getProfiles();
|
||||
mController.onProfileConnectionStateChanged(
|
||||
mCachedDevice, BluetoothAdapter.STATE_CONNECTED, BluetoothProfile.A2DP);
|
||||
@@ -405,7 +405,7 @@ public class AudioSharingDevicePreferenceControllerTest {
|
||||
.thenReturn(BluetoothAdapter.STATE_DISCONNECTED);
|
||||
doReturn(ImmutableList.of(mA2dpProfile, mHeadsetProfile))
|
||||
.when(mCachedDevice)
|
||||
.getConnectableProfiles();
|
||||
.getUiAccessibleProfiles();
|
||||
doReturn(ImmutableList.of(mA2dpProfile, mHeadsetProfile)).when(mCachedDevice).getProfiles();
|
||||
mController.onProfileConnectionStateChanged(
|
||||
mCachedDevice, BluetoothAdapter.STATE_CONNECTED, BluetoothProfile.A2DP);
|
||||
|
||||
@@ -31,15 +31,11 @@ import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.robolectric.Shadows.shadowOf;
|
||||
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothLeBroadcastReceiveState;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.content.Context;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Looper;
|
||||
import android.provider.Settings;
|
||||
import android.widget.SeekBar;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
@@ -47,7 +43,6 @@ import androidx.test.core.app.ApplicationProvider;
|
||||
import com.android.settings.bluetooth.BluetoothDevicePreference;
|
||||
import com.android.settings.bluetooth.Utils;
|
||||
import com.android.settings.connecteddevice.DevicePreferenceCallback;
|
||||
import com.android.settings.testutils.FakeFeatureFactory;
|
||||
import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
|
||||
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
|
||||
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
|
||||
@@ -55,7 +50,6 @@ import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
|
||||
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
|
||||
import com.android.settingslib.bluetooth.LocalBluetoothManager;
|
||||
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
|
||||
import com.android.settingslib.bluetooth.VolumeControlProfile;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
@@ -80,13 +74,7 @@ import java.util.List;
|
||||
@Config(shadows = {ShadowBluetoothUtils.class})
|
||||
public class AudioSharingDeviceVolumeControlUpdaterTest {
|
||||
private static final String TEST_DEVICE_NAME = "test";
|
||||
private static final String TAG = "AudioSharingDeviceVolumeControlUpdater";
|
||||
private static final String TEST_SETTINGS_KEY =
|
||||
"bluetooth_le_broadcast_fallback_active_group_id";
|
||||
private static final int TEST_DEVICE_GROUP_ID = 1;
|
||||
private static final int TEST_VOLUME_VALUE = 255;
|
||||
private static final int TEST_MAX_STREAM_VALUE = 10;
|
||||
private static final int TEST_MIN_STREAM_VALUE = 0;
|
||||
private static final String TAG = "AudioSharingVolUpdater";
|
||||
|
||||
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||
|
||||
@@ -98,39 +86,32 @@ public class AudioSharingDeviceVolumeControlUpdaterTest {
|
||||
@Mock private LocalBluetoothProfileManager mLocalBtProfileManager;
|
||||
@Mock private LocalBluetoothLeBroadcast mBroadcast;
|
||||
@Mock private LocalBluetoothLeBroadcastAssistant mAssistant;
|
||||
@Mock private VolumeControlProfile mVolumeControl;
|
||||
@Mock private BluetoothLeBroadcastReceiveState mState;
|
||||
@Mock private AudioManager mAudioManager;
|
||||
|
||||
private Context mContext;
|
||||
private AudioSharingDeviceVolumeControlUpdater mDeviceUpdater;
|
||||
private Collection<CachedBluetoothDevice> mCachedDevices;
|
||||
private FakeFeatureFactory mFeatureFactory;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mContext = spy(ApplicationProvider.getApplicationContext());
|
||||
mContext = ApplicationProvider.getApplicationContext();
|
||||
ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBtManager;
|
||||
mLocalBtManager = Utils.getLocalBtManager(mContext);
|
||||
mFeatureFactory = FakeFeatureFactory.setupForTest();
|
||||
when(mLocalBtManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager);
|
||||
when(mLocalBtManager.getProfileManager()).thenReturn(mLocalBtProfileManager);
|
||||
when(mLocalBtProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast);
|
||||
when(mLocalBtProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(mAssistant);
|
||||
when(mLocalBtProfileManager.getVolumeControlProfile()).thenReturn(mVolumeControl);
|
||||
List<Long> bisSyncState = new ArrayList<>();
|
||||
bisSyncState.add(1L);
|
||||
when(mState.getBisSyncState()).thenReturn(bisSyncState);
|
||||
doReturn(TEST_DEVICE_NAME).when(mCachedBluetoothDevice).getName();
|
||||
doReturn(mBluetoothDevice).when(mCachedBluetoothDevice).getDevice();
|
||||
doReturn(ImmutableSet.of()).when(mCachedBluetoothDevice).getMemberDevice();
|
||||
doReturn(TEST_DEVICE_GROUP_ID).when(mCachedBluetoothDevice).getGroupId();
|
||||
mCachedDevices = new ArrayList<>();
|
||||
mCachedDevices.add(mCachedBluetoothDevice);
|
||||
when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(mCachedDevices);
|
||||
doNothing().when(mDevicePreferenceCallback).onDeviceAdded(any(Preference.class));
|
||||
doNothing().when(mDevicePreferenceCallback).onDeviceRemoved(any(Preference.class));
|
||||
when(mContext.getSystemService(AudioManager.class)).thenReturn(mAudioManager);
|
||||
mDeviceUpdater =
|
||||
spy(
|
||||
new AudioSharingDeviceVolumeControlUpdater(
|
||||
@@ -250,76 +231,6 @@ public class AudioSharingDeviceVolumeControlUpdaterTest {
|
||||
.isEqualTo(mCachedBluetoothDevice);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addPreference_notFallbackDevice_setDeviceVolume() {
|
||||
ArgumentCaptor<Preference> captor = ArgumentCaptor.forClass(Preference.class);
|
||||
setupPreferenceMapWithDevice();
|
||||
|
||||
verify(mDevicePreferenceCallback).onDeviceAdded(captor.capture());
|
||||
assertThat(captor.getValue() instanceof AudioSharingDeviceVolumePreference).isTrue();
|
||||
AudioSharingDeviceVolumePreference preference =
|
||||
(AudioSharingDeviceVolumePreference) captor.getValue();
|
||||
|
||||
SeekBar seekBar = mock(SeekBar.class);
|
||||
when(seekBar.getProgress()).thenReturn(TEST_VOLUME_VALUE);
|
||||
preference.onStopTrackingTouch(seekBar);
|
||||
|
||||
verify(mVolumeControl)
|
||||
.setDeviceVolume(mBluetoothDevice, TEST_VOLUME_VALUE, /* isGroupOp= */ true);
|
||||
verifyNoInteractions(mAudioManager);
|
||||
verify(mFeatureFactory.metricsFeatureProvider)
|
||||
.action(
|
||||
mContext,
|
||||
SettingsEnums.ACTION_AUDIO_SHARING_CHANGE_MEDIA_DEVICE_VOLUME,
|
||||
/* isPrimary= */ false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addPreference_fallbackDevice_setStreamVolume() {
|
||||
ArgumentCaptor<Preference> captor = ArgumentCaptor.forClass(Preference.class);
|
||||
setupPreferenceMapWithDevice();
|
||||
|
||||
verify(mDevicePreferenceCallback).onDeviceAdded(captor.capture());
|
||||
assertThat(captor.getValue() instanceof AudioSharingDeviceVolumePreference).isTrue();
|
||||
AudioSharingDeviceVolumePreference preference =
|
||||
(AudioSharingDeviceVolumePreference) captor.getValue();
|
||||
|
||||
Settings.Secure.putInt(
|
||||
mContext.getContentResolver(), TEST_SETTINGS_KEY, TEST_DEVICE_GROUP_ID);
|
||||
when(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
|
||||
.thenReturn(TEST_MAX_STREAM_VALUE);
|
||||
when(mAudioManager.getStreamMinVolume(AudioManager.STREAM_MUSIC))
|
||||
.thenReturn(TEST_MIN_STREAM_VALUE);
|
||||
SeekBar seekBar = mock(SeekBar.class);
|
||||
when(seekBar.getProgress()).thenReturn(TEST_VOLUME_VALUE);
|
||||
preference.onStopTrackingTouch(seekBar);
|
||||
|
||||
verifyNoInteractions(mVolumeControl);
|
||||
verify(mAudioManager)
|
||||
.setStreamVolume(AudioManager.STREAM_MUSIC, TEST_MAX_STREAM_VALUE, /* flags= */ 0);
|
||||
verify(mFeatureFactory.metricsFeatureProvider)
|
||||
.action(
|
||||
mContext,
|
||||
SettingsEnums.ACTION_AUDIO_SHARING_CHANGE_MEDIA_DEVICE_VOLUME,
|
||||
/* isPrimary= */ true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnSeekBarChangeListener_doNothing() {
|
||||
ArgumentCaptor<Preference> captor = ArgumentCaptor.forClass(Preference.class);
|
||||
setupPreferenceMapWithDevice();
|
||||
|
||||
verify(mDevicePreferenceCallback).onDeviceAdded(captor.capture());
|
||||
assertThat(captor.getValue() instanceof AudioSharingDeviceVolumePreference).isTrue();
|
||||
AudioSharingDeviceVolumePreference preference =
|
||||
(AudioSharingDeviceVolumePreference) captor.getValue();
|
||||
SeekBar seekBar = mock(SeekBar.class);
|
||||
preference.onProgressChanged(seekBar, TEST_VOLUME_VALUE, /* fromUser= */ false);
|
||||
|
||||
verifyNoInteractions(mAudioManager);
|
||||
verifyNoInteractions(mVolumeControl);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getLogTag_returnsCorrectTag() {
|
||||
assertThat(mDeviceUpdater.getLogTag()).isEqualTo(TAG);
|
||||
|
||||
@@ -18,11 +18,32 @@ package com.android.settings.connecteddevice.audiosharing;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.content.Context;
|
||||
import android.media.AudioManager;
|
||||
import android.provider.Settings;
|
||||
import android.widget.SeekBar;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import com.android.settings.bluetooth.Utils;
|
||||
import com.android.settings.testutils.FakeFeatureFactory;
|
||||
import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
|
||||
import com.android.settingslib.bluetooth.BluetoothUtils;
|
||||
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
|
||||
import com.android.settingslib.bluetooth.LocalBluetoothManager;
|
||||
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
|
||||
import com.android.settingslib.bluetooth.VolumeControlProfile;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
@@ -32,18 +53,45 @@ import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(shadows = {ShadowBluetoothUtils.class})
|
||||
public class AudioSharingDeviceVolumePreferenceTest {
|
||||
private static final int TEST_DEVICE_GROUP_ID = 1;
|
||||
private static final int TEST_VOLUME_VALUE = 255;
|
||||
private static final int TEST_MAX_STREAM_VALUE = 10;
|
||||
private static final int TEST_MIN_STREAM_VALUE = 0;
|
||||
|
||||
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||
|
||||
@Mock private LocalBluetoothManager mLocalBtManager;
|
||||
@Mock private LocalBluetoothProfileManager mLocalBtProfileManager;
|
||||
@Mock private VolumeControlProfile mVolumeControl;
|
||||
@Mock private CachedBluetoothDevice mCachedDevice;
|
||||
@Mock private BluetoothDevice mDevice;
|
||||
@Mock private AudioManager mAudioManager;
|
||||
@Mock private SeekBar mSeekBar;
|
||||
private Context mContext;
|
||||
private AudioSharingDeviceVolumePreference mPreference;
|
||||
private FakeFeatureFactory mFeatureFactory;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
mContext = ApplicationProvider.getApplicationContext();
|
||||
mContext = spy(ApplicationProvider.getApplicationContext());
|
||||
ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBtManager;
|
||||
mLocalBtManager = Utils.getLocalBtManager(mContext);
|
||||
mFeatureFactory = FakeFeatureFactory.setupForTest();
|
||||
when(mLocalBtManager.getProfileManager()).thenReturn(mLocalBtProfileManager);
|
||||
when(mLocalBtProfileManager.getVolumeControlProfile()).thenReturn(mVolumeControl);
|
||||
when(mContext.getSystemService(AudioManager.class)).thenReturn(mAudioManager);
|
||||
when(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
|
||||
.thenReturn(TEST_MAX_STREAM_VALUE);
|
||||
when(mAudioManager.getStreamMinVolume(AudioManager.STREAM_MUSIC))
|
||||
.thenReturn(TEST_MIN_STREAM_VALUE);
|
||||
when(mCachedDevice.getDevice()).thenReturn(mDevice);
|
||||
when(mCachedDevice.getGroupId()).thenReturn(TEST_DEVICE_GROUP_ID);
|
||||
when(mSeekBar.getProgress()).thenReturn(TEST_VOLUME_VALUE);
|
||||
mPreference = new AudioSharingDeviceVolumePreference(mContext, mCachedDevice);
|
||||
}
|
||||
|
||||
@@ -58,4 +106,128 @@ public class AudioSharingDeviceVolumePreferenceTest {
|
||||
assertThat(mPreference.getMax()).isEqualTo(AudioSharingDeviceVolumePreference.MAX_VOLUME);
|
||||
assertThat(mPreference.getMin()).isEqualTo(AudioSharingDeviceVolumePreference.MIN_VOLUME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onStopTrackingTouch_notFallbackDevice_setDeviceVolume() {
|
||||
mPreference.onStopTrackingTouch(mSeekBar);
|
||||
|
||||
verify(mVolumeControl).setDeviceVolume(mDevice, TEST_VOLUME_VALUE, /* isGroupOp= */ true);
|
||||
verifyNoInteractions(mAudioManager);
|
||||
verify(mFeatureFactory.metricsFeatureProvider)
|
||||
.action(
|
||||
mContext,
|
||||
SettingsEnums.ACTION_AUDIO_SHARING_CHANGE_MEDIA_DEVICE_VOLUME,
|
||||
/* isPrimary= */ false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onProgressChanged_notFallbackDevice_fromUserNotInTouch_setDeviceVolume() {
|
||||
mPreference.onProgressChanged(mSeekBar, TEST_VOLUME_VALUE, /* fromUser= */ true);
|
||||
|
||||
verify(mVolumeControl).setDeviceVolume(mDevice, TEST_VOLUME_VALUE, /* isGroupOp= */ true);
|
||||
verifyNoInteractions(mAudioManager);
|
||||
verify(mFeatureFactory.metricsFeatureProvider)
|
||||
.action(
|
||||
mContext,
|
||||
SettingsEnums.ACTION_AUDIO_SHARING_CHANGE_MEDIA_DEVICE_VOLUME,
|
||||
/* isPrimary= */ false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onProgressChanged_notFallbackDevice_fromUserInTouch_doNothing() {
|
||||
mPreference.onStartTrackingTouch(mSeekBar);
|
||||
mPreference.onProgressChanged(mSeekBar, TEST_VOLUME_VALUE, /* fromUser= */ true);
|
||||
|
||||
verifyNoInteractions(mVolumeControl);
|
||||
verifyNoInteractions(mAudioManager);
|
||||
verify(mFeatureFactory.metricsFeatureProvider, never())
|
||||
.action(
|
||||
any(Context.class),
|
||||
eq(SettingsEnums.ACTION_AUDIO_SHARING_CHANGE_MEDIA_DEVICE_VOLUME),
|
||||
anyBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onProgressChanged_notFallbackDevice_notFromUserNotInTouch_doNothing() {
|
||||
mPreference.onProgressChanged(mSeekBar, TEST_VOLUME_VALUE, /* fromUser= */ false);
|
||||
|
||||
verifyNoInteractions(mVolumeControl);
|
||||
verifyNoInteractions(mAudioManager);
|
||||
verify(mFeatureFactory.metricsFeatureProvider, never())
|
||||
.action(
|
||||
any(Context.class),
|
||||
eq(SettingsEnums.ACTION_AUDIO_SHARING_CHANGE_MEDIA_DEVICE_VOLUME),
|
||||
anyBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onStopTrackingTouch_fallbackDevice_setDeviceVolume() {
|
||||
Settings.Secure.putInt(
|
||||
mContext.getContentResolver(),
|
||||
BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
|
||||
TEST_DEVICE_GROUP_ID);
|
||||
mPreference.onStopTrackingTouch(mSeekBar);
|
||||
|
||||
verifyNoInteractions(mVolumeControl);
|
||||
verify(mAudioManager)
|
||||
.setStreamVolume(AudioManager.STREAM_MUSIC, TEST_MAX_STREAM_VALUE, /* flags= */ 0);
|
||||
verify(mFeatureFactory.metricsFeatureProvider)
|
||||
.action(
|
||||
mContext,
|
||||
SettingsEnums.ACTION_AUDIO_SHARING_CHANGE_MEDIA_DEVICE_VOLUME,
|
||||
/* isPrimary= */ true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onProgressChanged_fallbackDevice_fromUserNotInTouch_setDeviceVolume() {
|
||||
Settings.Secure.putInt(
|
||||
mContext.getContentResolver(),
|
||||
BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
|
||||
TEST_DEVICE_GROUP_ID);
|
||||
mPreference.onProgressChanged(mSeekBar, TEST_VOLUME_VALUE, /* fromUser= */ true);
|
||||
|
||||
verifyNoInteractions(mVolumeControl);
|
||||
verify(mAudioManager)
|
||||
.setStreamVolume(AudioManager.STREAM_MUSIC, TEST_MAX_STREAM_VALUE, /* flags= */ 0);
|
||||
verify(mFeatureFactory.metricsFeatureProvider)
|
||||
.action(
|
||||
mContext,
|
||||
SettingsEnums.ACTION_AUDIO_SHARING_CHANGE_MEDIA_DEVICE_VOLUME,
|
||||
/* isPrimary= */ true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onProgressChanged_fallbackDevice_fromUserInTouch_doNothing() {
|
||||
Settings.Secure.putInt(
|
||||
mContext.getContentResolver(),
|
||||
BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
|
||||
TEST_DEVICE_GROUP_ID);
|
||||
mPreference.onStartTrackingTouch(mSeekBar);
|
||||
mPreference.onProgressChanged(mSeekBar, TEST_VOLUME_VALUE, /* fromUser= */ true);
|
||||
|
||||
verifyNoInteractions(mVolumeControl);
|
||||
verifyNoInteractions(mAudioManager);
|
||||
verify(mFeatureFactory.metricsFeatureProvider, never())
|
||||
.action(
|
||||
any(Context.class),
|
||||
eq(SettingsEnums.ACTION_AUDIO_SHARING_CHANGE_MEDIA_DEVICE_VOLUME),
|
||||
anyBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onProgressChanged_fallbackDevice_notFromUserNotInTouch_doNothing() {
|
||||
Settings.Secure.putInt(
|
||||
mContext.getContentResolver(),
|
||||
BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
|
||||
TEST_DEVICE_GROUP_ID);
|
||||
mPreference.onProgressChanged(mSeekBar, TEST_VOLUME_VALUE, /* fromUser= */ false);
|
||||
|
||||
verifyNoInteractions(mVolumeControl);
|
||||
verifyNoInteractions(mAudioManager);
|
||||
verify(mFeatureFactory.metricsFeatureProvider, never())
|
||||
.action(
|
||||
any(Context.class),
|
||||
eq(SettingsEnums.ACTION_AUDIO_SHARING_CHANGE_MEDIA_DEVICE_VOLUME),
|
||||
anyBoolean());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
package com.android.settings.connecteddevice.audiosharing.audiostreams;
|
||||
|
||||
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
|
||||
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
@@ -32,9 +35,13 @@ import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothLeBroadcastMetadata;
|
||||
import android.bluetooth.BluetoothLeBroadcastReceiveState;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.testutils.shadow.ShadowThreadUtils;
|
||||
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
|
||||
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
|
||||
@@ -42,6 +49,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
|
||||
import com.android.settingslib.bluetooth.LocalBluetoothManager;
|
||||
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
|
||||
|
||||
import com.google.android.material.appbar.AppBarLayout;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
@@ -91,8 +99,7 @@ public class AudioStreamsHelperTest {
|
||||
|
||||
@Test
|
||||
public void addSource_noDevice_doNothing() {
|
||||
when(mAssistant.getAllConnectedDevices())
|
||||
.thenReturn(Collections.emptyList());
|
||||
when(mAssistant.getAllConnectedDevices()).thenReturn(Collections.emptyList());
|
||||
mHelper.addSource(mMetadata);
|
||||
|
||||
verify(mAssistant, never()).addSource(any(), any(), anyBoolean());
|
||||
@@ -114,8 +121,7 @@ public class AudioStreamsHelperTest {
|
||||
|
||||
@Test
|
||||
public void removeSource_noDevice_doNothing() {
|
||||
when(mAssistant.getAllConnectedDevices())
|
||||
.thenReturn(Collections.emptyList());
|
||||
when(mAssistant.getAllConnectedDevices()).thenReturn(Collections.emptyList());
|
||||
mHelper.removeSource(BROADCAST_ID_1);
|
||||
|
||||
verify(mAssistant, never()).removeSource(any(), anyInt());
|
||||
@@ -236,4 +242,40 @@ public class AudioStreamsHelperTest {
|
||||
|
||||
verify(mContext).startService(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureAppBarByOrientation_landscape_shouldNotExpand() {
|
||||
FragmentActivity fragmentActivity = mock(FragmentActivity.class);
|
||||
// AppBarLayout requires a Theme.AppCompat.
|
||||
mContext.setTheme(R.style.Theme_Settings_Home);
|
||||
AppBarLayout appBarLayout = spy(new AppBarLayout(mContext));
|
||||
setUpFragment(fragmentActivity, appBarLayout, ORIENTATION_LANDSCAPE);
|
||||
|
||||
AudioStreamsHelper.configureAppBarByOrientation(fragmentActivity);
|
||||
|
||||
verify(appBarLayout).setExpanded(eq(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureAppBarByOrientation_portrait_shouldExpand() {
|
||||
FragmentActivity fragmentActivity = mock(FragmentActivity.class);
|
||||
// AppBarLayout requires a Theme.AppCompat.
|
||||
mContext.setTheme(R.style.Theme_Settings_Home);
|
||||
AppBarLayout appBarLayout = spy(new AppBarLayout(mContext));
|
||||
setUpFragment(fragmentActivity, appBarLayout, ORIENTATION_PORTRAIT);
|
||||
|
||||
AudioStreamsHelper.configureAppBarByOrientation(fragmentActivity);
|
||||
|
||||
verify(appBarLayout).setExpanded(eq(true));
|
||||
}
|
||||
|
||||
private void setUpFragment(
|
||||
FragmentActivity fragmentActivity, AppBarLayout appBarLayout, int orientationPortrait) {
|
||||
Resources resources = mock(Resources.class);
|
||||
when(fragmentActivity.getResources()).thenReturn(resources);
|
||||
Configuration configuration = new Configuration();
|
||||
configuration.orientation = orientationPortrait;
|
||||
when(resources.getConfiguration()).thenReturn(configuration);
|
||||
when(fragmentActivity.findViewById(anyInt())).thenReturn(appBarLayout);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
* 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.notification.modes;
|
||||
|
||||
import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
|
||||
|
||||
import static com.android.settings.SettingsActivity.EXTRA_SHOW_FRAGMENT;
|
||||
import static com.android.settings.SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.robolectric.Shadows.shadowOf;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import androidx.test.core.app.ActivityScenario;
|
||||
|
||||
import com.android.settingslib.notification.modes.TestModeBuilder;
|
||||
import com.android.settingslib.notification.modes.ZenMode;
|
||||
import com.android.settingslib.notification.modes.ZenModesBackend;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class SetupInterstitialActivityTest {
|
||||
private static final String MODE_ID = "modeId";
|
||||
|
||||
@Mock
|
||||
private ZenModesBackend mBackend;
|
||||
|
||||
@Mock
|
||||
private ImageView mImage;
|
||||
|
||||
@Mock
|
||||
private Drawable mDrawable;
|
||||
|
||||
@Mock
|
||||
private FrameLayout mFrame;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
// set global backend instance so that when the interstitial activity launches, it'll get
|
||||
// this mock backend
|
||||
ZenModesBackend.setInstance(mBackend);
|
||||
|
||||
when(mBackend.getMode(MODE_ID)).thenReturn(new TestModeBuilder().build());
|
||||
when(mImage.getDrawable()).thenReturn(mDrawable);
|
||||
when(mImage.getLayoutParams()).thenReturn(new ViewGroup.LayoutParams(0, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidIntent_doesNotQueryBackend() {
|
||||
// Mode is set up sensibly
|
||||
ZenMode mode = new TestModeBuilder().setId(MODE_ID).setEnabled(false).build();
|
||||
when(mBackend.getMode(MODE_ID)).thenReturn(mode);
|
||||
|
||||
// but the intent is lacking the zen mode extra
|
||||
ActivityScenario<SetupInterstitialActivity> scenario =
|
||||
ActivityScenario.launch(new Intent(Intent.ACTION_MAIN)
|
||||
.setClass(RuntimeEnvironment.getApplication(),
|
||||
SetupInterstitialActivity.class));
|
||||
// creating the scenario takes it through onResume(), which would query the backend if
|
||||
// it had mode data.
|
||||
scenario.onActivity(activity -> {
|
||||
assertThat(activity.isFinishing()).isTrue();
|
||||
verify(mBackend, never()).getMode(any());
|
||||
});
|
||||
scenario.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidModeId_doesNotCrash() {
|
||||
when(mBackend.getMode(MODE_ID)).thenReturn(null);
|
||||
ActivityScenario<SetupInterstitialActivity> scenario =
|
||||
ActivityScenario.launch(new Intent(Intent.ACTION_MAIN)
|
||||
.setClass(RuntimeEnvironment.getApplication(),
|
||||
SetupInterstitialActivity.class)
|
||||
.putExtra(EXTRA_AUTOMATIC_ZEN_RULE_ID, MODE_ID));
|
||||
// do nothing, but it would crash if attempting to work with a null mode at any point
|
||||
scenario.onActivity(activity -> {
|
||||
assertThat(activity.isFinishing()).isTrue();
|
||||
});
|
||||
scenario.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enableButton_enablesModeAndRedirectsToModePage() {
|
||||
ZenMode mode = new TestModeBuilder().setId(MODE_ID).setEnabled(false).build();
|
||||
when(mBackend.getMode(MODE_ID)).thenReturn(mode);
|
||||
|
||||
// Set up scenario with this mode information
|
||||
ActivityScenario<SetupInterstitialActivity> scenario =
|
||||
ActivityScenario.launch(new Intent(Intent.ACTION_MAIN)
|
||||
.setClass(RuntimeEnvironment.getApplication(),
|
||||
SetupInterstitialActivity.class)
|
||||
.putExtra(EXTRA_AUTOMATIC_ZEN_RULE_ID, MODE_ID));
|
||||
scenario.onActivity(activity -> {
|
||||
View.OnClickListener listener = activity.enableButtonListener(MODE_ID);
|
||||
|
||||
// simulate button press even though we don't actually have a button
|
||||
listener.onClick(null);
|
||||
|
||||
// verify that the backend got a request to enable the mode
|
||||
ArgumentCaptor<ZenMode> captor = ArgumentCaptor.forClass(ZenMode.class);
|
||||
verify(mBackend).updateMode(captor.capture());
|
||||
ZenMode updatedMode = captor.getValue();
|
||||
assertThat(updatedMode.getId()).isEqualTo(MODE_ID);
|
||||
assertThat(updatedMode.isEnabled()).isTrue();
|
||||
|
||||
// confirm that the next activity is the mode page
|
||||
Intent openModePageIntent = shadowOf(activity).getNextStartedActivity();
|
||||
assertThat(openModePageIntent.getStringExtra(EXTRA_SHOW_FRAGMENT))
|
||||
.isEqualTo(ZenModeFragment.class.getName());
|
||||
Bundle fragmentArgs = openModePageIntent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
|
||||
assertThat(fragmentArgs).isNotNull();
|
||||
assertThat(fragmentArgs.getString(EXTRA_AUTOMATIC_ZEN_RULE_ID)).isEqualTo(MODE_ID);
|
||||
});
|
||||
scenario.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setImageToFrame_sizeZero() {
|
||||
ActivityScenario<SetupInterstitialActivity> scenario =
|
||||
ActivityScenario.launch(new Intent(Intent.ACTION_MAIN)
|
||||
.setClass(RuntimeEnvironment.getApplication(),
|
||||
SetupInterstitialActivity.class)
|
||||
.putExtra(EXTRA_AUTOMATIC_ZEN_RULE_ID, MODE_ID));
|
||||
scenario.onActivity(activity -> {
|
||||
// when either the image or the frame has a size 0, we do nothing
|
||||
when(mDrawable.getIntrinsicWidth()).thenReturn(0);
|
||||
when(mDrawable.getIntrinsicHeight()).thenReturn(25);
|
||||
when(mFrame.getMeasuredWidth()).thenReturn(40);
|
||||
when(mFrame.getMeasuredHeight()).thenReturn(50);
|
||||
|
||||
activity.sizeImageToFrame(mImage, mFrame);
|
||||
verify(mImage, never()).setLayoutParams(any());
|
||||
});
|
||||
scenario.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setImageToFrame_imageLargerThanFrame() {
|
||||
ActivityScenario<SetupInterstitialActivity> scenario =
|
||||
ActivityScenario.launch(new Intent(Intent.ACTION_MAIN)
|
||||
.setClass(RuntimeEnvironment.getApplication(),
|
||||
SetupInterstitialActivity.class)
|
||||
.putExtra(EXTRA_AUTOMATIC_ZEN_RULE_ID, MODE_ID));
|
||||
scenario.onActivity(activity -> {
|
||||
// image: 900(w)x1500(h); frame: 600(w)x500(h)
|
||||
// image expected to be scaled down to match the height of the frame -> 300(w)x500(h)
|
||||
when(mDrawable.getIntrinsicWidth()).thenReturn(900);
|
||||
when(mDrawable.getIntrinsicHeight()).thenReturn(1500);
|
||||
when(mFrame.getMeasuredWidth()).thenReturn(600);
|
||||
when(mFrame.getMeasuredHeight()).thenReturn(500);
|
||||
|
||||
ArgumentCaptor<ViewGroup.LayoutParams> captor = ArgumentCaptor.forClass(
|
||||
ViewGroup.LayoutParams.class);
|
||||
activity.sizeImageToFrame(mImage, mFrame);
|
||||
verify(mImage).setLayoutParams(captor.capture());
|
||||
ViewGroup.LayoutParams out = captor.getValue();
|
||||
assertThat(out.width).isEqualTo(300);
|
||||
assertThat(out.height).isEqualTo(500);
|
||||
});
|
||||
scenario.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setImageToFrame_imageSmallerThanFrame() {
|
||||
ActivityScenario<SetupInterstitialActivity> scenario =
|
||||
ActivityScenario.launch(new Intent(Intent.ACTION_MAIN)
|
||||
.setClass(RuntimeEnvironment.getApplication(),
|
||||
SetupInterstitialActivity.class)
|
||||
.putExtra(EXTRA_AUTOMATIC_ZEN_RULE_ID, MODE_ID));
|
||||
scenario.onActivity(activity -> {
|
||||
// image: 300(w)x200(h); frame: 900(w)x1200(h)
|
||||
// image expected to be scaled up to match the width of the frame -> 900(w)x600(h)
|
||||
when(mDrawable.getIntrinsicWidth()).thenReturn(300);
|
||||
when(mDrawable.getIntrinsicHeight()).thenReturn(200);
|
||||
when(mFrame.getMeasuredWidth()).thenReturn(900);
|
||||
when(mFrame.getMeasuredHeight()).thenReturn(1200);
|
||||
|
||||
ArgumentCaptor<ViewGroup.LayoutParams> captor = ArgumentCaptor.forClass(
|
||||
ViewGroup.LayoutParams.class);
|
||||
activity.sizeImageToFrame(mImage, mFrame);
|
||||
verify(mImage).setLayoutParams(captor.capture());
|
||||
ViewGroup.LayoutParams out = captor.getValue();
|
||||
assertThat(out.width).isEqualTo(900);
|
||||
assertThat(out.height).isEqualTo(600);
|
||||
});
|
||||
scenario.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setImageToFrame_horizontalImageNarrowerThanFrame() {
|
||||
ActivityScenario<SetupInterstitialActivity> scenario =
|
||||
ActivityScenario.launch(new Intent(Intent.ACTION_MAIN)
|
||||
.setClass(RuntimeEnvironment.getApplication(),
|
||||
SetupInterstitialActivity.class)
|
||||
.putExtra(EXTRA_AUTOMATIC_ZEN_RULE_ID, MODE_ID));
|
||||
scenario.onActivity(activity -> {
|
||||
// image: 600(w)x400(h); frame: 1000(w)x100(h)
|
||||
// both image and frame are wider than tall, but frame is much narrower
|
||||
// so should fit image to height of frame -> 150(w)x100(h)
|
||||
when(mDrawable.getIntrinsicWidth()).thenReturn(600);
|
||||
when(mDrawable.getIntrinsicHeight()).thenReturn(400);
|
||||
when(mFrame.getMeasuredWidth()).thenReturn(1000);
|
||||
when(mFrame.getMeasuredHeight()).thenReturn(100);
|
||||
|
||||
ArgumentCaptor<ViewGroup.LayoutParams> captor = ArgumentCaptor.forClass(
|
||||
ViewGroup.LayoutParams.class);
|
||||
activity.sizeImageToFrame(mImage, mFrame);
|
||||
verify(mImage).setLayoutParams(captor.capture());
|
||||
ViewGroup.LayoutParams out = captor.getValue();
|
||||
assertThat(out.width).isEqualTo(150);
|
||||
assertThat(out.height).isEqualTo(100);
|
||||
});
|
||||
scenario.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setImageToFrame_accountsForPadding() {
|
||||
ActivityScenario<SetupInterstitialActivity> scenario =
|
||||
ActivityScenario.launch(new Intent(Intent.ACTION_MAIN)
|
||||
.setClass(RuntimeEnvironment.getApplication(),
|
||||
SetupInterstitialActivity.class)
|
||||
.putExtra(EXTRA_AUTOMATIC_ZEN_RULE_ID, MODE_ID));
|
||||
scenario.onActivity(activity -> {
|
||||
// image: 200(w)x300(h); frame: 1000(w)x1000(h), 50 top/bottom padding, 100 l/r padding
|
||||
// effective size of frame is therefore 800(w)x900(h)
|
||||
// scale image to the height of the effective frame -> 600(w)x900(h)
|
||||
when(mDrawable.getIntrinsicWidth()).thenReturn(200);
|
||||
when(mDrawable.getIntrinsicHeight()).thenReturn(300);
|
||||
when(mFrame.getMeasuredWidth()).thenReturn(1000);
|
||||
when(mFrame.getMeasuredHeight()).thenReturn(1000);
|
||||
when(mFrame.getPaddingTop()).thenReturn(50);
|
||||
when(mFrame.getPaddingBottom()).thenReturn(50);
|
||||
when(mFrame.getPaddingLeft()).thenReturn(100);
|
||||
when(mFrame.getPaddingRight()).thenReturn(100);
|
||||
|
||||
ArgumentCaptor<ViewGroup.LayoutParams> captor = ArgumentCaptor.forClass(
|
||||
ViewGroup.LayoutParams.class);
|
||||
activity.sizeImageToFrame(mImage, mFrame);
|
||||
verify(mImage).setLayoutParams(captor.capture());
|
||||
ViewGroup.LayoutParams out = captor.getValue();
|
||||
assertThat(out.width).isEqualTo(600);
|
||||
assertThat(out.height).isEqualTo(900);
|
||||
});
|
||||
scenario.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* 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.notification.modes;
|
||||
|
||||
import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.robolectric.Shadows.shadowOf;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.fragment.app.testing.FragmentScenario;
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
|
||||
import com.android.settingslib.notification.modes.TestModeBuilder;
|
||||
import com.android.settingslib.notification.modes.ZenMode;
|
||||
import com.android.settingslib.notification.modes.ZenModesBackend;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class ZenModeFragmentTest {
|
||||
private static final String MODE_ID = "modeId";
|
||||
|
||||
@Mock
|
||||
private ZenModesBackend mBackend;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
// set up static instance so that the fragment will get a mock version of the backend
|
||||
ZenModesBackend.setInstance(mBackend);
|
||||
}
|
||||
|
||||
// Sets up the scenario's fragment by passing in arguments setting the provided mode ID.
|
||||
// After running this method, users can then use scenario.onFragment(fragment -> {...}) on the
|
||||
// returned scenario to test fragment behavior.
|
||||
private FragmentScenario<ZenModeFragment> setUpScenarioForModeId(String modeId) {
|
||||
Bundle args = new Bundle();
|
||||
args.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, modeId);
|
||||
return FragmentScenario.launch(
|
||||
ZenModeFragment.class, /* bundle= */ args, 0, Lifecycle.State.INITIALIZED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disabledMode_redirectsToInterstitial() {
|
||||
// Mode is disabled, and not by the user
|
||||
ZenMode mode = new TestModeBuilder().setId(MODE_ID).setEnabled(false, false)
|
||||
.build();
|
||||
|
||||
when(mBackend.getMode(MODE_ID)).thenReturn(mode);
|
||||
|
||||
// actually set up fragment for testing
|
||||
FragmentScenario scenario = setUpScenarioForModeId(MODE_ID);
|
||||
scenario.moveToState(Lifecycle.State.STARTED);
|
||||
|
||||
scenario.onFragment(fragment -> {
|
||||
// since the mode is disabled & not by the user, we should go to the next activity
|
||||
Intent nextIntent = shadowOf(fragment.getActivity()).getNextStartedActivity();
|
||||
assertThat(nextIntent).isNotNull();
|
||||
assertThat(nextIntent.getComponent().getClassName()).isEqualTo(
|
||||
SetupInterstitialActivity.class.getCanonicalName());
|
||||
assertThat(nextIntent.getStringExtra(EXTRA_AUTOMATIC_ZEN_RULE_ID)).isEqualTo(MODE_ID);
|
||||
});
|
||||
scenario.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disabledMode_byUser_noRedirect() {
|
||||
// Mode is disabled by the user
|
||||
ZenMode mode = new TestModeBuilder().setId(MODE_ID).setEnabled(false, true)
|
||||
.build();
|
||||
|
||||
when(mBackend.getMode(MODE_ID)).thenReturn(mode);
|
||||
FragmentScenario scenario = setUpScenarioForModeId(MODE_ID);
|
||||
scenario.moveToState(Lifecycle.State.STARTED);
|
||||
|
||||
scenario.onFragment(fragment -> {
|
||||
// there shouldn't be a next started activity, because we don't redirect
|
||||
Intent nextIntent = shadowOf(fragment.getActivity()).getNextStartedActivity();
|
||||
assertThat(nextIntent).isNull();
|
||||
});
|
||||
scenario.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enabledMode_noRedirect() {
|
||||
// enabled rule
|
||||
ZenMode mode = new TestModeBuilder().setId(MODE_ID).setEnabled(true)
|
||||
.build();
|
||||
|
||||
when(mBackend.getMode(MODE_ID)).thenReturn(mode);
|
||||
FragmentScenario scenario = setUpScenarioForModeId(MODE_ID);
|
||||
scenario.moveToState(Lifecycle.State.STARTED);
|
||||
|
||||
scenario.onFragment(fragment -> {
|
||||
// there shouldn't be a next started activity, because we don't redirect
|
||||
Intent nextIntent = shadowOf(fragment.getActivity()).getNextStartedActivity();
|
||||
assertThat(nextIntent).isNull();
|
||||
});
|
||||
scenario.close();
|
||||
}
|
||||
}
|
||||
@@ -16,10 +16,6 @@
|
||||
|
||||
package com.android.settings.notification.modes;
|
||||
|
||||
import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
|
||||
import static android.provider.Settings.Global.ZEN_MODE_OFF;
|
||||
import static android.service.notification.Condition.SOURCE_UNKNOWN;
|
||||
import static android.service.notification.Condition.STATE_TRUE;
|
||||
import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_ANYONE;
|
||||
import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
|
||||
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_ANYONE;
|
||||
@@ -33,17 +29,16 @@ import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.robolectric.Shadows.shadowOf;
|
||||
|
||||
import android.app.AutomaticZenRule;
|
||||
import android.app.Flags;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.platform.test.annotations.EnableFlags;
|
||||
import android.platform.test.flag.junit.SetFlagsRule;
|
||||
import android.service.notification.Condition;
|
||||
import android.service.notification.SystemZenRules;
|
||||
import android.service.notification.ZenDeviceEffects;
|
||||
import android.service.notification.ZenModeConfig;
|
||||
import android.service.notification.ZenPolicy;
|
||||
@@ -65,7 +60,8 @@ import org.robolectric.RuntimeEnvironment;
|
||||
import java.util.Random;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class ZenModesSummaryHelperTest {
|
||||
@EnableFlags(Flags.FLAG_MODES_UI)
|
||||
public class ZenModeSummaryHelperTest {
|
||||
private static final int WORK_PROFILE_ID = 3;
|
||||
|
||||
private Context mContext;
|
||||
@@ -480,85 +476,90 @@ public class ZenModesSummaryHelperTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableFlags(Flags.FLAG_MODES_UI)
|
||||
public void getSoundSummary_off_noRules() {
|
||||
ZenModeConfig config = new ZenModeConfig();
|
||||
|
||||
assertThat(mSummaryHelper.getSoundSummary(ZEN_MODE_OFF, config)).isEqualTo("Off");
|
||||
public void getModesSummary_noRules_noSummary() {
|
||||
String summary = mSummaryHelper.getModesSummary(ImmutableList.of());
|
||||
assertThat(summary).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableFlags(Flags.FLAG_MODES_UI)
|
||||
public void getSoundSummary_off_oneRule() {
|
||||
ZenModeConfig config = new ZenModeConfig();
|
||||
ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
|
||||
rule.enabled = true;
|
||||
config.automaticRules.put("key", rule);
|
||||
|
||||
assertThat(mSummaryHelper.getSoundSummary(ZEN_MODE_OFF, config))
|
||||
.isEqualTo("Off / 1 mode can turn on automatically");
|
||||
public void getModesSummary_onlyDndAndNotActive_noSummary() {
|
||||
ImmutableList<ZenMode> modes = ImmutableList.of(TestModeBuilder.MANUAL_DND_INACTIVE);
|
||||
String summary = mSummaryHelper.getModesSummary(modes);
|
||||
assertThat(summary).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableFlags(Flags.FLAG_MODES_UI)
|
||||
public void getSoundSummary_off_twoRules() {
|
||||
ZenModeConfig config = new ZenModeConfig();
|
||||
ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
|
||||
rule.enabled = true;
|
||||
ZenModeConfig.ZenRule rule2 = new ZenModeConfig.ZenRule();
|
||||
rule2.enabled = true;
|
||||
config.automaticRules.put("key", rule);
|
||||
config.automaticRules.put("key2", rule2);
|
||||
public void getModesSummary_noRulesActive_countsOnlyEnabledAutomaticModes() {
|
||||
ImmutableList<ZenMode> modes = ImmutableList.of(
|
||||
TestModeBuilder.MANUAL_DND_INACTIVE, // Not automatic
|
||||
new TestModeBuilder().setName("Auto 1").build(), // App provided automatic
|
||||
new TestModeBuilder()
|
||||
.setName("Custom manual 1")
|
||||
.setPackage(SystemZenRules.PACKAGE_ANDROID)
|
||||
.setType(AutomaticZenRule.TYPE_OTHER)
|
||||
.setConditionId(ZenModeConfig.toCustomManualConditionId())
|
||||
.build(), // Custom manual, not automatic
|
||||
new TestModeBuilder()
|
||||
.setName("Disabled 1")
|
||||
.setEnabled(false)
|
||||
.build(), // Would be automatic, but it's disabled.
|
||||
new TestModeBuilder()
|
||||
.setName("Sleep")
|
||||
.setPackage(SystemZenRules.PACKAGE_ANDROID)
|
||||
.setType(AutomaticZenRule.TYPE_SCHEDULE_TIME)
|
||||
.build() // Time based, automatic.
|
||||
);
|
||||
|
||||
assertThat(mSummaryHelper.getSoundSummary(ZEN_MODE_OFF, config))
|
||||
.isEqualTo("Off / 2 modes can turn on automatically");
|
||||
String summary = mSummaryHelper.getModesSummary(modes);
|
||||
assertThat(summary).isEqualTo("2 modes can turn on automatically");
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableFlags(Flags.FLAG_MODES_UI)
|
||||
public void getSoundSummary_on_noDescription() {
|
||||
ZenModeConfig config = new ZenModeConfig();
|
||||
config.manualRule.conditionId = Uri.EMPTY;
|
||||
config.manualRule.pkg = "android";
|
||||
config.manualRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
|
||||
config.manualRule.condition = new Condition(Uri.EMPTY, "", STATE_TRUE, SOURCE_UNKNOWN);
|
||||
assertThat(mSummaryHelper.getSoundSummary(ZEN_MODE_IMPORTANT_INTERRUPTIONS, config))
|
||||
.isEqualTo("On");
|
||||
public void getModesSummary_oneModeActive_listsMode() {
|
||||
ImmutableList<ZenMode> modes = ImmutableList.of(
|
||||
TestModeBuilder.MANUAL_DND_ACTIVE,
|
||||
new TestModeBuilder().setName("Inactive").setActive(false).build());
|
||||
|
||||
String summary = mSummaryHelper.getModesSummary(modes);
|
||||
assertThat(summary).isEqualTo("Do Not Disturb is active");
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableFlags(Flags.FLAG_MODES_UI)
|
||||
public void getSoundSummary_on_manualDescription() {
|
||||
ZenModeConfig config = new ZenModeConfig();
|
||||
config.manualRule.conditionId = ZenModeConfig.toCountdownConditionId(
|
||||
System.currentTimeMillis() + 10000, false);
|
||||
config.manualRule.pkg = "android";
|
||||
config.manualRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
|
||||
config.manualRule.condition = new Condition(Uri.EMPTY, "", STATE_TRUE, SOURCE_UNKNOWN);
|
||||
assertThat(mSummaryHelper.getSoundSummary(ZEN_MODE_IMPORTANT_INTERRUPTIONS, config))
|
||||
.startsWith("On /");
|
||||
public void getModesSummary_twoModesActive_listsModes() {
|
||||
ImmutableList<ZenMode> modes = ImmutableList.of(
|
||||
TestModeBuilder.MANUAL_DND_ACTIVE,
|
||||
new TestModeBuilder().setName("Inactive").setActive(false).build(),
|
||||
new TestModeBuilder().setName("Active #1").setActive(true).build());
|
||||
|
||||
String summary = mSummaryHelper.getModesSummary(modes);
|
||||
assertThat(summary).isEqualTo("Do Not Disturb and Active #1 are active");
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableFlags(Flags.FLAG_MODES_UI)
|
||||
public void getSoundSummary_on_automatic() {
|
||||
ZenModeConfig config = new ZenModeConfig();
|
||||
ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
|
||||
rule.configurationActivity = new ComponentName("a", "a");
|
||||
rule.component = new ComponentName("b", "b");
|
||||
rule.conditionId = new Uri.Builder().scheme("hello").build();
|
||||
rule.condition = new Condition(rule.conditionId, "", STATE_TRUE);
|
||||
rule.enabled = true;
|
||||
rule.creationTime = 123;
|
||||
rule.id = "id";
|
||||
rule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
|
||||
rule.modified = true;
|
||||
rule.name = "name";
|
||||
rule.snoozing = false;
|
||||
rule.pkg = "b";
|
||||
config.automaticRules.put("key", rule);
|
||||
public void getModesSummary_threeModesActive_listsModes() {
|
||||
ImmutableList<ZenMode> modes = ImmutableList.of(
|
||||
TestModeBuilder.MANUAL_DND_INACTIVE,
|
||||
new TestModeBuilder().setName("Inactive #1").setActive(false).build(),
|
||||
new TestModeBuilder().setName("Active #1").setActive(true).build(),
|
||||
new TestModeBuilder().setName("Active #2").setActive(true).build(),
|
||||
new TestModeBuilder().setName("Inactive #2").setActive(false).build(),
|
||||
new TestModeBuilder().setName("Active #3").setActive(true).build());
|
||||
|
||||
assertThat(mSummaryHelper.getSoundSummary(ZEN_MODE_IMPORTANT_INTERRUPTIONS, config))
|
||||
.startsWith("On /");
|
||||
String summary = mSummaryHelper.getModesSummary(modes);
|
||||
assertThat(summary).isEqualTo("Active #1, Active #2, and Active #3 are active");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getModesSummary_manyModesActive_listsACouple() {
|
||||
ImmutableList<ZenMode> modes = ImmutableList.of(
|
||||
TestModeBuilder.MANUAL_DND_ACTIVE,
|
||||
new TestModeBuilder().setName("Inactive #1").setActive(false).build(),
|
||||
new TestModeBuilder().setName("Active #1").setActive(true).build(),
|
||||
new TestModeBuilder().setName("Active #2").setActive(true).build(),
|
||||
new TestModeBuilder().setName("Inactive #2").setActive(false).build(),
|
||||
new TestModeBuilder().setName("Active #3").setActive(true).build());
|
||||
|
||||
String summary = mSummaryHelper.getModesSummary(modes);
|
||||
assertThat(summary).isEqualTo("Do Not Disturb, Active #1, and 2 more are active");
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.settings.password;
|
||||
|
||||
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
|
||||
|
||||
import static com.android.settings.password.ConfirmLockPassword.ConfirmLockPasswordFragment;
|
||||
import static com.android.settings.password.TestUtils.GUESS_INVALID_RESULT;
|
||||
import static com.android.settings.password.TestUtils.GUESS_VALID_RESULT;
|
||||
@@ -42,6 +44,7 @@ import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import android.Manifest;
|
||||
import android.app.KeyguardManager;
|
||||
import android.app.admin.ManagedSubscriptionsPolicy;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.UserHandle;
|
||||
@@ -55,6 +58,7 @@ import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import com.android.internal.widget.LockPatternUtils;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.SetupRedactionInterstitial;
|
||||
import com.android.settings.testutils.shadow.ShadowDevicePolicyManager;
|
||||
import com.android.settings.testutils.shadow.ShadowLockPatternUtils;
|
||||
import com.android.settings.testutils.shadow.ShadowUserManager;
|
||||
@@ -305,6 +309,32 @@ public class ConfirmLockPasswordTest {
|
||||
assertThat(mLockPatternUtils.isSecure(fragment.mEffectiveUserId)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onChosenLockSaveFinished_setsRedactionInterstitial() throws Exception {
|
||||
// This test verifies that the RedactionInterstitial is available. This is the screen
|
||||
// responsible for allowing the user to show sensitive lockscreen content. This
|
||||
// also allows the Settings tile for RedactionInterstitial to appear in the "anything else"
|
||||
// page during SUW.
|
||||
final ConfirmDeviceCredentialBaseActivity activity =
|
||||
buildConfirmDeviceCredentialBaseActivity(
|
||||
ConfirmLockPassword.class,
|
||||
createRemoteLockscreenValidationIntent(
|
||||
KeyguardManager.PASSWORD, VALID_REMAINING_ATTEMPTS));
|
||||
final ConfirmLockPasswordFragment fragment =
|
||||
(ConfirmLockPasswordFragment) getConfirmDeviceCredentialBaseFragment(activity);
|
||||
final Intent intent = new Intent();
|
||||
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 1L);
|
||||
|
||||
fragment.onChosenLockSaveFinished(true, intent);
|
||||
|
||||
final ComponentName componentName =
|
||||
new ComponentName(mContext, SetupRedactionInterstitial.class);
|
||||
final int isEnabled = mContext.getPackageManager()
|
||||
.getComponentEnabledSetting(componentName);
|
||||
|
||||
assertThat(isEnabled).isEqualTo(COMPONENT_ENABLED_STATE_ENABLED);
|
||||
}
|
||||
|
||||
private void triggerHandleNext(
|
||||
ConfirmLockPasswordFragment fragment, ImeAwareEditText passwordEntry) {
|
||||
passwordEntry.setText("Password");
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
package com.android.settings.password;
|
||||
|
||||
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
|
||||
|
||||
import static com.android.settings.password.ConfirmLockPattern.ConfirmLockPatternFragment;
|
||||
import static com.android.settings.password.TestUtils.GUESS_INVALID_RESULT;
|
||||
import static com.android.settings.password.TestUtils.GUESS_VALID_RESULT;
|
||||
import static com.android.settings.password.TestUtils.LOCKOUT_RESULT;
|
||||
@@ -39,6 +42,7 @@ import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import android.Manifest;
|
||||
import android.app.KeyguardManager;
|
||||
import android.app.admin.ManagedSubscriptionsPolicy;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.UserHandle;
|
||||
@@ -51,6 +55,7 @@ import androidx.test.core.app.ApplicationProvider;
|
||||
import com.android.internal.widget.LockPatternUtils;
|
||||
import com.android.internal.widget.LockPatternView;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.SetupRedactionInterstitial;
|
||||
import com.android.settings.testutils.shadow.ShadowDevicePolicyManager;
|
||||
import com.android.settings.testutils.shadow.ShadowLockPatternUtils;
|
||||
import com.android.settings.testutils.shadow.ShadowUserManager;
|
||||
@@ -284,6 +289,32 @@ public class ConfirmLockPatternTest {
|
||||
assertThat(mLockPatternUtils.isSecure(fragment.mEffectiveUserId)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onChosenLockSaveFinished_setsRedactionInterstitial() throws Exception {
|
||||
// This test verifies that the RedactionInterstitial is available. This is the screen
|
||||
// responsible for allowing the user to show sensitive lockscreen content. This
|
||||
// also allows the Settings tile for RedactionInterstitial to appear in the "anything else"
|
||||
// page during SUW.
|
||||
final ConfirmDeviceCredentialBaseActivity activity =
|
||||
buildConfirmDeviceCredentialBaseActivity(
|
||||
ConfirmLockPattern.class,
|
||||
createRemoteLockscreenValidationIntent(
|
||||
KeyguardManager.PATTERN, VALID_REMAINING_ATTEMPTS));
|
||||
final ConfirmLockPatternFragment fragment = (ConfirmLockPatternFragment)
|
||||
getConfirmDeviceCredentialBaseFragment(activity);
|
||||
final Intent intent = new Intent();
|
||||
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 1L);
|
||||
|
||||
fragment.onChosenLockSaveFinished(true, intent);
|
||||
|
||||
final ComponentName componentName =
|
||||
new ComponentName(mContext, SetupRedactionInterstitial.class);
|
||||
final int isEnabled = mContext.getPackageManager()
|
||||
.getComponentEnabledSetting(componentName);
|
||||
|
||||
assertThat(isEnabled).isEqualTo(COMPONENT_ENABLED_STATE_ENABLED);
|
||||
}
|
||||
|
||||
private void triggerOnPatternDetected(LockPatternView lockPatternView) {
|
||||
List<LockPatternView.Cell> pattern = List.of(LockPatternView.Cell.of(0, 0));
|
||||
lockPatternView.setPattern(LockPatternView.DisplayMode.Correct, pattern);
|
||||
|
||||
@@ -38,48 +38,55 @@ import org.mockito.junit.MockitoRule
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.spy
|
||||
import org.mockito.kotlin.stub
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class AppDataUsageRepositoryTest {
|
||||
@get:Rule
|
||||
val mockito: MockitoRule = MockitoJUnit.rule()
|
||||
@get:Rule val mockito: MockitoRule = MockitoJUnit.rule()
|
||||
|
||||
private val mockUserManager = mock<UserManager> {
|
||||
on { userProfiles } doReturn listOf(UserHandle.of(USER_ID))
|
||||
on { getUserInfo(USER_ID) } doReturn UserInfo(USER_ID, "", 0)
|
||||
}
|
||||
private val mockUserManager =
|
||||
mock<UserManager> {
|
||||
on { userProfiles } doReturn listOf(UserHandle.of(USER_ID))
|
||||
on { getUserInfo(USER_ID) } doReturn UserInfo(USER_ID, "", 0)
|
||||
}
|
||||
|
||||
private val mockNetworkPolicyManager = mock<NetworkPolicyManager> {
|
||||
on { getUidsWithPolicy(NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND) } doReturn
|
||||
intArrayOf()
|
||||
}
|
||||
private val mockNetworkPolicyManager =
|
||||
mock<NetworkPolicyManager> {
|
||||
on { getUidsWithPolicy(NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND) } doReturn
|
||||
intArrayOf()
|
||||
}
|
||||
|
||||
private val mockResources = mock<Resources> {
|
||||
on { getIntArray(R.array.datausage_hiding_carrier_service_carrier_id) } doReturn
|
||||
intArrayOf(HIDING_CARRIER_ID)
|
||||
private val mockResources =
|
||||
mock<Resources> {
|
||||
on { getIntArray(R.array.datausage_hiding_carrier_service_carrier_id) } doReturn
|
||||
intArrayOf(HIDING_CARRIER_ID)
|
||||
|
||||
on { getStringArray(R.array.datausage_hiding_carrier_service_package_names) } doReturn
|
||||
arrayOf(HIDING_PACKAGE_NAME)
|
||||
}
|
||||
on { getStringArray(R.array.datausage_hiding_carrier_service_package_names) } doReturn
|
||||
arrayOf(HIDING_PACKAGE_NAME)
|
||||
}
|
||||
|
||||
private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
|
||||
on { userManager } doReturn mockUserManager
|
||||
on { getSystemService(NetworkPolicyManager::class.java) } doReturn mockNetworkPolicyManager
|
||||
on { resources } doReturn mockResources
|
||||
}
|
||||
private val context: Context =
|
||||
spy(ApplicationProvider.getApplicationContext()) {
|
||||
on { userManager } doReturn mockUserManager
|
||||
on { getSystemService(NetworkPolicyManager::class.java) } doReturn
|
||||
mockNetworkPolicyManager
|
||||
on { resources } doReturn mockResources
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getAppPercent_noAppToHide() {
|
||||
val repository = AppDataUsageRepository(
|
||||
context = context,
|
||||
currentUserId = USER_ID,
|
||||
template = Template,
|
||||
getPackageName = { null },
|
||||
)
|
||||
val buckets = listOf(
|
||||
Bucket(uid = APP_ID_1, bytes = 1, startTimeStamp = 0, endTimeStamp = 0),
|
||||
Bucket(uid = APP_ID_2, bytes = 2, startTimeStamp = 0, endTimeStamp = 0),
|
||||
)
|
||||
val repository =
|
||||
AppDataUsageRepository(
|
||||
context = context,
|
||||
currentUserId = USER_ID,
|
||||
template = Template,
|
||||
getPackageName = { null },
|
||||
)
|
||||
val buckets =
|
||||
listOf(
|
||||
Bucket(uid = APP_ID_1, bytes = 1, startTimeStamp = 0, endTimeStamp = 0),
|
||||
Bucket(uid = APP_ID_2, bytes = 2, startTimeStamp = 0, endTimeStamp = 0),
|
||||
)
|
||||
|
||||
val appPercentList = repository.getAppPercent(null, buckets)
|
||||
|
||||
@@ -100,16 +107,18 @@ class AppDataUsageRepositoryTest {
|
||||
|
||||
@Test
|
||||
fun getAppPercent_hasAppToHide() {
|
||||
val repository = AppDataUsageRepository(
|
||||
context = context,
|
||||
currentUserId = USER_ID,
|
||||
template = Template,
|
||||
getPackageName = { if (it.key == APP_ID_1) HIDING_PACKAGE_NAME else null },
|
||||
)
|
||||
val buckets = listOf(
|
||||
Bucket(uid = APP_ID_1, bytes = 1, startTimeStamp = 0, endTimeStamp = 0),
|
||||
Bucket(uid = APP_ID_2, bytes = 2, startTimeStamp = 0, endTimeStamp = 0),
|
||||
)
|
||||
val repository =
|
||||
AppDataUsageRepository(
|
||||
context = context,
|
||||
currentUserId = USER_ID,
|
||||
template = Template,
|
||||
getPackageName = { if (it.key == APP_ID_1) HIDING_PACKAGE_NAME else null },
|
||||
)
|
||||
val buckets =
|
||||
listOf(
|
||||
Bucket(uid = APP_ID_1, bytes = 1, startTimeStamp = 0, endTimeStamp = 0),
|
||||
Bucket(uid = APP_ID_2, bytes = 2, startTimeStamp = 0, endTimeStamp = 0),
|
||||
)
|
||||
|
||||
val appPercentList = repository.getAppPercent(HIDING_CARRIER_ID, buckets)
|
||||
|
||||
@@ -122,6 +131,43 @@ class AppDataUsageRepositoryTest {
|
||||
assertThat(appPercentList[0].second).isEqualTo(100)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getAppPercent_restricted() {
|
||||
mockNetworkPolicyManager.stub {
|
||||
on { getUidsWithPolicy(NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND) } doReturn
|
||||
intArrayOf(APP_ID_1)
|
||||
}
|
||||
val repository =
|
||||
AppDataUsageRepository(
|
||||
context = context,
|
||||
currentUserId = USER_ID,
|
||||
template = Template,
|
||||
getPackageName = { null },
|
||||
)
|
||||
val buckets =
|
||||
listOf(
|
||||
Bucket(uid = APP_ID_2, bytes = 2, startTimeStamp = 0, endTimeStamp = 0),
|
||||
)
|
||||
|
||||
val appPercentList = repository.getAppPercent(null, buckets)
|
||||
|
||||
assertThat(appPercentList).hasSize(2)
|
||||
appPercentList[0].first.apply {
|
||||
assertThat(key).isEqualTo(APP_ID_2)
|
||||
assertThat(category).isEqualTo(AppItem.CATEGORY_APP)
|
||||
assertThat(total).isEqualTo(2)
|
||||
assertThat(restricted).isFalse()
|
||||
}
|
||||
assertThat(appPercentList[0].second).isEqualTo(100)
|
||||
appPercentList[1].first.apply {
|
||||
assertThat(key).isEqualTo(APP_ID_1)
|
||||
assertThat(category).isEqualTo(AppItem.CATEGORY_APP)
|
||||
assertThat(total).isEqualTo(0)
|
||||
assertThat(restricted).isTrue()
|
||||
}
|
||||
assertThat(appPercentList[1].second).isEqualTo(0)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val USER_ID = 1
|
||||
const val APP_ID_1 = 110001
|
||||
|
||||
Reference in New Issue
Block a user