Snap for 4579913 from 3d8e2c44cb to pi-release

Change-Id: I76fe600416a1e9f3da51915a34efd7f78e1b8ca5
This commit is contained in:
android-build-team Robot
2018-02-01 08:52:12 +00:00
95 changed files with 2566 additions and 3898 deletions

View File

@@ -2012,13 +2012,21 @@
</intent-filter>
</activity>
<activity android:name=".deviceinfo.UsbModeChooserActivity"
<activity android:name=".connecteddevice.usb.UsbModeChooserActivity"
android:excludeFromRecents="true"
android:exported="true"
android:permission="android.permission.MANAGE_USB"
android:theme="@*android:style/Theme.DeviceDefault.Settings.Dialog.NoActionBar">
</activity>
<activity android:name=".Settings$UsbDetailsActivity"
android:excludeFromRecents="true"
android:permission="android.permission.MANAGE_USB"
android:exported="true">
<meta-data android:name="com.android.settings.FRAGMENT_CLASS"
android:value="com.android.settings.connecteddevice.usb.UsbDetailsFragment"/>
</activity>
<activity android:name=".RemoteBugreportActivity"
android:excludeFromRecents="true"
android:exported="true"
@@ -2645,19 +2653,6 @@
android:value="com.android.settings.notification.SoundSettings" />
</activity-alias>
<!-- Show apps for which application-level notification settings are applicable -->
<activity android:name="Settings$NotificationAppListActivity"
android:label="@string/app_notifications_title"
android:icon="@drawable/ic_notifications"
android:exported="true"
android:taskAffinity="">
<intent-filter android:priority="150">
<action android:name="com.android.settings.action.SETTINGS" />
</intent-filter>
<meta-data android:name="com.android.settings.FRAGMENT_CLASS"
android:value="com.android.settings.applications.NotificationApps" />
</activity>
<!-- Show application-level notification settings (app passed in as extras) -->
<activity android:name="Settings$AppNotificationSettingsActivity"
android:exported="true">
@@ -3202,16 +3197,11 @@
<activity android:name="Settings$AdvancedConnectedDeviceActivity"
android:label="@string/connected_device_connections_title"
android:taskAffinity="com.android.settings"
android:parentActivityName="Settings$ConnectedDeviceDashboardActivity"
android:enabled="false">
android:parentActivityName="Settings$ConnectedDeviceDashboardActivity">
<intent-filter android:priority="1">
<action android:name="com.android.settings.ADVANCED_CONNECTED_DEVICE_SETTINGS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data android:name="com.android.settings.FRAGMENT_CLASS"
android:value="com.android.settings.connecteddevice.AdvancedConnectedDeviceDashboardFragment" />
<meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"

View File

@@ -1,43 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
style="@style/SuggestionConditionStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="5dp"
android:paddingEnd="5dp"
android:paddingBottom="@dimen/dashboard_padding_bottom">
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardUseCompatPadding="true"
app:cardElevation="2dp">
<android.support.v7.widget.RecyclerView
android:id="@+id/data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/material_grey_300"
android:scrollbars="none"/>
</android.support.v7.widget.CardView>
</FrameLayout>

View File

@@ -14,51 +14,69 @@
limitations under the License.
-->
<LinearLayout
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/suggestion_card"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
app:cardPreventCornerOverlap="false"
app:cardUseCompatPadding="true"
app:cardElevation="2dp"
app:cardCornerRadius="@dimen/suggestion_card_corner_radius">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:gravity="center_vertical"
android:orientation="horizontal"
android:minHeight="@dimen/dashboard_tile_minimum_height">
android:minHeight="112dp"
android:orientation="vertical">
<ImageView
android:id="@android:id/icon"
android:layout_width="@dimen/suggestion_card_icon_size"
android:layout_height="@dimen/suggestion_card_icon_size"
android:layout_marginStart="14dp"
android:layout_marginEnd="24dp" />
<LinearLayout
android:layout_width="wrap_content"
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:orientation="horizontal">
<TextView
android:id="@android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.TileTitle"
android:ellipsize="marquee"
android:fadingEdge="horizontal" />
<ImageView
android:id="@android:id/icon"
android:layout_width="@dimen/suggestion_card_icon_size"
android:layout_height="@dimen/suggestion_card_icon_size"
style="@style/SuggestionCardIcon"
android:layout_marginTop="16dp"
android:layout_marginBottom="8dp" />
<TextView
android:id="@android:id/summary"
android:textAppearance="@style/TextAppearance.SuggestionSummary"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageView
android:id="@+id/close_button"
android:layout_width="18dp"
android:layout_height="18dp"
android:alpha="0.54"
android:layout_alignParentEnd="true"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:src="@drawable/ic_suggestion_close_button"/>
</LinearLayout>
</RelativeLayout>
<TextView
android:id="@android:id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/SuggestionCardText"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.SuggestionTitleV2"
android:ellipsize="end"
android:fadingEdge="horizontal" />
<TextView
android:id="@android:id/summary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/SuggestionCardText"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:textAppearance="@style/TextAppearance.SuggestionSummary" />
</LinearLayout>
<include layout="@layout/horizontal_divider" />
</LinearLayout>
</android.support.v7.widget.CardView>

View File

@@ -1,82 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2018 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/suggestion_card"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:cardPreventCornerOverlap="false"
app:cardUseCompatPadding="true"
app:cardElevation="2dp"
app:cardCornerRadius="@dimen/suggestion_card_corner_radius">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="112dp"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="@android:id/icon"
android:layout_width="@dimen/suggestion_card_icon_size"
android:layout_height="@dimen/suggestion_card_icon_size"
style="@style/SuggestionCardIcon"
android:layout_marginTop="16dp"
android:layout_marginBottom="8dp" />
<ImageView
android:id="@+id/close_button"
android:layout_width="18dp"
android:layout_height="18dp"
android:alpha="0.54"
android:layout_alignParentEnd="true"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:src="@drawable/ic_suggestion_close_button"/>
</RelativeLayout>
<TextView
android:id="@android:id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/SuggestionCardText"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.SuggestionTitleV2"
android:ellipsize="end"
android:fadingEdge="horizontal" />
<TextView
android:id="@android:id/summary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/SuggestionCardText"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:textAppearance="@style/TextAppearance.SuggestionSummary" />
</LinearLayout>
</android.support.v7.widget.CardView>

View File

@@ -14,58 +14,78 @@
limitations under the License.
-->
<LinearLayout
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/suggestion_card"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:clipChildren="false"
android:clipToPadding="false"
android:paddingStart="16dp"
android:paddingEnd="12dp"
android:paddingBottom="20dp"
android:paddingTop="16dp"
android:orientation="horizontal"
android:minHeight="@dimen/dashboard_tile_minimum_height">
<ImageView
android:id="@android:id/icon"
android:layout_width="@dimen/dashboard_tile_image_size"
android:layout_height="@dimen/dashboard_tile_image_size" />
app:cardPreventCornerOverlap="false"
app:cardUseCompatPadding="true"
app:cardElevation="2dp"
app:cardCornerRadius="@dimen/suggestion_card_corner_radius">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_marginStart="18dp"
android:layout_marginTop="2dp"
android:clipChildren="false"
android:clipToPadding="false"
android:minHeight="112dp"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="@android:id/icon"
android:layout_width="@dimen/suggestion_card_icon_size"
android:layout_height="@dimen/suggestion_card_icon_size"
style="@style/SuggestionCardIcon"
android:layout_marginTop="16dp"
android:layout_marginBottom="8dp" />
<ImageView
android:id="@+id/close_button"
android:layout_width="18dp"
android:layout_height="18dp"
android:alpha="0.54"
android:layout_alignParentEnd="true"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:src="@drawable/ic_suggestion_close_button"/>
</RelativeLayout>
<TextView
android:id="@android:id/title"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:textAppearance="@style/TextAppearance.TileTitle" />
style="@style/SuggestionCardText"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.SuggestionTitleV2"
android:ellipsize="end"
android:fadingEdge="horizontal" />
<TextView
android:id="@android:id/summary"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:layout_marginStart="6dp"
android:layout_marginEnd="50dp"
style="@style/SuggestionCardText"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:textAppearance="@style/TextAppearance.SuggestionSummary" />
<Button
android:id="@android:id/primary"
style="@style/ActionPrimaryButton"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/suggestion_button_text" />
</LinearLayout>
</LinearLayout>
</android.support.v7.widget.CardView>

View File

@@ -1,91 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2018 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/suggestion_card"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:cardPreventCornerOverlap="false"
app:cardUseCompatPadding="true"
app:cardElevation="2dp"
app:cardCornerRadius="@dimen/suggestion_card_corner_radius">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="112dp"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="@android:id/icon"
android:layout_width="@dimen/suggestion_card_icon_size"
android:layout_height="@dimen/suggestion_card_icon_size"
style="@style/SuggestionCardIcon"
android:layout_marginTop="16dp"
android:layout_marginBottom="8dp" />
<ImageView
android:id="@+id/close_button"
android:layout_width="18dp"
android:layout_height="18dp"
android:alpha="0.54"
android:layout_alignParentEnd="true"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:src="@drawable/ic_suggestion_close_button"/>
</RelativeLayout>
<TextView
android:id="@android:id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/SuggestionCardText"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.SuggestionTitleV2"
android:ellipsize="end"
android:fadingEdge="horizontal" />
<TextView
android:id="@android:id/summary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/SuggestionCardText"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:textAppearance="@style/TextAppearance.SuggestionSummary" />
<Button
android:id="@android:id/primary"
style="@style/ActionPrimaryButton"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/suggestion_button_text" />
</LinearLayout>
</android.support.v7.widget.CardView>

View File

@@ -119,8 +119,8 @@
<!-- The following two margins need to match, with the caveat that
the second should be negative. The second one ensures that the icons and text
align despite the additional padding caused by the search bar's card background. -->
<dimen name="search_bar_margin">8dp</dimen>
<dimen name="search_bar_negative_margin">-8dp</dimen>
<dimen name="search_bar_margin">16dp</dimen>
<dimen name="search_bar_negative_margin">-16dp</dimen>
<dimen name="search_bar_height">48dp</dimen>
<dimen name="search_bar_corner_radius">2dp</dimen>

View File

@@ -6652,6 +6652,8 @@
<string name="app_and_notification_dashboard_summary">Permissions, default apps</string>
<!-- Title for setting tile leading to account settings [CHAR LIMIT=40]-->
<string name="account_dashboard_title">Accounts</string>
<!-- Summary for account settings tiles when there is no accounts on device [CHAR LIMIT=NONE]-->
<string name="account_dashboard_default_summary">No accounts added</string>
<!-- Title for setting tile leading to setting UI which allows user set default app to
handle actions such as open web page, making phone calls, default SMS apps [CHAR LIMIT=40]-->
<string name="app_default_dashboard_title">Default apps</string>
@@ -6983,6 +6985,9 @@
<!-- notification header - apps that have recently sent notifications -->
<string name="recent_notifications">Recently sent</string>
<!-- Preference title for showing all apps on device [CHAR_LIMIT=50]-->
<string name="recent_notifications_see_all_title">See all apps</string>
<!-- Configure Notifications: Advanced section header [CHAR LIMIT=30] -->
<string name="advanced_section_header">Advanced</string>
@@ -8000,15 +8005,15 @@
<!-- Title of one of the choices in a dialog (with title defined in usb_use) that lets the user
select what the USB connection for this device should be used for. This choice
is for powering the other device only. -->
<string name="usb_use_power_only">Supply power</string>
<string name="usb_use_power_only">Charging connected device</string>
<!-- Decription of one of the choices in a dialog (with title defined in usb_use) that lets the
user select what the USB connection for this device should be used for. This choice
is for powering the other device. -->
<string name="usb_use_power_only_desc">Charge the connected device. Works only with devices that support USB charging.</string>
<string name="usb_use_power_only_desc">Other settings unavailable when turned on</string>
<!-- Title of one of the choices in a dialog (with title defined in usb_use) that lets the user
select what the USB connection for this device should be used for. This choice
is for transferring files via MTP. -->
<string name="usb_use_file_transfers">Transfer files</string>
<string name="usb_use_file_transfers">File Transfer</string>
<!-- Description of one of the choices in a dialog (with title defined in usb_use) that lets the user
select what the USB connection for this device should be used for. This choice
is for transferring files via MTP. -->
@@ -8016,23 +8021,31 @@
<!-- Title of one of the choices in a dialog (with title defined in usb_use) that lets the user
select what the USB connection for this device should be used for. This choice
is for transferring photos via PTP. -->
<string name="usb_use_photo_transfers">Transfer photos (PTP)</string>
<string name="usb_use_photo_transfers">PTP</string>
<!-- Description of one of the choices in a dialog (with title defined in usb_use) that lets the user
select what the USB connection for this device should be used for. This choice
is for transferring photos via PTP. -->
<string name="usb_use_photo_transfers_desc">Transfer photos or files if MTP is not supported (PTP)</string>
<!-- Title of one of the choices in a dialog (with title defined in usb_use) that lets the user
select what the USB connection for this device should be used for. This choice
is for USB tethering. -->
<string name="usb_use_tethering">USB tethering</string>
<!-- Title of one of the choices in a dialog (with title defined in usb_use) that lets the user
select what the USB connection for this device should be used for. This choice
is for entering MIDI mode. -->
<string name="usb_use_MIDI">Use device as MIDI</string>
<string name="usb_use_MIDI">MIDI</string>
<!-- Description of one of the choices in a dialog (with title defined in usb_use) that lets the user
select what the USB connection for this device should be used for. This choice
is for entering MIDI mode. -->
<string name="usb_use_MIDI_desc">Use this device as MIDI</string>
<!-- The title used in a dialog which lets the user select what the USB connection
for this device should be used for. Choices are usb_use_charging_only,
usb_use_file_transfer, use_use_photo_transfer, and usb_use_MIDI -->
<string name="usb_use">Use USB to</string>
for this device should be used for. These options are more commonly used.
Choices are usb_use_file_transfer.-->
<string name="usb_use">Use USB for</string>
<!-- The title used in a dialog which lets the user select what the USB connection
for this device should be used for. These options are less commonly used.
Choices are usb_use_tethering, usb_use_photo_transfers, usb_use_MIDI, and usb_use_power_only.-->
<string name="usb_use_also">Also use USB for</string>
<!-- Settings item title for USB preference [CHAR LIMIT=35] -->
<string name="usb_pref">USB</string>
@@ -8040,13 +8053,27 @@
<!-- Settings item summary for USB preference when set to charging only [CHAR LIMIT=NONE] -->
<string name="usb_summary_charging_only">Charging this device</string>
<!-- Settings item summary for USB preference when set to powering the other device only [CHAR LIMIT=NONE] -->
<string name="usb_summary_power_only">Supplying power</string>
<string name="usb_summary_power_only">Charging connected device</string>
<!-- Settings item summary for USB preference when set to transferring files via MTP [CHAR LIMIT=NONE] -->
<string name="usb_summary_file_transfers">Transferring files</string>
<string name="usb_summary_file_transfers">File transfer</string>
<!-- Settings item summary for USB preference when set to USB tethering [CHAR LIMIT=NONE] -->
<string name="usb_summary_tether">USB tethering</string>
<!-- Settings item summary for USB preference when set to transferring photos via PTP [CHAR LIMIT=NONE] -->
<string name="usb_summary_photo_transfers">Transferring photos (PTP)</string>
<string name="usb_summary_photo_transfers">PTP</string>
<!-- Settings item summary for USB preference when set to entering MIDI mode [CHAR LIMIT=NONE] -->
<string name="usb_summary_MIDI">Using device as MIDI</string>
<string name="usb_summary_MIDI">MIDI</string>
<!-- Settings item summary for USB preference when set to transferring files via MTP
and powering other device [CHAR LIMIT=NONE] -->
<string name="usb_summary_file_transfers_power">File transfer and supplying power</string>
<!-- Settings item summary for USB preference when set to USB tethering
and powering other device [CHAR LIMIT=NONE] -->
<string name="usb_summary_tether_power">USB tethering and supplying power</string>
<!-- Settings item summary for USB preference when set to transferring photos via PTP
and powering other device [CHAR LIMIT=NONE] -->
<string name="usb_summary_photo_transfers_power">PTP and supplying power</string>
<!-- Settings item summary for USB preference when set to entering MIDI mode
and powering other device [CHAR LIMIT=NONE] -->
<string name="usb_summary_MIDI_power">MIDI and supplying power</string>
<!-- Settings item title for SMS Mirroring preference [CHAR LIMIT=35] -->
<string name="sms_mirroring_pref">SMS Mirroring</string>
@@ -8340,8 +8367,10 @@
<!-- Backup disabled summary [CHAR LIMIT=NONE] -->
<string name="backup_disabled">Back up disabled</string>
<!-- Summary of device info page [CHAR LIMIT=NONE] -->
<string name="about_summary">Updated to Android <xliff:g id="version" example="6.0">%1$s</xliff:g></string>
<!-- Summary of Android version info [CHAR LIMIT=NONE] -->
<string name="android_version_summary">Updated to Android <xliff:g id="version" example="6.0">%1$s</xliff:g></string>
<!-- Summary of Android version info (when there is a pending upgrade available) [CHAR LIMIT=NONE] -->
<string name="android_version_pending_update_summary">Update available</string>
<!-- Title for dialog displayed when user clicks on a setting locked by an admin [CHAR LIMIT=30] -->
<string name="disabled_by_policy_title">Action not allowed</string>

View File

@@ -15,7 +15,8 @@
-->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto">
xmlns:settings="http://schemas.android.com/apk/res-auto"
settings:initialExpandedChildrenCount="500">
<com.android.settings.applications.LayoutPreference
android:key="pref_app_header"
@@ -25,24 +26,24 @@
android:key="block"
android:layout="@layout/styled_switch_bar" />
<com.android.settings.notification.NotificationFooterPreference
android:key="block_desc" />
<!-- Channels/Channel groups added here -->
<!-- Show badge -->
<com.android.settingslib.RestrictedSwitchPreference
android:key="badge"
android:title="@string/notification_badge_title"
android:order="501"
settings:useAdditionalSummary="true"
settings:allowDividerAbove="true"
settings:restrictedSwitchSummary="@string/enabled_by_admin" />
<!-- Channels/Channel groups added here -->
<Preference
android:key="app_link"
android:title="@string/app_settings_link"
android:order="500"
settings:allowDividerAbove="true"/>
<com.android.settings.notification.NotificationFooterPreference
android:key="block_desc"
android:order="1000" />
android:order="502" />
<com.android.settings.notification.NotificationFooterPreference
android:key="desc"

View File

@@ -27,7 +27,7 @@
android:layout="@layout/styled_switch_bar" />
<!-- Importance -->
<Preference
<com.android.settings.RestrictedListPreference
android:key="importance"
android:title="@string/notification_importance_title" />

View File

@@ -79,7 +79,7 @@
<intent
android:action="android.intent.action.MAIN"
android:targetPackage="com.android.settings"
android:targetClass="com.android.settings.Settings$NotificationAppListActivity">
android:targetClass="com.android.settings.Settings$ManageApplicationsActivity">
</intent>
</Preference>
</PreferenceCategory>

View File

@@ -56,16 +56,6 @@
android:summary="@string/bluetooth_on_while_driving_summary"
android:order="-2"/>
<Preference
android:key="usb_mode"
android:title="@string/usb_pref"
android:icon="@drawable/ic_usb"
android:order="-1">
<intent android:action="android.intent.action.MAIN"
android:targetPackage="com.android.settings"
android:targetClass="com.android.settings.deviceinfo.UsbModeChooserActivity"/>
</Preference>
<Preference
android:key="bt_received_files"
android:icon="@drawable/ic_folder_vd_theme_24"

View File

@@ -53,7 +53,7 @@
android:order="-2">
<intent android:action="android.intent.action.MAIN"
android:targetPackage="com.android.settings"
android:targetClass="com.android.settings.deviceinfo.UsbModeChooserActivity"/>
android:targetClass="com.android.settings.connecteddevice.usb.UsbModeChooserActivity"/>
</Preference>
<PreferenceCategory

View File

@@ -55,7 +55,7 @@
android:summary="@string/summary_placeholder"
android:icon="@drawable/ic_system_update"
android:order="-30"
settings:controller="com.android.settings.deviceinfo.SystemUpdatePreferenceController">
settings:controller="com.android.settings.system.SystemUpdatePreferenceController">
<intent android:action="android.settings.SYSTEM_UPDATE_SETTINGS" />
</Preference>
@@ -63,7 +63,7 @@
android:key="additional_system_update_settings"
android:title="@string/additional_system_update_settings_list_item_title"
android:order="-31"
settings:controller="com.android.settings.deviceinfo.AdditionalSystemUpdatePreferenceController">
settings:controller="com.android.settings.system.AdditionalSystemUpdatePreferenceController">
<intent android:action="android.intent.action.MAIN"
android:targetPackage="@string/additional_system_update"
android:targetClass="@string/additional_system_update_menu" />

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:title="@string/device_details_title">
<com.android.settings.applications.LayoutPreference
android:key="usb_device_header"
android:layout="@layout/settings_entity_header"
android:selectable="false"/>
<PreferenceCategory
android:key="usb_main_options"
android:title="@string/usb_use"/>
<PreferenceCategory
android:key="usb_secondary_options"
android:title="@string/usb_use_also"/>
</PreferenceScreen>

View File

@@ -123,24 +123,34 @@ public class MasterClear extends InstrumentedPreferenceFragment {
return !((requestCode != KEYGUARD_REQUEST) && (requestCode != CREDENTIAL_CONFIRM_REQUEST));
}
@VisibleForTesting
boolean isShowFinalConfirmation(int requestCode, int resultCode) {
return (resultCode == Activity.RESULT_OK) || (requestCode == CREDENTIAL_CONFIRM_REQUEST);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
onActivityResultInternal(requestCode, resultCode, data);
}
/*
* Internal method that allows easy testing without dealing with super references.
*/
@VisibleForTesting
void onActivityResultInternal(int requestCode, int resultCode, Intent data) {
if (!isValidRequestCode(requestCode)) {
return;
}
// If the user entered a valid keyguard trace, present the final
// confirmation prompt; otherwise, go back to the initial state.
if (isShowFinalConfirmation(requestCode, resultCode)) {
showFinalConfirmation();
} else {
if (resultCode != Activity.RESULT_OK) {
establishInitialState();
return;
}
Intent intent = null;
// If returning from a Keyguard request, try to show an account confirmation request if
// applciable.
if (CREDENTIAL_CONFIRM_REQUEST != requestCode
&& (intent = getAccountConfirmationIntent()) != null) {
showAccountCredentialConfirmation(intent);
} else {
showFinalConfirmation();
}
}
@@ -155,7 +165,12 @@ public class MasterClear extends InstrumentedPreferenceFragment {
}
@VisibleForTesting
boolean tryShowAccountConfirmation() {
void showAccountCredentialConfirmation(Intent intent) {
startActivityForResult(intent, CREDENTIAL_CONFIRM_REQUEST);
}
@VisibleForTesting
Intent getAccountConfirmationIntent() {
final Context context = getActivity();
final String accountType = context.getString(R.string.account_type);
final String packageName = context.getString(R.string.account_confirmation_package);
@@ -163,7 +178,8 @@ public class MasterClear extends InstrumentedPreferenceFragment {
if (TextUtils.isEmpty(accountType)
|| TextUtils.isEmpty(packageName)
|| TextUtils.isEmpty(className)) {
return false;
Log.i(TAG, "Resources not set for account confirmation.");
return null;
}
final AccountManager am = AccountManager.get(context);
Account[] accounts = am.getAccountsByType(accountType);
@@ -179,12 +195,14 @@ public class MasterClear extends InstrumentedPreferenceFragment {
&& packageName.equals(resolution.activityInfo.packageName)) {
// Note that we need to check the packagename to make sure that an Activity resolver
// wasn't returned.
startActivityForResult(
requestAccountConfirmation, CREDENTIAL_CONFIRM_REQUEST);
return true;
return requestAccountConfirmation;
} else {
Log.i(TAG, "Unable to resolve Activity: " + packageName + "/" + className);
}
} else {
Log.d(TAG, "No " + accountType + " accounts installed!");
}
return false;
return null;
}
/**
@@ -210,7 +228,14 @@ public class MasterClear extends InstrumentedPreferenceFragment {
return;
}
if (!tryShowAccountConfirmation() && !runKeyguardConfirmation(KEYGUARD_REQUEST)) {
if (runKeyguardConfirmation(KEYGUARD_REQUEST)) {
return;
}
Intent intent = getAccountConfirmationIntent();
if (intent != null) {
showAccountCredentialConfirmation(intent);
} else {
showFinalConfirmation();
}
}
@@ -228,7 +253,8 @@ public class MasterClear extends InstrumentedPreferenceFragment {
* time, then simply reuse the inflated views directly whenever we need
* to change contents.
*/
private void establishInitialState() {
@VisibleForTesting
void establishInitialState() {
mInitiateButton = (Button) mContentView.findViewById(R.id.initiate_master_clear);
mInitiateButton.setOnClickListener(mInitiateListener);
mExternalStorageContainer = mContentView.findViewById(R.id.erase_external_container);

View File

@@ -96,6 +96,7 @@ public class Settings extends SettingsActivity {
public static class ZenAccessSettingsActivity extends SettingsActivity { /* empty */ }
public static class ConditionProviderSettingsActivity extends SettingsActivity { /* empty */ }
public static class UsbSettingsActivity extends SettingsActivity { /* empty */ }
public static class UsbDetailsActivity extends SettingsActivity { /* empty */ }
public static class TrustedCredentialsSettingsActivity extends SettingsActivity { /* empty */ }
public static class PaymentSettingsActivity extends SettingsActivity { /* empty */ }
public static class PrintSettingsActivity extends SettingsActivity { /* empty */ }
@@ -108,7 +109,6 @@ public class Settings extends SettingsActivity {
public static class ZenModeExternalRuleSettingsActivity extends SettingsActivity { /* empty */ }
public static class SoundSettingsActivity extends SettingsActivity { /* empty */ }
public static class ConfigureNotificationSettingsActivity extends SettingsActivity { /* empty */ }
public static class NotificationAppListActivity extends SettingsActivity { /* empty */ }
public static class AppNotificationSettingsActivity extends SettingsActivity { /* empty */ }
public static class ChannelNotificationSettingsActivity extends SettingsActivity { /* empty */ }
public static class ChannelGroupNotificationSettingsActivity extends SettingsActivity { /* empty */ }

View File

@@ -91,17 +91,20 @@ public class AccountDashboardFragment extends DashboardFragment {
final BidiFormatter bidiFormatter = BidiFormatter.getInstance();
CharSequence summary = null;
if (types == null || types.length == 0) {
summary = mContext.getString(R.string.account_dashboard_default_summary);
} else {
// Show up to 3 account types
final int size = Math.min(3, types.length);
// Show up to 3 account types
final int size = Math.min(3, types.length);
for (int i = 0; i < size; i++) {
final CharSequence label = authHelper.getLabelForType(mContext, types[i]);
if (summary == null) {
summary = bidiFormatter.unicodeWrap(label);
} else {
summary = mContext.getString(R.string.join_many_items_middle, summary,
bidiFormatter.unicodeWrap(label));
for (int i = 0; i < size; i++) {
final CharSequence label = authHelper.getLabelForType(mContext, types[i]);
if (summary == null) {
summary = bidiFormatter.unicodeWrap(label);
} else {
summary = mContext.getString(R.string.join_many_items_middle, summary,
bidiFormatter.unicodeWrap(label));
}
}
}
mSummaryLoader.setSummary(this, summary);

View File

@@ -1,83 +0,0 @@
/*
* Copyright (C) 2016 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.applications;
import android.app.Activity;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import com.android.settings.R;
import com.android.settings.applications.manageapplications.ManageApplications;
import com.android.settings.dashboard.SummaryLoader;
import com.android.settings.notification.NotificationBackend;
import com.android.settingslib.wrapper.PackageManagerWrapper;
/**
* Extension of ManageApplications with no changes other than having its own
* SummaryProvider.
*/
public class NotificationApps extends ManageApplications {
public static class SummaryProvider implements SummaryLoader.SummaryProvider {
private final Context mContext;
private final SummaryLoader mLoader;
private final NotificationBackend mNotificationBackend;
private final PackageManagerWrapper mPackageManager;
public SummaryProvider(Context context, SummaryLoader loader) {
mContext = context;
mLoader = loader;
mNotificationBackend = new NotificationBackend();
mPackageManager = new PackageManagerWrapper(mContext.getPackageManager());
}
@Override
public void setListening(boolean listening) {
if (listening) {
new AppCounter(mContext, mPackageManager) {
@Override
protected void onCountComplete(int num) {
updateSummary(num);
}
@Override
protected boolean includeInCount(ApplicationInfo info) {
return mNotificationBackend.getNotificationsBanned(info.packageName,
info.uid);
}
}.execute();
}
}
private void updateSummary(int count) {
if (count == 0) {
mLoader.setSummary(this, mContext.getString(R.string.notification_summary_none));
} else {
mLoader.setSummary(this, mContext.getResources().getQuantityString(
R.plurals.notification_summary, count, count));
}
}
}
public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY
= new SummaryLoader.SummaryProviderFactory() {
@Override
public SummaryLoader.SummaryProvider createSummaryProvider(Activity activity,
SummaryLoader summaryLoader) {
return new SummaryProvider(activity, summaryLoader);
}
};
}

View File

@@ -70,7 +70,6 @@ import com.android.settings.Settings.GamesStorageActivity;
import com.android.settings.Settings.HighPowerApplicationsActivity;
import com.android.settings.Settings.ManageExternalSourcesActivity;
import com.android.settings.Settings.MoviesStorageActivity;
import com.android.settings.Settings.NotificationAppListActivity;
import com.android.settings.Settings.OverlaySettingsActivity;
import com.android.settings.Settings.StorageUseActivity;
import com.android.settings.Settings.UsageAccessSettingsActivity;
@@ -90,7 +89,6 @@ import com.android.settings.applications.AppStateWriteSettingsBridge;
import com.android.settings.applications.AppStorageSettings;
import com.android.settings.applications.DefaultAppSettings;
import com.android.settings.applications.InstalledAppCounter;
import com.android.settings.applications.NotificationApps;
import com.android.settings.applications.DirectoryAccessDetails;
import com.android.settings.applications.UsageAccessDetails;
import com.android.settings.applications.appinfo.AppInfoDashboardFragment;
@@ -238,12 +236,7 @@ public class ManageApplications extends InstrumentedPreferenceFragment
if (className == null) {
className = intent.getComponent().getClassName();
}
if (className.equals(NotificationAppListActivity.class.getName())
|| this instanceof NotificationApps) {
mListType = LIST_TYPE_NOTIFICATION;
mNotifBackend = new NotificationBackend();
screenTitle = R.string.app_notifications_title;
} else if (className.equals(StorageUseActivity.class.getName())) {
if (className.equals(StorageUseActivity.class.getName())) {
if (args != null && args.containsKey(EXTRA_VOLUME_UUID)) {
mVolumeUuid = args.getString(EXTRA_VOLUME_UUID);
mStorageType = args.getInt(EXTRA_STORAGE_TYPE, STORAGE_TYPE_DEFAULT);

View File

@@ -24,8 +24,9 @@ import com.android.settings.R;
import com.android.settings.bluetooth.BluetoothFilesPreferenceController;
import com.android.settings.bluetooth.BluetoothMasterSwitchPreferenceController;
import com.android.settings.bluetooth.BluetoothSwitchPreferenceController;
import com.android.settings.connecteddevice.usb.UsbBackend;
import com.android.settings.connecteddevice.usb.UsbModePreferenceController;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.deviceinfo.UsbBackend;
import com.android.settings.nfc.NfcPreferenceController;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.BaseSearchIndexProvider;

View File

@@ -26,9 +26,10 @@ import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.bluetooth.BluetoothMasterSwitchPreferenceController;
import com.android.settings.bluetooth.Utils;
import com.android.settings.connecteddevice.usb.UsbBackend;
import com.android.settings.connecteddevice.usb.UsbModePreferenceController;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.dashboard.SummaryLoader;
import com.android.settings.deviceinfo.UsbBackend;
import com.android.settings.nfc.NfcPreferenceController;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.BaseSearchIndexProvider;

View File

@@ -20,6 +20,7 @@ import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceGroup;
import android.support.v7.preference.PreferenceScreen;
import com.android.settings.connecteddevice.usb.ConnectedUsbDeviceUpdater;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.bluetooth.BluetoothDeviceUpdater;
import com.android.settings.bluetooth.ConnectedBluetoothDeviceUpdater;
@@ -48,7 +49,7 @@ public class ConnectedDeviceGroupController extends AbstractPreferenceController
public ConnectedDeviceGroupController(DashboardFragment fragment, Lifecycle lifecycle) {
super(fragment.getContext());
init(lifecycle, new ConnectedBluetoothDeviceUpdater(fragment, this),
new ConnectedUsbDeviceUpdater(fragment.getContext(), this));
new ConnectedUsbDeviceUpdater(fragment, this));
}
@VisibleForTesting

View File

@@ -1,76 +0,0 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.connecteddevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.UsbManager;
/**
* Receiver to receive usb update and use {@link UsbConnectionListener} to invoke callback
*/
public class UsbConnectionBroadcastReceiver extends BroadcastReceiver {
private Context mContext;
private UsbConnectionListener mUsbConnectionListener;
private boolean mListeningToUsbEvents;
private boolean mConnected;
public UsbConnectionBroadcastReceiver(Context context,
UsbConnectionListener usbConnectionListener) {
mContext = context;
mUsbConnectionListener = usbConnectionListener;
}
@Override
public void onReceive(Context context, Intent intent) {
mConnected = intent != null
&& intent.getExtras().getBoolean(UsbManager.USB_CONNECTED);
if (mUsbConnectionListener != null) {
mUsbConnectionListener.onUsbConnectionChanged(mConnected);
}
}
public void register() {
if (!mListeningToUsbEvents) {
final IntentFilter intentFilter = new IntentFilter(UsbManager.ACTION_USB_STATE);
final Intent intent = mContext.registerReceiver(this, intentFilter);
mConnected = intent != null
&& intent.getExtras().getBoolean(UsbManager.USB_CONNECTED);
mListeningToUsbEvents = true;
}
}
public void unregister() {
if (mListeningToUsbEvents) {
mContext.unregisterReceiver(this);
mListeningToUsbEvents = false;
}
}
public boolean isConnected() {
return mConnected;
}
/**
* Interface definition for a callback to be invoked when usb connection is changed.
*/
interface UsbConnectionListener {
void onUsbConnectionChanged(boolean connected);
}
}

View File

@@ -13,22 +13,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.connecteddevice;
package com.android.settings.connecteddevice.usb;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.VisibleForTesting;
import android.support.v14.preference.PreferenceFragment;
import com.android.settings.R;
import com.android.settings.deviceinfo.UsbBackend;
import com.android.settings.deviceinfo.UsbModeChooserActivity;
import com.android.settings.SettingsActivity;
import com.android.settings.connecteddevice.DevicePreferenceCallback;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.widget.GearPreference;
/**
* Controller to maintain connected usb device
*/
public class ConnectedUsbDeviceUpdater {
private Context mContext;
private PreferenceFragment mFragment;
private UsbBackend mUsbBackend;
private DevicePreferenceCallback mDevicePreferenceCallback;
@VisibleForTesting
@@ -36,8 +38,9 @@ public class ConnectedUsbDeviceUpdater {
@VisibleForTesting
UsbConnectionBroadcastReceiver mUsbReceiver;
private UsbConnectionBroadcastReceiver.UsbConnectionListener mUsbConnectionListener =
(connected) -> {
@VisibleForTesting
UsbConnectionBroadcastReceiver.UsbConnectionListener mUsbConnectionListener =
(connected, newMode) -> {
if (connected) {
mUsbPreference.setSummary(
UsbModePreferenceController.getSummary(mUsbBackend.getCurrentMode()));
@@ -47,18 +50,19 @@ public class ConnectedUsbDeviceUpdater {
}
};
public ConnectedUsbDeviceUpdater(Context context,
public ConnectedUsbDeviceUpdater(DashboardFragment fragment,
DevicePreferenceCallback devicePreferenceCallback) {
this(context, devicePreferenceCallback, new UsbBackend(context));
this(fragment, devicePreferenceCallback, new UsbBackend(fragment.getContext()));
}
@VisibleForTesting
ConnectedUsbDeviceUpdater(Context context, DevicePreferenceCallback devicePreferenceCallback,
UsbBackend usbBackend) {
mContext = context;
ConnectedUsbDeviceUpdater(DashboardFragment fragment,
DevicePreferenceCallback devicePreferenceCallback, UsbBackend usbBackend) {
mFragment = fragment;
mDevicePreferenceCallback = devicePreferenceCallback;
mUsbBackend = usbBackend;
mUsbReceiver = new UsbConnectionBroadcastReceiver(context, mUsbConnectionListener);
mUsbReceiver = new UsbConnectionBroadcastReceiver(fragment.getContext(),
mUsbConnectionListener, mUsbBackend);
}
public void registerCallback() {
@@ -76,8 +80,12 @@ public class ConnectedUsbDeviceUpdater {
mUsbPreference.setIcon(R.drawable.ic_usb);
mUsbPreference.setSelectable(false);
mUsbPreference.setOnGearClickListener((GearPreference p) -> {
final Intent intent = new Intent(mContext, UsbModeChooserActivity.class);
mContext.startActivity(intent);
// New version - uses a separate screen.
final Bundle args = new Bundle();
final SettingsActivity activity = (SettingsActivity) mFragment.getContext();
activity.startPreferencePanel(mFragment,
UsbDetailsFragment.class.getName(), args,
R.string.device_details_title, null /* titleText */, null /* resultTo */, 0);
});
forceUpdate();
@@ -87,6 +95,5 @@ public class ConnectedUsbDeviceUpdater {
// Register so we can get the connection state from sticky intent.
//TODO(b/70336520): Use an API to get data instead of sticky intent
mUsbReceiver.register();
mUsbConnectionListener.onUsbConnectionChanged(mUsbReceiver.isConnected());
}
}

View File

@@ -0,0 +1,3 @@
# Default reviewers for this and subdirectories.
zhangjerry@google.com
badhri@google.com

View File

@@ -13,15 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.deviceinfo;
package com.android.settings.connecteddevice.usb;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.hardware.usb.UsbManager;
import android.hardware.usb.UsbPort;
import android.hardware.usb.UsbPortStatus;
import android.net.ConnectivityManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.support.annotation.VisibleForTesting;
@@ -32,34 +31,52 @@ public class UsbBackend {
public static final int MODE_POWER_SINK = 0x00;
public static final int MODE_POWER_SOURCE = 0x01;
public static final int MODE_DATA_MASK = 0x03 << 1;
public static final int MODE_DATA_NONE = 0x00 << 1;
public static final int MODE_DATA_MASK = 0x0f << 1;
public static final int MODE_DATA_NONE = 0;
public static final int MODE_DATA_MTP = 0x01 << 1;
public static final int MODE_DATA_PTP = 0x02 << 1;
public static final int MODE_DATA_MIDI = 0x03 << 1;
public static final int MODE_DATA_PTP = 0x01 << 2;
public static final int MODE_DATA_MIDI = 0x01 << 3;
public static final int MODE_DATA_TETHER = 0x01 << 4;
private final boolean mRestricted;
private final boolean mRestrictedBySystem;
private final boolean mMidi;
private final boolean mFileTransferRestricted;
private final boolean mFileTransferRestrictedBySystem;
private final boolean mTetheringRestricted;
private final boolean mTetheringRestrictedBySystem;
private final boolean mMidiSupported;
private final boolean mTetheringSupported;
private UsbManager mUsbManager;
@VisibleForTesting
UsbManagerPassThrough mUsbManagerPassThrough;
private UsbPort mPort;
private UsbPortStatus mPortStatus;
private Context mContext;
public UsbBackend(Context context) {
this(context, new UserRestrictionUtil(context));
this(context, new UserRestrictionUtil(context), null);
}
@VisibleForTesting
public UsbBackend(Context context, UserRestrictionUtil userRestrictionUtil) {
public UsbBackend(Context context, UserRestrictionUtil userRestrictionUtil,
UsbManagerPassThrough usbManagerPassThrough) {
mContext = context;
mUsbManager = context.getSystemService(UsbManager.class);
mRestricted = userRestrictionUtil.isUsbFileTransferRestricted();
mRestrictedBySystem = userRestrictionUtil.isUsbFileTransferRestrictedBySystem();
mMidi = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI);
mUsbManagerPassThrough = usbManagerPassThrough;
if (mUsbManagerPassThrough == null) {
mUsbManagerPassThrough = new UsbManagerPassThrough(mUsbManager);
}
mFileTransferRestricted = userRestrictionUtil.isUsbFileTransferRestricted();
mFileTransferRestrictedBySystem = userRestrictionUtil.isUsbFileTransferRestrictedBySystem();
mTetheringRestricted = userRestrictionUtil.isUsbTetheringRestricted();
mTetheringRestrictedBySystem = userRestrictionUtil.isUsbTetheringRestrictedBySystem();
mMidiSupported = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI);
ConnectivityManager cm =
(ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
mTetheringSupported = cm.isTetheringSupported();
UsbPort[] ports = mUsbManager.getPorts();
if (ports == null) {
@@ -81,6 +98,7 @@ public class UsbBackend {
public int getCurrentMode() {
if (mPort != null) {
int power = mPortStatus.getCurrentPowerRole() == UsbPort.POWER_ROLE_SOURCE
&& mPortStatus.isConnected()
? MODE_POWER_SOURCE : MODE_POWER_SINK;
return power | getUsbDataMode();
}
@@ -88,38 +106,35 @@ public class UsbBackend {
}
public int getUsbDataMode() {
if (!isUsbDataUnlocked()) {
return MODE_DATA_NONE;
} else if (mUsbManager.isFunctionEnabled(UsbManager.USB_FUNCTION_MTP)) {
long functions = mUsbManagerPassThrough.getCurrentFunctions();
if (functions == UsbManager.FUNCTION_MTP) {
return MODE_DATA_MTP;
} else if (mUsbManager.isFunctionEnabled(UsbManager.USB_FUNCTION_PTP)) {
} else if (functions == UsbManager.FUNCTION_PTP) {
return MODE_DATA_PTP;
} else if (mUsbManager.isFunctionEnabled(UsbManager.USB_FUNCTION_MIDI)) {
} else if (functions == UsbManager.FUNCTION_MIDI) {
return MODE_DATA_MIDI;
} else if (functions == UsbManager.FUNCTION_RNDIS) {
return MODE_DATA_TETHER;
}
return MODE_DATA_NONE; // ...
}
private boolean isUsbDataUnlocked() {
Intent intent = mContext.registerReceiver(null,
new IntentFilter(UsbManager.ACTION_USB_STATE));
return intent == null ?
false : intent.getBooleanExtra(UsbManager.USB_DATA_UNLOCKED, false);
return MODE_DATA_NONE;
}
private void setUsbFunction(int mode) {
switch (mode) {
case MODE_DATA_MTP:
mUsbManager.setCurrentFunction(UsbManager.USB_FUNCTION_MTP, true);
mUsbManager.setCurrentFunctions(UsbManager.FUNCTION_MTP);
break;
case MODE_DATA_PTP:
mUsbManager.setCurrentFunction(UsbManager.USB_FUNCTION_PTP, true);
mUsbManager.setCurrentFunctions(UsbManager.FUNCTION_PTP);
break;
case MODE_DATA_MIDI:
mUsbManager.setCurrentFunction(UsbManager.USB_FUNCTION_MIDI, true);
mUsbManager.setCurrentFunctions(UsbManager.FUNCTION_MIDI);
break;
case MODE_DATA_TETHER:
mUsbManager.setCurrentFunctions(UsbManager.FUNCTION_RNDIS);
break;
default:
mUsbManager.setCurrentFunction(null, false);
mUsbManager.setCurrentFunctions(UsbManager.FUNCTION_NONE);
break;
}
}
@@ -144,28 +159,32 @@ public class UsbBackend {
}
public boolean isModeDisallowed(int mode) {
if (mRestricted && (mode & MODE_DATA_MASK) != MODE_DATA_NONE
&& (mode & MODE_DATA_MASK) != MODE_DATA_MIDI) {
// No USB data modes are supported.
if (mFileTransferRestricted && ((mode & MODE_DATA_MASK) == MODE_DATA_MTP
|| (mode & MODE_DATA_MASK) == MODE_DATA_PTP)) {
return true;
} else if (mTetheringRestricted && ((mode & MODE_DATA_MASK) == MODE_DATA_TETHER)) {
return true;
}
return false;
}
public boolean isModeDisallowedBySystem(int mode) {
if (mRestrictedBySystem && (mode & MODE_DATA_MASK) != MODE_DATA_NONE
&& (mode & MODE_DATA_MASK) != MODE_DATA_MIDI) {
// No USB data modes are supported.
if (mFileTransferRestrictedBySystem && ((mode & MODE_DATA_MASK) == MODE_DATA_MTP
|| (mode & MODE_DATA_MASK) == MODE_DATA_PTP)) {
return true;
} else if (mTetheringRestrictedBySystem && ((mode & MODE_DATA_MASK) == MODE_DATA_TETHER)) {
return true;
}
return false;
}
public boolean isModeSupported(int mode) {
if (!mMidi && (mode & MODE_DATA_MASK) == MODE_DATA_MIDI) {
if (!mMidiSupported && (mode & MODE_DATA_MASK) == MODE_DATA_MIDI) {
return false;
}
if (!mTetheringSupported && (mode & MODE_DATA_MASK) == MODE_DATA_TETHER) {
return false;
}
if (mPort != null) {
int power = modeToPower(mode);
if ((mode & MODE_DATA_MASK) != 0) {
@@ -194,9 +213,35 @@ public class UsbBackend {
return mUserManager.hasUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER);
}
public boolean isUsbTetheringRestricted() {
return mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING);
}
public boolean isUsbFileTransferRestrictedBySystem() {
return mUserManager.hasBaseUserRestriction(
UserManager.DISALLOW_USB_FILE_TRANSFER, UserHandle.of(UserHandle.myUserId()));
}
public boolean isUsbTetheringRestrictedBySystem() {
return mUserManager.hasBaseUserRestriction(
UserManager.DISALLOW_CONFIG_TETHERING, UserHandle.of(UserHandle.myUserId()));
}
}
// Temporary pass-through to allow roboelectric to use getCurrentFunctions()
public static class UsbManagerPassThrough {
private UsbManager mUsbManager;
public UsbManagerPassThrough(UsbManager manager) {
mUsbManager = manager;
}
public long getCurrentFunctions() {
return mUsbManager.getCurrentFunctions();
}
public long usbFunctionsFromString(String str) {
return UsbManager.usbFunctionsFromString(str);
}
}
}

View File

@@ -0,0 +1,129 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.connecteddevice.usb;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.UsbManager;
import android.hardware.usb.UsbPort;
import android.hardware.usb.UsbPortStatus;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnResume;
import com.android.settingslib.core.lifecycle.events.OnPause;
/**
* Receiver to receive usb update and use {@link UsbConnectionListener} to invoke callback
*/
public class UsbConnectionBroadcastReceiver extends BroadcastReceiver implements LifecycleObserver,
OnResume, OnPause {
private Context mContext;
private UsbConnectionListener mUsbConnectionListener;
private boolean mListeningToUsbEvents;
private int mMode;
private boolean mConnected;
private UsbBackend mUsbBackend;
public UsbConnectionBroadcastReceiver(Context context,
UsbConnectionListener usbConnectionListener, UsbBackend backend) {
mContext = context;
mUsbConnectionListener = usbConnectionListener;
mUsbBackend = backend;
}
@Override
public void onReceive(Context context, Intent intent) {
if (UsbManager.ACTION_USB_STATE.equals(intent.getAction())) {
mConnected = intent.getExtras().getBoolean(UsbManager.USB_CONNECTED)
|| intent.getExtras().getBoolean(UsbManager.USB_HOST_CONNECTED);
if (mConnected) {
mMode &= UsbBackend.MODE_POWER_MASK;
if (intent.getExtras().getBoolean(UsbManager.USB_FUNCTION_MTP)
&& intent.getExtras().getBoolean(UsbManager.USB_DATA_UNLOCKED, false)) {
mMode |= UsbBackend.MODE_DATA_MTP;
}
if (intent.getExtras().getBoolean(UsbManager.USB_FUNCTION_PTP)
&& intent.getExtras().getBoolean(UsbManager.USB_DATA_UNLOCKED, false)) {
mMode |= UsbBackend.MODE_DATA_PTP;
}
if (intent.getExtras().getBoolean(UsbManager.USB_FUNCTION_MIDI)) {
mMode |= UsbBackend.MODE_DATA_MIDI;
}
if (intent.getExtras().getBoolean(UsbManager.USB_FUNCTION_RNDIS)) {
mMode |= UsbBackend.MODE_DATA_TETHER;
}
}
} else if (UsbManager.ACTION_USB_PORT_CHANGED.equals(intent.getAction())) {
mMode &= UsbBackend.MODE_DATA_MASK;
UsbPortStatus portStatus = intent.getExtras()
.getParcelable(UsbManager.EXTRA_PORT_STATUS);
if (portStatus != null) {
mConnected = portStatus.isConnected();
if (mConnected) {
mMode |= portStatus.getCurrentPowerRole() == UsbPort.POWER_ROLE_SOURCE
? UsbBackend.MODE_POWER_SOURCE : UsbBackend.MODE_POWER_SINK;
}
}
}
if (mUsbConnectionListener != null) {
mUsbConnectionListener.onUsbConnectionChanged(mConnected, mMode);
}
}
public void register() {
if (!mListeningToUsbEvents) {
mMode = mUsbBackend.getCurrentMode();
mConnected = false;
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(UsbManager.ACTION_USB_STATE);
intentFilter.addAction(UsbManager.ACTION_USB_PORT_CHANGED);
mContext.registerReceiver(this, intentFilter);
mListeningToUsbEvents = true;
}
}
public void unregister() {
if (mListeningToUsbEvents) {
mContext.unregisterReceiver(this);
mListeningToUsbEvents = false;
}
}
public boolean isConnected() {
return mConnected;
}
@Override
public void onResume() {
register();
}
@Override
public void onPause() {
unregister();
}
/**
* Interface definition for a callback to be invoked when usb connection is changed.
*/
interface UsbConnectionListener {
void onUsbConnectionChanged(boolean connected, int newMode);
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.connecteddevice.usb;
import android.content.Context;
import android.support.annotation.UiThread;
import android.support.v14.preference.PreferenceFragment;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settingslib.core.AbstractPreferenceController;
/**
* This class provides common members and refresh functionality for usb controllers.
*/
public abstract class UsbDetailsController extends AbstractPreferenceController
implements PreferenceControllerMixin {
protected final Context mContext;
protected final PreferenceFragment mFragment;
protected final UsbBackend mUsbBackend;
public UsbDetailsController(Context context, PreferenceFragment fragment, UsbBackend backend) {
super(context);
mContext = context;
mFragment = fragment;
mUsbBackend = backend;
}
@Override
public boolean isAvailable() {
return true;
}
/**
* This method is called when the USB mode has changed and the controller needs to update.
* @param newMode the new mode, made up of OR'd values from UsbBackend
*/
@UiThread
protected abstract void refresh(int newMode);
}

View File

@@ -0,0 +1,131 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.connecteddevice.usb;
import android.content.Context;
import android.hardware.usb.UsbManager;
import android.os.Bundle;
import android.provider.SearchIndexableResource;
import android.support.annotation.VisibleForTesting;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.search.Indexable;
import com.android.settingslib.core.AbstractPreferenceController;
import com.google.android.collect.Lists;
import java.util.ArrayList;
import java.util.List;
/**
* Controls the USB device details and provides updates to individual controllers.
*/
public class UsbDetailsFragment extends DashboardFragment {
private static final String TAG = UsbDetailsFragment.class.getSimpleName();
private List<UsbDetailsController> mControllers;
private UsbBackend mUsbBackend;
@VisibleForTesting
UsbConnectionBroadcastReceiver mUsbReceiver;
private UsbConnectionBroadcastReceiver.UsbConnectionListener mUsbConnectionListener =
(connected, newMode) -> {
if (!connected) {
this.finish();
} else {
for (UsbDetailsController controller : mControllers) {
controller.refresh(newMode);
}
}
};
@Override
public int getMetricsCategory() {
return MetricsProto.MetricsEvent.USB_DEVICE_DETAILS;
}
@Override
protected String getLogTag() {
return TAG;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.usb_details_fragment;
}
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
super.onCreatePreferences(savedInstanceState, rootKey);
}
@Override
protected List<AbstractPreferenceController> getPreferenceControllers(Context context) {
mUsbBackend = new UsbBackend(context);
mControllers = createControllerList(context, mUsbBackend, this);
mUsbReceiver = new UsbConnectionBroadcastReceiver(context, mUsbConnectionListener,
mUsbBackend);
this.getLifecycle().addObserver(mUsbReceiver);
List<AbstractPreferenceController> ret = new ArrayList<>();
ret.addAll(mControllers);
return ret;
}
private static List<UsbDetailsController> createControllerList(Context context,
UsbBackend usbBackend, DashboardFragment fragment) {
List<UsbDetailsController> ret = new ArrayList<>();
ret.add(new UsbDetailsHeaderController(context, fragment, usbBackend));
ret.add(new UsbDetailsProfilesController(context, fragment,
usbBackend, Lists.newArrayList(UsbManager.USB_FUNCTION_MTP), "usb_main_options"));
ret.add(new UsbDetailsProfilesController(context, fragment,
usbBackend, Lists.newArrayList(UsbDetailsProfilesController.KEY_POWER,
UsbManager.USB_FUNCTION_RNDIS, UsbManager.USB_FUNCTION_MIDI,
UsbManager.USB_FUNCTION_PTP), "usb_secondary_options"));
return ret;
}
/**
* For Search.
*/
public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider() {
@Override
public List<SearchIndexableResource> getXmlResourcesToIndex(
Context context, boolean enabled) {
return new ArrayList<>();
}
@Override
public List<String> getNonIndexableKeys(Context context) {
return super.getNonIndexableKeys(context);
}
@Override
public List<AbstractPreferenceController> getPreferenceControllers(
Context context) {
List<AbstractPreferenceController> ret = new ArrayList<>();
ret.addAll(createControllerList(context, new UsbBackend(context), null));
return ret;
}
};
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.connecteddevice.usb;
import android.content.Context;
import android.support.v14.preference.PreferenceFragment;
import android.support.v7.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.applications.LayoutPreference;
import com.android.settings.widget.EntityHeaderController;
/**
* This class adds a header with device name and current function.
*/
public class UsbDetailsHeaderController extends UsbDetailsController {
private static final String KEY_DEVICE_HEADER = "usb_device_header";
private EntityHeaderController mHeaderController;
public UsbDetailsHeaderController(Context context, PreferenceFragment fragment,
UsbBackend backend) {
super(context, fragment, backend);
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
final LayoutPreference headerPreference =
(LayoutPreference) screen.findPreference(KEY_DEVICE_HEADER);
mHeaderController = EntityHeaderController.newInstance(mFragment.getActivity(), mFragment,
headerPreference.findViewById(R.id.entity_header));
screen.addPreference(headerPreference);
}
@Override
protected void refresh(int newMode) {
mHeaderController.setLabel(mContext.getString(R.string.usb_pref));
mHeaderController.setIcon(mContext.getDrawable(R.drawable.ic_usb));
mHeaderController.setSummary(
mContext.getString(UsbModePreferenceController.getSummary(newMode)));
mHeaderController.done(mFragment.getActivity(), true /* rebindActions */);
}
@Override
public String getPreferenceKey() {
return KEY_DEVICE_HEADER;
}
}

View File

@@ -0,0 +1,147 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.connecteddevice.usb;
import com.android.settings.R;
import android.content.Context;
import android.hardware.usb.UsbManager;
import android.support.v14.preference.PreferenceFragment;
import android.support.v14.preference.SwitchPreference;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceCategory;
import android.support.v7.preference.PreferenceScreen;
import java.util.List;
/**
* This class adds switches for toggling individual USB options, such as "transfer files",
* "supply power", "usb tethering", etc.
*/
public class UsbDetailsProfilesController extends UsbDetailsController
implements Preference.OnPreferenceClickListener {
static final String KEY_POWER = "power";
private PreferenceCategory mProfilesContainer;
private List<String> mOptions;
private String mKey;
public UsbDetailsProfilesController(Context context, PreferenceFragment fragment,
UsbBackend backend, List<String> options, String key) {
super(context, fragment, backend);
mOptions = options;
mKey = key;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mProfilesContainer = (PreferenceCategory) screen.findPreference(getPreferenceKey());
}
/**
* Gets a switch preference for the particular option, creating it if needed.
*/
private SwitchPreference getProfilePreference(String key, int titleId) {
SwitchPreference pref = (SwitchPreference) mProfilesContainer.findPreference(key);
if (pref == null) {
pref = new SwitchPreference(mProfilesContainer.getContext());
pref.setKey(key);
pref.setTitle(titleId);
pref.setOnPreferenceClickListener(this);
mProfilesContainer.addPreference(pref);
}
return pref;
}
@Override
protected void refresh(int mode) {
SwitchPreference pref;
for (String option : mOptions) {
int newMode;
int summary = -1;
int title;
if (option.equals(UsbManager.USB_FUNCTION_MTP)) {
newMode = UsbBackend.MODE_DATA_MTP;
title = R.string.usb_use_file_transfers;
} else if (option.equals(KEY_POWER)) {
newMode = UsbBackend.MODE_POWER_SOURCE;
title = R.string.usb_use_power_only;
summary = R.string.usb_use_power_only_desc;
} else if (option.equals(UsbManager.USB_FUNCTION_PTP)) {
newMode = UsbBackend.MODE_DATA_PTP;
title = R.string.usb_use_photo_transfers;
} else if (option.equals(UsbManager.USB_FUNCTION_MIDI)) {
newMode = UsbBackend.MODE_DATA_MIDI;
title = R.string.usb_use_MIDI;
} else if (option.equals(UsbManager.USB_FUNCTION_RNDIS)) {
newMode = UsbBackend.MODE_DATA_TETHER;
title = R.string.usb_use_tethering;
} else {
continue;
}
pref = getProfilePreference(option, title);
// Only show supported and allowed options
if (mUsbBackend.isModeSupported(newMode)
&& !mUsbBackend.isModeDisallowedBySystem(newMode)
&& !mUsbBackend.isModeDisallowed(newMode)) {
if (summary != -1) {
pref.setSummary(summary);
}
pref.setChecked((mode & newMode) != 0);
} else {
mProfilesContainer.removePreference(pref);
}
}
}
@Override
public boolean onPreferenceClick(Preference preference) {
SwitchPreference profilePref = (SwitchPreference) preference;
String key = profilePref.getKey();
int mode = mUsbBackend.getCurrentMode();
int thisMode = 0;
if (key.equals(KEY_POWER)) {
thisMode = UsbBackend.MODE_POWER_SOURCE;
} else if (key.equals(UsbManager.USB_FUNCTION_MTP)) {
thisMode = UsbBackend.MODE_DATA_MTP;
} else if (key.equals(UsbManager.USB_FUNCTION_PTP)) {
thisMode = UsbBackend.MODE_DATA_PTP;
} else if (key.equals(UsbManager.USB_FUNCTION_RNDIS)) {
thisMode = UsbBackend.MODE_DATA_TETHER;
} else if (key.equals(UsbManager.USB_FUNCTION_MIDI)) {
thisMode = UsbBackend.MODE_DATA_MIDI;
}
if (profilePref.isChecked()) {
if (!key.equals(KEY_POWER)) {
// Only one non power mode can currently be set at once.
mode &= UsbBackend.MODE_POWER_MASK;
}
mode |= thisMode;
} else {
mode &= ~thisMode;
}
mUsbBackend.setMode(mode);
return false;
}
@Override
public String getPreferenceKey() {
return mKey;
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.android.settings.deviceinfo;
package com.android.settings.connecteddevice.usb;
import android.annotation.Nullable;
import android.app.Activity;

View File

@@ -13,15 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.connecteddevice;
package com.android.settings.connecteddevice.usb;
import android.content.Context;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceScreen;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.deviceinfo.UsbBackend;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnPause;
@@ -33,27 +33,27 @@ public class UsbModePreferenceController extends AbstractPreferenceController
private static final String KEY_USB_MODE = "usb_mode";
private UsbBackend mUsbBackend;
private UsbConnectionBroadcastReceiver mUsbReceiver;
@VisibleForTesting
UsbConnectionBroadcastReceiver mUsbReceiver;
private Preference mUsbPreference;
public UsbModePreferenceController(Context context, UsbBackend usbBackend) {
super(context);
mUsbBackend = usbBackend;
mUsbReceiver = new UsbConnectionBroadcastReceiver(mContext, (connected) -> {
updateSummary(mUsbPreference);
});
mUsbReceiver = new UsbConnectionBroadcastReceiver(mContext, (connected, newMode) -> {
updateSummary(mUsbPreference, connected, newMode);
}, mUsbBackend);
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mUsbPreference = screen.findPreference(KEY_USB_MODE);
updateSummary(mUsbPreference);
}
@Override
public void updateState(Preference preference) {
updateSummary(preference);
updateSummary(preference, mUsbReceiver.isConnected(), mUsbBackend.getCurrentMode());
}
@Override
@@ -88,17 +88,24 @@ public class UsbModePreferenceController extends AbstractPreferenceController
return R.string.usb_summary_photo_transfers;
case UsbBackend.MODE_POWER_SINK | UsbBackend.MODE_DATA_MIDI:
return R.string.usb_summary_MIDI;
case UsbBackend.MODE_POWER_SINK | UsbBackend.MODE_DATA_TETHER:
return R.string.usb_summary_tether;
case UsbBackend.MODE_POWER_SOURCE | UsbBackend.MODE_DATA_MTP:
return R.string.usb_summary_file_transfers_power;
case UsbBackend.MODE_POWER_SOURCE | UsbBackend.MODE_DATA_PTP:
return R.string.usb_summary_photo_transfers_power;
case UsbBackend.MODE_POWER_SOURCE | UsbBackend.MODE_DATA_MIDI:
return R.string.usb_summary_MIDI_power;
case UsbBackend.MODE_POWER_SOURCE | UsbBackend.MODE_DATA_TETHER:
return R.string.usb_summary_tether_power;
default:
return R.string.usb_summary_charging_only;
}
return 0;
}
private void updateSummary(Preference preference) {
updateSummary(preference, mUsbBackend.getCurrentMode());
}
private void updateSummary(Preference preference, int mode) {
private void updateSummary(Preference preference, boolean connected, int mode) {
if (preference != null) {
if (mUsbReceiver.isConnected()) {
if (connected) {
preference.setEnabled(true);
preference.setSummary(getSummary(mode));
} else {
@@ -107,5 +114,4 @@ public class UsbModePreferenceController extends AbstractPreferenceController
}
}
}
}

View File

@@ -24,7 +24,6 @@ public class FeatureFlags {
public static final String BATTERY_SETTINGS_V2 = "settings_battery_v2";
public static final String BATTERY_DISPLAY_APP_LIST = "settings_battery_display_app_list";
public static final String ZONE_PICKER_V2 = "settings_zone_picker_v2";
public static final String SUGGESTION_UI_V2 = "settings_suggestion_ui_v2";
public static final String ABOUT_PHONE_V2 = "settings_about_phone_v2";
public static final String BLUETOOTH_WHILE_DRIVING = "settings_bluetooth_while_driving";
}

View File

@@ -40,7 +40,6 @@ import com.android.settings.accounts.AccountDashboardFragment;
import com.android.settings.applications.AppAndNotificationDashboardFragment;
import com.android.settings.applications.DefaultAppSettings;
import com.android.settings.applications.ManageDomainUrls;
import com.android.settings.applications.NotificationApps;
import com.android.settings.applications.ProcessStatsSummary;
import com.android.settings.applications.ProcessStatsUi;
import com.android.settings.applications.DirectoryAccessDetails;
@@ -59,6 +58,7 @@ import com.android.settings.bluetooth.BluetoothSettings;
import com.android.settings.connecteddevice.AdvancedConnectedDeviceDashboardFragment;
import com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment;
import com.android.settings.connecteddevice.ConnectedDeviceDashboardFragmentOld;
import com.android.settings.connecteddevice.usb.UsbDetailsFragment;
import com.android.settings.datausage.DataPlanUsageSummary;
import com.android.settings.datausage.DataUsageList;
import com.android.settings.datausage.DataUsageSummary;
@@ -157,7 +157,6 @@ public class SettingsGateway {
DisplaySettings.class.getName(),
DeviceInfoSettings.class.getName(),
ManageApplications.class.getName(),
NotificationApps.class.getName(),
ManageAssist.class.getName(),
ProcessStatsUi.class.getName(),
NotificationStation.class.getName(),
@@ -244,6 +243,7 @@ public class SettingsGateway {
NetworkDashboardFragment.class.getName(),
ConnectedDeviceDashboardFragment.class.getName(),
ConnectedDeviceDashboardFragmentOld.class.getName(),
UsbDetailsFragment.class.getName(),
AppAndNotificationDashboardFragment.class.getName(),
AccountDashboardFragment.class.getName(),
EnterprisePrivacySettings.class.getName(),

View File

@@ -17,9 +17,6 @@ package com.android.settings.dashboard;
import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Bundle;
@@ -41,44 +38,37 @@ import android.widget.TextView;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R;
import com.android.settings.R.id;
import com.android.settings.dashboard.DashboardData.SuggestionConditionHeaderData;
import com.android.settings.dashboard.DashboardData.ConditionHeaderData;
import com.android.settings.dashboard.conditional.Condition;
import com.android.settings.dashboard.conditional.ConditionAdapter;
import com.android.settings.dashboard.suggestions.SuggestionAdapter;
import com.android.settings.dashboard.suggestions.SuggestionDismissController;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.Utils;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnSaveInstanceState;
import com.android.settingslib.drawer.DashboardCategory;
import com.android.settingslib.drawer.Tile;
import com.android.settingslib.suggestions.SuggestionControllerMixin;
import java.util.ArrayList;
import java.util.List;
public class DashboardAdapter extends RecyclerView.Adapter<DashboardAdapter.DashboardItemHolder>
implements SummaryLoader.SummaryConsumer {
implements SummaryLoader.SummaryConsumer, SuggestionAdapter.Callback, LifecycleObserver,
OnSaveInstanceState {
public static final String TAG = "DashboardAdapter";
private static final String STATE_SUGGESTION_LIST = "suggestion_list";
private static final String STATE_CATEGORY_LIST = "category_list";
private static final String STATE_SUGGESTIONS_SHOWN_LOGGED = "suggestions_shown_logged";
@VisibleForTesting
static final String STATE_SUGGESTION_CONDITION_MODE = "suggestion_condition_mode";
@VisibleForTesting
static final int SUGGESTION_CONDITION_HEADER_POSITION = 0;
static final String STATE_CONDITION_EXPANDED = "condition_expanded";
private final IconCache mCache;
private final Context mContext;
private final SuggestionControllerMixin mSuggestionControllerMixin;
private final MetricsFeatureProvider mMetricsFeatureProvider;
private final DashboardFeatureProvider mDashboardFeatureProvider;
private final ArrayList<String> mSuggestionsShownLogged;
private boolean mFirstFrameDrawn;
private RecyclerView mRecyclerView;
private SuggestionAdapter mSuggestionAdapter;
private SuggestionDismissController mSuggestionDismissHandler;
private SuggestionDismissController.Callback mCallback;
@VisibleForTesting
DashboardData mDashboardData;
@@ -92,57 +82,54 @@ public class DashboardAdapter extends RecyclerView.Adapter<DashboardAdapter.Dash
};
public DashboardAdapter(Context context, Bundle savedInstanceState,
List<Condition> conditions, SuggestionControllerMixin suggestionControllerMixin,
SuggestionDismissController.Callback callback) {
List<Condition> conditions, SuggestionControllerMixin suggestionControllerMixin,
Lifecycle lifecycle) {
List<Suggestion> suggestions = null;
DashboardCategory category = null;
int suggestionConditionMode = DashboardData.HEADER_MODE_DEFAULT;
boolean conditionExpanded = false;
mContext = context;
final FeatureFactory factory = FeatureFactory.getFactory(context);
mSuggestionControllerMixin = suggestionControllerMixin;
mMetricsFeatureProvider = factory.getMetricsFeatureProvider();
mDashboardFeatureProvider = factory.getDashboardFeatureProvider(context);
mCache = new IconCache(context);
mCallback = callback;
mSuggestionAdapter = new SuggestionAdapter(mContext, suggestionControllerMixin,
savedInstanceState, this /* callback */, lifecycle);
setHasStableIds(true);
if (savedInstanceState != null) {
suggestions = savedInstanceState.getParcelableArrayList(STATE_SUGGESTION_LIST);
category = savedInstanceState.getParcelable(STATE_CATEGORY_LIST);
suggestionConditionMode = savedInstanceState.getInt(
STATE_SUGGESTION_CONDITION_MODE, suggestionConditionMode);
mSuggestionsShownLogged = savedInstanceState.getStringArrayList(
STATE_SUGGESTIONS_SHOWN_LOGGED);
} else {
mSuggestionsShownLogged = new ArrayList<>();
conditionExpanded = savedInstanceState.getBoolean(
STATE_CONDITION_EXPANDED, conditionExpanded);
}
if (lifecycle != null) {
lifecycle.addObserver(this);
}
mDashboardData = new DashboardData.Builder()
.setConditions(conditions)
.setSuggestions(suggestions)
.setCategory(category)
.setSuggestionConditionMode(suggestionConditionMode)
.build();
.setConditions(conditions)
.setSuggestions(mSuggestionAdapter.getSuggestions())
.setCategory(category)
.setConditionExpanded(conditionExpanded)
.build();
}
public void setSuggestions(List<Suggestion> data) {
final DashboardData prevData = mDashboardData;
mDashboardData = new DashboardData.Builder(prevData)
.setSuggestions(data)
.build();
.setSuggestions(data)
.build();
notifyDashboardDataChanged(prevData);
}
public void setCategory(DashboardCategory category) {
tintIcons(category, null);
final DashboardData prevData = mDashboardData;
Log.d(TAG, "adapter setCategory called");
mDashboardData = new DashboardData.Builder(prevData)
.setCategory(category)
.build();
.setCategory(category)
.build();
notifyDashboardDataChanged(prevData);
}
@@ -150,12 +137,13 @@ public class DashboardAdapter extends RecyclerView.Adapter<DashboardAdapter.Dash
final DashboardData prevData = mDashboardData;
Log.d(TAG, "adapter setConditions called");
mDashboardData = new DashboardData.Builder(prevData)
.setConditions(conditions)
.build();
.setConditions(conditions)
.build();
notifyDashboardDataChanged(prevData);
}
public void onSuggestionDismissed(Suggestion suggestion) {
@Override
public void onSuggestionClosed(Suggestion suggestion) {
final List<Suggestion> list = mDashboardData.getSuggestions();
if (list == null || list.size() == 0) {
return;
@@ -163,13 +151,10 @@ public class DashboardAdapter extends RecyclerView.Adapter<DashboardAdapter.Dash
if (list.size() == 1) {
// The only suggestion is dismissed, and the the empty suggestion container will
// remain as the dashboard item. Need to refresh the dashboard list.
final DashboardData prevData = mDashboardData;
mDashboardData = new DashboardData.Builder(prevData)
.setSuggestions(null)
.build();
notifyDashboardDataChanged(prevData);
setSuggestions(null);
} else {
mSuggestionAdapter.removeSuggestion(suggestion);
notifyItemChanged(0, null);
}
}
@@ -186,11 +171,14 @@ public class DashboardAdapter extends RecyclerView.Adapter<DashboardAdapter.Dash
@Override
public DashboardItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
if (viewType == R.layout.suggestion_condition_header) {
return new SuggestionAndConditionHeaderHolder(view);
if (viewType == R.layout.condition_header) {
return new ConditionHeaderHolder(view);
}
if (viewType == R.layout.suggestion_condition_container) {
return new SuggestionAndConditionContainerHolder(view);
if (viewType == R.layout.condition_container) {
return new ConditionContainerHolder(view);
}
if (viewType == R.layout.suggestion_container) {
return new SuggestionContainerHolder(view);
}
return new DashboardItemHolder(view);
}
@@ -205,24 +193,25 @@ public class DashboardAdapter extends RecyclerView.Adapter<DashboardAdapter.Dash
holder.itemView.setTag(tile);
holder.itemView.setOnClickListener(mTileClickListener);
break;
case R.layout.suggestion_condition_container:
onBindConditionAndSuggestion(
(SuggestionAndConditionContainerHolder) holder, position);
case R.layout.suggestion_container:
onBindSuggestion((SuggestionContainerHolder) holder, position);
break;
case R.layout.suggestion_condition_header:
onBindSuggestionConditionHeader((SuggestionAndConditionHeaderHolder) holder,
(SuggestionConditionHeaderData)
mDashboardData.getItemEntityByPosition(position));
case R.layout.condition_container:
onBindCondition((ConditionContainerHolder) holder, position);
break;
case R.layout.suggestion_condition_footer:
case R.layout.condition_header:
onBindConditionHeader((ConditionHeaderHolder) holder,
(ConditionHeaderData) mDashboardData.getItemEntityByPosition(position));
break;
case R.layout.condition_footer:
holder.itemView.setOnClickListener(v -> {
mMetricsFeatureProvider.action(mContext,
MetricsEvent.ACTION_SETTINGS_CONDITION_EXPAND, false);
MetricsEvent.ACTION_SETTINGS_CONDITION_EXPAND, false);
DashboardData prevData = mDashboardData;
mDashboardData = new DashboardData.Builder(prevData).setSuggestionConditionMode(
DashboardData.HEADER_MODE_COLLAPSED).build();
mDashboardData = new DashboardData.Builder(prevData).
setConditionExpanded(false).build();
notifyDashboardDataChanged(prevData);
mRecyclerView.scrollToPosition(SUGGESTION_CONDITION_HEADER_POSITION);
scrollToTopOfConditions();
});
break;
}
@@ -263,7 +252,7 @@ public class DashboardAdapter extends RecyclerView.Adapter<DashboardAdapter.Dash
void notifyDashboardDataChanged(DashboardData prevData) {
if (mFirstFrameDrawn && prevData != null) {
final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DashboardData
.ItemsDataDiffCallback(prevData.getItemList(), mDashboardData.getItemList()));
.ItemsDataDiffCallback(prevData.getItemList(), mDashboardData.getItemList()));
diffResult.dispatchUpdatesTo(this);
} else {
mFirstFrameDrawn = true;
@@ -272,120 +261,66 @@ public class DashboardAdapter extends RecyclerView.Adapter<DashboardAdapter.Dash
}
@VisibleForTesting
void onBindSuggestionConditionHeader(final SuggestionAndConditionHeaderHolder holder,
SuggestionConditionHeaderData data) {
final int curMode = mDashboardData.getSuggestionConditionMode();
final int nextMode = data.hiddenSuggestionCount > 0
&& data.conditionCount > 0
&& curMode != DashboardData.HEADER_MODE_SUGGESTION_EXPANDED
? DashboardData.HEADER_MODE_SUGGESTION_EXPANDED
: DashboardData.HEADER_MODE_FULLY_EXPANDED;
final boolean hasConditions = data.conditionCount > 0;
if (data.conditionCount > 0) {
holder.icon.setImageIcon(data.conditionIcons.get(0));
holder.icon.setVisibility(View.VISIBLE);
if (data.conditionCount == 1) {
holder.title.setText(data.title);
holder.title.setTextColor(Utils.getColorAccent(mContext));
holder.icons.setVisibility(View.INVISIBLE);
} else {
holder.title.setText(null);
updateConditionIcons(data.conditionIcons, holder.icons);
holder.icons.setVisibility(View.VISIBLE);
}
} else {
holder.icon.setVisibility(View.INVISIBLE);
holder.icons.setVisibility(View.INVISIBLE);
}
if (data.hiddenSuggestionCount > 0) {
holder.summary.setTextColor(Color.BLACK);
if (curMode == DashboardData.HEADER_MODE_COLLAPSED) {
if (data.conditionCount > 0) {
holder.summary.setText(mContext.getResources().getQuantityString(
R.plurals.suggestions_collapsed_summary,
data.hiddenSuggestionCount, data.hiddenSuggestionCount));
} else {
holder.title.setText(mContext.getResources().getQuantityString(
R.plurals.suggestions_collapsed_title,
data.hiddenSuggestionCount, data.hiddenSuggestionCount));
holder.title.setTextColor(Color.BLACK);
holder.summary.setText(null);
}
} else if (curMode == DashboardData.HEADER_MODE_DEFAULT) {
if (data.conditionCount > 0) {
holder.summary.setText(mContext.getString(
R.string.suggestions_summary, data.hiddenSuggestionCount));
} else {
holder.title.setText(mContext.getString(
R.string.suggestions_more_title, data.hiddenSuggestionCount));
holder.title.setTextColor(Color.BLACK);
holder.summary.setText(null);
}
}
} else if (data.conditionCount > 1) {
holder.summary.setTextColor(Utils.getColorAccent(mContext));
holder.summary.setText(
mContext.getString(R.string.condition_summary, data.conditionCount));
} else {
void onBindConditionHeader(final ConditionHeaderHolder holder, ConditionHeaderData data) {
holder.icon.setImageIcon(data.conditionIcons.get(0));
if (data.conditionCount == 1) {
holder.title.setText(data.title);
holder.summary.setText(null);
holder.icons.setVisibility(View.INVISIBLE);
} else {
holder.title.setText(null);
holder.summary.setText(
mContext.getString(R.string.condition_summary, data.conditionCount));
updateConditionIcons(data.conditionIcons, holder.icons);
holder.icons.setVisibility(View.VISIBLE);
}
final Resources res = mContext.getResources();
final int padding = res.getDimensionPixelOffset(
curMode == DashboardData.HEADER_MODE_COLLAPSED
? R.dimen.suggestion_condition_header_padding_collapsed
: R.dimen.suggestion_condition_header_padding_expanded);
holder.itemView.setPadding(0, padding, 0, padding);
holder.itemView.setOnClickListener(v -> {
if (hasConditions) {
mMetricsFeatureProvider.action(mContext,
MetricsEvent.ACTION_SETTINGS_CONDITION_EXPAND, true);
}
DashboardData prevData = mDashboardData;
final boolean wasCollapsed = curMode == DashboardData.HEADER_MODE_COLLAPSED;
mMetricsFeatureProvider.action(mContext,
MetricsEvent.ACTION_SETTINGS_CONDITION_EXPAND, true);
final DashboardData prevData = mDashboardData;
mDashboardData = new DashboardData.Builder(prevData)
.setSuggestionConditionMode(nextMode).build();
.setConditionExpanded(true).build();
notifyDashboardDataChanged(prevData);
if (wasCollapsed) {
mRecyclerView.scrollToPosition(SUGGESTION_CONDITION_HEADER_POSITION);
}
scrollToTopOfConditions();
});
}
@VisibleForTesting
void onBindConditionAndSuggestion(final SuggestionAndConditionContainerHolder holder,
int position) {
// If there is suggestions to show, it will be at position 0 as we don't show the suggestion
// header anymore.
final List<Suggestion> suggestions = mDashboardData.getSuggestions();
boolean conditionOnly = true;
if (position == SUGGESTION_CONDITION_HEADER_POSITION) {
if (suggestions != null && suggestions.size() > 0) {
conditionOnly = false;
mSuggestionAdapter = new SuggestionAdapter(mContext, mSuggestionControllerMixin,
(List<Suggestion>) mDashboardData.getItemEntityByPosition(position),
mSuggestionsShownLogged);
mSuggestionDismissHandler = new SuggestionDismissController(mContext,
holder.data, mSuggestionControllerMixin, mCallback);
holder.data.setAdapter(mSuggestionAdapter);
}
}
if (conditionOnly) {
ConditionAdapter adapter = new ConditionAdapter(mContext,
(List<Condition>) mDashboardData.getItemEntityByPosition(position),
mDashboardData.getSuggestionConditionMode());
adapter.addDismissHandling(holder.data);
holder.data.setAdapter(adapter);
}
void onBindCondition(final ConditionContainerHolder holder, int position) {
final ConditionAdapter adapter = new ConditionAdapter(mContext,
(List<Condition>) mDashboardData.getItemEntityByPosition(position),
mDashboardData.isConditionExpanded());
adapter.addDismissHandling(holder.data);
holder.data.setAdapter(adapter);
holder.data.setLayoutManager(new LinearLayoutManager(mContext));
}
private void onBindTile(DashboardItemHolder holder, Tile tile) {
holder.icon.setImageDrawable(mCache.getIcon(tile.icon));
@VisibleForTesting
void onBindSuggestion(final SuggestionContainerHolder holder, int position) {
// If there is suggestions to show, it will be at position 0 as we don't show the suggestion
// header anymore.
final List<Suggestion> suggestions =
(List<Suggestion>) mDashboardData.getItemEntityByPosition(position);
final int suggestionCount = suggestions.size();
if (suggestions != null && suggestionCount > 0) {
holder.summary.setText("" + suggestionCount);
mSuggestionAdapter.setSuggestions(suggestions);
holder.data.setAdapter(mSuggestionAdapter);
}
final LinearLayoutManager layoutManager = new LinearLayoutManager(mContext);
layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
holder.data.setLayoutManager(layoutManager);
}
@VisibleForTesting
void onBindTile(DashboardItemHolder holder, Tile tile) {
Drawable icon = mCache.getIcon(tile.icon);
if (!TextUtils.equals(tile.icon.getResPackage(), mContext.getPackageName())) {
icon = new RoundedHomepageIcon(mContext, icon);
mCache.updateIcon(tile.icon, icon);
}
holder.icon.setImageDrawable(icon);
holder.title.setText(tile.title);
if (!TextUtils.isEmpty(tile.summary)) {
holder.summary.setText(tile.summary);
@@ -395,45 +330,13 @@ public class DashboardAdapter extends RecyclerView.Adapter<DashboardAdapter.Dash
}
}
private void tintIcons(DashboardCategory category, List<Tile> suggestions) {
if (!mDashboardFeatureProvider.shouldTintIcon()) {
return;
}
// TODO: Better place for tinting?
final TypedArray a = mContext.obtainStyledAttributes(new int[]{
android.R.attr.colorControlNormal});
final int tintColor = a.getColor(0, mContext.getColor(R.color.fallback_tintColor));
a.recycle();
if (category != null) {
for (Tile tile : category.getTiles()) {
if (tile.isIconTintable) {
// If this drawable is tintable, tint it to match the color.
tile.icon.setTint(tintColor);
}
}
}
if (suggestions != null) {
for (Tile suggestion : suggestions) {
if (suggestion.isIconTintable) {
suggestion.icon.setTint(tintColor);
}
}
}
}
void onSaveInstanceState(Bundle outState) {
@Override
public void onSaveInstanceState(Bundle outState) {
final DashboardCategory category = mDashboardData.getCategory();
final List<Suggestion> suggestions = mDashboardData.getSuggestions();
if (suggestions != null) {
outState.putParcelableArrayList(STATE_SUGGESTION_LIST,
new ArrayList<>(suggestions));
}
if (category != null) {
outState.putParcelable(STATE_CATEGORY_LIST, category);
}
outState.putStringArrayList(STATE_SUGGESTIONS_SHOWN_LOGGED, mSuggestionsShownLogged);
outState.putInt(STATE_SUGGESTION_CONDITION_MODE,
mDashboardData.getSuggestionConditionMode());
outState.putBoolean(STATE_CONDITION_EXPANDED, mDashboardData.isConditionExpanded());
}
private void updateConditionIcons(List<Icon> icons, ViewGroup parent) {
@@ -452,6 +355,10 @@ public class DashboardAdapter extends RecyclerView.Adapter<DashboardAdapter.Dash
parent.setVisibility(View.VISIBLE);
}
private void scrollToTopOfConditions() {
mRecyclerView.scrollToPosition(mDashboardData.hasSuggestion() ? 1 : 0);
}
public static class IconCache {
private final Context mContext;
private final ArrayMap<Icon, Drawable> mMap = new ArrayMap<>();
@@ -467,10 +374,14 @@ public class DashboardAdapter extends RecyclerView.Adapter<DashboardAdapter.Dash
Drawable drawable = mMap.get(icon);
if (drawable == null) {
drawable = icon.loadDrawable(mContext);
mMap.put(icon, drawable);
updateIcon(icon, drawable);
}
return drawable;
}
public void updateIcon(Icon icon, Drawable drawable) {
mMap.put(icon, drawable);
}
}
public static class DashboardItemHolder extends RecyclerView.ViewHolder {
@@ -486,24 +397,33 @@ public class DashboardAdapter extends RecyclerView.Adapter<DashboardAdapter.Dash
}
}
public static class SuggestionAndConditionHeaderHolder extends DashboardItemHolder {
public static class ConditionHeaderHolder extends DashboardItemHolder {
public final LinearLayout icons;
public final ImageView expandIndicator;
public SuggestionAndConditionHeaderHolder(View itemView) {
public ConditionHeaderHolder(View itemView) {
super(itemView);
icons = itemView.findViewById(id.additional_icons);
expandIndicator = itemView.findViewById(id.expand_indicator);
}
}
public static class SuggestionAndConditionContainerHolder extends DashboardItemHolder {
public static class ConditionContainerHolder extends DashboardItemHolder {
public final RecyclerView data;
public SuggestionAndConditionContainerHolder(View itemView) {
public ConditionContainerHolder(View itemView) {
super(itemView);
data = itemView.findViewById(id.data);
}
}
public static class SuggestionContainerHolder extends DashboardItemHolder {
public final RecyclerView data;
public SuggestionContainerHolder(View itemView) {
super(itemView);
data = itemView.findViewById(id.suggestion_list);
}
}
}

View File

@@ -1,429 +0,0 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.dashboard;
import android.app.Activity;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.service.settings.suggestions.Suggestion;
import android.support.annotation.VisibleForTesting;
import android.support.v7.util.DiffUtil;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R;
import com.android.settings.R.id;
import com.android.settings.dashboard.DashboardDataV2.ConditionHeaderData;
import com.android.settings.dashboard.conditional.Condition;
import com.android.settings.dashboard.conditional.ConditionAdapterV2;
import com.android.settings.dashboard.suggestions.SuggestionAdapterV2;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnSaveInstanceState;
import com.android.settingslib.drawer.DashboardCategory;
import com.android.settingslib.drawer.Tile;
import com.android.settingslib.suggestions.SuggestionControllerMixin;
import java.util.List;
public class DashboardAdapterV2 extends RecyclerView.Adapter<DashboardAdapterV2.DashboardItemHolder>
implements SummaryLoader.SummaryConsumer, SuggestionAdapterV2.Callback, LifecycleObserver,
OnSaveInstanceState {
public static final String TAG = "DashboardAdapterV2";
private static final String STATE_CATEGORY_LIST = "category_list";
@VisibleForTesting
static final String STATE_CONDITION_EXPANDED = "condition_expanded";
private final IconCache mCache;
private final Context mContext;
private final MetricsFeatureProvider mMetricsFeatureProvider;
private final DashboardFeatureProvider mDashboardFeatureProvider;
private boolean mFirstFrameDrawn;
private RecyclerView mRecyclerView;
private SuggestionAdapterV2 mSuggestionAdapter;
@VisibleForTesting
DashboardDataV2 mDashboardData;
private View.OnClickListener mTileClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
//TODO: get rid of setTag/getTag
mDashboardFeatureProvider.openTileIntent((Activity) mContext, (Tile) v.getTag());
}
};
public DashboardAdapterV2(Context context, Bundle savedInstanceState,
List<Condition> conditions, SuggestionControllerMixin suggestionControllerMixin,
Lifecycle lifecycle) {
DashboardCategory category = null;
boolean conditionExpanded = false;
mContext = context;
final FeatureFactory factory = FeatureFactory.getFactory(context);
mMetricsFeatureProvider = factory.getMetricsFeatureProvider();
mDashboardFeatureProvider = factory.getDashboardFeatureProvider(context);
mCache = new IconCache(context);
mSuggestionAdapter = new SuggestionAdapterV2(mContext, suggestionControllerMixin,
savedInstanceState, this /* callback */, lifecycle);
setHasStableIds(true);
if (savedInstanceState != null) {
category = savedInstanceState.getParcelable(STATE_CATEGORY_LIST);
conditionExpanded = savedInstanceState.getBoolean(
STATE_CONDITION_EXPANDED, conditionExpanded);
}
if (lifecycle != null) {
lifecycle.addObserver(this);
}
mDashboardData = new DashboardDataV2.Builder()
.setConditions(conditions)
.setSuggestions(mSuggestionAdapter.getSuggestions())
.setCategory(category)
.setConditionExpanded(conditionExpanded)
.build();
}
public void setSuggestions(List<Suggestion> data) {
final DashboardDataV2 prevData = mDashboardData;
mDashboardData = new DashboardDataV2.Builder(prevData)
.setSuggestions(data)
.build();
notifyDashboardDataChanged(prevData);
}
public void setCategory(DashboardCategory category) {
final DashboardDataV2 prevData = mDashboardData;
Log.d(TAG, "adapter setCategory called");
mDashboardData = new DashboardDataV2.Builder(prevData)
.setCategory(category)
.build();
notifyDashboardDataChanged(prevData);
}
public void setConditions(List<Condition> conditions) {
final DashboardDataV2 prevData = mDashboardData;
Log.d(TAG, "adapter setConditions called");
mDashboardData = new DashboardDataV2.Builder(prevData)
.setConditions(conditions)
.build();
notifyDashboardDataChanged(prevData);
}
@Override
public void onSuggestionClosed(Suggestion suggestion) {
final List<Suggestion> list = mDashboardData.getSuggestions();
if (list == null || list.size() == 0) {
return;
}
if (list.size() == 1) {
// The only suggestion is dismissed, and the the empty suggestion container will
// remain as the dashboard item. Need to refresh the dashboard list.
setSuggestions(null);
} else {
mSuggestionAdapter.removeSuggestion(suggestion);
notifyItemChanged(0, null);
}
}
@Override
public void notifySummaryChanged(Tile tile) {
final int position = mDashboardData.getPositionByTile(tile);
if (position != DashboardDataV2.POSITION_NOT_FOUND) {
// Since usually tile in parameter and tile in mCategories are same instance,
// which is hard to be detected by DiffUtil, so we notifyItemChanged directly.
notifyItemChanged(position, mDashboardData.getItemTypeByPosition(position));
}
}
@Override
public DashboardItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
if (viewType == R.layout.suggestion_condition_header) {
return new ConditionHeaderHolder(view);
}
if (viewType == R.layout.condition_container) {
return new ConditionContainerHolder(view);
}
if (viewType == R.layout.suggestion_container) {
return new SuggestionContainerHolder(view);
}
return new DashboardItemHolder(view);
}
@Override
public void onBindViewHolder(DashboardItemHolder holder, int position) {
final int type = mDashboardData.getItemTypeByPosition(position);
switch (type) {
case R.layout.dashboard_tile:
final Tile tile = (Tile) mDashboardData.getItemEntityByPosition(position);
onBindTile(holder, tile);
holder.itemView.setTag(tile);
holder.itemView.setOnClickListener(mTileClickListener);
break;
case R.layout.suggestion_container:
onBindSuggestion((SuggestionContainerHolder) holder, position);
break;
case R.layout.condition_container:
onBindCondition((ConditionContainerHolder) holder, position);
break;
case R.layout.suggestion_condition_header:
onBindConditionHeader((ConditionHeaderHolder) holder,
(ConditionHeaderData) mDashboardData.getItemEntityByPosition(position));
break;
case R.layout.suggestion_condition_footer:
holder.itemView.setOnClickListener(v -> {
mMetricsFeatureProvider.action(mContext,
MetricsEvent.ACTION_SETTINGS_CONDITION_EXPAND, false);
DashboardDataV2 prevData = mDashboardData;
mDashboardData = new DashboardDataV2.Builder(prevData).
setConditionExpanded(false).build();
notifyDashboardDataChanged(prevData);
scrollToTopOfConditions();
});
break;
}
}
@Override
public long getItemId(int position) {
return mDashboardData.getItemIdByPosition(position);
}
@Override
public int getItemViewType(int position) {
return mDashboardData.getItemTypeByPosition(position);
}
@Override
public int getItemCount() {
return mDashboardData.size();
}
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
// save the view so that we can scroll it when expanding/collapsing the suggestion and
// conditions.
mRecyclerView = recyclerView;
}
public Object getItem(long itemId) {
return mDashboardData.getItemEntityById(itemId);
}
public Suggestion getSuggestion(int position) {
return mSuggestionAdapter.getSuggestion(position);
}
@VisibleForTesting
void notifyDashboardDataChanged(DashboardDataV2 prevData) {
if (mFirstFrameDrawn && prevData != null) {
final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DashboardDataV2
.ItemsDataDiffCallback(prevData.getItemList(), mDashboardData.getItemList()));
diffResult.dispatchUpdatesTo(this);
} else {
mFirstFrameDrawn = true;
notifyDataSetChanged();
}
}
@VisibleForTesting
void onBindConditionHeader(final ConditionHeaderHolder holder, ConditionHeaderData data) {
holder.icon.setImageIcon(data.conditionIcons.get(0));
if (data.conditionCount == 1) {
holder.title.setText(data.title);
holder.summary.setText(null);
holder.icons.setVisibility(View.INVISIBLE);
} else {
holder.title.setText(null);
holder.summary.setText(
mContext.getString(R.string.condition_summary, data.conditionCount));
updateConditionIcons(data.conditionIcons, holder.icons);
holder.icons.setVisibility(View.VISIBLE);
}
holder.itemView.setOnClickListener(v -> {
mMetricsFeatureProvider.action(mContext,
MetricsEvent.ACTION_SETTINGS_CONDITION_EXPAND, true);
final DashboardDataV2 prevData = mDashboardData;
mDashboardData = new DashboardDataV2.Builder(prevData)
.setConditionExpanded(true).build();
notifyDashboardDataChanged(prevData);
scrollToTopOfConditions();
});
}
@VisibleForTesting
void onBindCondition(final ConditionContainerHolder holder, int position) {
final ConditionAdapterV2 adapter = new ConditionAdapterV2(mContext,
(List<Condition>) mDashboardData.getItemEntityByPosition(position),
mDashboardData.isConditionExpanded());
adapter.addDismissHandling(holder.data);
holder.data.setAdapter(adapter);
holder.data.setLayoutManager(new LinearLayoutManager(mContext));
}
@VisibleForTesting
void onBindSuggestion(final SuggestionContainerHolder holder, int position) {
// If there is suggestions to show, it will be at position 0 as we don't show the suggestion
// header anymore.
final List<Suggestion> suggestions =
(List<Suggestion>) mDashboardData.getItemEntityByPosition(position);
final int suggestionCount = suggestions.size();
if (suggestions != null && suggestionCount > 0) {
holder.summary.setText("" + suggestionCount);
mSuggestionAdapter.setSuggestions(suggestions);
holder.data.setAdapter(mSuggestionAdapter);
}
final LinearLayoutManager layoutManager = new LinearLayoutManager(mContext);
layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
holder.data.setLayoutManager(layoutManager);
}
@VisibleForTesting
void onBindTile(DashboardItemHolder holder, Tile tile) {
Drawable icon = mCache.getIcon(tile.icon);
if (!TextUtils.equals(tile.icon.getResPackage(), mContext.getPackageName())) {
icon = new RoundedHomepageIcon(mContext, icon);
mCache.updateIcon(tile.icon, icon);
}
holder.icon.setImageDrawable(icon);
holder.title.setText(tile.title);
if (!TextUtils.isEmpty(tile.summary)) {
holder.summary.setText(tile.summary);
holder.summary.setVisibility(View.VISIBLE);
} else {
holder.summary.setVisibility(View.GONE);
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
final DashboardCategory category = mDashboardData.getCategory();
if (category != null) {
outState.putParcelable(STATE_CATEGORY_LIST, category);
}
outState.putBoolean(STATE_CONDITION_EXPANDED, mDashboardData.isConditionExpanded());
}
private void updateConditionIcons(List<Icon> icons, ViewGroup parent) {
if (icons == null || icons.size() < 2) {
parent.setVisibility(View.INVISIBLE);
return;
}
final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
parent.removeAllViews();
for (int i = 1, size = icons.size(); i < size; i++) {
ImageView icon = (ImageView) inflater.inflate(
R.layout.condition_header_icon, parent, false);
icon.setImageIcon(icons.get(i));
parent.addView(icon);
}
parent.setVisibility(View.VISIBLE);
}
private void scrollToTopOfConditions() {
mRecyclerView.scrollToPosition(mDashboardData.hasSuggestion() ? 1 : 0);
}
public static class IconCache {
private final Context mContext;
private final ArrayMap<Icon, Drawable> mMap = new ArrayMap<>();
public IconCache(Context context) {
mContext = context;
}
public Drawable getIcon(Icon icon) {
if (icon == null) {
return null;
}
Drawable drawable = mMap.get(icon);
if (drawable == null) {
drawable = icon.loadDrawable(mContext);
updateIcon(icon, drawable);
}
return drawable;
}
public void updateIcon(Icon icon, Drawable drawable) {
mMap.put(icon, drawable);
}
}
public static class DashboardItemHolder extends RecyclerView.ViewHolder {
public final ImageView icon;
public final TextView title;
public final TextView summary;
public DashboardItemHolder(View itemView) {
super(itemView);
icon = itemView.findViewById(android.R.id.icon);
title = itemView.findViewById(android.R.id.title);
summary = itemView.findViewById(android.R.id.summary);
}
}
public static class ConditionHeaderHolder extends DashboardItemHolder {
public final LinearLayout icons;
public final ImageView expandIndicator;
public ConditionHeaderHolder(View itemView) {
super(itemView);
icons = itemView.findViewById(id.additional_icons);
expandIndicator = itemView.findViewById(id.expand_indicator);
}
}
public static class ConditionContainerHolder extends DashboardItemHolder {
public final RecyclerView data;
public ConditionContainerHolder(View itemView) {
super(itemView);
data = itemView.findViewById(id.data);
}
}
public static class SuggestionContainerHolder extends DashboardItemHolder {
public final RecyclerView data;
public SuggestionContainerHolder(View itemView) {
super(itemView);
data = itemView.findViewById(id.suggestion_list);
}
}
}

View File

@@ -40,29 +40,17 @@ import java.util.Objects;
* ItemsData has inner class Item, which represents the Item in data list.
*/
public class DashboardData {
public static final int HEADER_MODE_DEFAULT = 0;
public static final int HEADER_MODE_SUGGESTION_EXPANDED = 1;
public static final int HEADER_MODE_FULLY_EXPANDED = 2;
public static final int HEADER_MODE_COLLAPSED = 3;
@Retention(RetentionPolicy.SOURCE)
@IntDef({HEADER_MODE_DEFAULT, HEADER_MODE_SUGGESTION_EXPANDED, HEADER_MODE_FULLY_EXPANDED,
HEADER_MODE_COLLAPSED})
public @interface HeaderMode {
}
public static final int POSITION_NOT_FOUND = -1;
public static final int DEFAULT_SUGGESTION_COUNT = 2;
public static final int MAX_SUGGESTION_COUNT = 4;
// stable id for different type of items.
@VisibleForTesting
static final int STABLE_ID_SUGGESTION_CONDITION_TOP_HEADER = 0;
static final int STABLE_ID_SUGGESTION_CONTAINER = 0;
static final int STABLE_ID_SUGGESTION_CONDITION_DIVIDER = 1;
@VisibleForTesting
static final int STABLE_ID_SUGGESTION_CONDITION_MIDDLE_HEADER = 1;
static final int STABLE_ID_CONDITION_HEADER = 2;
@VisibleForTesting
static final int STABLE_ID_SUGGESTION_CONDITION_FOOTER = 2;
@VisibleForTesting
static final int STABLE_ID_SUGGESTION_CONTAINER = 3;
static final int STABLE_ID_CONDITION_FOOTER = 3;
@VisibleForTesting
static final int STABLE_ID_CONDITION_CONTAINER = 4;
@@ -70,15 +58,13 @@ public class DashboardData {
private final DashboardCategory mCategory;
private final List<Condition> mConditions;
private final List<Suggestion> mSuggestions;
@HeaderMode
private final int mSuggestionConditionMode;
private final boolean mConditionExpanded;
private DashboardData(Builder builder) {
mCategory = builder.mCategory;
mConditions = builder.mConditions;
mSuggestions = builder.mSuggestionsV2;
mSuggestionConditionMode = builder.mSuggestionConditionMode;
mSuggestions = builder.mSuggestions;
mConditionExpanded = builder.mConditionExpanded;
mItems = new ArrayList<>();
buildItemsData();
@@ -125,8 +111,12 @@ public class DashboardData {
return mSuggestions;
}
public int getSuggestionConditionMode() {
return mSuggestionConditionMode;
public boolean hasSuggestion() {
return sizeOf(mSuggestions) > 0;
}
public boolean isConditionExpanded() {
return mConditionExpanded;
}
/**
@@ -188,69 +178,42 @@ public class DashboardData {
/**
* Build the mItems list using mConditions, mSuggestions, mCategories data
* and mIsShowingAll, mSuggestionConditionMode flag.
* and mIsShowingAll, mConditionExpanded flag.
*/
private void buildItemsData() {
final boolean hasSuggestions = sizeOf(mSuggestions) > 0;
final List<Condition> conditions = getConditionsToShow(mConditions);
final boolean hasConditions = sizeOf(conditions) > 0;
final List<Suggestion> suggestions = getSuggestionsToShow(mSuggestions);
final boolean hasSuggestions = sizeOf(suggestions) > 0;
final int hiddenSuggestion = hasSuggestions
? sizeOf(mSuggestions) - sizeOf(suggestions)
: 0;
/* Suggestion container. This is the card view that contains the list of suggestions.
* This will be added whenever the suggestion list is not empty */
addToItemList(suggestions, R.layout.suggestion_container,
STABLE_ID_SUGGESTION_CONTAINER, hasSuggestions);
final boolean hasSuggestionAndCollapsed = hasSuggestions
&& mSuggestionConditionMode == HEADER_MODE_COLLAPSED;
final boolean onlyHasConditionAndCollapsed = !hasSuggestions
&& hasConditions
&& mSuggestionConditionMode != HEADER_MODE_FULLY_EXPANDED;
/* Divider between suggestion and conditions if both are present. */
addToItemList(null /* item */, R.layout.horizontal_divider,
STABLE_ID_SUGGESTION_CONDITION_DIVIDER, hasSuggestions && hasConditions);
/* Top suggestion/condition header. This will be present when there is any suggestion
* and the mode is collapsed */
addToItemList(new SuggestionConditionHeaderData(conditions, hiddenSuggestion),
R.layout.suggestion_condition_header,
STABLE_ID_SUGGESTION_CONDITION_TOP_HEADER, hasSuggestionAndCollapsed);
/* Condition header. This will be present when there is condition and it is collapsed */
addToItemList(new ConditionHeaderData(conditions),
R.layout.condition_header,
STABLE_ID_CONDITION_HEADER, hasConditions && !mConditionExpanded);
/* Use mid header if there is only condition & it's in collapsed mode */
addToItemList(new SuggestionConditionHeaderData(conditions, hiddenSuggestion),
R.layout.suggestion_condition_header,
STABLE_ID_SUGGESTION_CONDITION_MIDDLE_HEADER, onlyHasConditionAndCollapsed);
/* Condition container. This is the card view that contains the list of conditions.
* This will be added whenever the condition list is not empty and expanded */
addToItemList(conditions, R.layout.condition_container,
STABLE_ID_CONDITION_CONTAINER, hasConditions && mConditionExpanded);
addToItemList(suggestions, R.layout.suggestion_condition_container,
STABLE_ID_SUGGESTION_CONTAINER, sizeOf(suggestions) > 0);
/* Second suggestion/condition header. This will be added when there is at least one
* suggestion or condition that is not currently displayed, and the user can expand the
* section to view more items. */
addToItemList(new SuggestionConditionHeaderData(conditions, hiddenSuggestion),
R.layout.suggestion_condition_header,
STABLE_ID_SUGGESTION_CONDITION_MIDDLE_HEADER,
mSuggestionConditionMode != HEADER_MODE_COLLAPSED
&& mSuggestionConditionMode != HEADER_MODE_FULLY_EXPANDED
&& (hiddenSuggestion > 0 || hasConditions && hasSuggestions));
/* Condition container. This is the card view that contains the list of conditions.
* This will be added whenever the condition list is not empty */
addToItemList(conditions, R.layout.suggestion_condition_container,
STABLE_ID_CONDITION_CONTAINER,
hasConditions && mSuggestionConditionMode == HEADER_MODE_FULLY_EXPANDED);
/* Suggestion/condition footer. This will be present when the section is fully expanded
* or when there is no conditions and no hidden suggestions */
addToItemList(null /* item */, R.layout.suggestion_condition_footer,
STABLE_ID_SUGGESTION_CONDITION_FOOTER,
(hasConditions || hasSuggestions)
&& mSuggestionConditionMode == HEADER_MODE_FULLY_EXPANDED
|| hasSuggestions
&& !hasConditions
&& hiddenSuggestion == 0);
/* Condition footer. This will be present when there is condition and it is expanded */
addToItemList(null /* item */, R.layout.condition_footer,
STABLE_ID_CONDITION_FOOTER, hasConditions && mConditionExpanded);
if (mCategory != null) {
final List<Tile> tiles = mCategory.getTiles();
for (int j = 0; j < tiles.size(); j++) {
final Tile tile = tiles.get(j);
for (int i = 0; i < tiles.size(); i++) {
final Tile tile = tiles.get(i);
addToItemList(tile, R.layout.dashboard_tile, Objects.hash(tile.title),
true /* add */);
}
@@ -277,28 +240,23 @@ public class DashboardData {
}
private List<Suggestion> getSuggestionsToShow(List<Suggestion> suggestions) {
if (suggestions == null || mSuggestionConditionMode == HEADER_MODE_COLLAPSED) {
if (suggestions == null) {
return null;
}
if (mSuggestionConditionMode != HEADER_MODE_DEFAULT
|| suggestions.size() <= DEFAULT_SUGGESTION_COUNT) {
if (suggestions.size() <= MAX_SUGGESTION_COUNT) {
return suggestions;
}
return suggestions.subList(0, DEFAULT_SUGGESTION_COUNT);
return suggestions.subList(0, MAX_SUGGESTION_COUNT);
}
/**
* Builder used to build the ItemsData
* <p>
* {@link #mSuggestionConditionMode} have default value while others are not.
*/
public static class Builder {
@HeaderMode
private int mSuggestionConditionMode = HEADER_MODE_DEFAULT;
private DashboardCategory mCategory;
private List<Condition> mConditions;
private List<Suggestion> mSuggestionsV2;
private List<Suggestion> mSuggestions;
private boolean mConditionExpanded;
public Builder() {
}
@@ -306,8 +264,8 @@ public class DashboardData {
public Builder(DashboardData dashboardData) {
mCategory = dashboardData.mCategory;
mConditions = dashboardData.mConditions;
mSuggestionsV2 = dashboardData.mSuggestions;
mSuggestionConditionMode = dashboardData.mSuggestionConditionMode;
mSuggestions = dashboardData.mSuggestions;
mConditionExpanded = dashboardData.mConditionExpanded;
}
public Builder setCategory(DashboardCategory category) {
@@ -321,12 +279,12 @@ public class DashboardData {
}
public Builder setSuggestions(List<Suggestion> suggestions) {
this.mSuggestionsV2 = suggestions;
this.mSuggestions = suggestions;
return this;
}
public Builder setSuggestionConditionMode(@HeaderMode int mode) {
this.mSuggestionConditionMode = mode;
public Builder setConditionExpanded(boolean expanded) {
this.mConditionExpanded = expanded;
return this;
}
@@ -376,17 +334,18 @@ public class DashboardData {
static class Item {
// valid types in field type
private static final int TYPE_DASHBOARD_TILE = R.layout.dashboard_tile;
private static final int TYPE_SUGGESTION_CONDITION_CONTAINER =
R.layout.suggestion_condition_container;
private static final int TYPE_SUGGESTION_CONDITION_HEADER =
R.layout.suggestion_condition_header;
private static final int TYPE_SUGGESTION_CONDITION_FOOTER =
R.layout.suggestion_condition_footer;
private static final int TYPE_DASHBOARD_SPACER = R.layout.dashboard_spacer;
private static final int TYPE_SUGGESTION_CONTAINER =
R.layout.suggestion_container;
private static final int TYPE_CONDITION_CONTAINER =
R.layout.condition_container;
private static final int TYPE_CONDITION_HEADER =
R.layout.condition_header;
private static final int TYPE_CONDITION_FOOTER =
R.layout.condition_footer;
private static final int TYPE_SUGGESTION_CONDITION_DIVIDER = R.layout.horizontal_divider;
@IntDef({TYPE_DASHBOARD_TILE, TYPE_SUGGESTION_CONDITION_CONTAINER,
TYPE_SUGGESTION_CONDITION_HEADER, TYPE_SUGGESTION_CONDITION_FOOTER,
TYPE_DASHBOARD_SPACER})
@IntDef({TYPE_DASHBOARD_TILE, TYPE_SUGGESTION_CONTAINER, TYPE_CONDITION_CONTAINER,
TYPE_CONDITION_HEADER, TYPE_CONDITION_FOOTER, TYPE_SUGGESTION_CONDITION_DIVIDER})
@Retention(RetentionPolicy.SOURCE)
public @interface ItemTypes {
}
@@ -444,8 +403,9 @@ public class DashboardData {
// Only check title and summary for dashboard tile
return TextUtils.equals(localTile.title, targetTile.title)
&& TextUtils.equals(localTile.summary, targetTile.summary);
case TYPE_SUGGESTION_CONDITION_CONTAINER:
&& TextUtils.equals(localTile.summary, targetTile.summary);
case TYPE_SUGGESTION_CONTAINER:
case TYPE_CONDITION_CONTAINER:
// If entity is suggestion and contains remote view, force refresh
final List entities = (List) entity;
if (!entities.isEmpty()) {
@@ -467,16 +427,13 @@ public class DashboardData {
* This class contains the data needed to build the suggestion/condition header. The data can
* also be used to check the diff in DiffUtil.Callback
*/
public static class SuggestionConditionHeaderData {
public static class ConditionHeaderData {
public final List<Icon> conditionIcons;
public final CharSequence title;
public final int conditionCount;
public final int hiddenSuggestionCount;
public SuggestionConditionHeaderData(List<Condition> conditions,
int hiddenSuggestionCount) {
public ConditionHeaderData(List<Condition> conditions) {
conditionCount = sizeOf(conditions);
this.hiddenSuggestionCount = hiddenSuggestionCount;
title = conditionCount > 0 ? conditions.get(0).getTitle() : null;
conditionIcons = new ArrayList<>();
for (int i = 0; conditions != null && i < conditions.size(); i++) {

View File

@@ -1,446 +0,0 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.dashboard;
import android.annotation.IntDef;
import android.graphics.drawable.Icon;
import android.service.settings.suggestions.Suggestion;
import android.support.annotation.VisibleForTesting;
import android.support.v7.util.DiffUtil;
import android.text.TextUtils;
import com.android.settings.R;
import com.android.settings.dashboard.conditional.Condition;
import com.android.settingslib.drawer.DashboardCategory;
import com.android.settingslib.drawer.Tile;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* Description about data list used in the DashboardAdapter. In the data list each item can be
* Condition, suggestion or category tile.
* <p>
* ItemsData has inner class Item, which represents the Item in data list.
*/
public class DashboardDataV2 {
public static final int POSITION_NOT_FOUND = -1;
public static final int MAX_SUGGESTION_COUNT = 4;
// stable id for different type of items.
@VisibleForTesting
static final int STABLE_ID_SUGGESTION_CONTAINER = 0;
static final int STABLE_ID_SUGGESTION_CONDITION_DIVIDER = 1;
@VisibleForTesting
static final int STABLE_ID_CONDITION_HEADER = 2;
@VisibleForTesting
static final int STABLE_ID_CONDITION_FOOTER = 3;
@VisibleForTesting
static final int STABLE_ID_CONDITION_CONTAINER = 4;
private final List<Item> mItems;
private final DashboardCategory mCategory;
private final List<Condition> mConditions;
private final List<Suggestion> mSuggestions;
private final boolean mConditionExpanded;
private DashboardDataV2(Builder builder) {
mCategory = builder.mCategory;
mConditions = builder.mConditions;
mSuggestions = builder.mSuggestions;
mConditionExpanded = builder.mConditionExpanded;
mItems = new ArrayList<>();
buildItemsData();
}
public int getItemIdByPosition(int position) {
return mItems.get(position).id;
}
public int getItemTypeByPosition(int position) {
return mItems.get(position).type;
}
public Object getItemEntityByPosition(int position) {
return mItems.get(position).entity;
}
public List<Item> getItemList() {
return mItems;
}
public int size() {
return mItems.size();
}
public Object getItemEntityById(long id) {
for (final Item item : mItems) {
if (item.id == id) {
return item.entity;
}
}
return null;
}
public DashboardCategory getCategory() {
return mCategory;
}
public List<Condition> getConditions() {
return mConditions;
}
public List<Suggestion> getSuggestions() {
return mSuggestions;
}
public boolean hasSuggestion() {
return sizeOf(mSuggestions) > 0;
}
public boolean isConditionExpanded() {
return mConditionExpanded;
}
/**
* Find the position of the object in mItems list, using the equals method to compare
*
* @param entity the object that need to be found in list
* @return position of the object, return POSITION_NOT_FOUND if object isn't in the list
*/
public int getPositionByEntity(Object entity) {
if (entity == null) return POSITION_NOT_FOUND;
final int size = mItems.size();
for (int i = 0; i < size; i++) {
final Object item = mItems.get(i).entity;
if (entity.equals(item)) {
return i;
}
}
return POSITION_NOT_FOUND;
}
/**
* Find the position of the Tile object.
* <p>
* First, try to find the exact identical instance of the tile object, if not found,
* then try to find a tile has the same title.
*
* @param tile tile that need to be found
* @return position of the object, return INDEX_NOT_FOUND if object isn't in the list
*/
public int getPositionByTile(Tile tile) {
final int size = mItems.size();
for (int i = 0; i < size; i++) {
final Object entity = mItems.get(i).entity;
if (entity == tile) {
return i;
} else if (entity instanceof Tile && tile.title.equals(((Tile) entity).title)) {
return i;
}
}
return POSITION_NOT_FOUND;
}
/**
* Add item into list when {@paramref add} is true.
*
* @param item maybe {@link Condition}, {@link Tile}, {@link DashboardCategory} or null
* @param type type of the item, and value is the layout id
* @param stableId The stable id for this item
* @param add flag about whether to add item into list
*/
private void addToItemList(Object item, int type, int stableId, boolean add) {
if (add) {
mItems.add(new Item(item, type, stableId));
}
}
/**
* Build the mItems list using mConditions, mSuggestions, mCategories data
* and mIsShowingAll, mConditionExpanded flag.
*/
private void buildItemsData() {
final List<Condition> conditions = getConditionsToShow(mConditions);
final boolean hasConditions = sizeOf(conditions) > 0;
final List<Suggestion> suggestions = getSuggestionsToShow(mSuggestions);
final boolean hasSuggestions = sizeOf(suggestions) > 0;
/* Suggestion container. This is the card view that contains the list of suggestions.
* This will be added whenever the suggestion list is not empty */
addToItemList(suggestions, R.layout.suggestion_container,
STABLE_ID_SUGGESTION_CONTAINER, hasSuggestions);
/* Divider between suggestion and conditions if both are present. */
addToItemList(suggestions, R.layout.horizontal_divider,
STABLE_ID_SUGGESTION_CONDITION_DIVIDER, hasSuggestions && hasConditions);
/* Condition header. This will be present when there is condition and it is collapsed */
addToItemList(new ConditionHeaderData(conditions),
R.layout.suggestion_condition_header,
STABLE_ID_CONDITION_HEADER, hasConditions && !mConditionExpanded);
/* Condition container. This is the card view that contains the list of conditions.
* This will be added whenever the condition list is not empty and expanded */
addToItemList(conditions, R.layout.condition_container,
STABLE_ID_CONDITION_CONTAINER, hasConditions && mConditionExpanded);
/* Condition footer. This will be present when there is condition and it is expanded */
addToItemList(null /* item */, R.layout.suggestion_condition_footer,
STABLE_ID_CONDITION_FOOTER, hasConditions && mConditionExpanded);
if (mCategory != null) {
final List<Tile> tiles = mCategory.getTiles();
for (int i = 0; i < tiles.size(); i++) {
final Tile tile = tiles.get(i);
addToItemList(tile, R.layout.dashboard_tile, Objects.hash(tile.title),
true /* add */);
}
}
}
private static int sizeOf(List<?> list) {
return list == null ? 0 : list.size();
}
private List<Condition> getConditionsToShow(List<Condition> conditions) {
if (conditions == null) {
return null;
}
List<Condition> result = new ArrayList<>();
final int size = conditions == null ? 0 : conditions.size();
for (int i = 0; i < size; i++) {
final Condition condition = conditions.get(i);
if (condition.shouldShow()) {
result.add(condition);
}
}
return result;
}
private List<Suggestion> getSuggestionsToShow(List<Suggestion> suggestions) {
if (suggestions == null) {
return null;
}
if (suggestions.size() <= MAX_SUGGESTION_COUNT) {
return suggestions;
}
return suggestions.subList(0, MAX_SUGGESTION_COUNT);
}
/**
* Builder used to build the ItemsData
*/
public static class Builder {
private DashboardCategory mCategory;
private List<Condition> mConditions;
private List<Suggestion> mSuggestions;
private boolean mConditionExpanded;
public Builder() {
}
public Builder(DashboardDataV2 dashboardData) {
mCategory = dashboardData.mCategory;
mConditions = dashboardData.mConditions;
mSuggestions = dashboardData.mSuggestions;
mConditionExpanded = dashboardData.mConditionExpanded;
}
public Builder setCategory(DashboardCategory category) {
this.mCategory = category;
return this;
}
public Builder setConditions(List<Condition> conditions) {
this.mConditions = conditions;
return this;
}
public Builder setSuggestions(List<Suggestion> suggestions) {
this.mSuggestions = suggestions;
return this;
}
public Builder setConditionExpanded(boolean expanded) {
this.mConditionExpanded = expanded;
return this;
}
public DashboardDataV2 build() {
return new DashboardDataV2(this);
}
}
/**
* A DiffCallback to calculate the difference between old and new Item
* List in DashboardDataV2
*/
public static class ItemsDataDiffCallback extends DiffUtil.Callback {
final private List<Item> mOldItems;
final private List<Item> mNewItems;
public ItemsDataDiffCallback(List<Item> oldItems, List<Item> newItems) {
mOldItems = oldItems;
mNewItems = newItems;
}
@Override
public int getOldListSize() {
return mOldItems.size();
}
@Override
public int getNewListSize() {
return mNewItems.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return mOldItems.get(oldItemPosition).id == mNewItems.get(newItemPosition).id;
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return mOldItems.get(oldItemPosition).equals(mNewItems.get(newItemPosition));
}
}
/**
* An item contains the data needed in the DashboardDataV2.
*/
static class Item {
// valid types in field type
private static final int TYPE_DASHBOARD_TILE = R.layout.dashboard_tile;
private static final int TYPE_SUGGESTION_CONTAINER =
R.layout.suggestion_container;
private static final int TYPE_CONDITION_CONTAINER =
R.layout.condition_container;
private static final int TYPE_CONDITION_HEADER =
R.layout.suggestion_condition_header;
private static final int TYPE_CONDITION_FOOTER =
R.layout.suggestion_condition_footer;
private static final int TYPE_SUGGESTION_CONDITION_DIVIDER = R.layout.horizontal_divider;
@IntDef({TYPE_DASHBOARD_TILE, TYPE_SUGGESTION_CONTAINER, TYPE_CONDITION_CONTAINER,
TYPE_CONDITION_HEADER, TYPE_CONDITION_FOOTER, TYPE_SUGGESTION_CONDITION_DIVIDER})
@Retention(RetentionPolicy.SOURCE)
public @interface ItemTypes {
}
/**
* The main data object in item, usually is a {@link Tile}, {@link Condition}
* object. This object can also be null when the
* item is an divider line. Please refer to {@link #buildItemsData()} for
* detail usage of the Item.
*/
public final Object entity;
/**
* The type of item, value inside is the layout id(e.g. R.layout.dashboard_tile)
*/
@ItemTypes
public final int type;
/**
* Id of this item, used in the {@link ItemsDataDiffCallback} to identify the same item.
*/
public final int id;
public Item(Object entity, @ItemTypes int type, int id) {
this.entity = entity;
this.type = type;
this.id = id;
}
/**
* Override it to make comparision in the {@link ItemsDataDiffCallback}
*
* @param obj object to compared with
* @return true if the same object or has equal value.
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Item)) {
return false;
}
final Item targetItem = (Item) obj;
if (type != targetItem.type || id != targetItem.id) {
return false;
}
switch (type) {
case TYPE_DASHBOARD_TILE:
final Tile localTile = (Tile) entity;
final Tile targetTile = (Tile) targetItem.entity;
// Only check title and summary for dashboard tile
return TextUtils.equals(localTile.title, targetTile.title)
&& TextUtils.equals(localTile.summary, targetTile.summary);
case TYPE_SUGGESTION_CONTAINER:
case TYPE_CONDITION_CONTAINER:
// If entity is suggestion and contains remote view, force refresh
final List entities = (List) entity;
if (!entities.isEmpty()) {
Object firstEntity = entities.get(0);
if (firstEntity instanceof Tile
&& ((Tile) firstEntity).remoteViews != null) {
return false;
}
}
// Otherwise Fall through to default
default:
return entity == null ? targetItem.entity == null
: entity.equals(targetItem.entity);
}
}
}
/**
* This class contains the data needed to build the suggestion/condition header. The data can
* also be used to check the diff in DiffUtil.Callback
*/
public static class ConditionHeaderData {
public final List<Icon> conditionIcons;
public final CharSequence title;
public final int conditionCount;
public ConditionHeaderData(List<Condition> conditions) {
conditionCount = sizeOf(conditions);
title = conditionCount > 0 ? conditions.get(0).getTitle() : null;
conditionIcons = new ArrayList<>();
for (int i = 0; conditions != null && i < conditions.size(); i++) {
final Condition condition = conditions.get(i);
conditionIcons.add(condition.getIcon());
}
}
}
}

View File

@@ -90,9 +90,4 @@ public interface DashboardFeatureProvider {
*/
void openTileIntent(Activity activity, Tile tile);
/**
* Whether or not we should use the v2 of suggestions UI.
*/
boolean useSuggestionUiV2();
}

View File

@@ -217,11 +217,6 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider {
launchIntentOrSelectProfile(activity, tile, intent, MetricsEvent.DASHBOARD_SUMMARY);
}
@Override
public boolean useSuggestionUiV2() {
return FeatureFlagUtils.isEnabled(mContext, FeatureFlags.SUGGESTION_UI_V2);
}
private void bindSummary(Preference preference, Tile tile) {
if (tile.summary != null) {
preference.setSummary(tile.summary);

View File

@@ -38,7 +38,6 @@ import com.android.settings.dashboard.conditional.ConditionManager;
import com.android.settings.dashboard.conditional.ConditionManager.ConditionListener;
import com.android.settings.dashboard.conditional.FocusRecyclerView;
import com.android.settings.dashboard.conditional.FocusRecyclerView.FocusListener;
import com.android.settings.dashboard.suggestions.SuggestionDismissController;
import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.widget.ActionBarShadowController;
@@ -53,8 +52,7 @@ import java.util.List;
public class DashboardSummary extends InstrumentedFragment
implements CategoryListener, ConditionListener,
FocusListener, SuggestionDismissController.Callback,
SuggestionControllerMixin.SuggestionControllerHost {
FocusListener, SuggestionControllerMixin.SuggestionControllerHost {
public static final boolean DEBUG = false;
private static final boolean DEBUG_TIMING = false;
private static final int MAX_WAIT_MILLIS = 700;
@@ -66,7 +64,6 @@ public class DashboardSummary extends InstrumentedFragment
private FocusRecyclerView mDashboard;
private DashboardAdapter mAdapter;
private DashboardAdapterV2 mAdapterV2;
private SummaryLoader mSummaryLoader;
private ConditionManager mConditionManager;
private LinearLayoutManager mLayoutManager;
@@ -181,13 +178,10 @@ public class DashboardSummary extends InstrumentedFragment
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (mLayoutManager == null) return;
outState.putInt(EXTRA_SCROLL_POSITION, mLayoutManager.findFirstVisibleItemPosition());
if (!mDashboardFeatureProvider.useSuggestionUiV2()) {
if (mAdapter != null) {
mAdapter.onSaveInstanceState(outState);
}
if (mLayoutManager == null) {
return;
}
outState.putInt(EXTRA_SCROLL_POSITION, mLayoutManager.findFirstVisibleItemPosition());
}
@Override
@@ -205,17 +199,10 @@ public class DashboardSummary extends InstrumentedFragment
mDashboard.setHasFixedSize(true);
mDashboard.setListener(this);
mDashboard.setItemAnimator(new DashboardItemAnimator());
if (mDashboardFeatureProvider.useSuggestionUiV2()) {
mAdapterV2 = new DashboardAdapterV2(getContext(), bundle,
mConditionManager.getConditions(), mSuggestionControllerMixin, getLifecycle());
mDashboard.setAdapter(mAdapterV2);
mSummaryLoader.setSummaryConsumer(mAdapterV2);
} else {
mAdapter = new DashboardAdapter(getContext(), bundle, mConditionManager.getConditions(),
mSuggestionControllerMixin, this /* SuggestionDismissController.Callback */);
mDashboard.setAdapter(mAdapter);
mSummaryLoader.setSummaryConsumer(mAdapter);
}
mAdapter = new DashboardAdapter(getContext(), bundle,
mConditionManager.getConditions(), mSuggestionControllerMixin, getLifecycle());
mDashboard.setAdapter(mAdapter);
mSummaryLoader.setSummaryConsumer(mAdapter);
ActionBarShadowController.attachToRecyclerView(
getActivity().findViewById(R.id.search_bar_container), getLifecycle(), mDashboard);
rebuildUI();
@@ -254,11 +241,7 @@ public class DashboardSummary extends InstrumentedFragment
if (mOnConditionsChangedCalled) {
final boolean scrollToTop =
mLayoutManager.findFirstCompletelyVisibleItemPosition() <= 1;
if (mDashboardFeatureProvider.useSuggestionUiV2()) {
mAdapterV2.setConditions(mConditionManager.getConditions());
} else {
mAdapter.setConditions(mConditionManager.getConditions());
}
mAdapter.setConditions(mConditionManager.getConditions());
if (scrollToTop) {
mDashboard.scrollToPosition(0);
}
@@ -267,37 +250,14 @@ public class DashboardSummary extends InstrumentedFragment
}
}
@Override
public Suggestion getSuggestionAt(int position) {
if (mDashboardFeatureProvider.useSuggestionUiV2()) {
return mAdapterV2.getSuggestion(position);
} else {
return mAdapter.getSuggestion(position);
}
}
@Override
public void onSuggestionDismissed(Suggestion suggestion) {
mAdapter.onSuggestionDismissed(suggestion);
}
@Override
public void onSuggestionReady(List<Suggestion> suggestions) {
mStagingSuggestions = suggestions;
if (mDashboardFeatureProvider.useSuggestionUiV2()) {
mAdapterV2.setSuggestions(suggestions);
if (mStagingCategory != null) {
Log.d(TAG, "Category has loaded, setting category from suggestionReady");
mHandler.removeCallbacksAndMessages(null);
mAdapterV2.setCategory(mStagingCategory);
}
} else {
mAdapter.setSuggestions(suggestions);
if (mStagingCategory != null) {
Log.d(TAG, "Category has loaded, setting category from suggestionReady");
mHandler.removeCallbacksAndMessages(null);
mAdapter.setCategory(mStagingCategory);
}
mAdapter.setSuggestions(suggestions);
if (mStagingCategory != null) {
Log.d(TAG, "Category has loaded, setting category from suggestionReady");
mHandler.removeCallbacksAndMessages(null);
mAdapter.setCategory(mStagingCategory);
}
}
@@ -313,26 +273,14 @@ public class DashboardSummary extends InstrumentedFragment
if (mSuggestionControllerMixin.isSuggestionLoaded()) {
Log.d(TAG, "Suggestion has loaded, setting suggestion/category");
ThreadUtils.postOnMainThread(() -> {
if (mDashboardFeatureProvider.useSuggestionUiV2()) {
if (mStagingSuggestions != null) {
mAdapterV2.setSuggestions(mStagingSuggestions);
}
mAdapterV2.setCategory(mStagingCategory);
} else {
if (mStagingSuggestions != null) {
mAdapter.setSuggestions(mStagingSuggestions);
}
mAdapter.setCategory(mStagingCategory);
if (mStagingSuggestions != null) {
mAdapter.setSuggestions(mStagingSuggestions);
}
mAdapter.setCategory(mStagingCategory);
});
} else {
Log.d(TAG, "Suggestion NOT loaded, delaying setCategory by " + MAX_WAIT_MILLIS + "ms");
if (mDashboardFeatureProvider.useSuggestionUiV2()) {
mHandler.postDelayed(()
-> mAdapterV2.setCategory(mStagingCategory), MAX_WAIT_MILLIS);
} else {
mHandler.postDelayed(() -> mAdapter.setCategory(mStagingCategory), MAX_WAIT_MILLIS);
}
mHandler.postDelayed(() -> mAdapter.setCategory(mStagingCategory), MAX_WAIT_MILLIS);
}
}
}

View File

@@ -27,10 +27,7 @@ import android.widget.Button;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardAdapter;
import com.android.settings.dashboard.DashboardAdapter.DashboardItemHolder;
import com.android.settings.dashboard.DashboardData;
import com.android.settings.dashboard.DashboardData.HeaderMode;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.WirelessUtils;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
@@ -44,7 +41,7 @@ public class ConditionAdapter extends RecyclerView.Adapter<DashboardItemHolder>
private final Context mContext;
private final MetricsFeatureProvider mMetricsFeatureProvider;
private List<Condition> mConditions;
private @HeaderMode int mMode;
private boolean mExpanded;
private View.OnClickListener mConditionClickListener = new View.OnClickListener() {
@@ -84,10 +81,10 @@ public class ConditionAdapter extends RecyclerView.Adapter<DashboardItemHolder>
}
};
public ConditionAdapter(Context context, List<Condition> conditions, @HeaderMode int mode) {
public ConditionAdapter(Context context, List<Condition> conditions, boolean expanded) {
mContext = context;
mConditions = conditions;
mMode = mode;
mExpanded = expanded;
mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
setHasStableIds(true);
@@ -126,7 +123,7 @@ public class ConditionAdapter extends RecyclerView.Adapter<DashboardItemHolder>
@Override
public int getItemCount() {
if (mMode == DashboardData.HEADER_MODE_FULLY_EXPANDED) {
if (mExpanded) {
return mConditions.size();
}
return 0;
@@ -138,7 +135,7 @@ public class ConditionAdapter extends RecyclerView.Adapter<DashboardItemHolder>
}
private void bindViews(final Condition condition,
DashboardAdapter.DashboardItemHolder view, boolean isLastItem,
DashboardItemHolder view, boolean isLastItem,
View.OnClickListener onClickListener) {
if (condition instanceof AirplaneModeCondition) {
Log.d(TAG, "Airplane mode condition has been bound with "

View File

@@ -1,186 +0,0 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.dashboard.conditional;
import android.content.Context;
import android.support.annotation.VisibleForTesting;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardAdapterV2.DashboardItemHolder;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.WirelessUtils;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import java.util.List;
import java.util.Objects;
public class ConditionAdapterV2 extends RecyclerView.Adapter<DashboardItemHolder> {
public static final String TAG = "ConditionAdapter";
private final Context mContext;
private final MetricsFeatureProvider mMetricsFeatureProvider;
private List<Condition> mConditions;
private boolean mExpanded;
private View.OnClickListener mConditionClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
//TODO: get rid of setTag/getTag
Condition condition = (Condition) v.getTag();
mMetricsFeatureProvider.action(mContext,
MetricsEvent.ACTION_SETTINGS_CONDITION_CLICK,
condition.getMetricsConstant());
condition.onPrimaryClick();
}
};
@VisibleForTesting
ItemTouchHelper.SimpleCallback mSwipeCallback = new ItemTouchHelper.SimpleCallback(0,
ItemTouchHelper.START | ItemTouchHelper.END) {
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
RecyclerView.ViewHolder target) {
return true;
}
@Override
public int getSwipeDirs(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
return viewHolder.getItemViewType() == R.layout.condition_tile
? super.getSwipeDirs(recyclerView, viewHolder) : 0;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
Object item = getItem(viewHolder.getItemId());
// item can become null when running monkey
if (item != null) {
((Condition) item).silence();
}
}
};
public ConditionAdapterV2(Context context, List<Condition> conditions, boolean expanded) {
mContext = context;
mConditions = conditions;
mExpanded = expanded;
mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
setHasStableIds(true);
}
public Object getItem(long itemId) {
for (Condition condition : mConditions) {
if (Objects.hash(condition.getTitle()) == itemId) {
return condition;
}
}
return null;
}
@Override
public DashboardItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new DashboardItemHolder(LayoutInflater.from(parent.getContext()).inflate(
viewType, parent, false));
}
@Override
public void onBindViewHolder(DashboardItemHolder holder, int position) {
bindViews(mConditions.get(position), holder,
position == mConditions.size() - 1, mConditionClickListener);
}
@Override
public long getItemId(int position) {
return Objects.hash(mConditions.get(position).getTitle());
}
@Override
public int getItemViewType(int position) {
return R.layout.condition_tile;
}
@Override
public int getItemCount() {
if (mExpanded) {
return mConditions.size();
}
return 0;
}
public void addDismissHandling(final RecyclerView recyclerView) {
final ItemTouchHelper itemTouchHelper = new ItemTouchHelper(mSwipeCallback);
itemTouchHelper.attachToRecyclerView(recyclerView);
}
private void bindViews(final Condition condition,
DashboardItemHolder view, boolean isLastItem,
View.OnClickListener onClickListener) {
if (condition instanceof AirplaneModeCondition) {
Log.d(TAG, "Airplane mode condition has been bound with "
+ "isActive=" + condition.isActive() + ". Airplane mode is currently " +
WirelessUtils.isAirplaneModeOn(condition.mManager.getContext()));
}
View card = view.itemView.findViewById(R.id.content);
card.setTag(condition);
card.setOnClickListener(onClickListener);
view.icon.setImageIcon(condition.getIcon());
view.title.setText(condition.getTitle());
CharSequence[] actions = condition.getActions();
final boolean hasButtons = actions.length > 0;
setViewVisibility(view.itemView, R.id.buttonBar, hasButtons);
view.summary.setText(condition.getSummary());
for (int i = 0; i < 2; i++) {
Button button = (Button) view.itemView.findViewById(i == 0
? R.id.first_action : R.id.second_action);
if (actions.length > i) {
button.setVisibility(View.VISIBLE);
button.setText(actions[i]);
final int index = i;
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Context context = v.getContext();
FeatureFactory.getFactory(context).getMetricsFeatureProvider()
.action(context, MetricsEvent.ACTION_SETTINGS_CONDITION_BUTTON,
condition.getMetricsConstant());
condition.onActionClick(index);
}
});
} else {
button.setVisibility(View.GONE);
}
}
setViewVisibility(view.itemView, R.id.divider, !isLastItem);
}
private void setViewVisibility(View containerView, int viewId, boolean visible) {
View view = containerView.findViewById(viewId);
if (view != null) {
view.setVisibility(visible ? View.VISIBLE : View.GONE);
}
}
}

View File

@@ -17,6 +17,10 @@ package com.android.settings.dashboard.suggestions;
import android.app.PendingIntent;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.service.settings.suggestions.Suggestion;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
@@ -24,37 +28,71 @@ import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardAdapter.DashboardItemHolder;
import com.android.settings.dashboard.DashboardAdapter.IconCache;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.Utils;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnSaveInstanceState;
import com.android.settingslib.suggestions.SuggestionControllerMixin;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public class SuggestionAdapter extends RecyclerView.Adapter<DashboardItemHolder> {
public class SuggestionAdapter extends RecyclerView.Adapter<DashboardItemHolder> implements
LifecycleObserver, OnSaveInstanceState {
public static final String TAG = "SuggestionAdapter";
private static final String STATE_SUGGESTIONS_SHOWN_LOGGED = "suggestions_shown_logged";
private static final String STATE_SUGGESTION_LIST = "suggestion_list";
private final Context mContext;
private final MetricsFeatureProvider mMetricsFeatureProvider;
private final List<Suggestion> mSuggestions;
private final IconCache mCache;
private final List<String> mSuggestionsShownLogged;
private final ArrayList<String> mSuggestionsShownLogged;
private final SuggestionFeatureProvider mSuggestionFeatureProvider;
private final SuggestionControllerMixin mSuggestionControllerMixin;
private final Callback mCallback;
private final CardConfig mConfig;
private List<Suggestion> mSuggestions;
public interface Callback {
/**
* Called when the close button of the suggestion card is clicked.
*/
void onSuggestionClosed(Suggestion suggestion);
}
public SuggestionAdapter(Context context, SuggestionControllerMixin suggestionControllerMixin,
List<Suggestion> suggestions, List<String> suggestionsShownLogged) {
Bundle savedInstanceState, Callback callback, Lifecycle lifecycle) {
mContext = context;
mSuggestionControllerMixin = suggestionControllerMixin;
mSuggestions = suggestions;
mSuggestionsShownLogged = suggestionsShownLogged;
mCache = new IconCache(context);
final FeatureFactory factory = FeatureFactory.getFactory(context);
mMetricsFeatureProvider = factory.getMetricsFeatureProvider();
mSuggestionFeatureProvider = factory.getSuggestionFeatureProvider(context);
mCallback = callback;
if (savedInstanceState != null) {
mSuggestions = savedInstanceState.getParcelableArrayList(STATE_SUGGESTION_LIST);
mSuggestionsShownLogged = savedInstanceState.getStringArrayList(
STATE_SUGGESTIONS_SHOWN_LOGGED);
} else {
mSuggestionsShownLogged = new ArrayList<>();
}
if (lifecycle != null) {
lifecycle.addObserver(this);
}
mConfig = CardConfig.get(context);
setHasStableIds(true);
}
@@ -67,31 +105,49 @@ public class SuggestionAdapter extends RecyclerView.Adapter<DashboardItemHolder>
@Override
public void onBindViewHolder(DashboardItemHolder holder, int position) {
bindSuggestion(holder, position);
}
private void bindSuggestion(DashboardItemHolder holder, int position) {
final Suggestion suggestion = mSuggestions.get(position);
final String id = suggestion.getId();
final int suggestionCount = mSuggestions.size();
if (!mSuggestionsShownLogged.contains(id)) {
mMetricsFeatureProvider.action(
mContext, MetricsEvent.ACTION_SHOW_SETTINGS_SUGGESTION, id);
mSuggestionsShownLogged.add(id);
}
holder.icon.setImageDrawable(mCache.getIcon(suggestion.getIcon()));
mConfig.setCardLayout(holder, suggestionCount, position);
final Icon icon = suggestion.getIcon();
final Drawable drawable = mCache.getIcon(icon);
if (drawable != null && TextUtils.equals(icon.getResPackage(), mContext.getPackageName())) {
drawable.setTint(Utils.getColorAccent(mContext));
}
holder.icon.setImageDrawable(drawable);
holder.title.setText(suggestion.getTitle());
final CharSequence summary = suggestion.getSummary();
if (!TextUtils.isEmpty(summary)) {
holder.summary.setText(summary);
holder.summary.setVisibility(View.VISIBLE);
holder.title.setSingleLine(suggestionCount == 1);
if (suggestionCount == 1) {
final CharSequence summary = suggestion.getSummary();
if (!TextUtils.isEmpty(summary)) {
holder.summary.setText(summary);
holder.summary.setVisibility(View.VISIBLE);
} else {
holder.summary.setVisibility(View.GONE);
}
} else {
// Do not show summary if there are more than 1 suggestions
holder.summary.setVisibility(View.GONE);
holder.title.setMaxLines(3);
}
final View divider = holder.itemView.findViewById(R.id.divider);
if (divider != null) {
divider.setVisibility(position < mSuggestions.size() - 1 ? View.VISIBLE : View.GONE);
final ImageView closeButton = holder.itemView.findViewById(R.id.close_button);
if (closeButton != null) {
closeButton.setOnClickListener(v -> {
mSuggestionFeatureProvider.dismissSuggestion(
mContext, mSuggestionControllerMixin, suggestion);
if (mCallback != null) {
mCallback.onSuggestionClosed(suggestion);
}
});
}
View clickHandler = holder.itemView;
// If a view with @android:id/primary is defined, use that as the click handler
// instead.
@@ -144,7 +200,83 @@ public class SuggestionAdapter extends RecyclerView.Adapter<DashboardItemHolder>
}
public void removeSuggestion(Suggestion suggestion) {
final int position = mSuggestions.indexOf(suggestion);
mSuggestions.remove(suggestion);
notifyDataSetChanged();
notifyItemRemoved(position);
}
@Override
public void onSaveInstanceState(Bundle outState) {
if (mSuggestions != null) {
outState.putParcelableArrayList(STATE_SUGGESTION_LIST,
new ArrayList<>(mSuggestions));
}
outState.putStringArrayList(STATE_SUGGESTIONS_SHOWN_LOGGED, mSuggestionsShownLogged);
}
public void setSuggestions(List<Suggestion> suggestions) {
mSuggestions = suggestions;
}
public List<Suggestion> getSuggestions() {
return mSuggestions;
}
private static class CardConfig {
// Card start/end margin
private final int mMarginInner;
private final int mMarginOuter;
// Card width for different numbers of cards
private final int mWidthSingleCard;
private final int mWidthTwoCards;
private final int mWidthMultipleCards;
// padding between icon and title
private final int mPaddingTitleTopSingleCard;
private final int mPaddingTitleTopMultipleCards;
private static CardConfig sConfig;
private CardConfig(Context context) {
final Resources res = context.getResources();
mMarginInner =
res.getDimensionPixelOffset(R.dimen.suggestion_card_inner_margin);
mMarginOuter =
res.getDimensionPixelOffset(R.dimen.suggestion_card_outer_margin);
mWidthSingleCard = res.getDimensionPixelOffset(R.dimen.suggestion_card_width_one_card);
mWidthTwoCards = res.getDimensionPixelOffset(R.dimen.suggestion_card_width_two_cards);
mWidthMultipleCards =
res.getDimensionPixelOffset(R.dimen.suggestion_card_width_multiple_cards);
mPaddingTitleTopSingleCard =
res.getDimensionPixelOffset(R.dimen.suggestion_card_title_padding_bottom_one_card);
mPaddingTitleTopMultipleCards = res.getDimensionPixelOffset(
R.dimen.suggestion_card_title_padding_bottom_multiple_cards);
}
public static CardConfig get(Context context) {
if (sConfig == null) {
sConfig = new CardConfig(context);
}
return sConfig;
}
private void setCardLayout(DashboardItemHolder holder, int suggestionCount,
int position) {
final LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
suggestionCount == 1
? mWidthSingleCard : suggestionCount == 2
? mWidthTwoCards : mWidthMultipleCards,
LinearLayout.LayoutParams.WRAP_CONTENT);
if (suggestionCount == 1) {
params.setMarginStart(mMarginOuter);
params.setMarginEnd(mMarginOuter);
} else {
params.setMarginStart(
position == 0 ? mMarginOuter : mMarginInner);
params.setMarginEnd(position == suggestionCount - 1 ? mMarginOuter : 0);
}
holder.itemView.setLayoutParams(params);
}
}
}

View File

@@ -1,282 +0,0 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.dashboard.suggestions;
import android.app.PendingIntent;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.service.settings.suggestions.Suggestion;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardAdapterV2.DashboardItemHolder;
import com.android.settings.dashboard.DashboardAdapterV2.IconCache;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.Utils;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnSaveInstanceState;
import com.android.settingslib.suggestions.SuggestionControllerMixin;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public class SuggestionAdapterV2 extends RecyclerView.Adapter<DashboardItemHolder> implements
LifecycleObserver, OnSaveInstanceState {
public static final String TAG = "SuggestionAdapterV2";
private static final String STATE_SUGGESTIONS_SHOWN_LOGGED = "suggestions_shown_logged";
private static final String STATE_SUGGESTION_LIST = "suggestion_list";
private final Context mContext;
private final MetricsFeatureProvider mMetricsFeatureProvider;
private final IconCache mCache;
private final ArrayList<String> mSuggestionsShownLogged;
private final SuggestionFeatureProvider mSuggestionFeatureProvider;
private final SuggestionControllerMixin mSuggestionControllerMixin;
private final Callback mCallback;
private final CardConfig mConfig;
private List<Suggestion> mSuggestions;
public interface Callback {
/**
* Called when the close button of the suggestion card is clicked.
*/
void onSuggestionClosed(Suggestion suggestion);
}
public SuggestionAdapterV2(Context context, SuggestionControllerMixin suggestionControllerMixin,
Bundle savedInstanceState, Callback callback, Lifecycle lifecycle) {
mContext = context;
mSuggestionControllerMixin = suggestionControllerMixin;
mCache = new IconCache(context);
final FeatureFactory factory = FeatureFactory.getFactory(context);
mMetricsFeatureProvider = factory.getMetricsFeatureProvider();
mSuggestionFeatureProvider = factory.getSuggestionFeatureProvider(context);
mCallback = callback;
if (savedInstanceState != null) {
mSuggestions = savedInstanceState.getParcelableArrayList(STATE_SUGGESTION_LIST);
mSuggestionsShownLogged = savedInstanceState.getStringArrayList(
STATE_SUGGESTIONS_SHOWN_LOGGED);
} else {
mSuggestionsShownLogged = new ArrayList<>();
}
if (lifecycle != null) {
lifecycle.addObserver(this);
}
mConfig = CardConfig.get(context);
setHasStableIds(true);
}
@Override
public DashboardItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new DashboardItemHolder(LayoutInflater.from(parent.getContext()).inflate(
viewType, parent, false));
}
@Override
public void onBindViewHolder(DashboardItemHolder holder, int position) {
final Suggestion suggestion = mSuggestions.get(position);
final String id = suggestion.getId();
final int suggestionCount = mSuggestions.size();
if (!mSuggestionsShownLogged.contains(id)) {
mMetricsFeatureProvider.action(
mContext, MetricsEvent.ACTION_SHOW_SETTINGS_SUGGESTION, id);
mSuggestionsShownLogged.add(id);
}
mConfig.setCardLayout(holder, suggestionCount, position);
final Icon icon = suggestion.getIcon();
final Drawable drawable = mCache.getIcon(icon);
if (drawable != null && TextUtils.equals(icon.getResPackage(), mContext.getPackageName())) {
drawable.setTint(Utils.getColorAccent(mContext));
}
holder.icon.setImageDrawable(drawable);
holder.title.setText(suggestion.getTitle());
holder.title.setSingleLine(suggestionCount == 1);
if (suggestionCount == 1) {
final CharSequence summary = suggestion.getSummary();
if (!TextUtils.isEmpty(summary)) {
holder.summary.setText(summary);
holder.summary.setVisibility(View.VISIBLE);
} else {
holder.summary.setVisibility(View.GONE);
}
} else {
// Do not show summary if there are more than 1 suggestions
holder.summary.setVisibility(View.GONE);
holder.title.setMaxLines(3);
}
final ImageView closeButton = holder.itemView.findViewById(R.id.close_button);
if (closeButton != null) {
closeButton.setOnClickListener(v -> {
mSuggestionFeatureProvider.dismissSuggestion(
mContext, mSuggestionControllerMixin, suggestion);
if (mCallback != null) {
mCallback.onSuggestionClosed(suggestion);
}
});
}
View clickHandler = holder.itemView;
// If a view with @android:id/primary is defined, use that as the click handler
// instead.
final View primaryAction = holder.itemView.findViewById(android.R.id.primary);
if (primaryAction != null) {
clickHandler = primaryAction;
}
clickHandler.setOnClickListener(v -> {
mMetricsFeatureProvider.action(mContext, MetricsEvent.ACTION_SETTINGS_SUGGESTION, id);
try {
suggestion.getPendingIntent().send();
mSuggestionControllerMixin.launchSuggestion(suggestion);
} catch (PendingIntent.CanceledException e) {
Log.w(TAG, "Failed to start suggestion " + suggestion.getTitle());
}
});
}
@Override
public long getItemId(int position) {
return Objects.hash(mSuggestions.get(position).getId());
}
@Override
public int getItemViewType(int position) {
final Suggestion suggestion = getSuggestion(position);
if ((suggestion.getFlags() & Suggestion.FLAG_HAS_BUTTON) != 0) {
return R.layout.suggestion_tile_with_button_v2;
} else {
return R.layout.suggestion_tile_v2;
}
}
@Override
public int getItemCount() {
return mSuggestions.size();
}
public Suggestion getSuggestion(int position) {
final long itemId = getItemId(position);
if (mSuggestions == null) {
return null;
}
for (Suggestion suggestion : mSuggestions) {
if (Objects.hash(suggestion.getId()) == itemId) {
return suggestion;
}
}
return null;
}
public void removeSuggestion(Suggestion suggestion) {
final int position = mSuggestions.indexOf(suggestion);
mSuggestions.remove(suggestion);
notifyItemRemoved(position);
}
@Override
public void onSaveInstanceState(Bundle outState) {
if (mSuggestions != null) {
outState.putParcelableArrayList(STATE_SUGGESTION_LIST,
new ArrayList<>(mSuggestions));
}
outState.putStringArrayList(STATE_SUGGESTIONS_SHOWN_LOGGED, mSuggestionsShownLogged);
}
public void setSuggestions(List<Suggestion> suggestions) {
mSuggestions = suggestions;
}
public List<Suggestion> getSuggestions() {
return mSuggestions;
}
private static class CardConfig {
// Card start/end margin
private final int mMarginInner;
private final int mMarginOuter;
// Card width for different numbers of cards
private final int mWidthSingleCard;
private final int mWidthTwoCards;
private final int mWidthMultipleCards;
// padding between icon and title
private final int mPaddingTitleTopSingleCard;
private final int mPaddingTitleTopMultipleCards;
private static CardConfig sConfig;
private CardConfig(Context context) {
final Resources res = context.getResources();
mMarginInner =
res.getDimensionPixelOffset(R.dimen.suggestion_card_inner_margin);
mMarginOuter =
res.getDimensionPixelOffset(R.dimen.suggestion_card_outer_margin);
mWidthSingleCard = res.getDimensionPixelOffset(R.dimen.suggestion_card_width_one_card);
mWidthTwoCards = res.getDimensionPixelOffset(R.dimen.suggestion_card_width_two_cards);
mWidthMultipleCards =
res.getDimensionPixelOffset(R.dimen.suggestion_card_width_multiple_cards);
mPaddingTitleTopSingleCard =
res.getDimensionPixelOffset(R.dimen.suggestion_card_title_padding_bottom_one_card);
mPaddingTitleTopMultipleCards = res.getDimensionPixelOffset(
R.dimen.suggestion_card_title_padding_bottom_multiple_cards);
}
public static CardConfig get(Context context) {
if (sConfig == null) {
sConfig = new CardConfig(context);
}
return sConfig;
}
private void setCardLayout(DashboardItemHolder holder, int suggestionCount,
int position) {
final LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
suggestionCount == 1
? mWidthSingleCard : suggestionCount == 2
? mWidthTwoCards : mWidthMultipleCards,
LinearLayout.LayoutParams.WRAP_CONTENT);
if (suggestionCount == 1) {
params.setMarginStart(mMarginOuter);
params.setMarginEnd(mMarginOuter);
} else {
params.setMarginStart(
position == 0 ? mMarginOuter : mMarginInner);
params.setMarginEnd(position == suggestionCount - 1 ? mMarginOuter : 0);
}
holder.itemView.setLayoutParams(params);
}
}
}

View File

@@ -1,90 +0,0 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.dashboard.suggestions;
import android.content.Context;
import android.service.settings.suggestions.Suggestion;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import com.android.settings.R;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.suggestions.SuggestionControllerMixin;
/**
* Deprecated as a close button is provided to dismiss the suggestion.
*/
@Deprecated
public class SuggestionDismissController extends ItemTouchHelper.SimpleCallback {
public interface Callback {
/**
* Returns suggestion tile data from the callback
*/
Suggestion getSuggestionAt(int position);
/**
* Called when a suggestion is dismissed.
*/
void onSuggestionDismissed(Suggestion suggestion);
}
private final Context mContext;
private final SuggestionFeatureProvider mSuggestionFeatureProvider;
private final SuggestionControllerMixin mSuggestionMixin;
private final Callback mCallback;
public SuggestionDismissController(Context context, RecyclerView recyclerView,
SuggestionControllerMixin suggestionMixin, Callback callback) {
super(0, ItemTouchHelper.START | ItemTouchHelper.END);
mSuggestionMixin = suggestionMixin;
mContext = context;
mSuggestionFeatureProvider = FeatureFactory.getFactory(context)
.getSuggestionFeatureProvider(context);
mCallback = callback;
final ItemTouchHelper itemTouchHelper = new ItemTouchHelper(this);
itemTouchHelper.attachToRecyclerView(recyclerView);
}
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
RecyclerView.ViewHolder target) {
return true;
}
@Override
public int getSwipeDirs(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
final int layoutId = viewHolder.getItemViewType();
if (layoutId == R.layout.suggestion_tile
|| layoutId == R.layout.suggestion_tile_with_button) {
// Only return swipe direction for suggestion tiles. All other types are not swipeable.
return super.getSwipeDirs(recyclerView, viewHolder);
}
return 0;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
if (mCallback == null) {
return;
}
final int position = viewHolder.getAdapterPosition();
final Suggestion suggestionV2 = mCallback.getSuggestionAt(position);
mSuggestionFeatureProvider.dismissSuggestion(mContext, mSuggestionMixin, suggestionV2);
mCallback.onSuggestionDismissed(suggestionV2);
}
}

View File

@@ -27,11 +27,11 @@ import android.support.annotation.VisibleForTesting;
import android.support.v7.preference.ListPreference;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceScreen;
import android.text.TextUtils;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.connecteddevice.usb.UsbBackend;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnCreate;
@@ -48,6 +48,8 @@ public class SelectUsbConfigPreferenceController extends
private final String[] mListValues;
private final String[] mListSummaries;
private final UsbManager mUsbManager;
@VisibleForTesting
UsbBackend.UsbManagerPassThrough mUsbManagerPassThrough;
private BroadcastReceiver mUsbReceiver;
private ListPreference mPreference;
@@ -57,6 +59,7 @@ public class SelectUsbConfigPreferenceController extends
mListValues = context.getResources().getStringArray(R.array.usb_configuration_values);
mListSummaries = context.getResources().getStringArray(R.array.usb_configuration_titles);
mUsbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
mUsbManagerPassThrough = new UsbBackend.UsbManagerPassThrough(mUsbManager);
mUsbReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -95,7 +98,8 @@ public class SelectUsbConfigPreferenceController extends
return false;
}
writeUsbConfigurationOption(newValue.toString());
writeUsbConfigurationOption(mUsbManagerPassThrough
.usbFunctionsFromString(newValue.toString()));
updateUsbConfigurationValues();
return true;
}
@@ -129,14 +133,15 @@ public class SelectUsbConfigPreferenceController extends
}
@VisibleForTesting
void setCurrentFunction(String newValue, boolean usbDataUnlocked) {
mUsbManager.setCurrentFunction(newValue, usbDataUnlocked);
void setCurrentFunctions(long functions) {
mUsbManager.setCurrentFunctions(functions);
}
private void updateUsbConfigurationValues() {
long functions = mUsbManagerPassThrough.getCurrentFunctions();
int index = 0;
for (int i = 0; i < mListValues.length; i++) {
if (mUsbManager.isFunctionEnabled(mListValues[i])) {
if (functions == mUsbManagerPassThrough.usbFunctionsFromString(mListValues[i])) {
index = i;
break;
}
@@ -145,11 +150,7 @@ public class SelectUsbConfigPreferenceController extends
mPreference.setSummary(mListSummaries[index]);
}
private void writeUsbConfigurationOption(String newValue) {
if (TextUtils.equals(newValue, "none")) {
setCurrentFunction(newValue, false);
} else {
setCurrentFunction(newValue, true);
}
private void writeUsbConfigurationOption(long newValue) {
setCurrentFunctions(newValue);
}
}

View File

@@ -56,6 +56,7 @@ public class AllowSoundPreferenceController extends NotificationPreferenceContro
}
@Override
public void updateState(Preference preference) {
if (mChannel != null) {
RestrictedSwitchPreference pref = (RestrictedSwitchPreference) preference;

View File

@@ -120,7 +120,8 @@ public class AppNotificationSettings extends NotificationSettingsBase {
mControllers.add(new BadgePreferenceController(context, mBackend));
mControllers.add(new AllowSoundPreferenceController(
context, mImportanceListener, mBackend));
mControllers.add(new ImportancePreferenceController(context));
mControllers.add(new ImportancePreferenceController(
context, mImportanceListener, mBackend));
mControllers.add(new SoundPreferenceController(context, this,
mImportanceListener, mBackend));
mControllers.add(new LightsPreferenceController(context, mBackend));

View File

@@ -1,166 +0,0 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.notification;
import static android.app.NotificationChannel.USER_LOCKED_IMPORTANCE;
import static android.app.NotificationChannel.USER_LOCKED_SOUND;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_MAX;
import static android.app.NotificationManager.IMPORTANCE_MIN;
import android.content.Context;
import android.media.RingtoneManager;
import android.provider.SearchIndexableResource;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceScreen;
import android.text.TextUtils;
import android.util.Log;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.search.Indexable;
import com.android.settings.widget.RadioButtonPreference;
import com.android.settingslib.core.AbstractPreferenceController;
import java.util.ArrayList;
import java.util.List;
public class ChannelImportanceSettings extends NotificationSettingsBase
implements RadioButtonPreference.OnClickListener, Indexable {
private static final String TAG = "NotiImportance";
private static final String KEY_IMPORTANCE_HIGH = "importance_high";
private static final String KEY_IMPORTANCE_DEFAULT = "importance_default";
private static final String KEY_IMPORTANCE_LOW = "importance_low";
private static final String KEY_IMPORTANCE_MIN = "importance_min";
List<RadioButtonPreference> mImportances = new ArrayList<>();
@Override
public int getMetricsCategory() {
return MetricsEvent.NOTIFICATION_CHANNEL_IMPORTANCE;
}
@Override
public void onResume() {
super.onResume();
if (mAppRow == null || mChannel == null) {
Log.w(TAG, "Missing package or channel");
finish();
return;
}
createPreferenceHierarchy();
}
@Override
protected String getLogTag() {
return TAG;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.notification_importance;
}
@Override
protected List<AbstractPreferenceController> getPreferenceControllers(Context context) {
return null;
}
@Override
public void onPause() {
super.onPause();
}
private PreferenceScreen createPreferenceHierarchy() {
PreferenceScreen root = getPreferenceScreen();
for (int i = 0; i < root.getPreferenceCount(); i++) {
Preference pref = root.getPreference(i);
if (pref instanceof RadioButtonPreference) {
RadioButtonPreference radioPref = (RadioButtonPreference) pref;
radioPref.setOnClickListener(this);
mImportances.add(radioPref);
}
}
switch (mChannel.getImportance()) {
case IMPORTANCE_MIN:
updateRadioButtons(KEY_IMPORTANCE_MIN);
break;
case IMPORTANCE_LOW:
updateRadioButtons(KEY_IMPORTANCE_LOW);
break;
case IMPORTANCE_DEFAULT:
updateRadioButtons(KEY_IMPORTANCE_DEFAULT);
break;
case IMPORTANCE_HIGH:
case IMPORTANCE_MAX:
updateRadioButtons(KEY_IMPORTANCE_HIGH);
break;
}
return root;
}
private void updateRadioButtons(String selectionKey) {
for (RadioButtonPreference pref : mImportances) {
if (selectionKey.equals(pref.getKey())) {
pref.setChecked(true);
} else {
pref.setChecked(false);
}
}
}
@Override
public void onRadioButtonClicked(RadioButtonPreference clicked) {
int oldImportance = mChannel.getImportance();
switch (clicked.getKey()) {
case KEY_IMPORTANCE_HIGH:
mChannel.setImportance(IMPORTANCE_HIGH);
break;
case KEY_IMPORTANCE_DEFAULT:
mChannel.setImportance(IMPORTANCE_DEFAULT);
break;
case KEY_IMPORTANCE_LOW:
mChannel.setImportance(IMPORTANCE_LOW);
break;
case KEY_IMPORTANCE_MIN:
mChannel.setImportance(IMPORTANCE_MIN);
break;
}
updateRadioButtons(clicked.getKey());
// If you are moving from an importance level without sound to one with sound,
// but the sound you had selected was "Silence",
// then set sound for this channel to your default sound,
// because you probably intended to cause this channel to actually start making sound.
if (oldImportance < IMPORTANCE_DEFAULT
&& !SoundPreferenceController.hasValidSound(mChannel)
&& mChannel.getImportance() >= IMPORTANCE_DEFAULT) {
mChannel.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION),
mChannel.getAudioAttributes());
mChannel.lockFields(USER_LOCKED_SOUND);
}
mChannel.lockFields(USER_LOCKED_IMPORTANCE);
mBackend.updateChannel(mAppRow.pkg, mAppRow.uid, mChannel);
}
}

View File

@@ -79,7 +79,8 @@ public class ChannelNotificationSettings extends NotificationSettingsBase {
mControllers = new ArrayList<>();
mControllers.add(new HeaderPreferenceController(context, this));
mControllers.add(new BlockPreferenceController(context, mImportanceListener, mBackend));
mControllers.add(new ImportancePreferenceController(context));
mControllers.add(new ImportancePreferenceController(
context, mImportanceListener, mBackend));
mControllers.add(new AllowSoundPreferenceController(
context, mImportanceListener, mBackend));
mControllers.add(new SoundPreferenceController(context, this,

View File

@@ -30,9 +30,7 @@ import android.support.v7.preference.Preference;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R;
import com.android.settings.RingtonePreference;
import com.android.settings.applications.NotificationApps;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.dashboard.SummaryLoader;
import com.android.settings.gestures.SwipeToNotificationPreferenceController;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.search.Indexable;
@@ -153,15 +151,6 @@ public class ConfigureNotificationSettings extends DashboardFragment {
}
}
public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY
= new SummaryLoader.SummaryProviderFactory() {
@Override
public SummaryLoader.SummaryProvider createSummaryProvider(Activity activity,
SummaryLoader summaryLoader) {
return new NotificationApps.SummaryProvider(activity, summaryLoader);
}
};
/**
* For Search.
*/

View File

@@ -55,7 +55,6 @@ public class DeletedChannelsPreferenceController extends NotificationPreferenceC
preference.setTitle(mContext.getResources().getQuantityString(
R.plurals.deleted_channels, deletedChannelCount, deletedChannelCount));
}
preference.setEnabled(false);
preference.setSelectable(false);
}
}

View File

@@ -16,31 +16,33 @@
package com.android.settings.notification;
import static android.app.NotificationChannel.USER_LOCKED_SOUND;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.IMPORTANCE_MIN;
import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.provider.Settings;
import android.media.RingtoneManager;
import android.support.v7.preference.Preference;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.applications.AppInfoBase;
import com.android.settings.RestrictedListPreference;
import com.android.settings.core.PreferenceControllerMixin;
public class ImportancePreferenceController extends NotificationPreferenceController
implements PreferenceControllerMixin {
implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener {
private static final String KEY_IMPORTANCE = "importance";
private NotificationSettingsBase.ImportanceListener mImportanceListener;
// Ironically doesn't take an importance listener because the importance is not changed
// by this controller's preference but by the screen it links to.
public ImportancePreferenceController(Context context) {
super(context, null);
public ImportancePreferenceController(Context context,
NotificationSettingsBase.ImportanceListener importanceListener,
NotificationBackend backend) {
super(context, backend);
mImportanceListener = importanceListener;
}
@Override
@@ -48,10 +50,6 @@ public class ImportancePreferenceController extends NotificationPreferenceContro
return KEY_IMPORTANCE;
}
private int getMetricsCategory() {
return MetricsProto.MetricsEvent.NOTIFICATION_TOPIC_NOTIFICATION;
}
@Override
public boolean isAvailable() {
if (!super.isAvailable()) {
@@ -63,51 +61,82 @@ public class ImportancePreferenceController extends NotificationPreferenceContro
return !NotificationChannel.DEFAULT_CHANNEL_ID.equals(mChannel.getId());
}
@Override
public void updateState(Preference preference) {
if (mAppRow!= null && mChannel != null) {
preference.setEnabled(mAdmin == null && isChannelConfigurable());
Bundle channelArgs = new Bundle();
channelArgs.putInt(AppInfoBase.ARG_PACKAGE_UID, mAppRow.uid);
channelArgs.putString(AppInfoBase.ARG_PACKAGE_NAME, mAppRow.pkg);
channelArgs.putString(Settings.EXTRA_CHANNEL_ID, mChannel.getId());
if (preference.isEnabled()) {
Intent channelIntent = Utils.onBuildStartFragmentIntent(mContext,
ChannelImportanceSettings.class.getName(),
channelArgs, null,
R.string.notification_importance_title, null,
false, getMetricsCategory());
preference.setIntent(channelIntent);
preference.setSummary(getImportanceSummary(mContext, mChannel));
preference.setSummary(getImportanceSummary(mChannel));
int importances = IMPORTANCE_HIGH - IMPORTANCE_MIN + 1;
CharSequence[] entries = new CharSequence[importances];
CharSequence[] values = new CharSequence[importances];
int index = 0;
for (int i = IMPORTANCE_HIGH; i >= IMPORTANCE_MIN; i--) {
NotificationChannel channel = new NotificationChannel("", "", i);
entries[index] = getImportanceSummary(channel);
values[index] = String.valueOf(i);
index++;
}
RestrictedListPreference pref = (RestrictedListPreference) preference;
pref.setEntries(entries);
pref.setEntryValues(values);
pref.setValue(String.valueOf(mChannel.getImportance()));
}
}
protected static String getImportanceSummary(Context context, NotificationChannel channel) {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (mChannel != null) {
final int importance = Integer.parseInt((String) newValue);
// If you are moving from an importance level without sound to one with sound,
// but the sound you had selected was "Silence",
// then set sound for this channel to your default sound,
// because you probably intended to cause this channel to actually start making sound.
if (mChannel.getImportance() < IMPORTANCE_DEFAULT
&& !SoundPreferenceController.hasValidSound(mChannel)
&& importance >= IMPORTANCE_DEFAULT) {
mChannel.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION),
mChannel.getAudioAttributes());
mChannel.lockFields(USER_LOCKED_SOUND);
}
mChannel.setImportance(importance);
mChannel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
saveChannel();
mImportanceListener.onImportanceChanged();
}
return true;
}
protected String getImportanceSummary(NotificationChannel channel) {
String summary = "";
int importance = channel.getImportance();
switch (importance) {
case IMPORTANCE_UNSPECIFIED:
summary = context.getString(R.string.notification_importance_unspecified);
summary = mContext.getString(R.string.notification_importance_unspecified);
break;
case NotificationManager.IMPORTANCE_MIN:
summary = context.getString(R.string.notification_importance_min);
summary = mContext.getString(R.string.notification_importance_min);
break;
case NotificationManager.IMPORTANCE_LOW:
summary = context.getString(R.string.notification_importance_low);
summary = mContext.getString(R.string.notification_importance_low);
break;
case NotificationManager.IMPORTANCE_DEFAULT:
if (SoundPreferenceController.hasValidSound(channel)) {
summary = context.getString(R.string.notification_importance_default);
summary = mContext.getString(R.string.notification_importance_default);
} else {
summary = context.getString(R.string.notification_importance_low);
summary = mContext.getString(R.string.notification_importance_low);
}
break;
case NotificationManager.IMPORTANCE_HIGH:
case NotificationManager.IMPORTANCE_MAX:
if (SoundPreferenceController.hasValidSound(channel)) {
summary = context.getString(R.string.notification_importance_high);
summary = mContext.getString(R.string.notification_importance_high);
} else {
summary = context.getString(R.string.notification_importance_high_silent);
summary = mContext.getString(R.string.notification_importance_high_silent);
}
break;
default:

View File

@@ -57,7 +57,6 @@ public class NotificationsOffPreferenceController extends NotificationPreference
preference.setTitle(R.string.app_notifications_off_desc);
}
}
preference.setEnabled(false);
preference.setSelectable(false);
}
}

View File

@@ -147,7 +147,8 @@ public class RecentNotifyingAppsPreferenceController extends AbstractPreferenceC
@Override
protected void onCountComplete(int num) {
if (mHasRecentApps) {
mSeeAllPref.setTitle(mContext.getString(R.string.see_all_apps_title, num));
mSeeAllPref.setTitle(
mContext.getString(R.string.recent_notifications_see_all_title));
} else {
mSeeAllPref.setSummary(mContext.getString(R.string.apps_summary, num));
}

View File

@@ -21,6 +21,8 @@ import android.support.annotation.VisibleForTesting;
import com.android.settings.DateTimeSettings;
import com.android.settings.DisplaySettings;
import com.android.settings.LegalSettings;
import com.android.settings.connecteddevice.ConnectedDeviceDashboardFragmentOld;
import com.android.settings.deviceinfo.aboutphone.MyDeviceInfoFragment;
import com.android.settings.accessibility.AccessibilitySettings;
import com.android.settings.accessibility.AccessibilityShortcutPreferenceFragment;
import com.android.settings.accessibility.MagnificationPreferenceFragment;
@@ -34,7 +36,7 @@ import com.android.settings.backup.BackupSettingsFragment;
import com.android.settings.bluetooth.BluetoothSettings;
import com.android.settings.connecteddevice.AdvancedConnectedDeviceDashboardFragment;
import com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment;
import com.android.settings.connecteddevice.ConnectedDeviceDashboardFragmentOld;
import com.android.settings.connecteddevice.usb.UsbDetailsFragment;
import com.android.settings.datausage.DataUsageSummary;
import com.android.settings.deletionhelper.AutomaticStorageManagerSettings;
import com.android.settings.development.DevelopmentSettingsDashboardFragment;
@@ -167,6 +169,7 @@ public class SearchIndexableResourcesImpl implements SearchIndexableResources {
addIndex(PowerUsageSummary.class);
addIndex(BatterySaverSettings.class);
addIndex(LockscreenDashboardFragment.class);
addIndex(UsbDetailsFragment.class);
addIndex(WifiDisplaySettings.class);
addIndex(ZenModeBehaviorSettings.class);
addIndex(ZenModeAutomationSettings.class);

View File

@@ -13,13 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.deviceinfo;
package com.android.settings.system;
import android.content.Context;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settingslib.core.AbstractPreferenceController;
public class AdditionalSystemUpdatePreferenceController extends BasePreferenceController {

View File

@@ -26,8 +26,6 @@ import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R;
import com.android.settings.backup.BackupSettingsActivityPreferenceController;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.deviceinfo.AdditionalSystemUpdatePreferenceController;
import com.android.settings.deviceinfo.SystemUpdatePreferenceController;
import com.android.settings.gestures.GesturesSettingPreferenceController;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.search.Indexable;

View File

@@ -13,14 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.deviceinfo;
package com.android.settings.system;
import static android.content.Context.CARRIER_CONFIG_SERVICE;
import static android.content.Context.SYSTEM_UPDATE_SERVICE;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.os.SystemUpdateManager;
import android.os.UserManager;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceScreen;
@@ -39,10 +42,12 @@ public class SystemUpdatePreferenceController extends BasePreferenceController {
private static final String KEY_SYSTEM_UPDATE_SETTINGS = "system_update_settings";
private final UserManager mUm;
private final SystemUpdateManager mUpdateManager;
public SystemUpdatePreferenceController(Context context) {
super(context, KEY_SYSTEM_UPDATE_SETTINGS);
mUm = UserManager.get(context);
mUpdateManager = (SystemUpdateManager) context.getSystemService(SYSTEM_UPDATE_SERVICE);
}
@Override
@@ -84,7 +89,27 @@ public class SystemUpdatePreferenceController extends BasePreferenceController {
@Override
public String getSummary() {
return mContext.getString(R.string.about_summary, Build.VERSION.RELEASE);
final Bundle updateInfo = mUpdateManager.retrieveSystemUpdateInfo();
String summary = mContext.getString(R.string.android_version_summary,
Build.VERSION.RELEASE);
switch (updateInfo.getInt(SystemUpdateManager.KEY_STATUS)) {
case SystemUpdateManager.STATUS_WAITING_DOWNLOAD:
case SystemUpdateManager.STATUS_IN_PROGRESS:
case SystemUpdateManager.STATUS_WAITING_INSTALL:
case SystemUpdateManager.STATUS_WAITING_REBOOT:
summary = mContext.getString(R.string.android_version_pending_update_summary);
break;
case SystemUpdateManager.STATUS_UNKNOWN:
Log.d(TAG, "Update statue unknown");
// fall through to next branch
case SystemUpdateManager.STATUS_IDLE:
final String version = updateInfo.getString(SystemUpdateManager.KEY_TITLE);
if (!TextUtils.isEmpty(version)) {
summary = mContext.getString(R.string.android_version_summary, version);
}
break;
}
return summary;
}
/**

View File

@@ -249,11 +249,14 @@ public class UserSettings extends SettingsPreferenceFragment
mAddUser.useAdminDisabledSummary(false);
// Determine if add user/profile button should be visible
if (mUserCaps.mCanAddUser && Utils.isDeviceProvisioned(getActivity())) {
mAddUser.setVisible(true);
mAddUser.setOnPreferenceClickListener(this);
// change label to only mention user, if restricted profiles are not supported
if (!mUserCaps.mCanAddRestrictedProfile) {
mAddUser.setTitle(R.string.user_add_user_menu);
}
} else {
mAddUser.setVisible(false);
}
final IntentFilter filter = new IntentFilter(Intent.ACTION_USER_REMOVED);
filter.addAction(Intent.ACTION_USER_INFO_CHANGED);

View File

@@ -10,7 +10,6 @@ com.android.settings.development.featureflags.FeatureFlagsDashboard
com.android.settings.development.qstile.DevelopmentTileConfigFragment
com.android.settings.deviceinfo.StorageProfileFragment
com.android.settings.notification.ChannelNotificationSettings
com.android.settings.notification.ChannelImportanceSettings
com.android.settings.notification.ChannelGroupNotificationSettings
com.android.settings.notification.AppNotificationSettings
com.android.settings.wifi.details.WifiNetworkDetailsFragment

View File

@@ -0,0 +1,110 @@
/*
* Copyright (C) 2010 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 android.hardware.usb;
import android.annotation.SystemService;
import android.content.Context;
import android.hardware.usb.gadget.V1_0.GadgetFunction;
import java.util.HashMap;
import java.util.Map;
import java.util.StringJoiner;
/**
* Definitions that were added to UsbManager in P.
*
* Copied partially from frameworks/base/core/java/android/hardware/usb/UsbManager to
* fix issues with roboelectric during test.
*/
@SystemService(Context.USB_SERVICE)
public class UsbManagerExtras {
public static final long NONE = 0;
public static final long MTP = GadgetFunction.MTP;
public static final long PTP = GadgetFunction.PTP;
public static final long RNDIS = GadgetFunction.RNDIS;
public static final long MIDI = GadgetFunction.MIDI;
public static final long ACCESSORY = GadgetFunction.ACCESSORY;
public static final long AUDIO_SOURCE = GadgetFunction.AUDIO_SOURCE;
public static final long ADB = GadgetFunction.ADB;
private static final long SETTABLE_FUNCTIONS = MTP | PTP | RNDIS | MIDI;
private static final Map<String, Long> STR_MAP = new HashMap<>();
static {
STR_MAP.put(UsbManager.USB_FUNCTION_MTP, MTP);
STR_MAP.put(UsbManager.USB_FUNCTION_PTP, PTP);
STR_MAP.put(UsbManager.USB_FUNCTION_RNDIS, RNDIS);
STR_MAP.put(UsbManager.USB_FUNCTION_MIDI, MIDI);
STR_MAP.put(UsbManager.USB_FUNCTION_ACCESSORY, ACCESSORY);
STR_MAP.put(UsbManager.USB_FUNCTION_AUDIO_SOURCE, AUDIO_SOURCE);
STR_MAP.put(UsbManager.USB_FUNCTION_ADB, ADB);
}
/**
* Returns whether the given functions are valid inputs to UsbManager.
* Currently the empty functions or any of MTP, PTP, RNDIS, MIDI are accepted.
*/
public static boolean isSettableFunctions(long functions) {
return (~SETTABLE_FUNCTIONS & functions) == 0;
}
/**
* Returns the string representation of the given functions.
*/
public static String usbFunctionsToString(long functions) {
StringJoiner joiner = new StringJoiner(",");
if ((functions | MTP) != 0) {
joiner.add(UsbManager.USB_FUNCTION_MTP);
}
if ((functions | PTP) != 0) {
joiner.add(UsbManager.USB_FUNCTION_PTP);
}
if ((functions | RNDIS) != 0) {
joiner.add(UsbManager.USB_FUNCTION_RNDIS);
}
if ((functions | MIDI) != 0) {
joiner.add(UsbManager.USB_FUNCTION_MIDI);
}
if ((functions | ACCESSORY) != 0) {
joiner.add(UsbManager.USB_FUNCTION_ACCESSORY);
}
if ((functions | AUDIO_SOURCE) != 0) {
joiner.add(UsbManager.USB_FUNCTION_AUDIO_SOURCE);
}
if ((functions | ADB) != 0) {
joiner.add(UsbManager.USB_FUNCTION_ADB);
}
return joiner.toString();
}
/**
* Parses a string of usb functions and returns a mask of the same functions.
*/
public static long usbFunctionsFromString(String functions) {
if (functions == null) {
return 0;
}
long ret = 0;
for (String function : functions.split(",")) {
if (STR_MAP.containsKey(function)) {
ret |= STR_MAP.get(function);
}
}
return ret;
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.os;
/**
* Duplicate class for platform SystemUpdateManager to get around Robolectric sdk problem.
*/
public class SystemUpdateManager {
public static final String KEY_STATUS = "status";
public static final String KEY_TITLE = "title";
public static final int STATUS_UNKNOWN = 0;
public static final int STATUS_IDLE = 1;
public static final int STATUS_WAITING_DOWNLOAD = 2;
public static final int STATUS_IN_PROGRESS = 3;
public static final int STATUS_WAITING_INSTALL = 4;
public static final int STATUS_WAITING_REBOOT = 5;
public Bundle retrieveSystemUpdateInfo() {
return null;
}
}

View File

@@ -18,11 +18,14 @@ package com.android.settings;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf;
@@ -86,6 +89,9 @@ public class MasterClearTest {
@Mock
private Activity mMockActivity;
@Mock
private Intent mMockIntent;
private ShadowActivity mShadowActivity;
private ShadowAccountManager mShadowAccountManager;
private Activity mActivity;
@@ -110,7 +116,7 @@ public class MasterClearTest {
MockitoAnnotations.initMocks(this);
mMasterClear = spy(new MasterClear());
mActivity = Robolectric.setupActivity(Activity.class);
mShadowActivity = shadowOf(mActivity);
mShadowActivity = shadowOf(mActivity);https://stackoverflow.com/questions/14889951/how-to-verify-a-method-is-called-two-times-with-mockito-verify
// mShadowAccountManager = shadowOf(AccountManager.get(mActivity));
mContentView = LayoutInflater.from(mActivity).inflate(R.layout.master_clear, null);
@@ -213,38 +219,115 @@ public class MasterClearTest {
}
@Test
public void testTryShowAccountConfirmation_unsupported() {
when(mMasterClear.getActivity()).thenReturn(mActivity);
/* Using the default resources, account confirmation shouldn't trigger */
assertThat(mMasterClear.tryShowAccountConfirmation()).isFalse();
public void testOnActivityResultInternal_invalideRequest() {
int invalidRequestCode = -1;
doReturn(false).when(mMasterClear).isValidRequestCode(eq(invalidRequestCode));
mMasterClear.onActivityResultInternal(invalidRequestCode, Activity.RESULT_OK, null);
verify(mMasterClear, times(1)).isValidRequestCode(eq(invalidRequestCode));
verify(mMasterClear, times(0)).establishInitialState();
verify(mMasterClear, times(0)).getAccountConfirmationIntent();
verify(mMasterClear, times(0)).showFinalConfirmation();
}
@Test
public void testTryShowAccountConfirmation_no_relevant_accounts() {
public void testOnActivityResultInternal_resultCanceled() {
doReturn(true).when(mMasterClear).isValidRequestCode(eq(MasterClear.KEYGUARD_REQUEST));
doNothing().when(mMasterClear).establishInitialState();
mMasterClear.onActivityResultInternal(
MasterClear.KEYGUARD_REQUEST, Activity.RESULT_CANCELED, null);
verify(mMasterClear, times(1)).isValidRequestCode(eq(MasterClear.KEYGUARD_REQUEST));
verify(mMasterClear, times(1)).establishInitialState();
verify(mMasterClear, times(0)).getAccountConfirmationIntent();
verify(mMasterClear, times(0)).showFinalConfirmation();
}
@Test
public void testOnActivityResultInternal_keyguardRequestTriggeringConfirmAccount() {
doReturn(true).when(mMasterClear).isValidRequestCode(eq(MasterClear.KEYGUARD_REQUEST));
doReturn(mMockIntent).when(mMasterClear).getAccountConfirmationIntent();
doNothing().when(mMasterClear).showAccountCredentialConfirmation(eq(mMockIntent));
mMasterClear.onActivityResultInternal(
MasterClear.KEYGUARD_REQUEST, Activity.RESULT_OK, null);
verify(mMasterClear, times(1)).isValidRequestCode(eq(MasterClear.KEYGUARD_REQUEST));
verify(mMasterClear, times(0)).establishInitialState();
verify(mMasterClear, times(1)).getAccountConfirmationIntent();
verify(mMasterClear, times(1)).showAccountCredentialConfirmation(eq(mMockIntent));
}
@Test
public void testOnActivityResultInternal_keyguardRequestTriggeringShowFinal() {
doReturn(true).when(mMasterClear).isValidRequestCode(eq(MasterClear.KEYGUARD_REQUEST));
doReturn(null).when(mMasterClear).getAccountConfirmationIntent();
doNothing().when(mMasterClear).showFinalConfirmation();
mMasterClear.onActivityResultInternal(
MasterClear.KEYGUARD_REQUEST, Activity.RESULT_OK, null);
verify(mMasterClear, times(1)).isValidRequestCode(eq(MasterClear.KEYGUARD_REQUEST));
verify(mMasterClear, times(0)).establishInitialState();
verify(mMasterClear, times(1)).getAccountConfirmationIntent();
verify(mMasterClear, times(1)).showFinalConfirmation();
}
@Test
public void testOnActivityResultInternal_confirmRequestTriggeringShowFinal() {
doReturn(true).when(mMasterClear)
.isValidRequestCode(eq(MasterClear.CREDENTIAL_CONFIRM_REQUEST));
doNothing().when(mMasterClear).showFinalConfirmation();
mMasterClear.onActivityResultInternal(
MasterClear.CREDENTIAL_CONFIRM_REQUEST, Activity.RESULT_OK, null);
verify(mMasterClear, times(1))
.isValidRequestCode(eq(MasterClear.CREDENTIAL_CONFIRM_REQUEST));
verify(mMasterClear, times(0)).establishInitialState();
verify(mMasterClear, times(0)).getAccountConfirmationIntent();
verify(mMasterClear, times(1)).showFinalConfirmation();
}
@Test
public void testGetAccountConfirmationIntent_unsupported() {
when(mMasterClear.getActivity()).thenReturn(mActivity);
/* Using the default resources, account confirmation shouldn't trigger */
assertThat(mMasterClear.getAccountConfirmationIntent()).isNull();
}
@Test
public void testGetAccountConfirmationIntent_no_relevant_accounts() {
when(mMasterClear.getActivity()).thenReturn(mMockActivity);
when(mMockActivity.getString(R.string.account_type)).thenReturn(TEST_ACCOUNT_TYPE);
when(mMockActivity.getString(R.string.account_confirmation_package)).thenReturn(TEST_CONFIRMATION_PACKAGE);
when(mMockActivity.getString(R.string.account_confirmation_class)).thenReturn(TEST_CONFIRMATION_CLASS);
when(mMockActivity.getString(R.string.account_confirmation_package))
.thenReturn(TEST_CONFIRMATION_PACKAGE);
when(mMockActivity.getString(R.string.account_confirmation_class))
.thenReturn(TEST_CONFIRMATION_CLASS);
Account[] accounts = new Account[0];
when(mMockActivity.getSystemService(Context.ACCOUNT_SERVICE)).thenReturn(mAccountManager);
when(mAccountManager.getAccountsByType(TEST_ACCOUNT_TYPE)).thenReturn(accounts);
assertThat(mMasterClear.tryShowAccountConfirmation()).isFalse();
assertThat(mMasterClear.getAccountConfirmationIntent()).isNull();
}
@Test
public void testTryShowAccountConfirmation_unresolved() {
public void testGetAccountConfirmationIntent_unresolved() {
when(mMasterClear.getActivity()).thenReturn(mMockActivity);
when(mMockActivity.getString(R.string.account_type)).thenReturn(TEST_ACCOUNT_TYPE);
when(mMockActivity.getString(R.string.account_confirmation_package)).thenReturn(TEST_CONFIRMATION_PACKAGE);
when(mMockActivity.getString(R.string.account_confirmation_class)).thenReturn(TEST_CONFIRMATION_CLASS);
when(mMockActivity.getString(R.string.account_confirmation_package))
.thenReturn(TEST_CONFIRMATION_PACKAGE);
when(mMockActivity.getString(R.string.account_confirmation_class))
.thenReturn(TEST_CONFIRMATION_CLASS);
Account[] accounts = new Account[] { new Account(TEST_ACCOUNT_NAME, TEST_ACCOUNT_TYPE) };
when(mMockActivity.getSystemService(Context.ACCOUNT_SERVICE)).thenReturn(mAccountManager);
when(mAccountManager.getAccountsByType(TEST_ACCOUNT_TYPE)).thenReturn(accounts);
// The package manager should not resolve the confirmation intent targeting the non-existent
// confirmation package.
when(mMockActivity.getPackageManager()).thenReturn(mPackageManager);
assertThat(mMasterClear.tryShowAccountConfirmation()).isFalse();
assertThat(mMasterClear.getAccountConfirmationIntent()).isNull();
}
@Test
@@ -252,8 +335,10 @@ public class MasterClearTest {
when(mMasterClear.getActivity()).thenReturn(mMockActivity);
// Only try to show account confirmation if the appropriate resource overlays are available.
when(mMockActivity.getString(R.string.account_type)).thenReturn(TEST_ACCOUNT_TYPE);
when(mMockActivity.getString(R.string.account_confirmation_package)).thenReturn(TEST_CONFIRMATION_PACKAGE);
when(mMockActivity.getString(R.string.account_confirmation_class)).thenReturn(TEST_CONFIRMATION_CLASS);
when(mMockActivity.getString(R.string.account_confirmation_package))
.thenReturn(TEST_CONFIRMATION_PACKAGE);
when(mMockActivity.getString(R.string.account_confirmation_class))
.thenReturn(TEST_CONFIRMATION_CLASS);
// Add accounts to trigger the search for a resolving intent.
Account[] accounts = new Account[] { new Account(TEST_ACCOUNT_NAME, TEST_ACCOUNT_TYPE) };
when(mMockActivity.getSystemService(Context.ACCOUNT_SERVICE)).thenReturn(mAccountManager);
@@ -268,10 +353,18 @@ public class MasterClearTest {
resolveInfo.activityInfo = activityInfo;
when(mPackageManager.resolveActivity(any(), eq(0))).thenReturn(resolveInfo);
// Finally mock out the startActivityForResultCall
doNothing().when(mMasterClear).startActivityForResult(any(), eq(MasterClear.CREDENTIAL_CONFIRM_REQUEST));
Intent actualIntent = mMasterClear.getAccountConfirmationIntent();
assertEquals(TEST_CONFIRMATION_PACKAGE, actualIntent.getComponent().getPackageName());
assertEquals(TEST_CONFIRMATION_CLASS, actualIntent.getComponent().getClassName());
}
assertThat(mMasterClear.tryShowAccountConfirmation()).isTrue();
public void testShowAccountCredentialConfirmation() {
// Finally mock out the startActivityForResultCall
doNothing().when(mMasterClear)
.startActivityForResult(eq(mMockIntent), eq(MasterClear.CREDENTIAL_CONFIRM_REQUEST));
mMasterClear.showAccountCredentialConfirmation(mMockIntent);
verify(mMasterClear, times(1))
.startActivityForResult(eq(mMockIntent), eq(MasterClear.CREDENTIAL_CONFIRM_REQUEST));
}
@Test

View File

@@ -27,11 +27,13 @@ import android.os.UserHandle;
import android.provider.SearchIndexableResource;
import android.text.TextUtils;
import com.android.settings.R;
import com.android.settings.TestConfig;
import com.android.settings.dashboard.SummaryLoader;
import com.android.settingslib.accounts.AuthenticatorHelper;
import com.android.settingslib.drawer.CategoryKey;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -41,6 +43,7 @@ import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.Resetter;
import org.robolectric.shadows.ShadowApplication;
import java.util.List;
@@ -57,6 +60,11 @@ public class AccountDashboardFragmentTest {
mFragment = new AccountDashboardFragment();
}
@After
public void tearDown() {
ShadowAuthenticationHelper.reset();
}
@Test
public void testCategory_isAccount() {
assertThat(mFragment.getCategoryKey()).isEqualTo(CategoryKey.CATEGORY_ACCOUNT);
@@ -66,7 +74,8 @@ public class AccountDashboardFragmentTest {
@Config(shadows = {
ShadowAuthenticationHelper.class
})
public void updateSummary_shouldDisplayUpTo3AccountTypes() {
public void updateSummary_hasAccount_shouldDisplayUpTo3AccountTypes() {
ShadowAuthenticationHelper.setHasAccount(true);
final SummaryLoader loader = mock(SummaryLoader.class);
final Activity activity = Robolectric.buildActivity(Activity.class).setup().get();
@@ -77,6 +86,23 @@ public class AccountDashboardFragmentTest {
verify(loader).setSummary(provider, LABELS[0] + ", " + LABELS[1] + ", " + LABELS[2]);
}
@Test
@Config(shadows = {
ShadowAuthenticationHelper.class
})
public void updateSummary_noAccount_shouldDisplayDefaultSummary() {
ShadowAuthenticationHelper.setHasAccount(false);
final SummaryLoader loader = mock(SummaryLoader.class);
final Activity activity = Robolectric.buildActivity(Activity.class).setup().get();
final SummaryLoader.SummaryProvider provider = mFragment.SUMMARY_PROVIDER_FACTORY
.createSummaryProvider(activity, loader);
provider.setListening(true);
verify(loader).setSummary(provider,
activity.getString(R.string.account_dashboard_default_summary));
}
@Test
public void testSearchIndexProvider_shouldIndexResource() {
final List<SearchIndexableResource> indexRes =
@@ -94,15 +120,24 @@ public class AccountDashboardFragmentTest {
static final String[] TYPES = new String[] {"type1", "type2", "type3", "type4"};
static final String[] LABELS = new String[] {"LABEL1", "LABEL2",
"LABEL3", "LABEL4"};
private static boolean sHasAccount = true;
public void __constructor__(Context context, UserHandle userHandle,
AuthenticatorHelper.OnAccountsUpdateListener listener) {
}
public static void setHasAccount(boolean hasAccount) {
sHasAccount = hasAccount;
}
@Resetter
public static void reset() {
sHasAccount = true;
}
@Implementation
public String[] getEnabledAccountTypes() {
return TYPES;
return sHasAccount ? TYPES : null;
}
@Implementation

View File

@@ -1,114 +0,0 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.applications;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.UserInfo;
import android.os.UserManager;
import com.android.settings.R;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
import com.android.settings.dashboard.SummaryLoader;
import com.android.settings.notification.NotificationBackend;
import com.android.settingslib.wrapper.PackageManagerWrapper;
import java.util.List;
import java.util.ArrayList;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowApplication;
import org.robolectric.util.ReflectionHelpers;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class NotificationAppsTest {
@Mock
private PackageManagerWrapper mPackageManager;
@Mock
private UserManager mUserManager;
@Mock
private SummaryLoader mSummaryLoader;
@Mock
private NotificationBackend mBackend;
private Context mContext;
private NotificationApps.SummaryProvider mSummaryProvider;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
ShadowApplication shadowApplication = ShadowApplication.getInstance();
shadowApplication.setSystemService(Context.USER_SERVICE, mUserManager);
mContext = shadowApplication.getApplicationContext();
mSummaryProvider = spy(new NotificationApps.SummaryProvider(mContext, mSummaryLoader));
ReflectionHelpers.setField(mSummaryProvider, "mNotificationBackend", mBackend);
ReflectionHelpers.setField(mSummaryProvider, "mPackageManager", mPackageManager);
}
@Test
public void setListening_shouldSetSummary() {
List<UserInfo> userInfos = new ArrayList<>();
userInfos.add(new UserInfo(1, "user1", 0));
when(mUserManager.getProfiles(anyInt())).thenReturn(userInfos);
List<ApplicationInfo> appInfos = new ArrayList<>();
ApplicationInfo info1 = new ApplicationInfo();
info1.packageName = "package1";
appInfos.add(info1);
ApplicationInfo info2 = new ApplicationInfo();
info2.packageName = "package2";
appInfos.add(info2);
when(mPackageManager.getInstalledApplicationsAsUser(anyInt(), anyInt()))
.thenReturn(appInfos);
// no notification off
when(mBackend.getNotificationsBanned(anyString(), anyInt())).thenReturn(false);
mSummaryProvider.setListening(true);
ShadowApplication.runBackgroundTasks();
verify(mSummaryLoader).setSummary(mSummaryProvider,
mContext.getString(R.string.notification_summary_none));
// some notification off
when(mBackend.getNotificationsBanned(eq("package1"), anyInt())).thenReturn(true);
mSummaryProvider.setListening(true);
ShadowApplication.runBackgroundTasks();
verify(mSummaryLoader).setSummary(mSummaryProvider,
mContext.getResources().getQuantityString(R.plurals.notification_summary, 1, 1));
when(mBackend.getNotificationsBanned(eq("package2"), anyInt())).thenReturn(true);
mSummaryProvider.setListening(true);
ShadowApplication.runBackgroundTasks();
verify(mSummaryLoader).setSummary(mSummaryProvider,
mContext.getResources().getQuantityString(R.plurals.notification_summary, 2, 2));
}
}

View File

@@ -31,6 +31,7 @@ import android.support.v7.preference.PreferenceScreen;
import com.android.settings.TestConfig;
import com.android.settings.bluetooth.ConnectedBluetoothDeviceUpdater;
import com.android.settings.connecteddevice.usb.ConnectedUsbDeviceUpdater;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settingslib.core.lifecycle.Lifecycle;

View File

@@ -13,18 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.settings.connecteddevice;
package com.android.settings.connecteddevice.usb;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import com.android.settings.R;
import com.android.settings.TestConfig;
import com.android.settings.deviceinfo.UsbBackend;
import com.android.settings.connecteddevice.DevicePreferenceCallback;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import org.junit.Before;
@@ -41,6 +43,8 @@ public class ConnectedUsbDeviceUpdaterTest {
private Context mContext;
private ConnectedUsbDeviceUpdater mDeviceUpdater;
@Mock
private DashboardFragment mFragment;
@Mock
private UsbConnectionBroadcastReceiver mUsbReceiver;
@Mock
@@ -53,7 +57,8 @@ public class ConnectedUsbDeviceUpdaterTest {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mDeviceUpdater = new ConnectedUsbDeviceUpdater(mContext, mDevicePreferenceCallback,
when(mFragment.getContext()).thenReturn(mContext);
mDeviceUpdater = new ConnectedUsbDeviceUpdater(mFragment, mDevicePreferenceCallback,
mUsbBackend);
mDeviceUpdater.mUsbReceiver = mUsbReceiver;
}
@@ -70,18 +75,18 @@ public class ConnectedUsbDeviceUpdaterTest {
@Test
public void testInitUsbPreference_usbConnected_preferenceAdded() {
doReturn(true).when(mUsbReceiver).isConnected();
mDeviceUpdater.initUsbPreference(mContext);
mDeviceUpdater.mUsbConnectionListener.onUsbConnectionChanged(true /* connected */,
UsbBackend.MODE_DATA_NONE);
verify(mDevicePreferenceCallback).onDeviceAdded(mDeviceUpdater.mUsbPreference);
}
@Test
public void testInitUsbPreference_usbDisconnected_preferenceRemoved() {
doReturn(false).when(mUsbReceiver).isConnected();
mDeviceUpdater.initUsbPreference(mContext);
mDeviceUpdater.mUsbConnectionListener.onUsbConnectionChanged(false /* connected */,
UsbBackend.MODE_DATA_NONE);
verify(mDevicePreferenceCallback).onDeviceRemoved(mDeviceUpdater.mUsbPreference);
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.android.settings.deviceinfo;
package com.android.settings.connecteddevice.usb;
import static org.mockito.Answers.RETURNS_DEEP_STUBS;
import static org.mockito.Matchers.argThat;
@@ -25,7 +25,9 @@ import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.usb.UsbManager;
import android.net.ConnectivityManager;
import com.android.settings.connecteddevice.usb.UsbBackend;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
@@ -46,6 +48,8 @@ public class UsbBackendTest {
private UsbManager mUsbManager;
@Mock
private UsbBackend.UserRestrictionUtil mUserRestrictionUtil;
@Mock
private ConnectivityManager mConnectivityManager;
@Before
public void setUp() {
@@ -53,22 +57,13 @@ public class UsbBackendTest {
when(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI))
.thenReturn(true);
when((Object)mContext.getSystemService(UsbManager.class)).thenReturn(mUsbManager);
when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE))
.thenReturn((Object) mConnectivityManager);
}
@Test
public void constructor_noUsbPort_shouldNotCrash() {
UsbBackend usbBackend = new UsbBackend(mContext, mUserRestrictionUtil);
UsbBackend usbBackend = new UsbBackend(mContext, mUserRestrictionUtil, null);
// Should not crash
}
@Test
public void getCurrentMode_shouldRegisterReceiverToGetUsbState() {
UsbBackend usbBackend = new UsbBackend(mContext, mUserRestrictionUtil);
usbBackend.getCurrentMode();
verify(mContext).registerReceiver(eq(null),
argThat(intentFilter -> intentFilter != null &&
UsbManager.ACTION_USB_STATE.equals(intentFilter.getAction(0))));
}
}

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.settings.connecteddevice;
package com.android.settings.connecteddevice.usb;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -52,6 +52,8 @@ public class UsbConnectionBroadcastReceiverTest {
@Mock
private UsbConnectionBroadcastReceiver.UsbConnectionListener mListener;
@Mock
private UsbBackend mUsbBackend;
@Before
public void setUp() {
@@ -59,27 +61,42 @@ public class UsbConnectionBroadcastReceiverTest {
mShadowApplication = ShadowApplication.getInstance();
mContext = RuntimeEnvironment.application;
mReceiver = new UsbConnectionBroadcastReceiver(mContext, mListener);
mReceiver = new UsbConnectionBroadcastReceiver(mContext, mListener, mUsbBackend);
}
@Test
public void testOnReceive_usbConnected_invokeCallback() {
final Intent intent = new Intent();
intent.setAction(UsbManager.ACTION_USB_STATE);
intent.putExtra(UsbManager.USB_CONNECTED, true);
mReceiver.onReceive(mContext, intent);
verify(mListener).onUsbConnectionChanged(true);
verify(mListener).onUsbConnectionChanged(true /* connected */, UsbBackend.MODE_DATA_NONE);
}
@Test
public void testOnReceive_usbDisconnected_invokeCallback() {
final Intent intent = new Intent();
intent.setAction(UsbManager.ACTION_USB_STATE);
intent.putExtra(UsbManager.USB_CONNECTED, false);
mReceiver.onReceive(mContext, intent);
verify(mListener).onUsbConnectionChanged(false);
verify(mListener).onUsbConnectionChanged(false /* connected */, UsbBackend.MODE_DATA_NONE);
}
@Test
public void testOnReceive_usbConnectedMtpEnabled_invokeCallback() {
final Intent intent = new Intent();
intent.setAction(UsbManager.ACTION_USB_STATE);
intent.putExtra(UsbManager.USB_CONNECTED, true);
intent.putExtra(UsbManager.USB_FUNCTION_MTP, true);
intent.putExtra(UsbManager.USB_DATA_UNLOCKED, true);
mReceiver.onReceive(mContext, intent);
verify(mListener).onUsbConnectionChanged(true /* connected */, UsbBackend.MODE_DATA_MTP);
}
@Test

View File

@@ -0,0 +1,122 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.connecteddevice.usb;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Activity;
import android.arch.lifecycle.LifecycleOwner;
import android.content.Context;
import android.support.v7.preference.PreferenceManager;
import android.support.v7.preference.PreferenceScreen;
import android.support.v14.preference.PreferenceFragment;
import com.android.settings.R;
import com.android.settings.TestConfig;
import com.android.settings.applications.LayoutPreference;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.testutils.shadow.SettingsShadowResources;
import com.android.settings.testutils.shadow.ShadowEntityHeaderController;
import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.core.lifecycle.Lifecycle;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
shadows = {ShadowEntityHeaderController.class, SettingsShadowResources.class})
public class UsbDetailsHeaderControllerTest {
private UsbDetailsHeaderController mDetailsHeaderController;
private Context mContext;
private Lifecycle mLifecycle;
private LifecycleOwner mLifecycleOwner;
private LayoutPreference mPreference;
private PreferenceManager mPreferenceManager;
private PreferenceScreen mScreen;
@Mock
private UsbBackend mUsbBackend;
@Mock
private PreferenceFragment mFragment;
@Mock
private Activity mActivity;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private EntityHeaderController mHeaderController;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mLifecycleOwner = () -> mLifecycle;
mLifecycle = new Lifecycle(mLifecycleOwner);
mPreferenceManager = new PreferenceManager(mContext);
mScreen = mPreferenceManager.createPreferenceScreen(mContext);
when(mFragment.getActivity()).thenReturn(mActivity);
when(mActivity.getApplicationContext()).thenReturn(mContext);
when(mFragment.getContext()).thenReturn(mContext);
when(mFragment.getPreferenceManager()).thenReturn(mPreferenceManager);
when(mFragment.getPreferenceScreen()).thenReturn(mScreen);
ShadowEntityHeaderController.setUseMock(mHeaderController);
mDetailsHeaderController = new UsbDetailsHeaderController(mContext, mFragment, mUsbBackend);
mPreference = new LayoutPreference(mContext, R.layout.settings_entity_header);
mPreference.setKey(mDetailsHeaderController.getPreferenceKey());
mScreen.addPreference(mPreference);
}
@After
public void tearDown() {
ShadowEntityHeaderController.reset();
}
@Test
public void displayRefresh_charging_shouldSetHeader() {
mDetailsHeaderController.displayPreference(mScreen);
mDetailsHeaderController.refresh(UsbBackend.MODE_DATA_NONE);
verify(mHeaderController).setLabel(mContext.getString(R.string.usb_pref));
verify(mHeaderController).setIcon(mContext.getDrawable(R.drawable.ic_usb));
verify(mHeaderController).setSummary(
mContext.getString(R.string.usb_summary_charging_only));
verify(mHeaderController).done(mActivity, true);
}
@Test
public void displayRefresh_mtp_shouldSetHeader() {
mDetailsHeaderController.displayPreference(mScreen);
mDetailsHeaderController.refresh(UsbBackend.MODE_DATA_MTP);
verify(mHeaderController).setLabel(mContext.getString(R.string.usb_pref));
verify(mHeaderController).setIcon(mContext.getDrawable(R.drawable.ic_usb));
verify(mHeaderController).setSummary(
mContext.getString(R.string.usb_summary_file_transfers));
verify(mHeaderController).done(mActivity, true);
}
}

View File

@@ -0,0 +1,238 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.connecteddevice.usb;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Activity;
import android.content.Context;
import android.hardware.usb.UsbManager;
import android.support.v7.preference.PreferenceCategory;
import android.support.v7.preference.PreferenceManager;
import android.support.v7.preference.PreferenceScreen;
import android.support.v14.preference.PreferenceFragment;
import android.support.v14.preference.SwitchPreference;
import com.android.settings.R;
import com.android.settings.TestConfig;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.testutils.shadow.SettingsShadowResources;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.google.android.collect.Lists;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import java.util.ArrayList;
import java.util.List;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class UsbDetailsProfilesControllerTest {
private UsbDetailsProfilesController mDetailsProfilesController;
private Context mContext;
private Lifecycle mLifecycle;
private PreferenceCategory mPreference;
private PreferenceManager mPreferenceManager;
private PreferenceScreen mScreen;
private List<String> mOptions;
@Mock
private UsbBackend mUsbBackend;
@Mock
private PreferenceFragment mFragment;
@Mock
private Activity mActivity;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mLifecycle = new Lifecycle(() -> mLifecycle);
mPreferenceManager = new PreferenceManager(mContext);
mScreen = mPreferenceManager.createPreferenceScreen(mContext);
when(mFragment.getActivity()).thenReturn(mActivity);
when(mActivity.getApplicationContext()).thenReturn(mContext);
when(mFragment.getContext()).thenReturn(mContext);
when(mFragment.getPreferenceManager()).thenReturn(mPreferenceManager);
when(mFragment.getPreferenceScreen()).thenReturn(mScreen);
mOptions = Lists.newArrayList(UsbManager.USB_FUNCTION_MTP, UsbManager.USB_FUNCTION_PTP,
UsbManager.USB_FUNCTION_MIDI, UsbDetailsProfilesController.KEY_POWER);
mDetailsProfilesController = new UsbDetailsProfilesController(mContext, mFragment,
mUsbBackend, mOptions, "usb_options");
mPreference = new PreferenceCategory(mContext);
mPreference.setKey(mDetailsProfilesController.getPreferenceKey());
mScreen.addPreference(mPreference);
}
@Test
public void testDisplayRefresh_allAllowed_shouldCreateSwitches() {
when(mUsbBackend.isModeSupported(anyInt())).thenReturn(true);
when(mUsbBackend.isModeDisallowed(anyInt())).thenReturn(false);
when(mUsbBackend.isModeDisallowedBySystem(anyInt())).thenReturn(false);
mDetailsProfilesController.displayPreference(mScreen);
mDetailsProfilesController.refresh(UsbBackend.MODE_DATA_NONE);
List<SwitchPreference> switches = getProfileSwitches();
for (int i = 0; i < switches.size(); i++) {
assertThat(switches.get(i).getKey().equals(mOptions.get(i)));
}
}
@Test
public void testDisplayRefresh_onlyMidiAllowed_shouldCreateOnlyMidiSwitch() {
when(mUsbBackend.isModeSupported(anyInt())).thenReturn(true);
when(mUsbBackend.isModeDisallowed(anyInt())).thenReturn(false);
when(mUsbBackend.isModeDisallowedBySystem(UsbBackend.MODE_DATA_MIDI)).thenReturn(false);
when(mUsbBackend.isModeDisallowedBySystem(UsbBackend.MODE_DATA_MTP)).thenReturn(true);
when(mUsbBackend.isModeDisallowedBySystem(UsbBackend.MODE_DATA_PTP)).thenReturn(true);
when(mUsbBackend.isModeDisallowedBySystem(UsbBackend.MODE_POWER_SOURCE)).thenReturn(true);
mDetailsProfilesController.displayPreference(mScreen);
mDetailsProfilesController.refresh(UsbBackend.MODE_DATA_NONE);
List<SwitchPreference> switches = getProfileSwitches();
assertThat(switches.size()).isEqualTo(1);
assertThat(switches.get(0).getKey()).isEqualTo(UsbManager.USB_FUNCTION_MIDI);
}
@Test
public void testDisplayRefresh_mtpEnabled_shouldCheckSwitches() {
when(mUsbBackend.isModeSupported(anyInt())).thenReturn(true);
when(mUsbBackend.isModeDisallowed(anyInt())).thenReturn(false);
when(mUsbBackend.isModeDisallowedBySystem(anyInt())).thenReturn(false);
mDetailsProfilesController.displayPreference(mScreen);
mDetailsProfilesController.refresh(UsbBackend.MODE_DATA_MTP);
List<SwitchPreference> switches = getProfileSwitches();
assertThat(switches.get(0).getKey().equals(UsbManager.USB_FUNCTION_MTP));
assertThat(switches.get(0).isChecked());
}
@Test
public void testDisplayRefresh_mtpSupplyPowerEnabled_shouldCheckSwitches() {
when(mUsbBackend.isModeSupported(anyInt())).thenReturn(true);
when(mUsbBackend.isModeDisallowed(anyInt())).thenReturn(false);
when(mUsbBackend.isModeDisallowedBySystem(anyInt())).thenReturn(false);
mDetailsProfilesController.displayPreference(mScreen);
mDetailsProfilesController.refresh(UsbBackend.MODE_DATA_MTP | UsbBackend.MODE_POWER_SOURCE);
List<SwitchPreference> switches = getProfileSwitches();
assertThat(switches.get(0).getKey()).isEqualTo(UsbManager.USB_FUNCTION_MTP);
assertThat(switches.get(0).isChecked());
assertThat(switches.get(3).getKey()).isEqualTo(UsbDetailsProfilesController.KEY_POWER);
assertThat(switches.get(3).isChecked());
}
@Test
public void testOnClickMtp_noneEnabled_shouldEnableMtp() {
when(mUsbBackend.isModeSupported(anyInt())).thenReturn(true);
when(mUsbBackend.isModeDisallowed(anyInt())).thenReturn(false);
when(mUsbBackend.isModeDisallowedBySystem(anyInt())).thenReturn(false);
mDetailsProfilesController.displayPreference(mScreen);
mDetailsProfilesController.refresh(UsbBackend.MODE_DATA_NONE);
List<SwitchPreference> switches = getProfileSwitches();
switches.get(0).performClick();
assertThat(switches.get(0).getKey()).isEqualTo(UsbManager.USB_FUNCTION_MTP);
verify(mUsbBackend).setMode(UsbBackend.MODE_DATA_MTP);
assertThat(switches.get(0).isChecked());
}
@Test
public void testOnClickMtp_supplyingPowerEnabled_shouldEnableBoth() {
when(mUsbBackend.isModeSupported(anyInt())).thenReturn(true);
when(mUsbBackend.isModeDisallowed(anyInt())).thenReturn(false);
when(mUsbBackend.isModeDisallowedBySystem(anyInt())).thenReturn(false);
mDetailsProfilesController.displayPreference(mScreen);
mDetailsProfilesController.refresh(UsbBackend.MODE_POWER_SOURCE);
when(mUsbBackend.getCurrentMode()).thenReturn(UsbBackend.MODE_POWER_SOURCE);
List<SwitchPreference> switches = getProfileSwitches();
switches.get(0).performClick();
assertThat(switches.get(0).getKey()).isEqualTo(UsbManager.USB_FUNCTION_MTP);
verify(mUsbBackend).setMode(UsbBackend.MODE_DATA_MTP | UsbBackend.MODE_POWER_SOURCE);
assertThat(switches.get(0).isChecked());
assertThat(switches.get(3).getKey()).isEqualTo(UsbDetailsProfilesController.KEY_POWER);
assertThat(switches.get(3).isChecked());
}
@Test
public void testOnClickMtp_ptpEnabled_shouldEnableMtpOnly() {
when(mUsbBackend.isModeSupported(anyInt())).thenReturn(true);
when(mUsbBackend.isModeDisallowed(anyInt())).thenReturn(false);
when(mUsbBackend.isModeDisallowedBySystem(anyInt())).thenReturn(false);
mDetailsProfilesController.displayPreference(mScreen);
mDetailsProfilesController.refresh(UsbBackend.MODE_DATA_PTP);
when(mUsbBackend.getCurrentMode()).thenReturn(UsbBackend.MODE_DATA_PTP);
List<SwitchPreference> switches = getProfileSwitches();
switches.get(0).performClick();
assertThat(switches.get(0).getKey()).isEqualTo(UsbManager.USB_FUNCTION_MTP);
verify(mUsbBackend).setMode(UsbBackend.MODE_DATA_MTP);
assertThat(switches.get(0).isChecked());
assertThat(switches.get(1).getKey()).isEqualTo(UsbManager.USB_FUNCTION_PTP);
assertThat(!switches.get(1).isChecked());
}
@Test
public void testOnClickMtp_mtpEnabled_shouldDisableMtp() {
when(mUsbBackend.isModeSupported(anyInt())).thenReturn(true);
when(mUsbBackend.isModeDisallowed(anyInt())).thenReturn(false);
when(mUsbBackend.isModeDisallowedBySystem(anyInt())).thenReturn(false);
mDetailsProfilesController.displayPreference(mScreen);
mDetailsProfilesController.refresh(UsbBackend.MODE_DATA_MTP);
when(mUsbBackend.getCurrentMode()).thenReturn(UsbBackend.MODE_DATA_MTP);
List<SwitchPreference> switches = getProfileSwitches();
switches.get(0).performClick();
assertThat(switches.get(0).getKey()).isEqualTo(UsbManager.USB_FUNCTION_MTP);
verify(mUsbBackend).setMode(UsbBackend.MODE_DATA_NONE);
assertThat(!switches.get(0).isChecked());
}
private List<SwitchPreference> getProfileSwitches() {
ArrayList<SwitchPreference> result = new ArrayList<>();
for (int i = 0; i < mPreference.getPreferenceCount(); i++) {
result.add((SwitchPreference) mPreference.getPreference(i));
}
return result;
}
}

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.deviceinfo;
package com.android.settings.connecteddevice.usb;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Matchers.anyInt;
@@ -22,6 +22,7 @@ import static org.mockito.Mockito.verify;
import android.widget.TextView;
import com.android.settings.R;
import com.android.settings.connecteddevice.usb.UsbModeChooserActivity;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
import org.junit.Before;

View File

@@ -1,16 +1,12 @@
package com.android.settings.connecteddevice;
package com.android.settings.connecteddevice.usb;
import android.content.Context;
import android.content.Intent;
import android.hardware.usb.UsbManager;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
import com.android.settings.deviceinfo.UsbBackend;
import com.android.settings.deviceinfo.UsbModeChooserActivity;
import org.junit.Before;
import org.junit.Test;
@@ -24,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Answers.RETURNS_DEEP_STUBS;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.verify;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
@@ -33,6 +30,8 @@ public class UsbModePreferenceControllerTest {
private UsbBackend mUsbBackend;
@Mock(answer = RETURNS_DEEP_STUBS)
private PreferenceScreen mScreen;
@Mock
private UsbConnectionBroadcastReceiver mUsbConnectionBroadcastReceiver;
private Context mContext;
private UsbModePreferenceController mController;
@@ -42,61 +41,67 @@ public class UsbModePreferenceControllerTest {
MockitoAnnotations.initMocks(this);
mContext = ShadowApplication.getInstance().getApplicationContext();
mController = new UsbModePreferenceController(mContext, mUsbBackend);
mController.mUsbReceiver = mUsbConnectionBroadcastReceiver;
}
@Test
public void testGetSummary_chargeDevice() {
assertThat(mController.getSummary(UsbModeChooserActivity.DEFAULT_MODES[0]))
assertThat(mController.getSummary(0))
.isEqualTo(R.string.usb_summary_charging_only);
}
@Test
public void testGetSummary_supplyPower() {
assertThat(mController.getSummary(UsbModeChooserActivity.DEFAULT_MODES[1]))
assertThat(mController.getSummary(UsbBackend.MODE_POWER_SOURCE))
.isEqualTo(R.string.usb_summary_power_only);
}
@Test
public void testGetSummary_TransferFiles() {
assertThat(mController.getSummary(UsbModeChooserActivity.DEFAULT_MODES[2]))
assertThat(mController.getSummary(UsbBackend.MODE_DATA_MTP))
.isEqualTo(R.string.usb_summary_file_transfers);
}
@Test
public void testGetSummary_TransferPhoto() {
assertThat(mController.getSummary(UsbModeChooserActivity.DEFAULT_MODES[3]))
assertThat(mController.getSummary(UsbBackend.MODE_DATA_PTP))
.isEqualTo(R.string.usb_summary_photo_transfers);
}
@Test
public void testGetSummary_MIDI() {
assertThat(mController.getSummary(UsbModeChooserActivity.DEFAULT_MODES[4]))
assertThat(mController.getSummary(UsbBackend.MODE_DATA_MIDI))
.isEqualTo(R.string.usb_summary_MIDI);
}
@Test
public void testGetSummary_Tethering() {
assertThat(mController.getSummary(UsbBackend.MODE_DATA_TETHER))
.isEqualTo(R.string.usb_summary_tether);
}
@Test
public void testPreferenceSummary_usbDisconnected() {
final Preference preference = new Preference(mContext);
preference.setKey("usb_mode");
preference.setEnabled(true);
when(mUsbBackend.getCurrentMode()).thenReturn(UsbBackend.MODE_POWER_SINK);
when(mUsbConnectionBroadcastReceiver.isConnected()).thenReturn(false);
mController.updateState(preference);
assertThat(preference.getKey()).isEqualTo("usb_mode");
assertThat(preference.getSummary()).isEqualTo(
mContext.getString(R.string.disconnected));
}
@Test
public void testUsbBoradcastReceiver_usbConnected_shouldUpdateSummary() {
public void testUsbBroadcastReceiver_usbConnected_shouldUpdateSummary() {
final Preference preference = new Preference(mContext);
preference.setKey("usb_mode");
preference.setEnabled(true);
when(mUsbBackend.getCurrentMode()).thenReturn(UsbModeChooserActivity.DEFAULT_MODES[0]);
when(mScreen.findPreference("usb_mode")).thenReturn(preference);
mController.displayPreference(mScreen);
mController.onResume();
final Intent intent = new Intent(UsbManager.ACTION_USB_STATE);
intent.putExtra(UsbManager.USB_CONNECTED, true);
mContext.sendStickyBroadcast(intent);
when(mUsbBackend.getCurrentMode()).thenReturn(UsbBackend.MODE_POWER_SINK);
when(mUsbConnectionBroadcastReceiver.isConnected()).thenReturn(true);
mController.updateState(preference);
assertThat(preference.getSummary()).isEqualTo(
mContext.getString(R.string.usb_summary_charging_only));

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2016 The Android Open Source Project
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,10 +16,8 @@
package com.android.settings.dashboard;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -30,14 +28,14 @@ import static org.mockito.Mockito.when;
import android.app.PendingIntent;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.service.settings.suggestions.Suggestion;
import android.support.v7.widget.RecyclerView;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
@@ -47,7 +45,6 @@ import com.android.settings.dashboard.suggestions.SuggestionAdapter;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.testutils.shadow.SettingsShadowResources;
import com.android.settingslib.drawer.DashboardCategory;
import com.android.settingslib.drawer.Tile;
import org.junit.Before;
@@ -58,6 +55,7 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.util.ReflectionHelpers;
import java.util.ArrayList;
import java.util.List;
@@ -81,8 +79,6 @@ public class DashboardAdapterTest {
private Resources mResources;
private FakeFeatureFactory mFactory;
private DashboardAdapter mDashboardAdapter;
private DashboardAdapter.SuggestionAndConditionHeaderHolder mSuggestionHolder;
private DashboardData.SuggestionConditionHeaderData mSuggestionHeaderData;
private List<Condition> mConditionList;
@Before
@@ -98,30 +94,17 @@ public class DashboardAdapterTest {
mConditionList = new ArrayList<>();
mConditionList.add(mCondition);
when(mCondition.shouldShow()).thenReturn(true);
mDashboardAdapter = new DashboardAdapter(mContext, null, mConditionList, null, null);
mSuggestionHeaderData = new DashboardData.SuggestionConditionHeaderData(mConditionList, 1);
mDashboardAdapter = new DashboardAdapter(mContext, null /* savedInstanceState */,
mConditionList, null /* suggestionControllerMixin */, null /* lifecycle */);
when(mView.getTag()).thenReturn(mCondition);
}
@Test
public void testSuggestionsLogs_nullSuggestionsList_shouldNotCrash() {
setupSuggestions(makeSuggestionsV2("pkg1", "pkg2", "pkg3", "pkg4", "pkg5"));
mDashboardAdapter.onBindSuggestionConditionHeader(mSuggestionHolder, mSuggestionHeaderData);
// set suggestions to null
final DashboardData prevData = mDashboardAdapter.mDashboardData;
mDashboardAdapter.mDashboardData = new DashboardData.Builder(prevData)
.setSuggestions(null)
.build();
mSuggestionHolder.itemView.callOnClick();
// no crash
}
@Test
public void testSuggestionDismissed_notOnlySuggestion_updateSuggestionOnly() {
final DashboardAdapter adapter =
spy(new DashboardAdapter(mContext, null, null, null, null));
spy(new DashboardAdapter(mContext, null /* savedInstanceState */,
null /* conditions */, null /* suggestionControllerMixin */, null /*
lifecycle */));
final List<Suggestion> suggestions = makeSuggestionsV2("pkg1", "pkg2", "pkg3");
adapter.setSuggestions(suggestions);
@@ -130,18 +113,18 @@ public class DashboardAdapterTest {
when(data.getContext()).thenReturn(mContext);
when(mResources.getDisplayMetrics()).thenReturn(mock(DisplayMetrics.class));
final View itemView = mock(View.class);
when(itemView.findViewById(R.id.data)).thenReturn(data);
final DashboardAdapter.SuggestionAndConditionContainerHolder holder =
new DashboardAdapter.SuggestionAndConditionContainerHolder(itemView);
when(itemView.findViewById(R.id.suggestion_list)).thenReturn(data);
when(itemView.findViewById(android.R.id.summary)).thenReturn(mock(TextView.class));
final DashboardAdapter.SuggestionContainerHolder holder =
new DashboardAdapter.SuggestionContainerHolder(itemView);
adapter.onBindConditionAndSuggestion(
holder, DashboardAdapter.SUGGESTION_CONDITION_HEADER_POSITION);
adapter.onBindSuggestion(holder, 0);
final DashboardData dashboardData = adapter.mDashboardData;
reset(adapter); // clear interactions tracking
final Suggestion suggestionToRemove = suggestions.get(1);
adapter.onSuggestionDismissed(suggestionToRemove);
adapter.onSuggestionClosed(suggestionToRemove);
assertThat(adapter.mDashboardData).isEqualTo(dashboardData);
assertThat(suggestions.size()).isEqualTo(2);
@@ -150,25 +133,25 @@ public class DashboardAdapterTest {
}
@Test
public void testSuggestionDismissed_moreThanTwoSuggestions_defaultMode_shouldNotCrash() {
public void testSuggestionDismissed_moreThanTwoSuggestions_shouldNotCrash() {
final RecyclerView data = new RecyclerView(RuntimeEnvironment.application);
final View itemView = mock(View.class);
when(itemView.findViewById(R.id.data)).thenReturn(data);
final DashboardAdapter.SuggestionAndConditionContainerHolder holder =
new DashboardAdapter.SuggestionAndConditionContainerHolder(itemView);
when(itemView.findViewById(R.id.suggestion_list)).thenReturn(data);
when(itemView.findViewById(android.R.id.summary)).thenReturn(mock(TextView.class));
final DashboardAdapter.SuggestionContainerHolder holder =
new DashboardAdapter.SuggestionContainerHolder(itemView);
final List<Suggestion> suggestions = makeSuggestionsV2("pkg1", "pkg2", "pkg3", "pkg4");
final DashboardAdapter adapter = spy(new DashboardAdapter(mContext, null /*savedInstance */,
null /* conditions */,
null /* suggestionControllerMixin */, null /* callback */));
final DashboardAdapter adapter = spy(new DashboardAdapter(mContext,
null /*savedInstance */, null /* conditions */,
null /* suggestionControllerMixin */,
null /* lifecycle */));
adapter.setSuggestions(suggestions);
adapter.onBindConditionAndSuggestion(
holder, DashboardAdapter.SUGGESTION_CONDITION_HEADER_POSITION);
// default mode, only displaying 2 suggestions
adapter.onBindSuggestion(holder, 0);
adapter.onSuggestionDismissed(suggestions.get(1));
adapter.onSuggestionClosed(suggestions.get(1));
// verify operations that access the lists will not cause ConcurrentModificationException
assertThat(holder.data.getAdapter().getItemCount()).isEqualTo(1);
assertThat(holder.data.getAdapter().getItemCount()).isEqualTo(3);
adapter.setSuggestions(suggestions);
// should not crash
}
@@ -176,42 +159,25 @@ public class DashboardAdapterTest {
@Test
public void testSuggestionDismissed_onlySuggestion_updateDashboardData() {
DashboardAdapter adapter =
spy(new DashboardAdapter(mContext, null, null, null, null));
spy(new DashboardAdapter(mContext, null /* savedInstanceState */,
null /* conditions */, null /* suggestionControllerMixin */, null /*
lifecycle */));
final List<Suggestion> suggestions = makeSuggestionsV2("pkg1");
adapter.setSuggestions(suggestions);
final DashboardData dashboardData = adapter.mDashboardData;
reset(adapter); // clear interactions tracking
adapter.onSuggestionDismissed(suggestions.get(0));
adapter.onSuggestionClosed(suggestions.get(0));
assertThat(adapter.mDashboardData).isNotEqualTo(dashboardData);
verify(adapter).notifyDashboardDataChanged(any());
}
@Test
public void testSetCategories_iconTinted() {
TypedArray mockTypedArray = mock(TypedArray.class);
doReturn(mockTypedArray).when(mContext).obtainStyledAttributes(any(int[].class));
doReturn(0x89000000).when(mockTypedArray).getColor(anyInt(), anyInt());
final DashboardCategory category = new DashboardCategory();
final Icon mockIcon = mock(Icon.class);
final Tile tile = new Tile();
tile.isIconTintable = true;
tile.icon = mockIcon;
category.addTile(tile);
mDashboardAdapter.setCategory(category);
verify(mockIcon).setTint(eq(0x89000000));
}
@Test
public void testBindConditionAndSuggestion_v2_shouldSetSuggestionAdapterAndNoCrash() {
mDashboardAdapter = new DashboardAdapter(mContext, null, null, null, null);
public void testBindSuggestion_shouldSetSuggestionAdapterAndNoCrash() {
mDashboardAdapter = new DashboardAdapter(mContext, null /* savedInstanceState */,
null /* conditions */, null /* suggestionControllerMixin */, null /* lifecycle */);
final List<Suggestion> suggestions = makeSuggestionsV2("pkg1");
final DashboardCategory category = new DashboardCategory();
category.addTile(mock(Tile.class));
mDashboardAdapter.setSuggestions(suggestions);
@@ -220,17 +186,88 @@ public class DashboardAdapterTest {
when(data.getContext()).thenReturn(mContext);
when(mResources.getDisplayMetrics()).thenReturn(mock(DisplayMetrics.class));
final View itemView = mock(View.class);
when(itemView.findViewById(R.id.data)).thenReturn(data);
final DashboardAdapter.SuggestionAndConditionContainerHolder holder =
new DashboardAdapter.SuggestionAndConditionContainerHolder(itemView);
when(itemView.findViewById(R.id.suggestion_list)).thenReturn(data);
when(itemView.findViewById(android.R.id.summary)).thenReturn(mock(TextView.class));
final DashboardAdapter.SuggestionContainerHolder holder =
new DashboardAdapter.SuggestionContainerHolder(itemView);
mDashboardAdapter.onBindConditionAndSuggestion(
holder, DashboardAdapter.SUGGESTION_CONDITION_HEADER_POSITION);
mDashboardAdapter.onBindSuggestion(holder, 0);
verify(data).setAdapter(any(SuggestionAdapter.class));
// should not crash
}
@Test
public void testBindSuggestion_shouldSetSummary() {
mDashboardAdapter = new DashboardAdapter(mContext, null /* savedInstanceState */,
null /* conditions */, null /* suggestionControllerMixin */, null /* lifecycle */);
final List<Suggestion> suggestions = makeSuggestionsV2("pkg1");
mDashboardAdapter.setSuggestions(suggestions);
final RecyclerView data = mock(RecyclerView.class);
when(data.getResources()).thenReturn(mResources);
when(data.getContext()).thenReturn(mContext);
when(mResources.getDisplayMetrics()).thenReturn(mock(DisplayMetrics.class));
final View itemView = mock(View.class);
when(itemView.findViewById(R.id.suggestion_list)).thenReturn(data);
final TextView summary = mock(TextView.class);
when(itemView.findViewById(android.R.id.summary)).thenReturn(summary);
final DashboardAdapter.SuggestionContainerHolder holder =
new DashboardAdapter.SuggestionContainerHolder(itemView);
mDashboardAdapter.onBindSuggestion(holder, 0);
verify(summary).setText("1");
suggestions.addAll(makeSuggestionsV2("pkg2", "pkg3", "pkg4"));
mDashboardAdapter.setSuggestions(suggestions);
mDashboardAdapter.onBindSuggestion(holder, 0);
verify(summary).setText("4");
}
@Test
public void onBindTile_internalTile_shouldNotUseGenericBackgroundIcon() {
final Context context = RuntimeEnvironment.application;
final View view = LayoutInflater.from(context).inflate(R.layout.dashboard_tile, null);
final DashboardAdapter.DashboardItemHolder holder =
new DashboardAdapter.DashboardItemHolder(view);
final Tile tile = new Tile();
tile.icon = Icon.createWithResource(context, R.drawable.ic_settings);
final DashboardAdapter.IconCache iconCache = mock(DashboardAdapter.IconCache.class);
when(iconCache.getIcon(tile.icon)).thenReturn(context.getDrawable(R.drawable.ic_settings));
mDashboardAdapter = new DashboardAdapter(context, null /* savedInstanceState */,
null /* conditions */, null /* suggestionControllerMixin */, null /* lifecycle */);
ReflectionHelpers.setField(mDashboardAdapter, "mCache", iconCache);
mDashboardAdapter.onBindTile(holder, tile);
verify(iconCache, never()).updateIcon(any(Icon.class), any(Drawable.class));
}
@Test
public void onBindTile_externalTile_shouldNotUseGenericBackgroundIcon() {
final Context context = RuntimeEnvironment.application;
final View view = LayoutInflater.from(context).inflate(R.layout.dashboard_tile, null);
final DashboardAdapter.DashboardItemHolder holder =
new DashboardAdapter.DashboardItemHolder(view);
final Tile tile = new Tile();
tile.icon = mock(Icon.class);
when(tile.icon.getResPackage()).thenReturn("another.package");
final DashboardAdapter.IconCache iconCache = mock(DashboardAdapter.IconCache.class);
when(iconCache.getIcon(tile.icon)).thenReturn(context.getDrawable(R.drawable.ic_settings));
mDashboardAdapter = new DashboardAdapter(context, null /* savedInstanceState */,
null /* conditions */, null /* suggestionControllerMixin */, null /* lifecycle */);
ReflectionHelpers.setField(mDashboardAdapter, "mCache", iconCache);
mDashboardAdapter.onBindTile(holder, tile);
verify(iconCache).updateIcon(eq(tile.icon), any(RoundedHomepageIcon.class));
}
private List<Suggestion> makeSuggestionsV2(String... pkgNames) {
final List<Suggestion> suggestions = new ArrayList<>();
for (String pkgName : pkgNames) {
@@ -245,8 +282,5 @@ public class DashboardAdapterTest {
private void setupSuggestions(List<Suggestion> suggestions) {
final Context context = RuntimeEnvironment.application;
mDashboardAdapter.setSuggestions(suggestions);
mSuggestionHolder = new DashboardAdapter.SuggestionAndConditionHeaderHolder(
LayoutInflater.from(context).inflate(
R.layout.suggestion_condition_header, new RelativeLayout(context), true));
}
}

View File

@@ -1,286 +0,0 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.dashboard;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.PendingIntent;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.service.settings.suggestions.Suggestion;
import android.support.v7.widget.RecyclerView;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.TestConfig;
import com.android.settings.dashboard.conditional.Condition;
import com.android.settings.dashboard.suggestions.SuggestionAdapterV2;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.testutils.shadow.SettingsShadowResources;
import com.android.settingslib.drawer.Tile;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.util.ReflectionHelpers;
import java.util.ArrayList;
import java.util.List;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH,
sdk = TestConfig.SDK_VERSION,
shadows = {
SettingsShadowResources.class,
SettingsShadowResources.SettingsShadowTheme.class,
})
public class DashboardAdapterV2Test {
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private SettingsActivity mContext;
@Mock
private View mView;
@Mock
private Condition mCondition;
@Mock
private Resources mResources;
private FakeFeatureFactory mFactory;
private DashboardAdapterV2 mDashboardAdapter;
private List<Condition> mConditionList;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mFactory = FakeFeatureFactory.setupForTest();
when(mFactory.dashboardFeatureProvider.shouldTintIcon()).thenReturn(true);
when(mContext.getResources()).thenReturn(mResources);
when(mResources.getQuantityString(any(int.class), any(int.class), any()))
.thenReturn("");
mConditionList = new ArrayList<>();
mConditionList.add(mCondition);
when(mCondition.shouldShow()).thenReturn(true);
mDashboardAdapter = new DashboardAdapterV2(mContext, null /* savedInstanceState */,
mConditionList, null /* suggestionControllerMixin */, null /* lifecycle */);
when(mView.getTag()).thenReturn(mCondition);
}
@Test
public void testSuggestionDismissed_notOnlySuggestion_updateSuggestionOnly() {
final DashboardAdapterV2 adapter =
spy(new DashboardAdapterV2(mContext, null /* savedInstanceState */,
null /* conditions */, null /* suggestionControllerMixin */, null /*
lifecycle */));
final List<Suggestion> suggestions = makeSuggestionsV2("pkg1", "pkg2", "pkg3");
adapter.setSuggestions(suggestions);
final RecyclerView data = mock(RecyclerView.class);
when(data.getResources()).thenReturn(mResources);
when(data.getContext()).thenReturn(mContext);
when(mResources.getDisplayMetrics()).thenReturn(mock(DisplayMetrics.class));
final View itemView = mock(View.class);
when(itemView.findViewById(R.id.suggestion_list)).thenReturn(data);
when(itemView.findViewById(android.R.id.summary)).thenReturn(mock(TextView.class));
final DashboardAdapterV2.SuggestionContainerHolder holder =
new DashboardAdapterV2.SuggestionContainerHolder(itemView);
adapter.onBindSuggestion(holder, 0);
final DashboardDataV2 dashboardData = adapter.mDashboardData;
reset(adapter); // clear interactions tracking
final Suggestion suggestionToRemove = suggestions.get(1);
adapter.onSuggestionClosed(suggestionToRemove);
assertThat(adapter.mDashboardData).isEqualTo(dashboardData);
assertThat(suggestions.size()).isEqualTo(2);
assertThat(suggestions.contains(suggestionToRemove)).isFalse();
verify(adapter, never()).notifyDashboardDataChanged(any());
}
@Test
public void testSuggestionDismissed_moreThanTwoSuggestions_shouldNotCrash() {
final RecyclerView data = new RecyclerView(RuntimeEnvironment.application);
final View itemView = mock(View.class);
when(itemView.findViewById(R.id.suggestion_list)).thenReturn(data);
when(itemView.findViewById(android.R.id.summary)).thenReturn(mock(TextView.class));
final DashboardAdapterV2.SuggestionContainerHolder holder =
new DashboardAdapterV2.SuggestionContainerHolder(itemView);
final List<Suggestion> suggestions = makeSuggestionsV2("pkg1", "pkg2", "pkg3", "pkg4");
final DashboardAdapterV2 adapter = spy(new DashboardAdapterV2(mContext,
null /*savedInstance */, null /* conditions */,
null /* suggestionControllerMixin */,
null /* lifecycle */));
adapter.setSuggestions(suggestions);
adapter.onBindSuggestion(holder, 0);
adapter.onSuggestionClosed(suggestions.get(1));
// verify operations that access the lists will not cause ConcurrentModificationException
assertThat(holder.data.getAdapter().getItemCount()).isEqualTo(3);
adapter.setSuggestions(suggestions);
// should not crash
}
@Test
public void testSuggestionDismissed_onlySuggestion_updateDashboardData() {
DashboardAdapterV2 adapter =
spy(new DashboardAdapterV2(mContext, null /* savedInstanceState */,
null /* conditions */, null /* suggestionControllerMixin */, null /*
lifecycle */));
final List<Suggestion> suggestions = makeSuggestionsV2("pkg1");
adapter.setSuggestions(suggestions);
final DashboardDataV2 dashboardData = adapter.mDashboardData;
reset(adapter); // clear interactions tracking
adapter.onSuggestionClosed(suggestions.get(0));
assertThat(adapter.mDashboardData).isNotEqualTo(dashboardData);
verify(adapter).notifyDashboardDataChanged(any());
}
@Test
public void testBindSuggestion_shouldSetSuggestionAdapterAndNoCrash() {
mDashboardAdapter = new DashboardAdapterV2(mContext, null /* savedInstanceState */,
null /* conditions */, null /* suggestionControllerMixin */, null /* lifecycle */);
final List<Suggestion> suggestions = makeSuggestionsV2("pkg1");
mDashboardAdapter.setSuggestions(suggestions);
final RecyclerView data = mock(RecyclerView.class);
when(data.getResources()).thenReturn(mResources);
when(data.getContext()).thenReturn(mContext);
when(mResources.getDisplayMetrics()).thenReturn(mock(DisplayMetrics.class));
final View itemView = mock(View.class);
when(itemView.findViewById(R.id.suggestion_list)).thenReturn(data);
when(itemView.findViewById(android.R.id.summary)).thenReturn(mock(TextView.class));
final DashboardAdapterV2.SuggestionContainerHolder holder =
new DashboardAdapterV2.SuggestionContainerHolder(itemView);
mDashboardAdapter.onBindSuggestion(holder, 0);
verify(data).setAdapter(any(SuggestionAdapterV2.class));
// should not crash
}
@Test
public void testBindSuggestion_shouldSetSummary() {
mDashboardAdapter = new DashboardAdapterV2(mContext, null /* savedInstanceState */,
null /* conditions */, null /* suggestionControllerMixin */, null /* lifecycle */);
final List<Suggestion> suggestions = makeSuggestionsV2("pkg1");
mDashboardAdapter.setSuggestions(suggestions);
final RecyclerView data = mock(RecyclerView.class);
when(data.getResources()).thenReturn(mResources);
when(data.getContext()).thenReturn(mContext);
when(mResources.getDisplayMetrics()).thenReturn(mock(DisplayMetrics.class));
final View itemView = mock(View.class);
when(itemView.findViewById(R.id.suggestion_list)).thenReturn(data);
final TextView summary = mock(TextView.class);
when(itemView.findViewById(android.R.id.summary)).thenReturn(summary);
final DashboardAdapterV2.SuggestionContainerHolder holder =
new DashboardAdapterV2.SuggestionContainerHolder(itemView);
mDashboardAdapter.onBindSuggestion(holder, 0);
verify(summary).setText("1");
suggestions.addAll(makeSuggestionsV2("pkg2", "pkg3", "pkg4"));
mDashboardAdapter.setSuggestions(suggestions);
mDashboardAdapter.onBindSuggestion(holder, 0);
verify(summary).setText("4");
}
@Test
public void onBindTile_internalTile_shouldNotUseGenericBackgroundIcon() {
final Context context = RuntimeEnvironment.application;
final View view = LayoutInflater.from(context).inflate(R.layout.dashboard_tile, null);
final DashboardAdapterV2.DashboardItemHolder holder =
new DashboardAdapterV2.DashboardItemHolder(view);
final Tile tile = new Tile();
tile.icon = Icon.createWithResource(context, R.drawable.ic_settings);
final DashboardAdapterV2.IconCache iconCache = mock(DashboardAdapterV2.IconCache.class);
when(iconCache.getIcon(tile.icon)).thenReturn(context.getDrawable(R.drawable.ic_settings));
mDashboardAdapter = new DashboardAdapterV2(context, null /* savedInstanceState */,
null /* conditions */, null /* suggestionControllerMixin */, null /* lifecycle */);
ReflectionHelpers.setField(mDashboardAdapter, "mCache", iconCache);
mDashboardAdapter.onBindTile(holder, tile);
verify(iconCache, never()).updateIcon(any(Icon.class), any(Drawable.class));
}
@Test
public void onBindTile_externalTile_shouldNotUseGenericBackgroundIcon() {
final Context context = RuntimeEnvironment.application;
final View view = LayoutInflater.from(context).inflate(R.layout.dashboard_tile, null);
final DashboardAdapterV2.DashboardItemHolder holder =
new DashboardAdapterV2.DashboardItemHolder(view);
final Tile tile = new Tile();
tile.icon = mock(Icon.class);
when(tile.icon.getResPackage()).thenReturn("another.package");
final DashboardAdapterV2.IconCache iconCache = mock(DashboardAdapterV2.IconCache.class);
when(iconCache.getIcon(tile.icon)).thenReturn(context.getDrawable(R.drawable.ic_settings));
mDashboardAdapter = new DashboardAdapterV2(context, null /* savedInstanceState */,
null /* conditions */, null /* suggestionControllerMixin */, null /* lifecycle */);
ReflectionHelpers.setField(mDashboardAdapter, "mCache", iconCache);
mDashboardAdapter.onBindTile(holder, tile);
verify(iconCache).updateIcon(eq(tile.icon), any(RoundedHomepageIcon.class));
}
private List<Suggestion> makeSuggestionsV2(String... pkgNames) {
final List<Suggestion> suggestions = new ArrayList<>();
for (String pkgName : pkgNames) {
final Suggestion suggestion = new Suggestion.Builder(pkgName)
.setPendingIntent(mock(PendingIntent.class))
.build();
suggestions.add(suggestion);
}
return suggestions;
}
private void setupSuggestions(List<Suggestion> suggestions) {
final Context context = RuntimeEnvironment.application;
mDashboardAdapter.setSuggestions(suggestions);
}
}

View File

@@ -17,8 +17,9 @@
package com.android.settings.dashboard;
import static com.android.settings.dashboard.DashboardData.STABLE_ID_CONDITION_CONTAINER;
import static com.android.settings.dashboard.DashboardData.STABLE_ID_SUGGESTION_CONDITION_FOOTER;
import static com.android.settings.dashboard.DashboardData.STABLE_ID_CONDITION_FOOTER;
import static com.android.settings.dashboard.DashboardData.STABLE_ID_SUGGESTION_CONTAINER;
import static com.android.settings.dashboard.DashboardData.STABLE_ID_SUGGESTION_CONDITION_DIVIDER;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -103,14 +104,14 @@ public class DashboardDataTest {
.setConditions(oneItemConditions)
.setCategory(mDashboardCategory)
.setSuggestions(suggestions)
.setSuggestionConditionMode(DashboardData.HEADER_MODE_FULLY_EXPANDED)
.setConditionExpanded(true)
.build();
mDashboardDataWithTwoConditions = new DashboardData.Builder()
.setConditions(twoItemsConditions)
.setCategory(mDashboardCategory)
.setSuggestions(suggestions)
.setSuggestionConditionMode(DashboardData.HEADER_MODE_FULLY_EXPANDED)
.setConditionExpanded(true)
.build();
mDashboardDataWithNoItems = new DashboardData.Builder()
@@ -124,21 +125,23 @@ public class DashboardDataTest {
public void testBuildItemsData_shouldSetstableId() {
final List<DashboardData.Item> items = mDashboardDataWithOneConditions.getItemList();
// Header, suggestion, condition, footer, 1 tile
assertThat(items).hasSize(4);
// suggestion, seperator, condition, footer, 1 tile
assertThat(items).hasSize(5);
assertThat(items.get(0).id).isEqualTo(STABLE_ID_SUGGESTION_CONTAINER);
assertThat(items.get(1).id).isEqualTo(STABLE_ID_CONDITION_CONTAINER);
assertThat(items.get(2).id).isEqualTo(STABLE_ID_SUGGESTION_CONDITION_FOOTER);
assertThat(items.get(3).id).isEqualTo(Objects.hash(mTestCategoryTile.title));
assertThat(items.get(1).id).isEqualTo(STABLE_ID_SUGGESTION_CONDITION_DIVIDER);
assertThat(items.get(2).id).isEqualTo(STABLE_ID_CONDITION_CONTAINER);
assertThat(items.get(3).id).isEqualTo(STABLE_ID_CONDITION_FOOTER);
assertThat(items.get(4).id).isEqualTo(Objects.hash(mTestCategoryTile.title));
}
@Test
public void testBuildItemsData_containsAllData() {
final Object[] expectedObjects = {
mDashboardDataWithOneConditions.getSuggestions(),
null /* divider */,
mDashboardDataWithOneConditions.getConditions(),
null, mTestCategoryTile};
null /* footer */, mTestCategoryTile};
final int expectedSize = expectedObjects.length;
assertThat(mDashboardDataWithOneConditions.getItemList()).hasSize(expectedSize);
@@ -147,14 +150,13 @@ public class DashboardDataTest {
final Object item = mDashboardDataWithOneConditions.getItemEntityByPosition(i);
if (item instanceof List) {
assertThat(item).isEqualTo(expectedObjects[i]);
} else if (item instanceof DashboardData.SuggestionConditionHeaderData) {
DashboardData.SuggestionConditionHeaderData i1 =
(DashboardData.SuggestionConditionHeaderData) item;
DashboardData.SuggestionConditionHeaderData i2 =
(DashboardData.SuggestionConditionHeaderData) expectedObjects[i];
} else if (item instanceof DashboardData.ConditionHeaderData) {
DashboardData.ConditionHeaderData i1 =
(DashboardData.ConditionHeaderData) item;
DashboardData.ConditionHeaderData i2 =
(DashboardData.ConditionHeaderData) expectedObjects[i];
assertThat(i1.title).isEqualTo(i2.title);
assertThat(i1.conditionCount).isEqualTo(i2.conditionCount);
assertThat(i1.hiddenSuggestionCount).isEqualTo(i2.hiddenSuggestionCount);
} else {
assertThat(item).isSameAs(expectedObjects[i]);
}
@@ -209,10 +211,10 @@ public class DashboardDataTest {
public void testDiffUtil_InsertOneCondition_ResultDataOneChanged() {
//Build testResultData
final List<ListUpdateResult.ResultData> testResultData = new ArrayList<>();
// Item in position 2 is the condition container containing the list of conditions, which
// Item in position 3 is the condition container containing the list of conditions, which
// gets 1 more item
testResultData.add(new ListUpdateResult.ResultData(
ListUpdateResult.ResultData.TYPE_OPERATION_CHANGE, 1, 1));
ListUpdateResult.ResultData.TYPE_OPERATION_CHANGE, 2, 1));
testDiffUtil(mDashboardDataWithOneConditions,
mDashboardDataWithTwoConditions, testResultData);
@@ -222,10 +224,11 @@ public class DashboardDataTest {
public void testDiffUtil_RemoveOneSuggestion_causeItemRemoveAndChange() {
//Build testResultData
final List<ListUpdateResult.ResultData> testResultData = new ArrayList<>();
// removed suggestion and the divider
testResultData.add(new ListUpdateResult.ResultData(
ListUpdateResult.ResultData.TYPE_OPERATION_REMOVE, 0, 1));
ListUpdateResult.ResultData.TYPE_OPERATION_REMOVE, 0, 2));
testResultData.add(new ListUpdateResult.ResultData(
ListUpdateResult.ResultData.TYPE_OPERATION_CHANGE, 1, 1));
ListUpdateResult.ResultData.TYPE_OPERATION_CHANGE, 2, 1));
// Build DashboardData
final List<Condition> oneItemConditions = new ArrayList<>();
when(mTestCondition.shouldShow()).thenReturn(true);
@@ -237,13 +240,13 @@ public class DashboardDataTest {
.setConditions(oneItemConditions)
.setCategory(mDashboardCategory)
.setSuggestions(suggestions)
.setSuggestionConditionMode(DashboardData.HEADER_MODE_DEFAULT)
.setConditionExpanded(false)
.build();
final DashboardData newData = new DashboardData.Builder()
.setConditions(oneItemConditions)
.setSuggestions(null)
.setCategory(mDashboardCategory)
.setSuggestionConditionMode(DashboardData.HEADER_MODE_DEFAULT)
.setConditionExpanded(false)
.build();
testDiffUtil(oldData, newData, testResultData);
@@ -254,7 +257,7 @@ public class DashboardDataTest {
//Build testResultData
final List<ListUpdateResult.ResultData> testResultData = new ArrayList<>();
testResultData.add(new ListUpdateResult.ResultData(
ListUpdateResult.ResultData.TYPE_OPERATION_REMOVE, 0, 4));
ListUpdateResult.ResultData.TYPE_OPERATION_REMOVE, 0, 5));
testDiffUtil(mDashboardDataWithOneConditions, mDashboardDataWithNoItems, testResultData);
}

View File

@@ -164,10 +164,4 @@ public class DashboardSummaryTest {
mSummary.onCategoriesChanged();
verify(mSummary).rebuildUI();
}
@Test
public void onSuggestionDismissed_shouldNotRebuildUI() {
mSummary.onSuggestionDismissed(mock(Suggestion.class));
verify(mSummary, never()).rebuildUI();
}
}

View File

@@ -15,6 +15,11 @@
*/
package com.android.settings.dashboard.conditional;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
@@ -22,13 +27,9 @@ import android.view.View;
import android.widget.LinearLayout;
import com.android.settings.R;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
import com.android.settings.dashboard.DashboardAdapter;
import com.android.settings.dashboard.DashboardData;
import java.util.ArrayList;
import java.util.List;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import org.junit.Before;
import org.junit.Test;
@@ -38,9 +39,8 @@ import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.List;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
@@ -70,35 +70,23 @@ public class ConditionAdapterTest {
}
@Test
public void getItemCount_notFullyExpanded_shouldReturn0() {
mConditionAdapter = new ConditionAdapter(
mContext, mOneCondition, DashboardData.HEADER_MODE_DEFAULT);
assertThat(mConditionAdapter.getItemCount()).isEqualTo(0);
mConditionAdapter = new ConditionAdapter(
mContext, mOneCondition, DashboardData.HEADER_MODE_SUGGESTION_EXPANDED);
assertThat(mConditionAdapter.getItemCount()).isEqualTo(0);
mConditionAdapter = new ConditionAdapter(
mContext, mOneCondition, DashboardData.HEADER_MODE_COLLAPSED);
public void getItemCount_notExpanded_shouldReturn0() {
mConditionAdapter = new ConditionAdapter(mContext, mOneCondition, false);
assertThat(mConditionAdapter.getItemCount()).isEqualTo(0);
}
@Test
public void getItemCount_fullyExpanded_shouldReturnListSize() {
mConditionAdapter = new ConditionAdapter(
mContext, mOneCondition, DashboardData.HEADER_MODE_FULLY_EXPANDED);
public void getItemCount_expanded_shouldReturnListSize() {
mConditionAdapter = new ConditionAdapter(mContext, mOneCondition, true);
assertThat(mConditionAdapter.getItemCount()).isEqualTo(1);
mConditionAdapter = new ConditionAdapter(
mContext, mTwoConditions, DashboardData.HEADER_MODE_FULLY_EXPANDED);
mConditionAdapter = new ConditionAdapter(mContext, mTwoConditions, true);
assertThat(mConditionAdapter.getItemCount()).isEqualTo(2);
}
@Test
public void getItemViewType_shouldReturnConditionTile() {
mConditionAdapter = new ConditionAdapter(
mContext, mTwoConditions, DashboardData.HEADER_MODE_FULLY_EXPANDED);
mConditionAdapter = new ConditionAdapter(mContext, mTwoConditions, true);
assertThat(mConditionAdapter.getItemViewType(0)).isEqualTo(R.layout.condition_tile);
}
@@ -108,8 +96,7 @@ public class ConditionAdapterTest {
R.layout.condition_tile, new LinearLayout(mContext), true);
final DashboardAdapter.DashboardItemHolder viewHolder =
new DashboardAdapter.DashboardItemHolder(view);
mConditionAdapter = new ConditionAdapter(
mContext, mOneCondition, DashboardData.HEADER_MODE_SUGGESTION_EXPANDED);
mConditionAdapter = new ConditionAdapter(mContext, mOneCondition, true);
mConditionAdapter.onBindViewHolder(viewHolder, 0);
final View card = view.findViewById(R.id.content);
@@ -122,8 +109,7 @@ public class ConditionAdapterTest {
R.layout.condition_tile, new LinearLayout(mContext), true);
final DashboardAdapter.DashboardItemHolder viewHolder =
new DashboardAdapter.DashboardItemHolder(view);
mConditionAdapter = new ConditionAdapter(
mContext, mOneCondition, DashboardData.HEADER_MODE_SUGGESTION_EXPANDED);
mConditionAdapter = new ConditionAdapter(mContext, mOneCondition, true);
mConditionAdapter.onBindViewHolder(viewHolder, 0);
final View card = view.findViewById(R.id.content);
@@ -137,9 +123,8 @@ public class ConditionAdapterTest {
final View view = LayoutInflater.from(mContext).inflate(
R.layout.condition_tile, new LinearLayout(mContext), true);
final DashboardAdapter.DashboardItemHolder viewHolder =
new DashboardAdapter.DashboardItemHolder(view);
mConditionAdapter = new ConditionAdapter(
mContext, mOneCondition, DashboardData.HEADER_MODE_SUGGESTION_EXPANDED);
new DashboardAdapter.DashboardItemHolder(view);
mConditionAdapter = new ConditionAdapter(mContext, mOneCondition, true);
mConditionAdapter.addDismissHandling(recyclerView);
// do not bind viewholder to simulate the null condition scenario

View File

@@ -1,135 +0,0 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.dashboard.conditional;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import com.android.settings.R;
import com.android.settings.TestConfig;
import com.android.settings.dashboard.DashboardAdapterV2;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import java.util.ArrayList;
import java.util.List;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class ConditionAdapterV2Test {
@Mock
private Condition mCondition1;
@Mock
private Condition mCondition2;
private Context mContext;
private ConditionAdapterV2 mConditionAdapter;
private List<Condition> mOneCondition;
private List<Condition> mTwoConditions;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
final CharSequence[] actions = new CharSequence[2];
when(mCondition1.getActions()).thenReturn(actions);
when(mCondition1.shouldShow()).thenReturn(true);
mOneCondition = new ArrayList<>();
mOneCondition.add(mCondition1);
mTwoConditions = new ArrayList<>();
mTwoConditions.add(mCondition1);
mTwoConditions.add(mCondition2);
}
@Test
public void getItemCount_notExpanded_shouldReturn0() {
mConditionAdapter = new ConditionAdapterV2(mContext, mOneCondition, false);
assertThat(mConditionAdapter.getItemCount()).isEqualTo(0);
}
@Test
public void getItemCount_expanded_shouldReturnListSize() {
mConditionAdapter = new ConditionAdapterV2(mContext, mOneCondition, true);
assertThat(mConditionAdapter.getItemCount()).isEqualTo(1);
mConditionAdapter = new ConditionAdapterV2(mContext, mTwoConditions, true);
assertThat(mConditionAdapter.getItemCount()).isEqualTo(2);
}
@Test
public void getItemViewType_shouldReturnConditionTile() {
mConditionAdapter = new ConditionAdapterV2(mContext, mTwoConditions, true);
assertThat(mConditionAdapter.getItemViewType(0)).isEqualTo(R.layout.condition_tile);
}
@Test
public void onBindViewHolder_shouldSetListener() {
final View view = LayoutInflater.from(mContext).inflate(
R.layout.condition_tile, new LinearLayout(mContext), true);
final DashboardAdapterV2.DashboardItemHolder viewHolder =
new DashboardAdapterV2.DashboardItemHolder(view);
mConditionAdapter = new ConditionAdapterV2(mContext, mOneCondition, true);
mConditionAdapter.onBindViewHolder(viewHolder, 0);
final View card = view.findViewById(R.id.content);
assertThat(card.hasOnClickListeners()).isTrue();
}
@Test
public void viewClick_shouldInvokeConditionPrimaryClick() {
final View view = LayoutInflater.from(mContext).inflate(
R.layout.condition_tile, new LinearLayout(mContext), true);
final DashboardAdapterV2.DashboardItemHolder viewHolder =
new DashboardAdapterV2.DashboardItemHolder(view);
mConditionAdapter = new ConditionAdapterV2(mContext, mOneCondition, true);
mConditionAdapter.onBindViewHolder(viewHolder, 0);
final View card = view.findViewById(R.id.content);
card.performClick();
verify(mCondition1).onPrimaryClick();
}
@Test
public void onSwiped_nullCondition_shouldNotCrash() {
final RecyclerView recyclerView = new RecyclerView(mContext);
final View view = LayoutInflater.from(mContext).inflate(
R.layout.condition_tile, new LinearLayout(mContext), true);
final DashboardAdapterV2.DashboardItemHolder viewHolder =
new DashboardAdapterV2.DashboardItemHolder(view);
mConditionAdapter = new ConditionAdapterV2(mContext, mOneCondition, true);
mConditionAdapter.addDismissHandling(recyclerView);
// do not bind viewholder to simulate the null condition scenario
mConditionAdapter.mSwipeCallback.onSwiped(viewHolder, 0);
// no crash
}
}

View File

@@ -16,12 +16,19 @@
package com.android.settings.dashboard.suggestions;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.PendingIntent;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.service.settings.suggestions.Suggestion;
import android.view.LayoutInflater;
@@ -46,6 +53,7 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.util.ReflectionHelpers;
import java.util.ArrayList;
import java.util.List;
@@ -87,20 +95,21 @@ public class SuggestionAdapterTest {
@Test
public void getItemCount_shouldReturnListSize() {
mSuggestionAdapter = new SuggestionAdapter(mContext, mSuggestionControllerMixin,
mOneSuggestion, new ArrayList<>());
null /* savedInstanceState */, null /* callback */, null /* lifecycle */);
mSuggestionAdapter.setSuggestions(mOneSuggestion);
assertThat(mSuggestionAdapter.getItemCount()).isEqualTo(1);
mSuggestionAdapter = new SuggestionAdapter(mContext, mSuggestionControllerMixin,
mTwoSuggestions, new ArrayList<>());
mSuggestionAdapter.setSuggestions(mTwoSuggestions);
assertThat(mSuggestionAdapter.getItemCount()).isEqualTo(2);
}
@Test
public void getItemViewType_shouldReturnSuggestionTile() {
mSuggestionAdapter = new SuggestionAdapter(mContext, mSuggestionControllerMixin,
mOneSuggestion, new ArrayList<>());
null /* savedInstanceState */, null /* callback */, null /* lifecycle */);
mSuggestionAdapter.setSuggestions(mOneSuggestion);
assertThat(mSuggestionAdapter.getItemViewType(0))
.isEqualTo(R.layout.suggestion_tile);
.isEqualTo(R.layout.suggestion_tile);
}
@Test
@@ -112,19 +121,21 @@ public class SuggestionAdapterTest {
.setSummary("456")
.build());
mSuggestionAdapter = new SuggestionAdapter(mContext, mSuggestionControllerMixin,
suggestions, new ArrayList<>());
null /* savedInstanceState */, null /* callback */, null /* lifecycle */);
mSuggestionAdapter.setSuggestions(suggestions);
assertThat(mSuggestionAdapter.getItemViewType(0))
.isEqualTo(R.layout.suggestion_tile_with_button);
.isEqualTo(R.layout.suggestion_tile_with_button);
}
@Test
public void onBindViewHolder_shouldLog() {
final View view = spy(LayoutInflater.from(mContext).inflate(
R.layout.suggestion_tile, new LinearLayout(mContext), true));
R.layout.suggestion_tile, new LinearLayout(mContext), true));
mSuggestionHolder = new DashboardAdapter.DashboardItemHolder(view);
mSuggestionAdapter = new SuggestionAdapter(mContext, mSuggestionControllerMixin,
mOneSuggestion, new ArrayList<>());
null /* savedInstanceState */, null /* callback */, null /* lifecycle */);
mSuggestionAdapter.setSuggestions(mOneSuggestion);
// Bind twice
mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
@@ -149,6 +160,31 @@ public class SuggestionAdapterTest {
verify(suggestions.get(0).getPendingIntent()).send();
}
@Test
public void onBindViewHolder_hasButton_buttonShouldHandleClick()
throws PendingIntent.CanceledException {
final List<Suggestion> suggestions = new ArrayList<>();
final PendingIntent pendingIntent = mock(PendingIntent.class);
suggestions.add(new Suggestion.Builder("id")
.setFlags(Suggestion.FLAG_HAS_BUTTON)
.setTitle("123")
.setSummary("456")
.setPendingIntent(pendingIntent)
.build());
mSuggestionAdapter = new SuggestionAdapter(mContext, mSuggestionControllerMixin,
null /* savedInstanceState */, null /* callback */, null /* lifecycle */);
mSuggestionAdapter.setSuggestions(suggestions);
mSuggestionHolder = mSuggestionAdapter.onCreateViewHolder(
new FrameLayout(RuntimeEnvironment.application),
mSuggestionAdapter.getItemViewType(0));
mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
mSuggestionHolder.itemView.findViewById(android.R.id.primary).performClick();
verify(mSuggestionControllerMixin).launchSuggestion(suggestions.get(0));
verify(pendingIntent).send();
}
@Test
public void getSuggestions_shouldReturnSuggestionWhenMatch() {
final List<Suggestion> suggestions = makeSuggestions("pkg1");
@@ -157,9 +193,92 @@ public class SuggestionAdapterTest {
assertThat(mSuggestionAdapter.getSuggestion(0)).isNotNull();
}
@Test
public void onBindViewHolder_closeButtonShouldHandleClick()
throws PendingIntent.CanceledException {
final List<Suggestion> suggestions = makeSuggestions("pkg1");
final SuggestionAdapter.Callback callback = mock(SuggestionAdapter.Callback.class);
mSuggestionAdapter = new SuggestionAdapter(mActivity, mSuggestionControllerMixin,
null /* savedInstanceState */, callback, null /* lifecycle */);
mSuggestionAdapter.setSuggestions(suggestions);
mSuggestionHolder = mSuggestionAdapter.onCreateViewHolder(
new FrameLayout(RuntimeEnvironment.application),
mSuggestionAdapter.getItemViewType(0));
mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
mSuggestionHolder.itemView.findViewById(R.id.close_button).performClick();
final Suggestion suggestion = suggestions.get(0);
verify(mFeatureFactory.suggestionsFeatureProvider).dismissSuggestion(
mActivity, mSuggestionControllerMixin, suggestion);
verify(callback).onSuggestionClosed(suggestion);
}
@Test
public void onBindViewHolder_differentPackage_shouldNotTintIcon()
throws PendingIntent.CanceledException {
final Icon icon = mock(Icon.class);
when(icon.getResPackage()).thenReturn("pkg1");
when(mActivity.getPackageName()).thenReturn("pkg2");
final Suggestion suggestion = new Suggestion.Builder("pkg1")
.setPendingIntent(mock(PendingIntent.class))
.setIcon(icon)
.build();
final List<Suggestion> suggestions = new ArrayList<>();
suggestions.add(suggestion);
mSuggestionAdapter = new SuggestionAdapter(mActivity, mSuggestionControllerMixin,
null /* savedInstanceState */, null /* callback */, null /* lifecycle */);
mSuggestionAdapter.setSuggestions(suggestions);
mSuggestionHolder = mSuggestionAdapter.onCreateViewHolder(
new FrameLayout(RuntimeEnvironment.application),
mSuggestionAdapter.getItemViewType(0));
DashboardAdapter.IconCache cache = mock(DashboardAdapter.IconCache.class);
final Drawable drawable = mock(Drawable.class);
when(cache.getIcon(icon)).thenReturn(drawable);
ReflectionHelpers.setField(mSuggestionAdapter, "mCache", cache);
mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
verify(drawable, never()).setTint(anyInt());
}
@Test
public void onBindViewHolder_samePackage_shouldTintIcon()
throws PendingIntent.CanceledException {
final Icon icon = mock(Icon.class);
final String packageName = "pkg1";
when(icon.getResPackage()).thenReturn(packageName);
when(mActivity.getPackageName()).thenReturn(packageName);
final Suggestion suggestion = new Suggestion.Builder(packageName)
.setPendingIntent(mock(PendingIntent.class))
.setIcon(icon)
.build();
final List<Suggestion> suggestions = new ArrayList<>();
suggestions.add(suggestion);
mSuggestionAdapter = new SuggestionAdapter(mActivity, mSuggestionControllerMixin,
null /* savedInstanceState */, null /* callback */, null /* lifecycle */);
mSuggestionAdapter.setSuggestions(suggestions);
mSuggestionHolder = mSuggestionAdapter.onCreateViewHolder(
new FrameLayout(RuntimeEnvironment.application),
mSuggestionAdapter.getItemViewType(0));
DashboardAdapter.IconCache cache = mock(DashboardAdapter.IconCache.class);
final Drawable drawable = mock(Drawable.class);
when(cache.getIcon(icon)).thenReturn(drawable);
ReflectionHelpers.setField(mSuggestionAdapter, "mCache", cache);
TypedArray typedArray = mock(TypedArray.class);
final int colorAccent = 1234;
when(mActivity.obtainStyledAttributes(any())).thenReturn(typedArray);
when(typedArray.getColor(anyInt(), anyInt())).thenReturn(colorAccent);
mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
verify(drawable).setTint(colorAccent);
}
private void setupSuggestions(Context context, List<Suggestion> suggestions) {
mSuggestionAdapter = new SuggestionAdapter(context, mSuggestionControllerMixin,
suggestions, new ArrayList<>());
null /* savedInstanceState */, null /* callback */, null /* lifecycle */);
mSuggestionAdapter.setSuggestions(suggestions);
mSuggestionHolder = mSuggestionAdapter.onCreateViewHolder(
new FrameLayout(RuntimeEnvironment.application),
mSuggestionAdapter.getItemViewType(0));

View File

@@ -1,297 +0,0 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.dashboard.suggestions;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.PendingIntent;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.service.settings.suggestions.Suggestion;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.TestConfig;
import com.android.settings.dashboard.DashboardAdapterV2;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settingslib.suggestions.SuggestionControllerMixin;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.util.ReflectionHelpers;
import java.util.ArrayList;
import java.util.List;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class SuggestionAdapterV2Test {
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private SettingsActivity mActivity;
@Mock
private SuggestionControllerMixin mSuggestionControllerMixin;
private FakeFeatureFactory mFeatureFactory;
private Context mContext;
private SuggestionAdapterV2 mSuggestionAdapter;
private DashboardAdapterV2.DashboardItemHolder mSuggestionHolder;
private List<Suggestion> mOneSuggestion;
private List<Suggestion> mTwoSuggestions;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mFeatureFactory = FakeFeatureFactory.setupForTest();
final Suggestion suggestion1 = new Suggestion.Builder("id1")
.setTitle("Test suggestion 1")
.build();
final Suggestion suggestion2 = new Suggestion.Builder("id2")
.setTitle("Test suggestion 2")
.build();
mOneSuggestion = new ArrayList<>();
mOneSuggestion.add(suggestion1);
mTwoSuggestions = new ArrayList<>();
mTwoSuggestions.add(suggestion1);
mTwoSuggestions.add(suggestion2);
}
@Test
public void getItemCount_shouldReturnListSize() {
mSuggestionAdapter = new SuggestionAdapterV2(mContext, mSuggestionControllerMixin,
null /* savedInstanceState */, null /* callback */, null /* lifecycle */);
mSuggestionAdapter.setSuggestions(mOneSuggestion);
assertThat(mSuggestionAdapter.getItemCount()).isEqualTo(1);
mSuggestionAdapter.setSuggestions(mTwoSuggestions);
assertThat(mSuggestionAdapter.getItemCount()).isEqualTo(2);
}
@Test
public void getItemViewType_shouldReturnSuggestionTile() {
mSuggestionAdapter = new SuggestionAdapterV2(mContext, mSuggestionControllerMixin,
null /* savedInstanceState */, null /* callback */, null /* lifecycle */);
mSuggestionAdapter.setSuggestions(mOneSuggestion);
assertThat(mSuggestionAdapter.getItemViewType(0))
.isEqualTo(R.layout.suggestion_tile_v2);
}
@Test
public void getItemType_hasButton_shouldReturnSuggestionWithButton() {
final List<Suggestion> suggestions = new ArrayList<>();
suggestions.add(new Suggestion.Builder("id")
.setFlags(Suggestion.FLAG_HAS_BUTTON)
.setTitle("123")
.setSummary("456")
.build());
mSuggestionAdapter = new SuggestionAdapterV2(mContext, mSuggestionControllerMixin,
null /* savedInstanceState */, null /* callback */, null /* lifecycle */);
mSuggestionAdapter.setSuggestions(suggestions);
assertThat(mSuggestionAdapter.getItemViewType(0))
.isEqualTo(R.layout.suggestion_tile_with_button_v2);
}
@Test
public void onBindViewHolder_shouldLog() {
final View view = spy(LayoutInflater.from(mContext).inflate(
R.layout.suggestion_tile, new LinearLayout(mContext), true));
mSuggestionHolder = new DashboardAdapterV2.DashboardItemHolder(view);
mSuggestionAdapter = new SuggestionAdapterV2(mContext, mSuggestionControllerMixin,
null /* savedInstanceState */, null /* callback */, null /* lifecycle */);
mSuggestionAdapter.setSuggestions(mOneSuggestion);
// Bind twice
mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
// Log once
verify(mFeatureFactory.metricsFeatureProvider).action(
mContext, MetricsProto.MetricsEvent.ACTION_SHOW_SETTINGS_SUGGESTION,
mOneSuggestion.get(0).getId());
}
@Test
public void onBindViewHolder_itemViewShouldHandleClick()
throws PendingIntent.CanceledException {
final List<Suggestion> suggestions = makeSuggestions("pkg1");
setupSuggestions(mActivity, suggestions);
mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
mSuggestionHolder.itemView.performClick();
verify(mSuggestionControllerMixin).launchSuggestion(suggestions.get(0));
verify(suggestions.get(0).getPendingIntent()).send();
}
@Test
public void onBindViewHolder_hasButton_buttonShouldHandleClick()
throws PendingIntent.CanceledException {
final List<Suggestion> suggestions = new ArrayList<>();
final PendingIntent pendingIntent = mock(PendingIntent.class);
suggestions.add(new Suggestion.Builder("id")
.setFlags(Suggestion.FLAG_HAS_BUTTON)
.setTitle("123")
.setSummary("456")
.setPendingIntent(pendingIntent)
.build());
mSuggestionAdapter = new SuggestionAdapterV2(mContext, mSuggestionControllerMixin,
null /* savedInstanceState */, null /* callback */, null /* lifecycle */);
mSuggestionAdapter.setSuggestions(suggestions);
mSuggestionHolder = mSuggestionAdapter.onCreateViewHolder(
new FrameLayout(RuntimeEnvironment.application),
mSuggestionAdapter.getItemViewType(0));
mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
mSuggestionHolder.itemView.findViewById(android.R.id.primary).performClick();
verify(mSuggestionControllerMixin).launchSuggestion(suggestions.get(0));
verify(pendingIntent).send();
}
@Test
public void getSuggestions_shouldReturnSuggestionWhenMatch() {
final List<Suggestion> suggestions = makeSuggestions("pkg1");
setupSuggestions(mActivity, suggestions);
assertThat(mSuggestionAdapter.getSuggestion(0)).isNotNull();
}
@Test
public void onBindViewHolder_closeButtonShouldHandleClick()
throws PendingIntent.CanceledException {
final List<Suggestion> suggestions = makeSuggestions("pkg1");
final SuggestionAdapterV2.Callback callback = mock(SuggestionAdapterV2.Callback.class);
mSuggestionAdapter = new SuggestionAdapterV2(mActivity, mSuggestionControllerMixin,
null /* savedInstanceState */, callback, null /* lifecycle */);
mSuggestionAdapter.setSuggestions(suggestions);
mSuggestionHolder = mSuggestionAdapter.onCreateViewHolder(
new FrameLayout(RuntimeEnvironment.application),
mSuggestionAdapter.getItemViewType(0));
mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
mSuggestionHolder.itemView.findViewById(R.id.close_button).performClick();
final Suggestion suggestion = suggestions.get(0);
verify(mFeatureFactory.suggestionsFeatureProvider).dismissSuggestion(
mActivity, mSuggestionControllerMixin, suggestion);
verify(callback).onSuggestionClosed(suggestion);
}
@Test
public void onBindViewHolder_differentPackage_shouldNotTintIcon()
throws PendingIntent.CanceledException {
final Icon icon = mock(Icon.class);
when(icon.getResPackage()).thenReturn("pkg1");
when(mActivity.getPackageName()).thenReturn("pkg2");
final Suggestion suggestion = new Suggestion.Builder("pkg1")
.setPendingIntent(mock(PendingIntent.class))
.setIcon(icon)
.build();
final List<Suggestion> suggestions = new ArrayList<>();
suggestions.add(suggestion);
mSuggestionAdapter = new SuggestionAdapterV2(mActivity, mSuggestionControllerMixin,
null /* savedInstanceState */, null /* callback */, null /* lifecycle */);
mSuggestionAdapter.setSuggestions(suggestions);
mSuggestionHolder = mSuggestionAdapter.onCreateViewHolder(
new FrameLayout(RuntimeEnvironment.application),
mSuggestionAdapter.getItemViewType(0));
DashboardAdapterV2.IconCache cache = mock(DashboardAdapterV2.IconCache.class);
final Drawable drawable = mock(Drawable.class);
when(cache.getIcon(icon)).thenReturn(drawable);
ReflectionHelpers.setField(mSuggestionAdapter, "mCache", cache);
mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
verify(drawable, never()).setTint(anyInt());
}
@Test
public void onBindViewHolder_samePackage_shouldTintIcon()
throws PendingIntent.CanceledException {
final Icon icon = mock(Icon.class);
final String packageName = "pkg1";
when(icon.getResPackage()).thenReturn(packageName);
when(mActivity.getPackageName()).thenReturn(packageName);
final Suggestion suggestion = new Suggestion.Builder(packageName)
.setPendingIntent(mock(PendingIntent.class))
.setIcon(icon)
.build();
final List<Suggestion> suggestions = new ArrayList<>();
suggestions.add(suggestion);
mSuggestionAdapter = new SuggestionAdapterV2(mActivity, mSuggestionControllerMixin,
null /* savedInstanceState */, null /* callback */, null /* lifecycle */);
mSuggestionAdapter.setSuggestions(suggestions);
mSuggestionHolder = mSuggestionAdapter.onCreateViewHolder(
new FrameLayout(RuntimeEnvironment.application),
mSuggestionAdapter.getItemViewType(0));
DashboardAdapterV2.IconCache cache = mock(DashboardAdapterV2.IconCache.class);
final Drawable drawable = mock(Drawable.class);
when(cache.getIcon(icon)).thenReturn(drawable);
ReflectionHelpers.setField(mSuggestionAdapter, "mCache", cache);
TypedArray typedArray = mock(TypedArray.class);
final int colorAccent = 1234;
when(mActivity.obtainStyledAttributes(any())).thenReturn(typedArray);
when(typedArray.getColor(anyInt(), anyInt())).thenReturn(colorAccent);
mSuggestionAdapter.onBindViewHolder(mSuggestionHolder, 0);
verify(drawable).setTint(colorAccent);
}
private void setupSuggestions(Context context, List<Suggestion> suggestions) {
mSuggestionAdapter = new SuggestionAdapterV2(context, mSuggestionControllerMixin,
null /* savedInstanceState */, null /* callback */, null /* lifecycle */);
mSuggestionAdapter.setSuggestions(suggestions);
mSuggestionHolder = mSuggestionAdapter.onCreateViewHolder(
new FrameLayout(RuntimeEnvironment.application),
mSuggestionAdapter.getItemViewType(0));
}
private List<Suggestion> makeSuggestions(String... pkgNames) {
final List<Suggestion> suggestions = new ArrayList<>();
for (String pkgName : pkgNames) {
final Suggestion suggestion = new Suggestion.Builder(pkgName)
.setPendingIntent(mock(PendingIntent.class))
.build();
suggestions.add(suggestion);
}
return suggestions;
}
}

View File

@@ -1,117 +0,0 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.dashboard.suggestions;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.service.settings.suggestions.Suggestion;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import com.android.settings.R;
import com.android.settings.TestConfig;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settingslib.suggestions.SuggestionControllerMixin;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class SuggestionDismissControllerTest {
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Context mContext;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private RecyclerView mRecyclerView;
@Mock
private SuggestionControllerMixin mSuggestionControllerMixin;
@Mock
private SuggestionDismissController.Callback mCallback;
private FakeFeatureFactory mFactory;
private SuggestionDismissController mController;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mFactory = FakeFeatureFactory.setupForTest();
when(mRecyclerView.getResources().getDimension(anyInt())).thenReturn(50F);
mController = new SuggestionDismissController(mContext, mRecyclerView,
mSuggestionControllerMixin, mCallback);
}
@Test
public void onMove_alwaysReturnTrue() {
assertThat(mController.onMove(null, null, null)).isTrue();
}
@Test
public void getSwipeDirs_isSuggestionTile_shouldReturnDirection() {
final RecyclerView.ViewHolder vh = mock(RecyclerView.ViewHolder.class);
when(vh.getItemViewType()).thenReturn(R.layout.suggestion_tile);
assertThat(mController.getSwipeDirs(mRecyclerView, vh))
.isEqualTo(ItemTouchHelper.START | ItemTouchHelper.END);
}
@Test
public void getSwipeDirs_isSuggestionTileCard_shouldReturnDirection() {
final RecyclerView.ViewHolder vh = mock(RecyclerView.ViewHolder.class);
when(vh.getItemViewType()).thenReturn(R.layout.suggestion_tile_with_button);
assertThat(mController.getSwipeDirs(mRecyclerView, vh))
.isEqualTo(ItemTouchHelper.START | ItemTouchHelper.END);
}
@Test
public void getSwipeDirs_isNotSuggestionTile_shouldReturn0() {
final RecyclerView.ViewHolder vh = mock(RecyclerView.ViewHolder.class);
when(vh.getItemViewType()).thenReturn(R.layout.condition_tile);
assertThat(mController.getSwipeDirs(mRecyclerView, vh))
.isEqualTo(0);
}
@Test
public void onSwiped_shouldTriggerDismissSuggestion() {
final RecyclerView.ViewHolder vh = mock(RecyclerView.ViewHolder.class);
when(mCallback.getSuggestionAt(anyInt())).thenReturn(
new Suggestion.Builder("id").build());
mController.onSwiped(vh, ItemTouchHelper.START);
verify(mFactory.suggestionsFeatureProvider).dismissSuggestion(
eq(mContext), eq(mSuggestionControllerMixin), nullable(Suggestion.class));
verify(mCallback).onSuggestionDismissed(nullable(Suggestion.class));
}
}

View File

@@ -25,6 +25,7 @@ import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
@@ -37,11 +38,13 @@ import android.arch.lifecycle.LifecycleOwner;
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.usb.UsbManager;
import android.hardware.usb.UsbManagerExtras;
import android.support.v7.preference.ListPreference;
import android.support.v7.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.TestConfig;
import com.android.settings.connecteddevice.usb.UsbBackend;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.testutils.shadow.ShadowUtils;
import com.android.settingslib.core.lifecycle.Lifecycle;
@@ -69,6 +72,8 @@ public class SelectUsbConfigPreferenceControllerTest {
private UsbManager mUsbManager;
@Mock
private PackageManager mPackageManager;
@Mock
private UsbBackend.UsbManagerPassThrough mUsbManagerPassThrough;
private Context mContext;
private LifecycleOwner mLifecycleOwner;
@@ -101,6 +106,13 @@ public class SelectUsbConfigPreferenceControllerTest {
mController = spy(new SelectUsbConfigPreferenceController(mContext, mLifecycle));
when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference);
mController.displayPreference(mScreen);
mController.mUsbManagerPassThrough = mUsbManagerPassThrough;
when(mUsbManagerPassThrough.usbFunctionsFromString("mtp")).thenReturn(UsbManagerExtras.MTP);
when(mUsbManagerPassThrough.usbFunctionsFromString("rndis"))
.thenReturn(UsbManagerExtras.RNDIS);
when(mUsbManagerPassThrough.usbFunctionsFromString("none"))
.thenReturn(UsbManagerExtras.NONE);
}
@@ -111,11 +123,13 @@ public class SelectUsbConfigPreferenceControllerTest {
@Test
public void onPreferenceChange_setCharging_shouldEnableCharging() {
when(mUsbManager.isFunctionEnabled(mValues[0])).thenReturn(true);
doNothing().when(mController).setCurrentFunction(anyString(), anyBoolean());
when(mUsbManagerPassThrough.getCurrentFunctions()).thenReturn(
UsbManagerExtras.usbFunctionsFromString(mValues[0]));
doNothing().when(mController).setCurrentFunctions(anyLong());
mController.onPreferenceChange(mPreference, mValues[0]);
verify(mController).setCurrentFunction(mValues[0], false /* usb data unlock */);
verify(mController).setCurrentFunctions(
UsbManagerExtras.usbFunctionsFromString(mValues[0]));
}
@Test
@@ -144,28 +158,32 @@ public class SelectUsbConfigPreferenceControllerTest {
@Test
public void onPreferenceChange_setMtp_shouldEnableMtp() {
when(mUsbManager.isFunctionEnabled(mValues[1])).thenReturn(true);
doNothing().when(mController).setCurrentFunction(anyString(), anyBoolean());
when(mUsbManagerPassThrough.getCurrentFunctions())
.thenReturn(UsbManagerExtras.usbFunctionsFromString(mValues[1]));
doNothing().when(mController).setCurrentFunctions(anyLong());
mController.onPreferenceChange(mPreference, mValues[1]);
verify(mController).setCurrentFunction(mValues[1], true /* usb data unlock */);
verify(mController).setCurrentFunctions(
UsbManagerExtras.usbFunctionsFromString(mValues[1]));
}
@Test
public void onPreferenceChange_monkeyUser_shouldReturnFalse() {
when(mUsbManager.isFunctionEnabled(mValues[1])).thenReturn(true);
when(mUsbManagerPassThrough.getCurrentFunctions())
.thenReturn(UsbManagerExtras.usbFunctionsFromString(mValues[1]));
ShadowUtils.setIsUserAMonkey(true);
doNothing().when(mController).setCurrentFunction(anyString(), anyBoolean());
doNothing().when(mController).setCurrentFunctions(anyLong());
final boolean isHandled = mController.onPreferenceChange(mPreference, mValues[1]);
assertThat(isHandled).isFalse();
verify(mController, never()).setCurrentFunction(any(), anyBoolean());
verify(mController, never()).setCurrentFunctions(anyLong());
}
@Test
public void updateState_chargingEnabled_shouldSetPreferenceToCharging() {
when(mUsbManager.isFunctionEnabled(mValues[0])).thenReturn(true);
when(mUsbManagerPassThrough.getCurrentFunctions())
.thenReturn(UsbManagerExtras.usbFunctionsFromString(mValues[0]));
mController.updateState(mPreference);
@@ -175,7 +193,8 @@ public class SelectUsbConfigPreferenceControllerTest {
@Test
public void updateState_RndisEnabled_shouldEnableRndis() {
when(mUsbManager.isFunctionEnabled(mValues[3])).thenReturn(true);
when(mUsbManagerPassThrough.getCurrentFunctions())
.thenReturn(UsbManagerExtras.usbFunctionsFromString(mValues[3]));
mController.updateState(mPreference);
@@ -185,6 +204,7 @@ public class SelectUsbConfigPreferenceControllerTest {
@Test
public void updateState_noValueSet_shouldEnableChargingAsDefault() {
when(mUsbManagerPassThrough.getCurrentFunctions()).thenReturn(UsbManagerExtras.NONE);
mController.updateState(mPreference);
verify(mPreference).setValue(mValues[0]);

View File

@@ -119,7 +119,6 @@ public class DeletedChannelsPreferenceControllerTest {
Preference pref = mock(Preference.class);
mController.updateState(pref);
verify(pref, times(1)).setEnabled(false);
verify(pref, times(1)).setSelectable(false);
verify(mBackend, times(1)).getDeletedChannelCount(any(), anyInt());
ArgumentCaptor<CharSequence> argumentCaptor = ArgumentCaptor.forClass(CharSequence.class);

View File

@@ -21,6 +21,7 @@ import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_NONE;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
@@ -30,13 +31,16 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.os.UserManager;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceScreen;
import android.text.TextUtils;
import com.android.settings.RestrictedListPreference;
import com.android.settings.TestConfig;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settingslib.RestrictedLockUtils;
@@ -44,6 +48,7 @@ import com.android.settingslib.RestrictedLockUtils;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
@@ -58,7 +63,13 @@ public class ImportancePreferenceControllerTest {
@Mock
private NotificationManager mNm;
@Mock
private NotificationBackend mBackend;
@Mock
NotificationSettingsBase.ImportanceListener mImportanceListener;
@Mock
private UserManager mUm;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private PreferenceScreen mScreen;
private ImportancePreferenceController mController;
@@ -69,7 +80,8 @@ public class ImportancePreferenceControllerTest {
shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNm);
shadowApplication.setSystemService(Context.USER_SERVICE, mUm);
mContext = shadowApplication.getApplicationContext();
mController = spy(new ImportancePreferenceController(mContext));
mController = spy(new ImportancePreferenceController(
mContext, mImportanceListener, mBackend));
}
@Test
@@ -123,14 +135,15 @@ public class ImportancePreferenceControllerTest {
@Test
public void testUpdateState_disabledByAdmin() throws Exception {
NotificationChannel channel = mock(NotificationChannel.class);
when(channel.getImportance()).thenReturn(IMPORTANCE_HIGH);
mController.onResume(new NotificationBackend.AppRow(), channel, null, mock(
RestrictedLockUtils.EnforcedAdmin.class));
Preference pref = new Preference(RuntimeEnvironment.application);
Preference pref = new RestrictedListPreference(RuntimeEnvironment.application, null);
mController.updateState(pref);
assertFalse(pref.isEnabled());
assertNull(pref.getIntent());
assertFalse(TextUtils.isEmpty(pref.getSummary()));
}
@Test
@@ -140,13 +153,14 @@ public class ImportancePreferenceControllerTest {
appRow.lockedChannelId = lockedId;
NotificationChannel channel = mock(NotificationChannel.class);
when(channel.getId()).thenReturn(lockedId);
when(channel.getImportance()).thenReturn(IMPORTANCE_HIGH);
mController.onResume(appRow, channel, null, null);
Preference pref = new Preference(RuntimeEnvironment.application);
Preference pref = new RestrictedListPreference(RuntimeEnvironment.application, null);
mController.updateState(pref);
assertFalse(pref.isEnabled());
assertNull(pref.getIntent());
assertFalse(TextUtils.isEmpty(pref.getSummary()));
}
@Test
@@ -155,11 +169,50 @@ public class ImportancePreferenceControllerTest {
NotificationChannel channel = new NotificationChannel("", "", IMPORTANCE_HIGH);
mController.onResume(appRow, channel, null, null);
Preference pref = new Preference(RuntimeEnvironment.application);
Preference pref = new RestrictedListPreference(RuntimeEnvironment.application, null);
mController.updateState(pref);
assertTrue(pref.isEnabled());
assertNotNull(pref.getIntent());
assertFalse(TextUtils.isEmpty(pref.getSummary()));
}
@Test
public void testImportanceLowToHigh() {
NotificationChannel channel =
new NotificationChannel(DEFAULT_CHANNEL_ID, "a", IMPORTANCE_LOW);
channel.setSound(null, Notification.AUDIO_ATTRIBUTES_DEFAULT);
mController.onResume(new NotificationBackend.AppRow(), channel, null, null);
RestrictedListPreference pref =
new RestrictedListPreference(RuntimeEnvironment.application, null);
when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(pref);
mController.displayPreference(mScreen);
mController.updateState(pref);
pref.setValue(String.valueOf(IMPORTANCE_HIGH));
mController.onPreferenceChange(pref, pref.getValue());
assertEquals(IMPORTANCE_HIGH, channel.getImportance());
assertNotNull(channel.getSound());
}
@Test
public void testImportanceHightToLow() {
NotificationChannel channel =
new NotificationChannel(DEFAULT_CHANNEL_ID, "a", IMPORTANCE_HIGH);
channel.setSound(null, Notification.AUDIO_ATTRIBUTES_DEFAULT);
mController.onResume(new NotificationBackend.AppRow(), channel, null, null);
RestrictedListPreference pref =
new RestrictedListPreference(RuntimeEnvironment.application, null);
when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(pref);
mController.displayPreference(mScreen);
mController.updateState(pref);
pref.setValue(String.valueOf(IMPORTANCE_LOW));
mController.onPreferenceChange(pref, pref.getValue());
assertEquals(IMPORTANCE_LOW, channel.getImportance());
assertNull(channel.getSound());
}
}

View File

@@ -110,7 +110,6 @@ public class NotificationsOffPreferenceControllerTest {
mController.updateState(pref);
assertTrue(pref.getTitle().toString().contains("category"));
assertFalse(pref.isEnabled());
assertFalse(pref.isSelectable());
}
@@ -125,7 +124,6 @@ public class NotificationsOffPreferenceControllerTest {
mController.updateState(pref);
assertTrue(pref.getTitle().toString().contains("group"));
assertFalse(pref.isEnabled());
assertFalse(pref.isSelectable());
}
@@ -139,7 +137,6 @@ public class NotificationsOffPreferenceControllerTest {
mController.updateState(pref);
assertTrue(pref.getTitle().toString().contains("app"));
assertFalse(pref.isEnabled());
assertFalse(pref.isSelectable());
}
}

View File

@@ -13,20 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.deviceinfo;
package com.android.settings.system;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import com.android.settings.R;
import com.android.settings.TestConfig;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;

View File

@@ -13,17 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.deviceinfo;
package com.android.settings.system;
import static android.os.SystemUpdateManager.KEY_STATUS;
import static android.os.SystemUpdateManager.KEY_TITLE;
import static android.os.SystemUpdateManager.STATUS_IDLE;
import static android.os.SystemUpdateManager.STATUS_UNKNOWN;
import static android.os.SystemUpdateManager.STATUS_WAITING_DOWNLOAD;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Answers.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.os.Build;
import android.os.UserManager;
import android.os.Bundle;
import android.os.SystemUpdateManager;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceScreen;
@@ -55,6 +58,8 @@ public class SystemUpdatePreferenceControllerTest {
@Mock
private PreferenceScreen mScreen;
@Mock
private SystemUpdateManager mSystemUpdateManager;
private Context mContext;
private SystemUpdatePreferenceController mController;
@@ -64,7 +69,8 @@ public class SystemUpdatePreferenceControllerTest {
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
ShadowApplication.getInstance().setSystemService(Context.SYSTEM_UPDATE_SERVICE,
mSystemUpdateManager);
mController = new SystemUpdatePreferenceController(mContext);
mPreference = new Preference(RuntimeEnvironment.application);
mPreference.setKey(mController.getPreferenceKey());
@@ -118,11 +124,41 @@ public class SystemUpdatePreferenceControllerTest {
}
@Test
public void updateState_shouldSetToAndroidVersion() {
public void updateState_systemUpdateStatusUnknown_shouldSetToAndroidVersion() {
final Bundle bundle = new Bundle();
bundle.putInt(KEY_STATUS, STATUS_UNKNOWN);
when(mSystemUpdateManager.retrieveSystemUpdateInfo()).thenReturn(bundle);
mController.updateState(mPreference);
assertThat(mPreference.getSummary())
.isEqualTo(RuntimeEnvironment.application.getString(R.string.about_summary,
Build.VERSION.RELEASE));
assertThat(mPreference.getSummary()).isEqualTo(
mContext.getString(R.string.android_version_summary, Build.VERSION.RELEASE));
}
@Test
public void updateState_systemUpdateStatusIdle_shouldSetToAndroidVersion() {
final String testReleaseName = "ANDROID TEST VERSION";
final Bundle bundle = new Bundle();
bundle.putInt(KEY_STATUS, STATUS_IDLE);
bundle.putString(KEY_TITLE, testReleaseName);
when(mSystemUpdateManager.retrieveSystemUpdateInfo()).thenReturn(bundle);
mController.updateState(mPreference);
assertThat(mPreference.getSummary()).isEqualTo(
mContext.getString(R.string.android_version_summary, testReleaseName));
}
@Test
public void updateState_systemUpdateInProgress_shouldSetToUpdatePending() {
final Bundle bundle = new Bundle();
bundle.putInt(KEY_STATUS, STATUS_WAITING_DOWNLOAD);
when(mSystemUpdateManager.retrieveSystemUpdateInfo()).thenReturn(bundle);
mController.updateState(mPreference);
assertThat(mPreference.getSummary()).isEqualTo(
mContext.getString(R.string.android_version_pending_update_summary));
}
}

View File

@@ -26,6 +26,7 @@ import org.robolectric.annotation.Implements;
public class ShadowConnectivityManager extends org.robolectric.shadows.ShadowConnectivityManager {
private final SparseBooleanArray mSupportedNetworkTypes = new SparseBooleanArray();
private boolean mTetheringSupported = false;
public void setNetworkSupported(int networkType, boolean supported) {
mSupportedNetworkTypes.put(networkType, supported);
@@ -35,4 +36,13 @@ public class ShadowConnectivityManager extends org.robolectric.shadows.ShadowCon
public boolean isNetworkSupported(int networkType) {
return mSupportedNetworkTypes.get(networkType);
}
public void setTetheringSupported(boolean supported) {
mTetheringSupported = supported;
}
@Implementation
public boolean isTetheringSupported() {
return mTetheringSupported;
}
}