diff options
author | 2016-01-11 14:27:20 -0500 | |
---|---|---|
committer | 2016-01-19 13:55:26 -0500 | |
commit | b37e2887d3112082589997f1bbd802ec282cca6d (patch) | |
tree | 9646f034176f9b8c69b0b2128c45254585bd5864 | |
parent | 703dae96e163d0928e292105852950ce68adab0e (diff) |
Update data usage UX
Update the UX and dig the data usage screen out of a huge whole of
technical debt. Switch every to use Preferences rather than standard
layouts and ListViews.
Split data usage into several fragments, all separated.
DataUsageSummary:
- Shows a summary of the 'default' usage at the top, this will be
the default sim on phones, or wifi if it has it, or ethernet
as last attempt to show something.
- Also has individual categories for each network type that has
data, cell, wifi, and ethernet. Maybe should look into bt though?
DataUsageList:
- Takes a NetworkTemplate as an input, and can only be reached from
the network specific categories in DataUsageSummary
- Shows a graph of current usage for that network and links to
app detail page for any app.
- Has gear link to quick get to billing cycle screen if available
BillingCycleSettings:
- Just a screen with the cycle day and warning/limits separated
out from the data usage.
AppDataUsage:
- App specific data usage details
- May need some UX iteration given lack of clarity in the spec
Bug: 22459566
Change-Id: I0222d8d7ea7b75a9775207a6026ebbdcce8f5e46
58 files changed, 3552 insertions, 3308 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 186ddf7874c..3281adedf3f 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -2226,7 +2226,7 @@ <meta-data android:name="com.android.settings.category" android:value="com.android.settings.category.wireless" /> <meta-data android:name="com.android.settings.FRAGMENT_CLASS" - android:value="com.android.settings.DataUsageSummary" /> + android:value="com.android.settings.datausage.DataUsageSummary" /> </activity> <activity android:name="Settings$DreamSettingsActivity" diff --git a/res/drawable/data_sweep_limit.xml b/res/drawable/data_sweep_limit.xml index eba023ffa83..b0f5fda569f 100644 --- a/res/drawable/data_sweep_limit.xml +++ b/res/drawable/data_sweep_limit.xml @@ -14,6 +14,12 @@ limitations under the License. --> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:drawable="@drawable/data_sweep_limit_activated" /> -</selector> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="2dp" + android:height="14.5dp" + android:viewportWidth="2" + android:viewportHeight="56"> + <path + android:fillColor="#ffffffff" + android:pathData="M0,48l2,0l0,8l-2,0l0,-8z" /> +</vector> diff --git a/res/drawable/data_sweep_warning.xml b/res/drawable/data_sweep_warning.xml index 5c0a1e9f9b3..b0f5fda569f 100644 --- a/res/drawable/data_sweep_warning.xml +++ b/res/drawable/data_sweep_warning.xml @@ -14,6 +14,12 @@ limitations under the License. --> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:drawable="@drawable/data_sweep_warning_activated" /> -</selector> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="2dp" + android:height="14.5dp" + android:viewportWidth="2" + android:viewportHeight="56"> + <path + android:fillColor="#ffffffff" + android:pathData="M0,48l2,0l0,8l-2,0l0,-8z" /> +</vector> diff --git a/res/layout/app_header.xml b/res/layout/app_header.xml index ffc68296309..6c13ae886ea 100644 --- a/res/layout/app_header.xml +++ b/res/layout/app_header.xml @@ -53,11 +53,5 @@ android:src="@drawable/ic_info" style="?android:attr/borderlessButtonStyle" /> - <View - android:id="@+id/row_divider" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="?android:attr/listDivider" /> - </RelativeLayout> diff --git a/res/layout/apps_filter_spinner.xml b/res/layout/apps_filter_spinner.xml index 45e64c4e123..b8b5c908c25 100644 --- a/res/layout/apps_filter_spinner.xml +++ b/res/layout/apps_filter_spinner.xml @@ -20,22 +20,34 @@ android:layout_height="?android:attr/actionBarSize" android:background="@drawable/switchbar_background" android:gravity="center_vertical" + android:paddingEnd="@dimen/switchbar_subsettings_margin_end" android:theme="?attr/switchBarTheme" > <Spinner android:id="@+id/filter_spinner" android:layout_height="wrap_content" android:layout_width="wrap_content" + android:layout_alignParentStart="true" android:layout_marginStart="64dp" + android:layout_marginEnd="70dp" android:layout_alignWithParentIfMissing="true" android:layout_centerVertical="true" android:textAlignment="viewStart" /> - <View - android:id="@+id/row_divider" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="?android:attr/listDivider" /> + <ImageView + android:id="@+id/filter_settings" + android:layout_width="56dp" + android:layout_height="56dp" + android:layout_alignParentEnd="true" + android:layout_centerVertical="true" + android:minHeight="0dp" + android:minWidth="0dp" + android:contentDescription="@string/configure" + android:scaleType="center" + android:src="@drawable/ic_settings_24dp" + style="?android:attr/borderlessButtonStyle" + android:visibility="gone" /> + </RelativeLayout> diff --git a/res/layout/data_usage_app_header.xml b/res/layout/data_usage_app_header.xml new file mode 100644 index 00000000000..8ca391a051c --- /dev/null +++ b/res/layout/data_usage_app_header.xml @@ -0,0 +1,31 @@ +<!-- + 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <include layout="@layout/app_header" /> + + <View + android:layout_width="match_parent" + android:layout_height=".5dp" + android:background="@android:color/white" /> + + <include layout="@layout/apps_filter_spinner" /> + +</LinearLayout> diff --git a/res/layout/data_usage_chart.xml b/res/layout/data_usage_chart.xml index 648c7f8d7e4..3a2ee098631 100644 --- a/res/layout/data_usage_chart.xml +++ b/res/layout/data_usage_chart.xml @@ -22,8 +22,8 @@ android:id="@+id/chart" android:layout_width="match_parent" android:layout_height="@dimen/data_usage_chart_height" - android:paddingLeft="?android:attr/listPreferredItemPaddingStart" - android:paddingRight="40dp" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" android:paddingTop="16dp" android:paddingBottom="24dp"> diff --git a/res/layout/data_usage_cycles.xml b/res/layout/data_usage_cycles.xml index 45143ecef3c..5267e26a602 100644 --- a/res/layout/data_usage_cycles.xml +++ b/res/layout/data_usage_cycles.xml @@ -31,14 +31,4 @@ android:textAppearance="@android:style/TextAppearance.Material.Subhead" android:textColor="?android:attr/textColorPrimary" /> - <TextView - android:id="@+id/cycle_summary" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - android:layout_gravity="center_vertical" - android:textAppearance="@android:style/TextAppearance.Material.Subhead" - android:textColor="?android:attr/textColorPrimary" - android:textAlignment="viewEnd" /> - </LinearLayout> diff --git a/res/layout/memory_key.xml b/res/layout/memory_key.xml deleted file mode 100644 index 62db2fc5123..00000000000 --- a/res/layout/memory_key.xml +++ /dev/null @@ -1,58 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:gravity="center_vertical|end" - android:layout_marginBottom="5dp" > - - <ImageView - android:layout_width="16dp" - android:layout_height="16dp" - android:scaleType="centerInside" - android:src="@color/memory_avg_use" - android:contentDescription="@null" /> - - <TextView - android:id="@+id/memory_avg" - android:text="@string/memory_avg_use" - android:textAppearance="@android:style/TextAppearance.Material.Body1" - android:textColor="?android:attr/textColorSecondary" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginStart="8dp" - android:layout_marginEnd="16dp" /> - - <ImageView - android:layout_width="16dp" - android:layout_height="16dp" - android:scaleType="centerInside" - android:src="@color/memory_max_use" - android:contentDescription="@null" /> - - <TextView - android:id="@+id/memory_max" - android:text="@string/memory_max_use" - android:textAppearance="@android:style/TextAppearance.Material.Body1" - android:textColor="?android:attr/textColorSecondary" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginStart="8dp" - android:layout_marginEnd="16dp" /> - -</LinearLayout> diff --git a/res/layout/preference_category_no_label.xml b/res/layout/preference_category_no_label.xml new file mode 100644 index 00000000000..dafabb23124 --- /dev/null +++ b/res/layout/preference_category_no_label.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> +<Space xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="0dp" /> + diff --git a/res/layout/preference_category_short.xml b/res/layout/preference_category_short.xml new file mode 100644 index 00000000000..452d0bc80b4 --- /dev/null +++ b/res/layout/preference_category_short.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<!-- Layout used for PreferenceCategory in a PreferenceActivity. --> +<TextView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@android:id/title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="0dp" + android:textAppearance="@android:style/TextAppearance.Material.Body2" + android:textColor="?android:attr/colorAccent" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:paddingTop="16dip" /> diff --git a/res/layout/preference_list_fragment.xml b/res/layout/preference_list_fragment.xml index 99e764fd56d..f8badcf5d33 100644 --- a/res/layout/preference_list_fragment.xml +++ b/res/layout/preference_list_fragment.xml @@ -18,95 +18,95 @@ --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/container_material" - android:orientation="vertical" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="@android:color/transparent"> + android:id="@+id/container_material" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@android:color/transparent"> <FrameLayout android:id="@+id/pinned_header" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:visibility="gone" /> + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:visibility="gone" /> - <FrameLayout - android:id="@android:id/list_container" - android:layout_height="0px" - android:layout_weight="1" - android:layout_width="match_parent"> + <FrameLayout + android:id="@android:id/list_container" + android:layout_height="0px" + android:layout_weight="1" + android:layout_width="match_parent"> <ListView android:id="@+id/backup_list" - style="@style/PreferenceFragmentListSinglePane" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:paddingStart="@dimen/settings_side_margin" - android:paddingEnd="@dimen/settings_side_margin" - android:paddingTop="@dimen/dashboard_padding_top" - android:paddingBottom="@dimen/dashboard_padding_bottom" - android:scrollbarStyle="@*android:integer/preference_fragment_scrollbarStyle" - android:clipToPadding="false" - android:drawSelectorOnTop="false" - android:elevation="@dimen/dashboard_category_elevation" - android:visibility="gone" - android:scrollbarAlwaysDrawVerticalTrack="true" /> + style="@style/PreferenceFragmentListSinglePane" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:paddingStart="@dimen/settings_side_margin" + android:paddingEnd="@dimen/settings_side_margin" + android:paddingTop="@dimen/dashboard_padding_top" + android:paddingBottom="@dimen/dashboard_padding_bottom" + android:scrollbarStyle="@*android:integer/preference_fragment_scrollbarStyle" + android:clipToPadding="false" + android:drawSelectorOnTop="false" + android:elevation="@dimen/dashboard_category_elevation" + android:visibility="gone" + android:scrollbarAlwaysDrawVerticalTrack="true" /> <include layout="@layout/loading_container" /> <com.android.settings.widget.FloatingActionButton - android:id="@+id/fab" - android:visibility="gone" - android:clickable="true" - android:layout_width="@dimen/fab_size" - android:layout_height="@dimen/fab_size" - android:layout_gravity="bottom|end" - android:layout_marginEnd="@dimen/fab_margin" - android:layout_marginBottom="@dimen/fab_margin" - android:elevation="@dimen/fab_elevation" - android:background="@drawable/fab_background" /> + android:id="@+id/fab" + android:visibility="gone" + android:clickable="true" + android:layout_width="@dimen/fab_size" + android:layout_height="@dimen/fab_size" + android:layout_gravity="bottom|end" + android:layout_marginEnd="@dimen/fab_margin" + android:layout_marginBottom="@dimen/fab_margin" + android:elevation="@dimen/fab_elevation" + android:background="@drawable/fab_background" /> </FrameLayout> <TextView android:id="@android:id/empty" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:padding="@*android:dimen/preference_fragment_padding_side" - android:gravity="center" - android:visibility="gone" /> + android:layout_width="match_parent" + android:layout_height="match_parent" + android:padding="@*android:dimen/preference_fragment_padding_side" + android:gravity="center" + android:visibility="gone" /> <RelativeLayout android:id="@+id/button_bar" - android:layout_height="wrap_content" - android:layout_width="match_parent" - android:layout_weight="0" - android:visibility="gone"> + android:layout_height="wrap_content" + android:layout_width="match_parent" + android:layout_weight="0" + android:visibility="gone"> <Button android:id="@+id/back_button" - android:layout_width="150dip" - android:layout_height="wrap_content" - android:layout_margin="5dip" - android:layout_alignParentStart="true" - android:text="@*android:string/back_button_label" - /> + android:layout_width="150dip" + android:layout_height="wrap_content" + android:layout_margin="5dip" + android:layout_alignParentStart="true" + android:text="@*android:string/back_button_label" + /> <LinearLayout - android:orientation="horizontal" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentEnd="true"> + android:orientation="horizontal" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true"> <Button android:id="@+id/skip_button" - android:layout_width="150dip" - android:layout_height="wrap_content" - android:layout_margin="5dip" - android:text="@*android:string/skip_button_label" - android:visibility="gone" - /> + android:layout_width="150dip" + android:layout_height="wrap_content" + android:layout_margin="5dip" + android:text="@*android:string/skip_button_label" + android:visibility="gone" + /> <Button android:id="@+id/next_button" - android:layout_width="150dip" - android:layout_height="wrap_content" - android:layout_margin="5dip" - android:text="@*android:string/next_button_label" - /> + android:layout_width="150dip" + android:layout_height="wrap_content" + android:layout_margin="5dip" + android:text="@*android:string/next_button_label" + /> </LinearLayout> diff --git a/res/layout/proc_stats_ui.xml b/res/layout/proc_stats_ui.xml deleted file mode 100644 index 9d0a22df493..00000000000 --- a/res/layout/proc_stats_ui.xml +++ /dev/null @@ -1,45 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2015 The Android Open Source Project - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at"+ - - http://www.apache.org/licenses/LICENSE-2.0 - - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/all_details" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" - android:orientation="vertical"> - - <TextView - android:id="@+id/memory_state" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="5dp" - android:layout_marginBottom="5dp" - android:textColor="?android:attr/colorAccent" - android:textAppearance="@android:style/TextAppearance.Material.Display1" - /> - - <com.android.settings.applications.LinearColorBar - android:id="@+id/color_bar" - android:layout_width="match_parent" - android:layout_height="28dp" - android:layout_marginBottom="15dp" - /> - -</LinearLayout> - diff --git a/res/layout/process_stats_details.xml b/res/layout/process_stats_details.xml deleted file mode 100644 index aa402f6bec4..00000000000 --- a/res/layout/process_stats_details.xml +++ /dev/null @@ -1,47 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2013 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/all_details" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical" - android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> - - <com.android.settings.applications.LinearColorBar - android:id="@+id/color_bar" - android:layout_width="match_parent" - android:layout_height="40dp" - android:layout_marginTop="12dp" - android:layout_marginBottom="10dp" - /> - - <include layout="@layout/memory_key" /> - - <!-- Force stop and report buttons --> - <LinearLayout - android:id="@+id/two_buttons_panel" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingBottom="6dip" - android:orientation="vertical"> - - <include layout="@layout/two_buttons_panel"/> - </LinearLayout> - -</LinearLayout> diff --git a/res/layout/settings_summary_preference.xml b/res/layout/settings_summary_preference.xml new file mode 100644 index 00000000000..7614fca1384 --- /dev/null +++ b/res/layout/settings_summary_preference.xml @@ -0,0 +1,77 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:orientation="vertical"> + + <TextView + android:id="@android:id/title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingTop="0dp" + android:layout_marginTop="0dp" + android:layout_marginBottom="5dp" + android:textColor="?android:attr/colorAccent" + android:textAppearance="@android:style/TextAppearance.Material.Display1" + /> + + <TextView android:id="@android:id/summary" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceListItemSecondary" + android:textColor="?android:attr/textColorSecondary" + android:paddingBottom="5dp" + android:maxLines="10" /> + + <com.android.settings.applications.LinearColorBar + android:id="@+id/color_bar" + android:layout_width="match_parent" + android:layout_height="28dp" + /> + + <LinearLayout + android:id="@+id/label_bar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingTop="2dp" + android:orientation="horizontal"> + + <TextView android:id="@android:id/text1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceListItemSecondary" + android:textColor="?android:attr/textColorSecondary" /> + + <Space + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" /> + + <TextView android:id="@android:id/text2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceListItemSecondary" + android:textColor="?android:attr/textColorSecondary" /> + + </LinearLayout> + +</LinearLayout> + diff --git a/res/menu/data_usage.xml b/res/menu/data_usage.xml index 749eb16e8f2..9fe6b601182 100644 --- a/res/menu/data_usage.xml +++ b/res/menu/data_usage.xml @@ -16,24 +16,6 @@ <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item - android:id="@+id/data_usage_menu_restrict_background" - android:title="@string/data_usage_menu_restrict_background" /> - <item - android:id="@+id/data_usage_menu_show_wifi" - android:title="@string/data_usage_menu_show_wifi" /> - <item - android:id="@+id/data_usage_menu_show_ethernet" - android:title="@string/data_usage_menu_show_ethernet" /> - <item - android:id="@+id/data_usage_menu_metered" - android:title="@string/data_usage_menu_metered" /> - <item - android:id="@+id/data_usage_menu_sim_cards" - android:title="@string/data_usage_menu_sim_cards" /> - <item android:id="@+id/data_usage_menu_cellular_networks" android:title="@string/data_usage_menu_cellular_networks" /> - <item - android:id="@+id/data_usage_menu_help" - android:title="@string/help_label" /> </menu> diff --git a/res/values/colors.xml b/res/values/colors.xml index f3fea105b1e..a9517764bf1 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -94,12 +94,13 @@ <color name="memory_critical">#ffff5621</color> <color name="memory_avg_use">#ff384248</color> - <color name="memory_max_use">#ff009587</color> - <color name="memory_remaining">#ffced7db</color> <color name="zen_rule_name_warning">@color/system_warning_color</color> <!-- Accent color that matches the settings launcher icon --> <color name="icon_accent">#ffabffec</color> + <color name="summary_default_start">#ff009587</color> + <color name="summary_default_end">#ffced7db</color> + </resources> diff --git a/res/values/strings.xml b/res/values/strings.xml index 6e28a694031..f0f75e43b0b 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -586,7 +586,7 @@ <!-- mobile network settings screen, button on dialog box that appears when you are roaming and clear the "Data roaming" check box --> <string name="roaming_turn_it_on_button">Turn it on</string> <!-- mobile network settings screen, message in dialog box that appears when you select the "Data roaming" check box --> - <string name="roaming_warning">When you allow data roaming, you may incur significant roaming charges!</string> + <string name="roaming_warning">You may incur significant charges.</string> <!-- mobile network settings screen, message in dialog box that appears when you select the "Data roaming" check box. This is for multiuser tablets [CHAR LIMIT=none] --> <string name="roaming_warning_multiuser" product="tablet">When you allow data roaming, you may incur significant roaming charges!\n\nThis setting affects all users on this tablet.</string> <!-- mobile network settings screen, message in dialog box that appears when you select the "Data roaming" check box. This is for multiuser phones [CHAR LIMIT=none] --> @@ -6809,4 +6809,75 @@ <!-- Description of the setting to change the display's color temperature --> <string name="color_temperature_desc">Enable cool temperature</string> + + <!-- Label for category for data usage [CHAR LIMIT=30] --> + <string name="usage">Usage</string> + + <!-- Label for cellular data usage in data usage screen [CHAR LIMIT=60] --> + <string name="cellular_data_usage">Cellular data usage</string> + + <!-- Label for wifi data usage in data usage screen [CHAR LIMIT=60] --> + <string name="wifi_data_usage">Wi-Fi data usage</string> + + <!-- Label for ethernet data usage in data usage screen [CHAR LIMIT=60] --> + <string name="ethernet_data_usage">Ethernet data usage</string> + + <!-- Label for section about wifi in data usage screen [CHAR LIMIT=60] --> + <string name="wifi">Wi-Fi</string> + + <!-- Label for section about ethernet in data usage screen [CHAR LIMIT=60] --> + <string name="ethernet">Ethernet</string> + + <!-- Format string for amount of cellular data used [CHAR LIMIT=30] --> + <string name="cell_data_template"><xliff:g name="units" example="GB">%1$s</xliff:g> cellular data</string> + + <!-- Format string for amount of wifi data used [CHAR LIMIT=30] --> + <string name="wifi_data_template"><xliff:g name="units" example="GB">%1$s</xliff:g> Wi-Fi data</string> + + <!-- Format string for amount of ethernet data used [CHAR LIMIT=30] --> + <string name="ethernet_data_template"><xliff:g name="units" example="GB">%1$s</xliff:g> ethernet data</string> + + <!-- Format for a summary describing the amount of data before the user is warned [CHAR LIMIT=NONE] --> + <string name="cell_warning_only"><xliff:g name="amount" example="1 GB">%1$s</xliff:g> Data warning</string> + + <!-- Format for a summary describing the amount of data before the user is warned or limited [CHAR LIMIT=NONE] --> + <string name="cell_warning_and_limit"><xliff:g name="amount" example="1 GB">%1$s</xliff:g> Data warning / <xliff:g name="amount" example="2 GB">%2$s</xliff:g> Data limit</string> + + <!-- Title of button and screen for billing cycle preferences [CHAR LIMIT=30 --> + <string name="billing_cycle">Billing cycle</string> + + <!-- Summary describing when the billing cycle for their phone carrier starts [CHAR LIMIT=NONE] --> + <string name="billing_cycle_summary">Monthly cycle starts on the <xliff:g name="day" example="1st">%1$s</xliff:g> of every month</string> + + <!-- Summary describing when the billing cycle for their phone carrier starts [CHAR LIMIT=NONE] --> + <string name="billing_cycle_fragment_summary">Monthly starting <xliff:g name="day_of_month" example="1st">%1$s</xliff:g></string> + + <!-- Title of button and screen for which wifi networks have data restrictions [CHAR LIMIT=30 --> + <string name="network_restrictions">Network restrictions</string> + + <!-- A summary shown on data usage screens to indicate inaccuracy of data tracking [CHAR LIMIT=NONE] --> + <string name="operator_warning">Operator data accounting may differ from your device.</string> + + <!-- Format string describing how much data has been used [CHAR LIMIT=20] --> + <string name="data_used_template"><xliff:g name="amount" example="1 GB">%1$s</xliff:g> used</string> + + <!-- Label for button to set the amount of data before user is warned about usage [CHAR LIMIT=30] --> + <string name="data_warning">Data warning</string> + + <!-- Label for switch about whether to limit how much data can be used [CHAR LIMIT=30] --> + <string name="set_data_limit">Set data limit</string> + + <!-- Label for button to set the amount of data before user is limited [CHAR LIMIT=30] --> + <string name="data_limit">Data limit</string> + + <!-- Summary about how much data has been used in a date range [CHAR LIMIT=NONE] --> + <string name="data_usage_template"><xliff:g name="amount" example="200 MB">%1$s</xliff:g> used between <xliff:g name="date_range" example="Jan 1 -- Feb 2">%2$s</xliff:g></string> + + <!-- Accessibility label for button that leads to screen with more configuration options [CHAR LIMIT=NONE] --> + <string name="configure">Configure</string> + + <!-- TODO: Actually figure out what to do with the extra apps, and update + the code to do that --> + <string name="data_usage_other_apps" translatable="false">Other apps included in usage</string> + </resources> diff --git a/res/xml/app_data_usage.xml b/res/xml/app_data_usage.xml new file mode 100644 index 00000000000..b082b56b10e --- /dev/null +++ b/res/xml/app_data_usage.xml @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" + android:title="@string/data_usage_summary_title"> + + <com.android.settings.applications.SpacePreference + android:layout_height="8dp" /> + + <Preference + android:key="total_usage" + android:title="@string/total_size_label" + android:selectable="false" + android:layout="@layout/horizontal_preference" /> + + <Preference + android:key="foreground_usage" + android:title="@string/data_usage_label_foreground" + android:selectable="false" + android:layout="@layout/horizontal_preference" /> + + <Preference + android:key="background_usage" + android:title="@string/data_usage_label_background" + android:selectable="false" + android:layout="@layout/horizontal_preference" /> + + <com.android.settings.applications.SpacePreference + android:layout_height="8dp" /> + + <Preference + android:key="app_settings" + android:title="@string/data_usage_app_settings" /> + + <SwitchPreference + android:key="restrict_background" + android:title="@string/data_usage_app_restrict_background" + android:summary="@string/data_usage_app_restrict_background_summary" /> + + <PreferenceCategory + android:key="app_list" + android:title="@string/data_usage_other_apps" /> + +</PreferenceScreen> diff --git a/res/xml/app_memory_settings.xml b/res/xml/app_memory_settings.xml index 03ce0fff7a6..56d18244e18 100644 --- a/res/xml/app_memory_settings.xml +++ b/res/xml/app_memory_settings.xml @@ -18,11 +18,15 @@ android:title="@string/memory_usage"> <PreferenceCategory - android:title="@string/average_memory_use" /> + android:title="@string/average_memory_use" + android:layout="@layout/preference_category_short" /> - <com.android.settings.applications.LayoutPreference + <com.android.settings.SummaryPreference android:key="status_header" - android:layout="@layout/proc_stats_ui" /> + android:selectable="false" /> + + <com.android.settings.applications.SpacePreference + android:layout_height="5dp" /> <Preference android:key="frequency" diff --git a/res/xml/billing_cycle.xml b/res/xml/billing_cycle.xml new file mode 100644 index 00000000000..9beebce6872 --- /dev/null +++ b/res/xml/billing_cycle.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" + android:title="@string/data_usage_summary_title"> + + <Preference + android:key="billing_cycle" + android:title="@string/billing_cycle" /> + + <Preference + android:key="data_warning" + android:title="@string/data_warning" /> + + <SwitchPreference + android:key="set_data_limit" + android:title="@string/set_data_limit" /> + + <Preference + android:key="data_limit" + android:title="@string/data_limit" /> + +</PreferenceScreen> diff --git a/res/xml/data_usage.xml b/res/xml/data_usage.xml new file mode 100644 index 00000000000..378496ed90a --- /dev/null +++ b/res/xml/data_usage.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" + android:title="@string/data_usage_summary_title"> + + <PreferenceCategory + android:title="@string/usage" + android:layout="@layout/preference_category_short"> + + <com.android.settings.SummaryPreference + android:key="status_header" + android:selectable="false" /> + + <Preference + android:key="limit_summary" + android:selectable="false" /> + + <com.android.settings.datausage.RestrictBackgroundDataPreference + android:key="restrict_background" + android:title="@string/data_usage_menu_restrict_background" /> + + </PreferenceCategory> + +</PreferenceScreen> diff --git a/res/xml/data_usage_cellular.xml b/res/xml/data_usage_cellular.xml new file mode 100644 index 00000000000..8bfd37d6041 --- /dev/null +++ b/res/xml/data_usage_cellular.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<PreferenceScreen + xmlns:android="http://schemas.android.com/apk/res/android"> + + <com.android.settings.datausage.TemplatePreferenceCategory + android:key="mobile_category" + android:title="@string/data_usage_tab_mobile"> + + <com.android.settings.datausage.CellDataPreference + android:key="data_usage_enable" + android:title="@string/data_usage_enable_mobile" /> + + <com.android.settings.datausage.DataUsagePreference + android:key="cellular_data_usage" + android:title="@string/cellular_data_usage" /> + + <com.android.settings.datausage.BillingCyclePreference + android:key="billing_preference" + android:title="@string/billing_cycle" /> + + </com.android.settings.datausage.TemplatePreferenceCategory> + +</PreferenceScreen> diff --git a/res/xml/data_usage_ethernet.xml b/res/xml/data_usage_ethernet.xml new file mode 100644 index 00000000000..508b365194b --- /dev/null +++ b/res/xml/data_usage_ethernet.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<PreferenceScreen + xmlns:android="http://schemas.android.com/apk/res/android"> + + <com.android.settings.datausage.TemplatePreferenceCategory + android:key="ethernet_category" + android:title="@string/ethernet"> + + <com.android.settings.datausage.DataUsagePreference + android:key="ethernet_data_usage" + android:title="@string/ethernet_data_usage" /> + + </com.android.settings.datausage.TemplatePreferenceCategory> + +</PreferenceScreen> diff --git a/res/xml/data_usage_list.xml b/res/xml/data_usage_list.xml new file mode 100644 index 00000000000..725105a3556 --- /dev/null +++ b/res/xml/data_usage_list.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" + android:title="@string/data_usage_summary_title"> + + <PreferenceCategory + android:key="usage_amount" + android:layout="@layout/preference_category_short"> + + <com.android.settings.datausage.ChartDataUsagePreference + android:key="chart_data" /> + + <Preference + android:summary="@string/operator_warning" + android:selectable="false" /> + + </PreferenceCategory> + + <PreferenceCategory + android:key="apps_group" + android:layout="@layout/preference_category_no_label" /> + +</PreferenceScreen> diff --git a/res/xml/data_usage_wifi.xml b/res/xml/data_usage_wifi.xml new file mode 100644 index 00000000000..62ff17fe5ed --- /dev/null +++ b/res/xml/data_usage_wifi.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<PreferenceScreen + xmlns:android="http://schemas.android.com/apk/res/android"> + + <com.android.settings.datausage.TemplatePreferenceCategory + android:key="wifi_category" + android:title="@string/wifi"> + + <com.android.settings.datausage.DataUsagePreference + android:key="wifi_data_usage" + android:title="@string/wifi_data_usage" /> + + <com.android.settings.datausage.NetworkRestrictionsPreference + android:key="network_restrictions" + android:title="@string/network_restrictions" + android:fragment="com.android.settings.datausage.DataUsageMeteredSettings" /> + + </com.android.settings.datausage.TemplatePreferenceCategory> + +</PreferenceScreen> diff --git a/res/xml/process_stats_summary.xml b/res/xml/process_stats_summary.xml index d2672727ad0..f36fd11d8cb 100644 --- a/res/xml/process_stats_summary.xml +++ b/res/xml/process_stats_summary.xml @@ -18,13 +18,17 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:title="@string/app_memory_use" android:key="app_list"> + <PreferenceCategory - android:title="@string/average_memory_use" /> + android:title="@string/average_memory_use" + android:layout="@layout/preference_category_short" /> - <com.android.settings.applications.LayoutPreference + <com.android.settings.SummaryPreference android:key="status_header" - android:selectable="false" - android:layout="@layout/proc_stats_ui" /> + android:selectable="false" /> + + <com.android.settings.applications.SpacePreference + android:layout_height="5dp" /> <Preference android:key="performance" diff --git a/src/com/android/settings/AppHeader.java b/src/com/android/settings/AppHeader.java index 257b2f0ad8b..36026bb53a1 100644 --- a/src/com/android/settings/AppHeader.java +++ b/src/com/android/settings/AppHeader.java @@ -57,7 +57,7 @@ public class AppHeader { tintColorRes, bar); } - private static View setupHeaderView(final Activity activity, Drawable icon, CharSequence label, + public static View setupHeaderView(final Activity activity, Drawable icon, CharSequence label, final String pkgName, final int uid, boolean includeAppInfo, int tintColorRes, View bar) { final ImageView appIcon = (ImageView) bar.findViewById(R.id.app_icon); @@ -86,7 +86,7 @@ public class AppHeader { return bar; } - private static boolean includeAppInfo(final Fragment fragment) { + public static boolean includeAppInfo(final Fragment fragment) { Bundle args = fragment.getArguments(); boolean showInfo = true; if (args != null && args.getBoolean(EXTRA_HIDE_INFO_BUTTON, false)) { diff --git a/src/com/android/settings/DataUsageSummary.java b/src/com/android/settings/DataUsageSummary.java deleted file mode 100644 index c9a2999b13d..00000000000 --- a/src/com/android/settings/DataUsageSummary.java +++ /dev/null @@ -1,2848 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.settings; - -import android.animation.LayoutTransition; -import android.app.Activity; -import android.app.ActivityManager; -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.DialogFragment; -import android.app.Fragment; -import android.app.FragmentTransaction; -import android.app.LoaderManager.LoaderCallbacks; -import android.content.ComponentName; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.Loader; -import android.content.SharedPreferences; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.UserInfo; -import android.content.res.Resources; -import android.graphics.Color; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; -import android.icu.impl.ICUResourceBundle; -import android.icu.util.UResourceBundle; -import android.net.ConnectivityManager; -import android.net.INetworkPolicyManager; -import android.net.INetworkStatsService; -import android.net.INetworkStatsSession; -import android.net.NetworkPolicy; -import android.net.NetworkPolicyManager; -import android.net.NetworkStats; -import android.net.NetworkStatsHistory; -import android.net.NetworkTemplate; -import android.net.TrafficStats; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.INetworkManagementService; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.SystemProperties; -import android.os.UserHandle; -import android.os.UserManager; -import android.support.v7.preference.Preference; -import android.telephony.SubscriptionInfo; -import android.telephony.SubscriptionManager; -import android.telephony.TelephonyManager; -import android.text.TextUtils; -import android.text.format.DateUtils; -import android.text.format.Formatter; -import android.text.format.Time; -import android.util.Log; -import android.util.SparseArray; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.AdapterView.OnItemSelectedListener; -import android.widget.ArrayAdapter; -import android.widget.BaseAdapter; -import android.widget.Button; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.ListView; -import android.widget.NumberPicker; -import android.widget.ProgressBar; -import android.widget.Spinner; -import android.widget.Switch; -import android.widget.TabHost; -import android.widget.TabHost.OnTabChangeListener; -import android.widget.TabHost.TabContentFactory; -import android.widget.TabHost.TabSpec; -import android.widget.TabWidget; -import android.widget.TextView; -import android.widget.Toast; -import com.android.internal.logging.MetricsLogger; -import com.android.internal.telephony.PhoneConstants; -import com.android.settings.dashboard.SummaryLoader; -import com.android.settings.dashboard.conditional.BackgroundDataCondition; -import com.android.settings.dashboard.conditional.ConditionManager; -import com.android.settings.drawable.InsetBoundsDrawable; -import com.android.settings.net.DataUsageMeteredSettings; -import com.android.settings.search.BaseSearchIndexProvider; -import com.android.settings.search.Indexable; -import com.android.settings.search.SearchIndexableRaw; -import com.android.settings.widget.ChartDataUsageView; -import com.android.settings.widget.ChartDataUsageView.DataUsageChartListener; -import com.android.settings.widget.ChartNetworkSeriesView; -import com.android.settingslib.AppItem; -import com.android.settingslib.NetworkPolicyEditor; -import com.android.settingslib.net.ChartData; -import com.android.settingslib.net.ChartDataLoader; -import com.android.settingslib.net.MobileDataController; -import com.android.settingslib.net.SummaryForAllUidLoader; -import com.android.settingslib.net.UidDetail; -import com.android.settingslib.net.UidDetailProvider; -import com.google.android.collect.Lists; -import libcore.util.Objects; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; - -import static android.net.ConnectivityManager.TYPE_ETHERNET; -import static android.net.ConnectivityManager.TYPE_MOBILE; -import static android.net.ConnectivityManager.TYPE_WIFI; -import static android.net.ConnectivityManager.TYPE_WIMAX; -import static android.net.NetworkPolicy.LIMIT_DISABLED; -import static android.net.NetworkPolicy.WARNING_DISABLED; -import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE; -import static android.net.NetworkPolicyManager.POLICY_NONE; -import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND; -import static android.net.NetworkPolicyManager.computeLastCycleBoundary; -import static android.net.NetworkPolicyManager.computeNextCycleBoundary; -import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER; -import static android.net.NetworkTemplate.MATCH_MOBILE_4G; -import static android.net.NetworkTemplate.MATCH_MOBILE_ALL; -import static android.net.NetworkTemplate.MATCH_WIFI; -import static android.net.NetworkTemplate.buildTemplateEthernet; -import static android.net.NetworkTemplate.buildTemplateMobile3gLower; -import static android.net.NetworkTemplate.buildTemplateMobile4g; -import static android.net.NetworkTemplate.buildTemplateMobileAll; -import static android.net.NetworkTemplate.buildTemplateWifiWildcard; -import static android.net.TrafficStats.GB_IN_BYTES; -import static android.net.TrafficStats.MB_IN_BYTES; -import static android.net.TrafficStats.UID_REMOVED; -import static android.net.TrafficStats.UID_TETHERING; -import static android.telephony.TelephonyManager.SIM_STATE_READY; -import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH; -import static android.text.format.DateUtils.FORMAT_SHOW_DATE; -import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; -import static com.android.internal.util.Preconditions.checkNotNull; -import static com.android.settings.Utils.prepareCustomPreferencesList; - -/** - * Panel showing data usage history across various networks, including options - * to inspect based on usage cycle and control through {@link NetworkPolicy}. - */ -public class DataUsageSummary extends HighlightingFragment implements Indexable { - private static final String TAG = "DataUsage"; - private static final boolean LOGD = false; - - // TODO: remove this testing code - private static final boolean TEST_ANIM = false; - private static final boolean TEST_RADIOS = false; - - private static final String TEST_RADIOS_PROP = "test.radios"; - private static final String TEST_SUBSCRIBER_PROP = "test.subscriberid"; - - private static final String TAB_3G = "3g"; - private static final String TAB_4G = "4g"; - private static final String TAB_MOBILE = "mobile"; - private static final String TAB_WIFI = "wifi"; - private static final String TAB_ETHERNET = "ethernet"; - - private static final String TAG_CONFIRM_DATA_DISABLE = "confirmDataDisable"; - private static final String TAG_CONFIRM_LIMIT = "confirmLimit"; - private static final String TAG_CYCLE_EDITOR = "cycleEditor"; - private static final String TAG_WARNING_EDITOR = "warningEditor"; - private static final String TAG_LIMIT_EDITOR = "limitEditor"; - private static final String TAG_CONFIRM_RESTRICT = "confirmRestrict"; - private static final String TAG_DENIED_RESTRICT = "deniedRestrict"; - private static final String TAG_CONFIRM_APP_RESTRICT = "confirmAppRestrict"; - private static final String TAG_APP_DETAILS = "appDetails"; - - private static final String DATA_USAGE_ENABLE_MOBILE_KEY = "data_usage_enable_mobile"; - private static final String DATA_USAGE_DISABLE_MOBILE_LIMIT_KEY = - "data_usage_disable_mobile_limit"; - private static final String DATA_USAGE_CYCLE_KEY = "data_usage_cycle"; - - public static final String EXTRA_SHOW_APP_IMMEDIATE_PKG = "showAppImmediatePkg"; - - private static final int LOADER_CHART_DATA = 2; - private static final int LOADER_SUMMARY = 3; - - private INetworkManagementService mNetworkService; - private INetworkStatsService mStatsService; - private NetworkPolicyManager mPolicyManager; - private TelephonyManager mTelephonyManager; - private SubscriptionManager mSubscriptionManager; - private UserManager mUserManager; - - private INetworkStatsSession mStatsSession; - - private static final String PREF_FILE = "data_usage"; - private static final String PREF_SHOW_WIFI = "show_wifi"; - private static final String PREF_SHOW_ETHERNET = "show_ethernet"; - - private SharedPreferences mPrefs; - - private TabHost mTabHost; - private ViewGroup mTabsContainer; - private TabWidget mTabWidget; - private ListView mListView; - private ChartNetworkSeriesView mSeries; - private ChartNetworkSeriesView mDetailedSeries; - private DataUsageAdapter mAdapter; - - /** Distance to inset content from sides, when needed. */ - private int mInsetSide = 0; - - private ViewGroup mHeader; - - private ViewGroup mNetworkSwitchesContainer; - private LinearLayout mNetworkSwitches; - private boolean mDataEnabledSupported; - private Switch mDataEnabled; - private View mDataEnabledView; - private boolean mDisableAtLimitSupported; - private Switch mDisableAtLimit; - private View mDisableAtLimitView; - - private View mCycleView; - private Spinner mCycleSpinner; - private CycleAdapter mCycleAdapter; - private TextView mCycleSummary; - - private ChartDataUsageView mChart; - private View mDisclaimer; - private TextView mEmpty; - private View mStupidPadding; - - private View mAppDetail; - private ImageView mAppIcon; - private ViewGroup mAppTitles; - private TextView mAppTotal; - private TextView mAppForeground; - private TextView mAppBackground; - private Button mAppSettings; - - private LinearLayout mAppSwitches; - private Switch mAppRestrict; - private View mAppRestrictView; - - private boolean mShowWifi = false; - private boolean mShowEthernet = false; - - private NetworkTemplate mTemplate; - private ChartData mChartData; - - private AppItem mCurrentApp = null; - - private Intent mAppSettingsIntent; - - private NetworkPolicyEditor mPolicyEditor; - - private String mCurrentTab = null; - private String mIntentTab = null; - - private MenuItem mMenuRestrictBackground; - private MenuItem mMenuShowWifi; - private MenuItem mMenuShowEthernet; - private MenuItem mMenuSimCards; - private MenuItem mMenuCellularNetworks; - - private List<SubscriptionInfo> mSubInfoList; - private Map<Integer,String> mMobileTagMap; - - /** Flag used to ignore listeners during binding. */ - private boolean mBinding; - - private UidDetailProvider mUidDetailProvider; - - // Indicates request to show app immediately rather than list. - private String mShowAppImmediatePkg; - - /** - * Local cache of data enabled for subId, used to work around delays. - */ - private final Map<String, Boolean> mMobileDataEnabled = new HashMap<String, Boolean>(); - - @Override - protected int getMetricsCategory() { - return MetricsLogger.DATA_USAGE_SUMMARY; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - final Context context = getActivity(); - - mNetworkService = INetworkManagementService.Stub.asInterface( - ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)); - mStatsService = INetworkStatsService.Stub.asInterface( - ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); - mPolicyManager = NetworkPolicyManager.from(context); - mTelephonyManager = TelephonyManager.from(context); - mSubscriptionManager = SubscriptionManager.from(context); - mUserManager = UserManager.get(context); - - mPrefs = getActivity().getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE); - - mPolicyEditor = new NetworkPolicyEditor(mPolicyManager); - mPolicyEditor.read(); - - mSubInfoList = mSubscriptionManager.getActiveSubscriptionInfoList(); - mMobileTagMap = initMobileTabTag(mSubInfoList); - - try { - if (!mNetworkService.isBandwidthControlEnabled()) { - Log.w(TAG, "No bandwidth control; leaving"); - getActivity().finish(); - } - } catch (RemoteException e) { - Log.w(TAG, "No bandwidth control; leaving"); - getActivity().finish(); - } - - try { - mStatsSession = mStatsService.openSession(); - } catch (RemoteException e) { - throw new RuntimeException(e); - } - - mShowWifi = mPrefs.getBoolean(PREF_SHOW_WIFI, false); - mShowEthernet = mPrefs.getBoolean(PREF_SHOW_ETHERNET, false); - - // override preferences when no mobile radio - if (!hasReadyMobileRadio(context)) { - mShowWifi = true; - mShowEthernet = true; - } - - mUidDetailProvider = new UidDetailProvider(context); - - Bundle arguments = getArguments(); - if (arguments != null) { - mShowAppImmediatePkg = arguments.getString(EXTRA_SHOW_APP_IMMEDIATE_PKG); - } - - setHasOptionsMenu(true); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - - final Context context = inflater.getContext(); - final View view = inflater.inflate(R.layout.data_usage_summary, container, false); - - - mTabHost = (TabHost) view.findViewById(android.R.id.tabhost); - mTabsContainer = (ViewGroup) view.findViewById(R.id.tabs_container); - mTabWidget = (TabWidget) view.findViewById(android.R.id.tabs); - mListView = (ListView) view.findViewById(android.R.id.list); - - // decide if we need to manually inset our content, or if we should rely - // on parent container for inset. - final boolean shouldInset = mListView.getScrollBarStyle() - == View.SCROLLBARS_OUTSIDE_OVERLAY; - mInsetSide = 0; - - // adjust padding around tabwidget as needed - prepareCustomPreferencesList(container, view, mListView, false); - - mTabHost.setup(); - mTabHost.setOnTabChangedListener(mTabListener); - - mHeader = (ViewGroup) inflater.inflate(R.layout.data_usage_header, mListView, false); - mHeader.setClickable(true); - - mListView.addHeaderView(new View(context), null, true); - mListView.addHeaderView(mHeader, null, true); - mListView.setItemsCanFocus(true); - - if (mInsetSide > 0) { - // inset selector and divider drawables - insetListViewDrawables(mListView, mInsetSide); - mHeader.setPaddingRelative(mInsetSide, 0, mInsetSide, 0); - } - - { - // bind network switches - mNetworkSwitchesContainer = (ViewGroup) mHeader.findViewById( - R.id.network_switches_container); - mNetworkSwitches = (LinearLayout) mHeader.findViewById(R.id.network_switches); - - mDataEnabled = new Switch(inflater.getContext()); - mDataEnabled.setClickable(false); - mDataEnabled.setFocusable(false); - mDataEnabledView = inflatePreference(inflater, mNetworkSwitches, mDataEnabled); - mDataEnabledView.setTag(R.id.preference_highlight_key, - DATA_USAGE_ENABLE_MOBILE_KEY); - mDataEnabledView.setClickable(true); - mDataEnabledView.setFocusable(true); - mDataEnabledView.setOnClickListener(mDataEnabledListener); - mNetworkSwitches.addView(mDataEnabledView); - - mDisableAtLimit = new Switch(inflater.getContext()); - mDisableAtLimit.setClickable(false); - mDisableAtLimit.setFocusable(false); - mDisableAtLimitView = inflatePreference(inflater, mNetworkSwitches, mDisableAtLimit); - mDisableAtLimitView.setTag(R.id.preference_highlight_key, - DATA_USAGE_DISABLE_MOBILE_LIMIT_KEY); - mDisableAtLimitView.setClickable(true); - mDisableAtLimitView.setFocusable(true); - mDisableAtLimitView.setOnClickListener(mDisableAtLimitListener); - mNetworkSwitches.addView(mDisableAtLimitView); - - mCycleView = inflater.inflate(R.layout.data_usage_cycles, mNetworkSwitches, false); - mCycleView.setTag(R.id.preference_highlight_key, DATA_USAGE_CYCLE_KEY); - mCycleSpinner = (Spinner) mCycleView.findViewById(R.id.cycles_spinner); - mCycleAdapter = new CycleAdapter(context); - mCycleSpinner.setAdapter(mCycleAdapter); - mCycleSpinner.setOnItemSelectedListener(mCycleListener); - mCycleSummary = (TextView) mCycleView.findViewById(R.id.cycle_summary); - mNetworkSwitches.addView(mCycleView); - mSeries = (ChartNetworkSeriesView)view.findViewById(R.id.series); - mDetailedSeries = (ChartNetworkSeriesView)view.findViewById(R.id.detail_series); - } - - mChart = (ChartDataUsageView) mHeader.findViewById(R.id.chart); - mChart.setListener(mChartListener); - mChart.bindNetworkPolicy(null); - - { - // bind app detail controls - mAppDetail = mHeader.findViewById(R.id.app_detail); - mAppIcon = (ImageView) mAppDetail.findViewById(R.id.app_icon); - mAppTitles = (ViewGroup) mAppDetail.findViewById(R.id.app_titles); - mAppForeground = (TextView) mAppDetail.findViewById(R.id.app_foreground); - mAppBackground = (TextView) mAppDetail.findViewById(R.id.app_background); - mAppSwitches = (LinearLayout) mAppDetail.findViewById(R.id.app_switches); - - mAppSettings = (Button) mAppDetail.findViewById(R.id.app_settings); - - mAppRestrict = new Switch(inflater.getContext()); - mAppRestrict.setClickable(false); - mAppRestrict.setFocusable(false); - mAppRestrictView = inflatePreference(inflater, mAppSwitches, mAppRestrict); - mAppRestrictView.setClickable(true); - mAppRestrictView.setFocusable(true); - mAppRestrictView.setOnClickListener(mAppRestrictListener); - mAppSwitches.addView(mAppRestrictView); - } - - mDisclaimer = mHeader.findViewById(R.id.disclaimer); - mEmpty = (TextView) mHeader.findViewById(android.R.id.empty); - mStupidPadding = mHeader.findViewById(R.id.stupid_padding); - - final UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE); - mAdapter = new DataUsageAdapter(um, mUidDetailProvider, mInsetSide); - mListView.setOnItemClickListener(mListListener); - mListView.setAdapter(mAdapter); - - showRequestedAppIfNeeded(view); - - return view; - } - - private void showRequestedAppIfNeeded(View rootView) { - if (mShowAppImmediatePkg == null) { - return; - } - try { - int uid = getActivity().getPackageManager().getPackageUidAsUser(mShowAppImmediatePkg, - UserHandle.myUserId()); - AppItem app = new AppItem(uid); - app.addUid(uid); - - final UidDetail detail = mUidDetailProvider.getUidDetail(app.key, true); - // When we are going straight to an app then we are coming from App Info and want - // a header at the top. - FrameLayout pinnedHeader = (FrameLayout) rootView.findViewById(R.id.pinned_header); - AppHeader.createAppHeader(getActivity(), detail.icon, detail.label, - mShowAppImmediatePkg, uid, pinnedHeader); - AppDetailsFragment.show(DataUsageSummary.this, app, detail.label, false); - } catch (NameNotFoundException e) { - Log.w(TAG, "Could not find " + mShowAppImmediatePkg, e); - Toast.makeText(getActivity(), getString(R.string.unknown_app), Toast.LENGTH_LONG) - .show(); - getActivity().finish(); - } - } - - @Override - public void onViewStateRestored(Bundle savedInstanceState) { - super.onViewStateRestored(savedInstanceState); - - // pick default tab based on incoming intent - final Intent intent = getActivity().getIntent(); - mIntentTab = computeTabFromIntent(intent); - - // this kicks off chain reaction which creates tabs, binds the body to - // selected network, and binds chart, cycles and detail list. - updateTabs(); - } - - @Override - public void onResume() { - super.onResume(); - - getView().post(new Runnable() { - @Override - public void run() { - highlightViewIfNeeded(); - } - }); - - // kick off background task to update stats - new AsyncTask<Void, Void, Void>() { - @Override - protected Void doInBackground(Void... params) { - try { - // wait a few seconds before kicking off - Thread.sleep(2 * DateUtils.SECOND_IN_MILLIS); - mStatsService.forceUpdate(); - } catch (InterruptedException e) { - } catch (RemoteException e) { - } - return null; - } - - @Override - protected void onPostExecute(Void result) { - if (isAdded()) { - updateBody(); - } - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - inflater.inflate(R.menu.data_usage, menu); - } - - @Override - public void onPrepareOptionsMenu(Menu menu) { - final Context context = getContext(); - if (context == null) return; - final boolean appDetailMode = isAppDetailMode(); - final boolean isAdmin = mUserManager.isAdminUser(); - - mMenuShowWifi = menu.findItem(R.id.data_usage_menu_show_wifi); - if (hasWifiRadio(context) && hasReadyMobileRadio(context)) { - mMenuShowWifi.setVisible(!appDetailMode); - } else { - mMenuShowWifi.setVisible(false); - } - - mMenuShowEthernet = menu.findItem(R.id.data_usage_menu_show_ethernet); - if (hasEthernet(context) && hasReadyMobileRadio(context)) { - mMenuShowEthernet.setVisible(!appDetailMode); - } else { - mMenuShowEthernet.setVisible(false); - } - - mMenuRestrictBackground = menu.findItem(R.id.data_usage_menu_restrict_background); - mMenuRestrictBackground.setVisible( - hasReadyMobileRadio(context) && isAdmin && !appDetailMode); - - final MenuItem metered = menu.findItem(R.id.data_usage_menu_metered); - if (hasReadyMobileRadio(context) || hasWifiRadio(context)) { - metered.setVisible(!appDetailMode); - } else { - metered.setVisible(false); - } - - // TODO: show when multiple sims available - mMenuSimCards = menu.findItem(R.id.data_usage_menu_sim_cards); - mMenuSimCards.setVisible(false); - - mMenuCellularNetworks = menu.findItem(R.id.data_usage_menu_cellular_networks); - mMenuCellularNetworks.setVisible(hasReadyMobileRadio(context) - && !appDetailMode && isAdmin); - - final MenuItem help = menu.findItem(R.id.data_usage_menu_help); - String helpUrl; - if (!TextUtils.isEmpty(helpUrl = getResources().getString(R.string.help_url_data_usage))) { - HelpUtils.prepareHelpMenuItem(getActivity(), help, helpUrl, getClass().getName()); - } else { - help.setVisible(false); - } - - updateMenuTitles(); - } - - private void updateMenuTitles() { - if (mPolicyManager.getRestrictBackground()) { - mMenuRestrictBackground.setTitle(R.string.data_usage_menu_allow_background); - } else { - mMenuRestrictBackground.setTitle(R.string.data_usage_menu_restrict_background); - } - - if (mShowWifi) { - mMenuShowWifi.setTitle(R.string.data_usage_menu_hide_wifi); - } else { - mMenuShowWifi.setTitle(R.string.data_usage_menu_show_wifi); - } - - if (mShowEthernet) { - mMenuShowEthernet.setTitle(R.string.data_usage_menu_hide_ethernet); - } else { - mMenuShowEthernet.setTitle(R.string.data_usage_menu_show_ethernet); - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.data_usage_menu_restrict_background: { - final boolean restrictBackground = !mPolicyManager.getRestrictBackground(); - if (restrictBackground) { - ConfirmRestrictFragment.show(this); - } else { - // no confirmation to drop restriction - setRestrictBackground(false); - } - return true; - } - case R.id.data_usage_menu_show_wifi: { - mShowWifi = !mShowWifi; - mPrefs.edit().putBoolean(PREF_SHOW_WIFI, mShowWifi).apply(); - updateMenuTitles(); - updateTabs(); - return true; - } - case R.id.data_usage_menu_show_ethernet: { - mShowEthernet = !mShowEthernet; - mPrefs.edit().putBoolean(PREF_SHOW_ETHERNET, mShowEthernet).apply(); - updateMenuTitles(); - updateTabs(); - return true; - } - case R.id.data_usage_menu_sim_cards: { - // TODO: hook up to sim cards - return true; - } - case R.id.data_usage_menu_cellular_networks: { - final Intent intent = new Intent(Intent.ACTION_MAIN); - intent.setComponent(new ComponentName("com.android.phone", - "com.android.phone.MobileNetworkSettings")); - startActivity(intent); - return true; - } - case R.id.data_usage_menu_metered: { - final SettingsActivity sa = (SettingsActivity) getActivity(); - sa.startPreferencePanel(DataUsageMeteredSettings.class.getCanonicalName(), null, - R.string.data_usage_metered_title, null, this, 0); - return true; - } - } - return false; - } - - @Override - public void onDestroy() { - mDataEnabledView = null; - mDisableAtLimitView = null; - - mUidDetailProvider.clearCache(); - mUidDetailProvider = null; - - TrafficStats.closeQuietly(mStatsSession); - - super.onDestroy(); - } - - /** - * Build and assign {@link LayoutTransition} to various containers. Should - * only be assigned after initial layout is complete. - */ - private void ensureLayoutTransitions() { - if (mShowAppImmediatePkg != null) { - // If we are skipping right to showing an app, we don't care about transitions. - return; - } - // skip when already setup - if (mChart.getLayoutTransition() != null) return; - - mTabsContainer.setLayoutTransition(buildLayoutTransition()); - mHeader.setLayoutTransition(buildLayoutTransition()); - mNetworkSwitchesContainer.setLayoutTransition(buildLayoutTransition()); - - final LayoutTransition chartTransition = buildLayoutTransition(); - chartTransition.disableTransitionType(LayoutTransition.APPEARING); - chartTransition.disableTransitionType(LayoutTransition.DISAPPEARING); - mChart.setLayoutTransition(chartTransition); - } - - private static LayoutTransition buildLayoutTransition() { - final LayoutTransition transition = new LayoutTransition(); - if (TEST_ANIM) { - transition.setDuration(1500); - } - transition.setAnimateParentHierarchy(false); - return transition; - } - - /** - * Rebuild all tabs based on {@link NetworkPolicyEditor} and - * {@link #mShowWifi}, hiding the tabs entirely when applicable. Selects - * first tab, and kicks off a full rebind of body contents. - */ - private void updateTabs() { - final Context context = getActivity(); - mTabHost.clearAllTabs(); - - int simCount = mTelephonyManager.getSimCount(); - - List<SubscriptionInfo> sirs = mSubscriptionManager.getActiveSubscriptionInfoList(); - if (sirs != null) { - for (SubscriptionInfo sir : sirs) { - addMobileTab(context, sir, (simCount > 1)); - } - } - - if (mShowWifi && hasWifiRadio(context)) { - mTabHost.addTab(buildTabSpec(TAB_WIFI, R.string.data_usage_tab_wifi)); - } - - if (mShowEthernet && hasEthernet(context)) { - mTabHost.addTab(buildTabSpec(TAB_ETHERNET, R.string.data_usage_tab_ethernet)); - } - - final boolean noTabs = mTabWidget.getTabCount() == 0; - final boolean multipleTabs = mTabWidget.getTabCount() > 1; - mTabWidget.setVisibility(multipleTabs ? View.VISIBLE : View.GONE); - if (mIntentTab != null) { - if (Objects.equal(mIntentTab, mTabHost.getCurrentTabTag())) { - // already hit updateBody() when added; ignore - updateBody(); - } else { - mTabHost.setCurrentTabByTag(mIntentTab); - } - mIntentTab = null; - } else if (noTabs) { - // no usable tabs, so hide body - updateBody(); - } else { - // already hit updateBody() when added; ignore - } - } - - /** - * Factory that provide empty {@link View} to make {@link TabHost} happy. - */ - private TabContentFactory mEmptyTabContent = new TabContentFactory() { - @Override - public View createTabContent(String tag) { - return new View(mTabHost.getContext()); - } - }; - - /** - * Build {@link TabSpec} with thin indicator, and empty content. - */ - private TabSpec buildTabSpec(String tag, int titleRes) { - return mTabHost.newTabSpec(tag).setIndicator(getText(titleRes)).setContent( - mEmptyTabContent); - } - - /** - * Build {@link TabSpec} with thin indicator, and empty content. - */ - private TabSpec buildTabSpec(String tag, CharSequence title) { - return mTabHost.newTabSpec(tag).setIndicator(title).setContent( - mEmptyTabContent); - } - - - private OnTabChangeListener mTabListener = new OnTabChangeListener() { - @Override - public void onTabChanged(String tabId) { - // user changed tab; update body - updateBody(); - } - }; - - /** - * Update body content based on current tab. Loads - * {@link NetworkStatsHistory} and {@link NetworkPolicy} from system, and - * binds them to visible controls. - */ - private void updateBody() { - mBinding = true; - if (!isAdded()) return; - - final Context context = getActivity(); - final String currentTab = mTabHost.getCurrentTabTag(); - final boolean isAdmin = mUserManager.isAdminUser(); - - if (currentTab == null) { - Log.w(TAG, "no tab selected; hiding body"); - mListView.setVisibility(View.GONE); - return; - } else { - mListView.setVisibility(View.VISIBLE); - } - - mCurrentTab = currentTab; - - if (LOGD) Log.d(TAG, "updateBody() with currentTab=" + currentTab); - - mDataEnabledSupported = isAdmin; - mDisableAtLimitSupported = true; - - // TODO: remove mobile tabs when SIM isn't ready probably by - // TODO: using SubscriptionManager.getActiveSubscriptionInfoList. - if (LOGD) Log.d(TAG, "updateBody() isMobileTab=" + isMobileTab(currentTab)); - - if (isMobileTab(currentTab)) { - if (LOGD) Log.d(TAG, "updateBody() mobile tab"); - setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_mobile); - setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_mobile_limit); - mDataEnabledSupported = isMobileDataAvailable(getSubId(currentTab)); - - // Match mobile traffic for this subscriber, but normalize it to - // catch any other merged subscribers. - mTemplate = buildTemplateMobileAll( - getActiveSubscriberId(context, getSubId(currentTab))); - mTemplate = NetworkTemplate.normalize(mTemplate, - mTelephonyManager.getMergedSubscriberIds()); - - } else if (TAB_3G.equals(currentTab)) { - if (LOGD) Log.d(TAG, "updateBody() 3g tab"); - setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_3g); - setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_3g_limit); - // TODO: bind mDataEnabled to 3G radio state - mTemplate = buildTemplateMobile3gLower(getActiveSubscriberId(context)); - - } else if (TAB_4G.equals(currentTab)) { - if (LOGD) Log.d(TAG, "updateBody() 4g tab"); - setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_4g); - setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_4g_limit); - // TODO: bind mDataEnabled to 4G radio state - mTemplate = buildTemplateMobile4g(getActiveSubscriberId(context)); - - } else if (TAB_WIFI.equals(currentTab)) { - // wifi doesn't have any controls - if (LOGD) Log.d(TAG, "updateBody() wifi tab"); - mDataEnabledSupported = false; - mDisableAtLimitSupported = false; - mTemplate = buildTemplateWifiWildcard(); - - } else if (TAB_ETHERNET.equals(currentTab)) { - // ethernet doesn't have any controls - if (LOGD) Log.d(TAG, "updateBody() ethernet tab"); - mDataEnabledSupported = false; - mDisableAtLimitSupported = false; - mTemplate = buildTemplateEthernet(); - - } else { - if (LOGD) Log.d(TAG, "updateBody() unknown tab"); - throw new IllegalStateException("unknown tab: " + currentTab); - } - - mPolicyEditor.read(); - final NetworkPolicy policy = mPolicyEditor.getPolicy(mTemplate); - if (policy != null) { - final long currentTime = System.currentTimeMillis(); - final long start = computeLastCycleBoundary(currentTime, policy); - final long end = currentTime; - long totalBytes = 0; - - try { - totalBytes = mStatsService.getNetworkTotalBytes(policy.template, start, end); - } catch (RuntimeException e) { - } catch (RemoteException e) { - } - - if (policy.isOverLimit(totalBytes) && policy.lastLimitSnooze < start) { - setPreferenceSummary(mDataEnabledView, - getString(R.string.data_usage_cellular_data_summary)); - } else { - final TextView summary = (TextView) mDataEnabledView - .findViewById(android.R.id.summary); - summary.setVisibility(View.GONE); - } - } - - // kick off loader for network history - // TODO: consider chaining two loaders together instead of reloading - // network history when showing app detail. - getLoaderManager().restartLoader(LOADER_CHART_DATA, - ChartDataLoader.buildArgs(mTemplate, mCurrentApp), mChartDataCallbacks); - - // detail mode can change visible menus, invalidate - getActivity().invalidateOptionsMenu(); - - mBinding = false; - - int seriesColor = context.getColor(R.color.sim_noitification); - if (mCurrentTab != null && mCurrentTab.length() > TAB_MOBILE.length() ){ - final int slotId = Integer.parseInt(mCurrentTab.substring(TAB_MOBILE.length(), - mCurrentTab.length())); - final SubscriptionInfo sir = mSubscriptionManager - .getActiveSubscriptionInfoForSimSlotIndex(slotId); - - if (sir != null) { - seriesColor = sir.getIconTint(); - } - } - - final int secondaryColor = Color.argb(127, Color.red(seriesColor), Color.green(seriesColor), - Color.blue(seriesColor)); - mSeries.setChartColor(Color.BLACK, seriesColor, secondaryColor); - mDetailedSeries.setChartColor(Color.BLACK, seriesColor, secondaryColor); - } - - private boolean isAppDetailMode() { - return mCurrentApp != null; - } - - /** - * Update UID details panels to match {@link #mCurrentApp}, showing or - * hiding them depending on {@link #isAppDetailMode()}. - */ - private void updateAppDetail() { - final Context context = getActivity(); - final PackageManager pm = context.getPackageManager(); - final LayoutInflater inflater = getActivity().getLayoutInflater(); - - if (isAppDetailMode()) { - mAppDetail.setVisibility(View.VISIBLE); - mCycleAdapter.setChangeVisible(false); - mChart.setVisibility(View.GONE); - } else { - mAppDetail.setVisibility(View.GONE); - mCycleAdapter.setChangeVisible(true); - mChart.setVisibility(View.VISIBLE); - - // hide detail stats when not in detail mode - mChart.bindDetailNetworkStats(null); - return; - } - - // remove warning/limit sweeps while in detail mode - mChart.bindNetworkPolicy(null); - - // show icon and all labels appearing under this app - final int uid = mCurrentApp.key; - final UidDetail detail = mUidDetailProvider.getUidDetail(uid, true); - mAppIcon.setImageDrawable(detail.icon); - - mAppTitles.removeAllViews(); - - View title = null; - if (detail.detailLabels != null) { - final int n = detail.detailLabels.length; - for (int i = 0; i < n; ++i) { - CharSequence label = detail.detailLabels[i]; - CharSequence contentDescription = detail.detailContentDescriptions[i]; - title = inflater.inflate(R.layout.data_usage_app_title, mAppTitles, false); - TextView appTitle = (TextView) title.findViewById(R.id.app_title); - appTitle.setText(label); - appTitle.setContentDescription(contentDescription); - mAppTitles.addView(title); - } - } else { - title = inflater.inflate(R.layout.data_usage_app_title, mAppTitles, false); - TextView appTitle = (TextView) title.findViewById(R.id.app_title); - appTitle.setText(detail.label); - appTitle.setContentDescription(detail.contentDescription); - mAppTitles.addView(title); - } - - // Remember last slot for summary - if (title != null) { - mAppTotal = (TextView) title.findViewById(R.id.app_summary); - } else { - mAppTotal = null; - } - - // enable settings button when package provides it - final String[] packageNames = pm.getPackagesForUid(uid); - if (packageNames != null && packageNames.length > 0) { - mAppSettingsIntent = new Intent(Intent.ACTION_MANAGE_NETWORK_USAGE); - mAppSettingsIntent.addCategory(Intent.CATEGORY_DEFAULT); - - // Search for match across all packages - boolean matchFound = false; - for (String packageName : packageNames) { - mAppSettingsIntent.setPackage(packageName); - if (pm.resolveActivity(mAppSettingsIntent, 0) != null) { - matchFound = true; - break; - } - } - - mAppSettings.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - if (!isAdded()) { - return; - } - - // TODO: target towards entire UID instead of just first package - getActivity().startActivityAsUser(mAppSettingsIntent, - new UserHandle(UserHandle.getUserId(uid))); - } - }); - mAppSettings.setEnabled(matchFound); - mAppSettings.setVisibility(View.VISIBLE); - - } else { - mAppSettingsIntent = null; - mAppSettings.setOnClickListener(null); - mAppSettings.setVisibility(View.GONE); - } - - updateDetailData(); - - if (UserHandle.isApp(uid) && !mPolicyManager.getRestrictBackground() - && isBandwidthControlEnabled() && hasReadyMobileRadio(context)) { - setPreferenceTitle(mAppRestrictView, R.string.data_usage_app_restrict_background); - setPreferenceSummary(mAppRestrictView, - getString(R.string.data_usage_app_restrict_background_summary)); - - mAppRestrictView.setVisibility(View.VISIBLE); - mAppRestrict.setChecked(getAppRestrictBackground()); - - } else { - mAppRestrictView.setVisibility(View.GONE); - } - } - - private void setPolicyWarningBytes(long warningBytes) { - if (LOGD) Log.d(TAG, "setPolicyWarningBytes()"); - mPolicyEditor.setPolicyWarningBytes(mTemplate, warningBytes); - updatePolicy(false); - } - - private void setPolicyLimitBytes(long limitBytes) { - if (LOGD) Log.d(TAG, "setPolicyLimitBytes()"); - mPolicyEditor.setPolicyLimitBytes(mTemplate, limitBytes); - updatePolicy(false); - } - - private boolean isMobileDataEnabled(int subId) { - if (LOGD) Log.d(TAG, "isMobileDataEnabled:+ subId=" + subId); - boolean isEnable = false; - if (mMobileDataEnabled.get(String.valueOf(subId)) != null) { - //TODO: deprecate and remove this once enabled flag is on policy - //Multiple Subscriptions, the value need to be reseted - isEnable = mMobileDataEnabled.get(String.valueOf(subId)).booleanValue(); - if (LOGD) { - Log.d(TAG, "isMobileDataEnabled: != null, subId=" + subId - + " isEnable=" + isEnable); - } - mMobileDataEnabled.put(String.valueOf(subId), null); - } else { - // SUB SELECT - isEnable = mTelephonyManager.getDataEnabled(subId); - if (LOGD) { - Log.d(TAG, "isMobileDataEnabled: == null, subId=" + subId - + " isEnable=" + isEnable); - } - } - return isEnable; - } - - private void setMobileDataEnabled(int subId, boolean enabled) { - if (LOGD) Log.d(TAG, "setMobileDataEnabled()"); - mTelephonyManager.setDataEnabled(subId, enabled); - mMobileDataEnabled.put(String.valueOf(subId), enabled); - updatePolicy(false); - } - - private boolean isNetworkPolicyModifiable(NetworkPolicy policy) { - return policy != null && isBandwidthControlEnabled() && mDataEnabled.isChecked() - && mUserManager.isAdminUser(); - } - - private boolean isBandwidthControlEnabled() { - try { - return mNetworkService.isBandwidthControlEnabled(); - } catch (RemoteException e) { - Log.w(TAG, "problem talking with INetworkManagementService: " + e); - return false; - } - } - - public void setRestrictBackground(boolean restrictBackground) { - mPolicyManager.setRestrictBackground(restrictBackground); - updateMenuTitles(); - ConditionManager.get(getContext()).getCondition(BackgroundDataCondition.class) - .refreshState(); - } - - private boolean getAppRestrictBackground() { - final int uid = mCurrentApp.key; - final int uidPolicy = mPolicyManager.getUidPolicy(uid); - return (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0; - } - - private void setAppRestrictBackground(boolean restrictBackground) { - if (LOGD) Log.d(TAG, "setAppRestrictBackground()"); - final int uid = mCurrentApp.key; - mPolicyManager.setUidPolicy( - uid, restrictBackground ? POLICY_REJECT_METERED_BACKGROUND : POLICY_NONE); - mAppRestrict.setChecked(restrictBackground); - } - - /** - * Update chart sweeps and cycle list to reflect {@link NetworkPolicy} for - * current {@link #mTemplate}. - */ - private void updatePolicy(boolean refreshCycle) { - boolean dataEnabledVisible = mDataEnabledSupported; - boolean disableAtLimitVisible = mDisableAtLimitSupported; - - if (isAppDetailMode()) { - dataEnabledVisible = false; - disableAtLimitVisible = false; - } - - // TODO: move enabled state directly into policy - if (isMobileTab(mCurrentTab)) { - mBinding = true; - mDataEnabled.setChecked(isMobileDataEnabled(getSubId(mCurrentTab))); - mBinding = false; - } - - final NetworkPolicy policy = mPolicyEditor.getPolicy(mTemplate); - //SUB SELECT - if (isNetworkPolicyModifiable(policy) && isMobileDataAvailable(getSubId(mCurrentTab))) { - mDisableAtLimit.setChecked(policy != null && policy.limitBytes != LIMIT_DISABLED); - if (!isAppDetailMode()) { - mChart.bindNetworkPolicy(policy); - } - - } else { - // controls are disabled; don't bind warning/limit sweeps - disableAtLimitVisible = false; - mChart.bindNetworkPolicy(null); - } - - mDataEnabledView.setVisibility(dataEnabledVisible ? View.VISIBLE : View.GONE); - mDisableAtLimitView.setVisibility(disableAtLimitVisible ? View.VISIBLE : View.GONE); - - if (refreshCycle) { - // generate cycle list based on policy and available history - updateCycleList(policy); - } - } - - /** - * Rebuild {@link #mCycleAdapter} based on {@link NetworkPolicy#cycleDay} - * and available {@link NetworkStatsHistory} data. Always selects the newest - * item, updating the inspection range on {@link #mChart}. - */ - private void updateCycleList(NetworkPolicy policy) { - // stash away currently selected cycle to try restoring below - final CycleItem previousItem = (CycleItem) mCycleSpinner.getSelectedItem(); - mCycleAdapter.clear(); - - final Context context = mCycleSpinner.getContext(); - NetworkStatsHistory.Entry entry = null; - - long historyStart = Long.MAX_VALUE; - long historyEnd = Long.MIN_VALUE; - if (mChartData != null) { - historyStart = mChartData.network.getStart(); - historyEnd = mChartData.network.getEnd(); - } - - final long now = System.currentTimeMillis(); - if (historyStart == Long.MAX_VALUE) historyStart = now; - if (historyEnd == Long.MIN_VALUE) historyEnd = now + 1; - - boolean hasCycles = false; - if (policy != null) { - // find the next cycle boundary - long cycleEnd = computeNextCycleBoundary(historyEnd, policy); - - // walk backwards, generating all valid cycle ranges - while (cycleEnd > historyStart) { - final long cycleStart = computeLastCycleBoundary(cycleEnd, policy); - Log.d(TAG, "generating cs=" + cycleStart + " to ce=" + cycleEnd + " waiting for hs=" - + historyStart); - - final boolean includeCycle; - if (mChartData != null) { - entry = mChartData.network.getValues(cycleStart, cycleEnd, entry); - includeCycle = (entry.rxBytes + entry.txBytes) > 0; - } else { - includeCycle = true; - } - - if (includeCycle) { - mCycleAdapter.add(new CycleItem(context, cycleStart, cycleEnd)); - hasCycles = true; - } - cycleEnd = cycleStart; - } - - // one last cycle entry to modify policy cycle day - mCycleAdapter.setChangePossible(isNetworkPolicyModifiable(policy)); - } - - if (!hasCycles) { - // no policy defined cycles; show entry for each four-week period - long cycleEnd = historyEnd; - while (cycleEnd > historyStart) { - final long cycleStart = cycleEnd - (DateUtils.WEEK_IN_MILLIS * 4); - - final boolean includeCycle; - if (mChartData != null) { - entry = mChartData.network.getValues(cycleStart, cycleEnd, entry); - includeCycle = (entry.rxBytes + entry.txBytes) > 0; - } else { - includeCycle = true; - } - - if (includeCycle) { - mCycleAdapter.add(new CycleItem(context, cycleStart, cycleEnd)); - } - cycleEnd = cycleStart; - } - - mCycleAdapter.setChangePossible(false); - } - - // force pick the current cycle (first item) - if (mCycleAdapter.getCount() > 0) { - final int position = mCycleAdapter.findNearestPosition(previousItem); - mCycleSpinner.setSelection(position); - - // only force-update cycle when changed; skipping preserves any - // user-defined inspection region. - final CycleItem selectedItem = mCycleAdapter.getItem(position); - if (!Objects.equal(selectedItem, previousItem)) { - mCycleListener.onItemSelected(mCycleSpinner, null, position, 0); - } else { - // but still kick off loader for detailed list - updateDetailData(); - } - } else { - updateDetailData(); - } - } - - private void disableDataForOtherSubscriptions(SubscriptionInfo currentSir) { - if (mSubInfoList != null) { - for (SubscriptionInfo subInfo : mSubInfoList) { - if (subInfo.getSubscriptionId() != currentSir.getSubscriptionId()) { - setMobileDataEnabled(subInfo.getSubscriptionId(), false); - } - } - } - } - - private View.OnClickListener mDataEnabledListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - if (mBinding) return; - - final boolean enabled = !mDataEnabled.isChecked(); - final String currentTab = mCurrentTab; - if (isMobileTab(currentTab)) { - MetricsLogger.action(getContext(), MetricsLogger.ACTION_CELL_DATA_TOGGLE, enabled); - if (enabled) { - // If we are showing the Sim Card tile then we are a Multi-Sim device. - if (Utils.showSimCardTile(getActivity())) { - handleMultiSimDataDialog(); - } else { - setMobileDataEnabled(getSubId(currentTab), true); - } - } else { - // disabling data; show confirmation dialog which eventually - // calls setMobileDataEnabled() once user confirms. - ConfirmDataDisableFragment.show(DataUsageSummary.this, getSubId(mCurrentTab)); - } - } - - updatePolicy(false); - } - }; - - private void handleMultiSimDataDialog() { - final Context context = getActivity(); - final SubscriptionInfo currentSir = getCurrentTabSubInfo(context); - - //If sim has not loaded after toggling data switch, return. - if (currentSir == null) { - return; - } - - final SubscriptionInfo nextSir = mSubscriptionManager.getDefaultDataSubscriptionInfo(); - - // If the device is single SIM or is enabling data on the active data SIM then forgo - // the pop-up. - if (!Utils.showSimCardTile(context) || - (nextSir != null && currentSir != null && - currentSir.getSubscriptionId() == nextSir.getSubscriptionId())) { - setMobileDataEnabled(currentSir.getSubscriptionId(), true); - if (nextSir != null && currentSir != null && - currentSir.getSubscriptionId() == nextSir.getSubscriptionId()) { - disableDataForOtherSubscriptions(currentSir); - } - updateBody(); - return; - } - - final String previousName = (nextSir == null) - ? context.getResources().getString(R.string.sim_selection_required_pref) - : nextSir.getDisplayName().toString(); - - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - - builder.setTitle(R.string.sim_change_data_title); - builder.setMessage(getActivity().getResources().getString(R.string.sim_change_data_message, - currentSir.getDisplayName(), previousName)); - - builder.setPositiveButton(R.string.okay, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) { - mSubscriptionManager.setDefaultDataSubId(currentSir.getSubscriptionId()); - setMobileDataEnabled(currentSir.getSubscriptionId(), true); - disableDataForOtherSubscriptions(currentSir); - updateBody(); - } - }); - builder.setNegativeButton(R.string.cancel, null); - - builder.create().show(); - } - - private View.OnClickListener mDisableAtLimitListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - final boolean disableAtLimit = !mDisableAtLimit.isChecked(); - if (disableAtLimit) { - // enabling limit; show confirmation dialog which eventually - // calls setPolicyLimitBytes() once user confirms. - ConfirmLimitFragment.show(DataUsageSummary.this); - } else { - setPolicyLimitBytes(LIMIT_DISABLED); - } - } - }; - - private View.OnClickListener mAppRestrictListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - final boolean restrictBackground = !mAppRestrict.isChecked(); - - if (restrictBackground) { - // enabling restriction; show confirmation dialog which - // eventually calls setRestrictBackground() once user - // confirms. - ConfirmAppRestrictFragment.show(DataUsageSummary.this); - } else { - setAppRestrictBackground(false); - } - } - }; - - private OnItemClickListener mListListener = new OnItemClickListener() { - @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - final Context context = view.getContext(); - final AppItem app = (AppItem) parent.getItemAtPosition(position); - - // TODO: sigh, remove this hack once we understand 6450986 - if (mUidDetailProvider == null || app == null) return; - - final UidDetail detail = mUidDetailProvider.getUidDetail(app.key, true); - AppDetailsFragment.show(DataUsageSummary.this, app, detail.label); - } - }; - - private OnItemSelectedListener mCycleListener = new OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { - final CycleItem cycle = (CycleItem) parent.getItemAtPosition(position); - if (cycle instanceof CycleChangeItem) { - // show cycle editor; will eventually call setPolicyCycleDay() - // when user finishes editing. - CycleEditorFragment.show(DataUsageSummary.this); - - // reset spinner to something other than "change cycle..." - mCycleSpinner.setSelection(0); - - } else { - if (LOGD) { - Log.d(TAG, "showing cycle " + cycle + ", start=" + cycle.start + ", end=" - + cycle.end + "]"); - } - - // update chart to show selected cycle, and update detail data - // to match updated sweep bounds. - mChart.setVisibleRange(cycle.start, cycle.end); - - updateDetailData(); - } - } - - @Override - public void onNothingSelected(AdapterView<?> parent) { - // ignored - } - }; - - /** - * Update details based on {@link #mChart} inspection range depending on - * current mode. In network mode, updates {@link #mAdapter} with sorted list - * of applications data usage, and when {@link #isAppDetailMode()} update - * app details. - */ - private void updateDetailData() { - if (LOGD) Log.d(TAG, "updateDetailData()"); - - final long start = mChart.getInspectStart(); - final long end = mChart.getInspectEnd(); - final long now = System.currentTimeMillis(); - - final Context context = getActivity(); - - NetworkStatsHistory.Entry entry = null; - if (isAppDetailMode() && mChartData != null && mChartData.detail != null) { - // bind foreground/background to piechart and labels - entry = mChartData.detailDefault.getValues(start, end, now, entry); - final long defaultBytes = entry.rxBytes + entry.txBytes; - entry = mChartData.detailForeground.getValues(start, end, now, entry); - final long foregroundBytes = entry.rxBytes + entry.txBytes; - final long totalBytes = defaultBytes + foregroundBytes; - - if (mAppTotal != null) { - mAppTotal.setText(Formatter.formatFileSize(context, totalBytes)); - } - mAppBackground.setText(Formatter.formatFileSize(context, defaultBytes)); - mAppForeground.setText(Formatter.formatFileSize(context, foregroundBytes)); - - // and finally leave with summary data for label below - entry = mChartData.detail.getValues(start, end, now, null); - - getLoaderManager().destroyLoader(LOADER_SUMMARY); - - mCycleSummary.setVisibility(View.GONE); - - } else { - if (mChartData != null) { - entry = mChartData.network.getValues(start, end, now, null); - } - - mCycleSummary.setVisibility(View.VISIBLE); - - // kick off loader for detailed stats - getLoaderManager().restartLoader(LOADER_SUMMARY, - SummaryForAllUidLoader.buildArgs(mTemplate, start, end), mSummaryCallbacks); - } - - final long totalBytes = entry != null ? entry.rxBytes + entry.txBytes : 0; - final String totalPhrase = Formatter.formatFileSize(context, totalBytes); - mCycleSummary.setText(totalPhrase); - - if (isMobileTab(mCurrentTab) || TAB_3G.equals(mCurrentTab) - || TAB_4G.equals(mCurrentTab)) { - if (isAppDetailMode()) { - mDisclaimer.setVisibility(View.GONE); - } else { - mDisclaimer.setVisibility(View.VISIBLE); - } - } else { - mDisclaimer.setVisibility(View.GONE); - } - - // initial layout is finished above, ensure we have transitions - ensureLayoutTransitions(); - } - - private final LoaderCallbacks<ChartData> mChartDataCallbacks = new LoaderCallbacks< - ChartData>() { - @Override - public Loader<ChartData> onCreateLoader(int id, Bundle args) { - return new ChartDataLoader(getActivity(), mStatsSession, args); - } - - @Override - public void onLoadFinished(Loader<ChartData> loader, ChartData data) { - mChartData = data; - mChart.bindNetworkStats(mChartData.network); - mChart.bindDetailNetworkStats(mChartData.detail); - - // calcuate policy cycles based on available data - updatePolicy(true); - updateAppDetail(); - - // force scroll to top of body when showing detail - if (mChartData.detail != null) { - mListView.smoothScrollToPosition(0); - } - } - - @Override - public void onLoaderReset(Loader<ChartData> loader) { - mChartData = null; - mChart.bindNetworkStats(null); - mChart.bindDetailNetworkStats(null); - } - }; - - private final LoaderCallbacks<NetworkStats> mSummaryCallbacks = new LoaderCallbacks< - NetworkStats>() { - @Override - public Loader<NetworkStats> onCreateLoader(int id, Bundle args) { - return new SummaryForAllUidLoader(getActivity(), mStatsSession, args); - } - - @Override - public void onLoadFinished(Loader<NetworkStats> loader, NetworkStats data) { - final int[] restrictedUids = mPolicyManager.getUidsWithPolicy( - POLICY_REJECT_METERED_BACKGROUND); - mAdapter.bindStats(data, restrictedUids); - updateEmptyVisible(); - } - - @Override - public void onLoaderReset(Loader<NetworkStats> loader) { - mAdapter.bindStats(null, new int[0]); - updateEmptyVisible(); - } - - private void updateEmptyVisible() { - final boolean isEmpty = mAdapter.isEmpty() && !isAppDetailMode(); - mEmpty.setVisibility(isEmpty ? View.VISIBLE : View.GONE); - mStupidPadding.setVisibility(isEmpty ? View.VISIBLE : View.GONE); - } - }; - - private static String getActiveSubscriberId(Context context) { - final TelephonyManager tele = TelephonyManager.from(context); - final String actualSubscriberId = tele.getSubscriberId(); - String retVal = SystemProperties.get(TEST_SUBSCRIBER_PROP, actualSubscriberId); - if (LOGD) Log.d(TAG, "getActiveSubscriberId=" + retVal + " actualSubscriberId=" + actualSubscriberId); - return retVal; - } - - private static String getActiveSubscriberId(Context context, int subId) { - final TelephonyManager tele = TelephonyManager.from(context); - String retVal = tele.getSubscriberId(subId); - if (LOGD) Log.d(TAG, "getActiveSubscriberId=" + retVal + " subId=" + subId); - return retVal; - } - - private DataUsageChartListener mChartListener = new DataUsageChartListener() { - @Override - public void onWarningChanged() { - setPolicyWarningBytes(mChart.getWarningBytes()); - } - - @Override - public void onLimitChanged() { - setPolicyLimitBytes(mChart.getLimitBytes()); - updateBody(); - } - - @Override - public void requestWarningEdit() { - WarningEditorFragment.show(DataUsageSummary.this); - } - - @Override - public void requestLimitEdit() { - LimitEditorFragment.show(DataUsageSummary.this); - } - }; - - /** - * List item that reflects a specific data usage cycle. - */ - public static class CycleItem implements Comparable<CycleItem> { - public CharSequence label; - public long start; - public long end; - - CycleItem(CharSequence label) { - this.label = label; - } - - public CycleItem(Context context, long start, long end) { - this.label = formatDateRange(context, start, end); - this.start = start; - this.end = end; - } - - @Override - public String toString() { - return label.toString(); - } - - @Override - public boolean equals(Object o) { - if (o instanceof CycleItem) { - final CycleItem another = (CycleItem) o; - return start == another.start && end == another.end; - } - return false; - } - - @Override - public int compareTo(CycleItem another) { - return Long.compare(start, another.start); - } - } - - private static final StringBuilder sBuilder = new StringBuilder(50); - private static final java.util.Formatter sFormatter = new java.util.Formatter( - sBuilder, Locale.getDefault()); - - public static String formatDateRange(Context context, long start, long end) { - final int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_MONTH; - - synchronized (sBuilder) { - sBuilder.setLength(0); - return DateUtils.formatDateRange(context, sFormatter, start, end, flags, null) - .toString(); - } - } - - /** - * Special-case data usage cycle that triggers dialog to change - * {@link NetworkPolicy#cycleDay}. - */ - public static class CycleChangeItem extends CycleItem { - public CycleChangeItem(Context context) { - super(context.getString(R.string.data_usage_change_cycle)); - } - } - - public static class CycleAdapter extends ArrayAdapter<CycleItem> { - private boolean mChangePossible = false; - private boolean mChangeVisible = false; - - private final CycleChangeItem mChangeItem; - - public CycleAdapter(Context context) { - super(context, R.layout.data_usage_cycle_item); - setDropDownViewResource(R.layout.data_usage_cycle_item_dropdown); - mChangeItem = new CycleChangeItem(context); - } - - public void setChangePossible(boolean possible) { - mChangePossible = possible; - updateChange(); - } - - public void setChangeVisible(boolean visible) { - mChangeVisible = visible; - updateChange(); - } - - private void updateChange() { - remove(mChangeItem); - if (mChangePossible && mChangeVisible) { - add(mChangeItem); - } - } - - /** - * Find position of {@link CycleItem} in this adapter which is nearest - * the given {@link CycleItem}. - */ - public int findNearestPosition(CycleItem target) { - if (target != null) { - final int count = getCount(); - for (int i = count - 1; i >= 0; i--) { - final CycleItem item = getItem(i); - if (item instanceof CycleChangeItem) { - continue; - } else if (item.compareTo(target) >= 0) { - return i; - } - } - } - return 0; - } - } - - /** - * Adapter of applications, sorted by total usage descending. - */ - public static class DataUsageAdapter extends BaseAdapter { - private final UidDetailProvider mProvider; - private final int mInsetSide; - private final UserManager mUm; - - private ArrayList<AppItem> mItems = Lists.newArrayList(); - private long mLargest; - - public DataUsageAdapter(final UserManager userManager, UidDetailProvider provider, int insetSide) { - mProvider = checkNotNull(provider); - mInsetSide = insetSide; - mUm = userManager; - } - - /** - * Bind the given {@link NetworkStats}, or {@code null} to clear list. - */ - public void bindStats(NetworkStats stats, int[] restrictedUids) { - mItems.clear(); - mLargest = 0; - - final int currentUserId = ActivityManager.getCurrentUser(); - final List<UserHandle> profiles = mUm.getUserProfiles(); - final SparseArray<AppItem> knownItems = new SparseArray<AppItem>(); - - NetworkStats.Entry entry = null; - final int size = stats != null ? stats.size() : 0; - for (int i = 0; i < size; i++) { - entry = stats.getValues(i, entry); - - // Decide how to collapse items together - final int uid = entry.uid; - - final int collapseKey; - final int category; - final int userId = UserHandle.getUserId(uid); - if (UserHandle.isApp(uid)) { - if (profiles.contains(new UserHandle(userId))) { - if (userId != currentUserId) { - // Add to a managed user item. - final int managedKey = UidDetailProvider.buildKeyForUser(userId); - accumulate(managedKey, knownItems, entry, - AppItem.CATEGORY_USER); - } - // Add to app item. - collapseKey = uid; - category = AppItem.CATEGORY_APP; - } else { - // If it is a removed user add it to the removed users' key - final UserInfo info = mUm.getUserInfo(userId); - if (info == null) { - collapseKey = UID_REMOVED; - category = AppItem.CATEGORY_APP; - } else { - // Add to other user item. - collapseKey = UidDetailProvider.buildKeyForUser(userId); - category = AppItem.CATEGORY_USER; - } - } - } else if (uid == UID_REMOVED || uid == UID_TETHERING) { - collapseKey = uid; - category = AppItem.CATEGORY_APP; - } else { - collapseKey = android.os.Process.SYSTEM_UID; - category = AppItem.CATEGORY_APP; - } - accumulate(collapseKey, knownItems, entry, category); - } - - final int restrictedUidsMax = restrictedUids.length; - for (int i = 0; i < restrictedUidsMax; ++i) { - final int uid = restrictedUids[i]; - // Only splice in restricted state for current user or managed users - if (!profiles.contains(new UserHandle(UserHandle.getUserId(uid)))) { - continue; - } - - AppItem item = knownItems.get(uid); - if (item == null) { - item = new AppItem(uid); - item.total = -1; - mItems.add(item); - knownItems.put(item.key, item); - } - item.restricted = true; - } - - if (!mItems.isEmpty()) { - final AppItem title = new AppItem(); - title.category = AppItem.CATEGORY_APP_TITLE; - mItems.add(title); - } - - Collections.sort(mItems); - notifyDataSetChanged(); - } - - /** - * Accumulate data usage of a network stats entry for the item mapped by the collapse key. - * Creates the item if needed. - * - * @param collapseKey the collapse key used to map the item. - * @param knownItems collection of known (already existing) items. - * @param entry the network stats entry to extract data usage from. - * @param itemCategory the item is categorized on the list view by this category. Must be - * either AppItem.APP_ITEM_CATEGORY or AppItem.MANAGED_USER_ITEM_CATEGORY - */ - private void accumulate(int collapseKey, final SparseArray<AppItem> knownItems, - NetworkStats.Entry entry, int itemCategory) { - final int uid = entry.uid; - AppItem item = knownItems.get(collapseKey); - if (item == null) { - item = new AppItem(collapseKey); - item.category = itemCategory; - mItems.add(item); - knownItems.put(item.key, item); - } - item.addUid(uid); - item.total += entry.rxBytes + entry.txBytes; - if (mLargest < item.total) { - mLargest = item.total; - } - } - - @Override - public int getCount() { - return mItems.size(); - } - - @Override - public Object getItem(int position) { - return mItems.get(position); - } - - @Override - public long getItemId(int position) { - return mItems.get(position).key; - } - - /** - * See {@link #getItemViewType} for the view types. - */ - @Override - public int getViewTypeCount() { - return 2; - } - - /** - * Returns 1 for separator items and 0 for anything else. - */ - @Override - public int getItemViewType(int position) { - final AppItem item = mItems.get(position); - if (item.category == AppItem.CATEGORY_APP_TITLE) { - return 1; - } else { - return 0; - } - } - - @Override - public boolean areAllItemsEnabled() { - return false; - } - - @Override - public boolean isEnabled(int position) { - if (position > mItems.size()) { - throw new ArrayIndexOutOfBoundsException(); - } - return getItemViewType(position) == 0; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - final AppItem item = mItems.get(position); - LayoutInflater inflater = LayoutInflater.from(parent.getContext()); - if (getItemViewType(position) == 1) { - if (convertView == null) { - convertView = Utils.inflateCategoryHeader(inflater, parent); - } - - final TextView title = (TextView) convertView.findViewById(android.R.id.title); - title.setText(R.string.data_usage_app); - - } else { - if (convertView == null) { - convertView = inflater.inflate(R.layout.data_usage_item, parent, false); - inflater.inflate(R.layout.widget_progress_bar, - (ViewGroup) convertView.findViewById(android.R.id.widget_frame)); - - if (mInsetSide > 0) { - convertView.setPaddingRelative(mInsetSide, 0, mInsetSide, 0); - } - } - - final Context context = parent.getContext(); - - final TextView summary = (TextView) convertView.findViewById(android.R.id.summary); - final ProgressBar progress = (ProgressBar) convertView.findViewById( - android.R.id.progress); - - // kick off async load of app details - UidDetailTask.bindView(mProvider, item, convertView); - - if (item.restricted && item.total <= 0) { - summary.setText(R.string.data_usage_app_restricted); - progress.setVisibility(View.GONE); - } else { - summary.setText(Formatter.formatFileSize(context, item.total)); - progress.setVisibility(View.VISIBLE); - } - - final int percentTotal = mLargest != 0 ? (int) (item.total * 100 / mLargest) : 0; - progress.setProgress(percentTotal); - } - - return convertView; - } - } - - /** - * Empty {@link Fragment} that controls display of UID details in - * {@link DataUsageSummary}. - */ - public static class AppDetailsFragment extends Fragment { - private static final String EXTRA_APP = "app"; - - public static void show(DataUsageSummary parent, AppItem app, CharSequence label) { - show(parent, app, label, true); - } - - public static void show(DataUsageSummary parent, AppItem app, CharSequence label, - boolean addToBack) { - if (!parent.isAdded()) return; - - final Bundle args = new Bundle(); - args.putParcelable(EXTRA_APP, app); - - final AppDetailsFragment fragment = new AppDetailsFragment(); - fragment.setArguments(args); - fragment.setTargetFragment(parent, 0); - final FragmentTransaction ft = parent.getFragmentManager().beginTransaction(); - ft.add(fragment, TAG_APP_DETAILS); - if (addToBack) { - ft.addToBackStack(TAG_APP_DETAILS); - } - ft.setBreadCrumbTitle( - parent.getResources().getString(R.string.data_usage_app_summary_title)); - ft.commitAllowingStateLoss(); - } - - @Override - public void onStart() { - super.onStart(); - final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); - target.mCurrentApp = getArguments().getParcelable(EXTRA_APP); - target.updateBody(); - } - - @Override - public void onStop() { - super.onStop(); - final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); - target.mCurrentApp = null; - target.updateBody(); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - getFragmentManager().popBackStack(); - return true; - } - return super.onOptionsItemSelected(item); - } - } - - /** - * Dialog to request user confirmation before setting - * {@link NetworkPolicy#limitBytes}. - */ - public static class ConfirmLimitFragment extends DialogFragment { - private static final String EXTRA_MESSAGE = "message"; - private static final String EXTRA_LIMIT_BYTES = "limitBytes"; - - public static void show(DataUsageSummary parent) { - if (!parent.isAdded()) return; - - final NetworkPolicy policy = parent.mPolicyEditor.getPolicy(parent.mTemplate); - if (policy == null) return; - - final Resources res = parent.getResources(); - final CharSequence message; - final long minLimitBytes = (long) (policy.warningBytes * 1.2f); - final long limitBytes; - - // TODO: customize default limits based on network template - final String currentTab = parent.mCurrentTab; - if (TAB_3G.equals(currentTab)) { - message = res.getString(R.string.data_usage_limit_dialog_mobile); - limitBytes = Math.max(5 * GB_IN_BYTES, minLimitBytes); - } else if (TAB_4G.equals(currentTab)) { - message = res.getString(R.string.data_usage_limit_dialog_mobile); - limitBytes = Math.max(5 * GB_IN_BYTES, minLimitBytes); - } else if (isMobileTab(currentTab)) { - message = res.getString(R.string.data_usage_limit_dialog_mobile); - limitBytes = Math.max(5 * GB_IN_BYTES, minLimitBytes); - } else { - throw new IllegalArgumentException("unknown current tab: " + currentTab); - } - - final Bundle args = new Bundle(); - args.putCharSequence(EXTRA_MESSAGE, message); - args.putLong(EXTRA_LIMIT_BYTES, limitBytes); - - final ConfirmLimitFragment dialog = new ConfirmLimitFragment(); - dialog.setArguments(args); - dialog.setTargetFragment(parent, 0); - dialog.show(parent.getFragmentManager(), TAG_CONFIRM_LIMIT); - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - final Context context = getActivity(); - - final CharSequence message = getArguments().getCharSequence(EXTRA_MESSAGE); - final long limitBytes = getArguments().getLong(EXTRA_LIMIT_BYTES); - - final AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(R.string.data_usage_limit_dialog_title); - builder.setMessage(message); - - builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); - if (target != null) { - target.setPolicyLimitBytes(limitBytes); - } - } - }); - - return builder.create(); - } - } - - /** - * Dialog to edit {@link NetworkPolicy#cycleDay}. - */ - public static class CycleEditorFragment extends DialogFragment { - private static final String EXTRA_TEMPLATE = "template"; - - public static void show(DataUsageSummary parent) { - if (!parent.isAdded()) return; - - final Bundle args = new Bundle(); - args.putParcelable(EXTRA_TEMPLATE, parent.mTemplate); - - final CycleEditorFragment dialog = new CycleEditorFragment(); - dialog.setArguments(args); - dialog.setTargetFragment(parent, 0); - dialog.show(parent.getFragmentManager(), TAG_CYCLE_EDITOR); - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - final Context context = getActivity(); - final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); - final NetworkPolicyEditor editor = target.mPolicyEditor; - - final AlertDialog.Builder builder = new AlertDialog.Builder(context); - final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext()); - - final View view = dialogInflater.inflate(R.layout.data_usage_cycle_editor, null, false); - final NumberPicker cycleDayPicker = (NumberPicker) view.findViewById(R.id.cycle_day); - - final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE); - final int cycleDay = editor.getPolicyCycleDay(template); - - cycleDayPicker.setMinValue(1); - cycleDayPicker.setMaxValue(31); - cycleDayPicker.setValue(cycleDay); - cycleDayPicker.setWrapSelectorWheel(true); - - builder.setTitle(R.string.data_usage_cycle_editor_title); - builder.setView(view); - - builder.setPositiveButton(R.string.data_usage_cycle_editor_positive, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // clear focus to finish pending text edits - cycleDayPicker.clearFocus(); - - final int cycleDay = cycleDayPicker.getValue(); - final String cycleTimezone = new Time().timezone; - editor.setPolicyCycleDay(template, cycleDay, cycleTimezone); - target.updatePolicy(true); - } - }); - - return builder.create(); - } - } - - /** - * Dialog to edit {@link NetworkPolicy#warningBytes}. - */ - public static class WarningEditorFragment extends DialogFragment { - private static final String EXTRA_TEMPLATE = "template"; - - public static void show(DataUsageSummary parent) { - if (!parent.isAdded()) return; - - final Bundle args = new Bundle(); - args.putParcelable(EXTRA_TEMPLATE, parent.mTemplate); - - final WarningEditorFragment dialog = new WarningEditorFragment(); - dialog.setArguments(args); - dialog.setTargetFragment(parent, 0); - dialog.show(parent.getFragmentManager(), TAG_WARNING_EDITOR); - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - final Context context = getActivity(); - final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); - final NetworkPolicyEditor editor = target.mPolicyEditor; - - final AlertDialog.Builder builder = new AlertDialog.Builder(context); - final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext()); - - final View view = dialogInflater.inflate(R.layout.data_usage_bytes_editor, null, false); - final NumberPicker bytesPicker = (NumberPicker) view.findViewById(R.id.bytes); - - final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE); - final long warningBytes = editor.getPolicyWarningBytes(template); - final long limitBytes = editor.getPolicyLimitBytes(template); - - bytesPicker.setMinValue(0); - if (limitBytes != LIMIT_DISABLED) { - bytesPicker.setMaxValue((int) (limitBytes / MB_IN_BYTES) - 1); - } else { - bytesPicker.setMaxValue(Integer.MAX_VALUE); - } - bytesPicker.setValue((int) (warningBytes / MB_IN_BYTES)); - bytesPicker.setWrapSelectorWheel(false); - - builder.setTitle(R.string.data_usage_warning_editor_title); - builder.setView(view); - - builder.setPositiveButton(R.string.data_usage_cycle_editor_positive, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // clear focus to finish pending text edits - bytesPicker.clearFocus(); - - final long bytes = bytesPicker.getValue() * MB_IN_BYTES; - editor.setPolicyWarningBytes(template, bytes); - target.updatePolicy(false); - } - }); - - return builder.create(); - } - } - - /** - * Dialog to edit {@link NetworkPolicy#limitBytes}. - */ - public static class LimitEditorFragment extends DialogFragment { - private static final String EXTRA_TEMPLATE = "template"; - - public static void show(DataUsageSummary parent) { - if (!parent.isAdded()) return; - - final Bundle args = new Bundle(); - args.putParcelable(EXTRA_TEMPLATE, parent.mTemplate); - - final LimitEditorFragment dialog = new LimitEditorFragment(); - dialog.setArguments(args); - dialog.setTargetFragment(parent, 0); - dialog.show(parent.getFragmentManager(), TAG_LIMIT_EDITOR); - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - final Context context = getActivity(); - final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); - final NetworkPolicyEditor editor = target.mPolicyEditor; - - final AlertDialog.Builder builder = new AlertDialog.Builder(context); - final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext()); - - final View view = dialogInflater.inflate(R.layout.data_usage_bytes_editor, null, false); - final NumberPicker bytesPicker = (NumberPicker) view.findViewById(R.id.bytes); - - final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE); - final long warningBytes = editor.getPolicyWarningBytes(template); - final long limitBytes = editor.getPolicyLimitBytes(template); - - bytesPicker.setMaxValue(Integer.MAX_VALUE); - if (warningBytes != WARNING_DISABLED && limitBytes > 0) { - bytesPicker.setMinValue((int) (warningBytes / MB_IN_BYTES) + 1); - } else { - bytesPicker.setMinValue(0); - } - bytesPicker.setValue((int) (limitBytes / MB_IN_BYTES)); - bytesPicker.setWrapSelectorWheel(false); - - builder.setTitle(R.string.data_usage_limit_editor_title); - builder.setView(view); - - builder.setPositiveButton(R.string.data_usage_cycle_editor_positive, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // clear focus to finish pending text edits - bytesPicker.clearFocus(); - - final long bytes = bytesPicker.getValue() * MB_IN_BYTES; - editor.setPolicyLimitBytes(template, bytes); - target.updatePolicy(false); - } - }); - - return builder.create(); - } - } - /** - * Dialog to request user confirmation before disabling data. - */ - public static class ConfirmDataDisableFragment extends DialogFragment { - static int mSubId; - public static void show(DataUsageSummary parent, int subId) { - mSubId = subId; - if (!parent.isAdded()) return; - - final ConfirmDataDisableFragment dialog = new ConfirmDataDisableFragment(); - dialog.setTargetFragment(parent, 0); - dialog.show(parent.getFragmentManager(), TAG_CONFIRM_DATA_DISABLE); - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - final Context context = getActivity(); - - final AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setMessage(R.string.data_usage_disable_mobile); - - builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); - if (target != null) { - // TODO: extend to modify policy enabled flag. - target.setMobileDataEnabled(mSubId, false); - } - } - }); - builder.setNegativeButton(android.R.string.cancel, null); - - return builder.create(); - } - } - - /** - * Dialog to request user confirmation before setting - * {@link INetworkPolicyManager#setRestrictBackground(boolean)}. - */ - public static class ConfirmRestrictFragment extends DialogFragment { - public static void show(DataUsageSummary parent) { - if (!parent.isAdded()) return; - - final ConfirmRestrictFragment dialog = new ConfirmRestrictFragment(); - dialog.setTargetFragment(parent, 0); - dialog.show(parent.getFragmentManager(), TAG_CONFIRM_RESTRICT); - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - final Context context = getActivity(); - - final AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(R.string.data_usage_restrict_background_title); - if (Utils.hasMultipleUsers(context)) { - builder.setMessage(R.string.data_usage_restrict_background_multiuser); - } else { - builder.setMessage(R.string.data_usage_restrict_background); - } - - builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); - if (target != null) { - target.setRestrictBackground(true); - } - } - }); - builder.setNegativeButton(android.R.string.cancel, null); - - return builder.create(); - } - } - - /** - * Dialog to inform user that {@link #POLICY_REJECT_METERED_BACKGROUND} - * change has been denied, usually based on - * {@link DataUsageSummary#hasLimitedNetworks()}. - */ - public static class DeniedRestrictFragment extends DialogFragment { - public static void show(DataUsageSummary parent) { - if (!parent.isAdded()) return; - - final DeniedRestrictFragment dialog = new DeniedRestrictFragment(); - dialog.setTargetFragment(parent, 0); - dialog.show(parent.getFragmentManager(), TAG_DENIED_RESTRICT); - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - final Context context = getActivity(); - - final AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(R.string.data_usage_app_restrict_background); - builder.setMessage(R.string.data_usage_restrict_denied_dialog); - builder.setPositiveButton(android.R.string.ok, null); - - return builder.create(); - } - } - - /** - * Dialog to request user confirmation before setting - * {@link #POLICY_REJECT_METERED_BACKGROUND}. - */ - public static class ConfirmAppRestrictFragment extends DialogFragment { - public static void show(DataUsageSummary parent) { - if (!parent.isAdded()) return; - - final ConfirmAppRestrictFragment dialog = new ConfirmAppRestrictFragment(); - dialog.setTargetFragment(parent, 0); - dialog.show(parent.getFragmentManager(), TAG_CONFIRM_APP_RESTRICT); - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - final Context context = getActivity(); - - final AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(R.string.data_usage_app_restrict_dialog_title); - builder.setMessage(R.string.data_usage_app_restrict_dialog); - - builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - final DataUsageSummary target = (DataUsageSummary) getTargetFragment(); - if (target != null) { - target.setAppRestrictBackground(true); - } - } - }); - builder.setNegativeButton(android.R.string.cancel, null); - - return builder.create(); - } - } - - /** - * Compute default tab that should be selected, based on - * {@link NetworkPolicyManager#EXTRA_NETWORK_TEMPLATE} extra. - */ - private static String computeTabFromIntent(Intent intent) { - final NetworkTemplate template = intent.getParcelableExtra(EXTRA_NETWORK_TEMPLATE); - if (template == null) { - final int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, - SubscriptionManager.INVALID_SUBSCRIPTION_ID); - if (SubscriptionManager.isValidSubscriptionId(subId)) { - return TAB_MOBILE + String.valueOf(subId); - } - return null; - } - - switch (template.getMatchRule()) { - case MATCH_MOBILE_3G_LOWER: - return TAB_3G; - case MATCH_MOBILE_4G: - return TAB_4G; - case MATCH_MOBILE_ALL: - return TAB_MOBILE; - case MATCH_WIFI: - return TAB_WIFI; - default: - return null; - } - } - - /** - * Background task that loads {@link UidDetail}, binding to - * {@link DataUsageAdapter} row item when finished. - */ - private static class UidDetailTask extends AsyncTask<Void, Void, UidDetail> { - private final UidDetailProvider mProvider; - private final AppItem mItem; - private final View mTarget; - - private UidDetailTask(UidDetailProvider provider, AppItem item, View target) { - mProvider = checkNotNull(provider); - mItem = checkNotNull(item); - mTarget = checkNotNull(target); - } - - public static void bindView( - UidDetailProvider provider, AppItem item, View target) { - final UidDetailTask existing = (UidDetailTask) target.getTag(); - if (existing != null) { - existing.cancel(false); - } - - final UidDetail cachedDetail = provider.getUidDetail(item.key, false); - if (cachedDetail != null) { - bindView(cachedDetail, target); - } else { - target.setTag(new UidDetailTask(provider, item, target).executeOnExecutor( - AsyncTask.THREAD_POOL_EXECUTOR)); - } - } - - private static void bindView(UidDetail detail, View target) { - final ImageView icon = (ImageView) target.findViewById(android.R.id.icon); - final TextView title = (TextView) target.findViewById(android.R.id.title); - - if (detail != null) { - icon.setImageDrawable(detail.icon); - title.setText(detail.label); - title.setContentDescription(detail.contentDescription); - } else { - icon.setImageDrawable(null); - title.setText(null); - } - } - - @Override - protected void onPreExecute() { - bindView(null, mTarget); - } - - @Override - protected UidDetail doInBackground(Void... params) { - return mProvider.getUidDetail(mItem.key, true); - } - - @Override - protected void onPostExecute(UidDetail result) { - bindView(result, mTarget); - } - } - - /** - * Test if device has a mobile data radio with SIM in ready state. - */ - public static boolean hasReadyMobileRadio(Context context) { - if (TEST_RADIOS) { - return SystemProperties.get(TEST_RADIOS_PROP).contains("mobile"); - } - - final ConnectivityManager conn = ConnectivityManager.from(context); - final TelephonyManager tele = TelephonyManager.from(context); - - final List<SubscriptionInfo> subInfoList = - SubscriptionManager.from(context).getActiveSubscriptionInfoList(); - // No activated Subscriptions - if (subInfoList == null) { - if (LOGD) Log.d(TAG, "hasReadyMobileRadio: subInfoList=null"); - return false; - } - // require both supported network and ready SIM - boolean isReady = true; - for (SubscriptionInfo subInfo : subInfoList) { - isReady = isReady & tele.getSimState(subInfo.getSimSlotIndex()) == SIM_STATE_READY; - if (LOGD) Log.d(TAG, "hasReadyMobileRadio: subInfo=" + subInfo); - } - boolean retVal = conn.isNetworkSupported(TYPE_MOBILE) && isReady; - if (LOGD) { - Log.d(TAG, "hasReadyMobileRadio:" - + " conn.isNetworkSupported(TYPE_MOBILE)=" - + conn.isNetworkSupported(TYPE_MOBILE) - + " isReady=" + isReady); - } - return retVal; - } - - /* - * TODO: consider adding to TelephonyManager or SubscritpionManager. - */ - public static boolean hasReadyMobileRadio(Context context, int subId) { - if (TEST_RADIOS) { - return SystemProperties.get(TEST_RADIOS_PROP).contains("mobile"); - } - - final ConnectivityManager conn = ConnectivityManager.from(context); - final TelephonyManager tele = TelephonyManager.from(context); - final int slotId = SubscriptionManager.getSlotId(subId); - final boolean isReady = tele.getSimState(slotId) == SIM_STATE_READY; - - boolean retVal = conn.isNetworkSupported(TYPE_MOBILE) && isReady; - if (LOGD) Log.d(TAG, "hasReadyMobileRadio: subId=" + subId - + " conn.isNetworkSupported(TYPE_MOBILE)=" + conn.isNetworkSupported(TYPE_MOBILE) - + " isReady=" + isReady); - return retVal; - } - - /** - * Test if device has a mobile 4G data radio. - */ - public static boolean hasReadyMobile4gRadio(Context context) { - if (!NetworkPolicyEditor.ENABLE_SPLIT_POLICIES) { - return false; - } - if (TEST_RADIOS) { - return SystemProperties.get(TEST_RADIOS_PROP).contains("4g"); - } - - final ConnectivityManager conn = ConnectivityManager.from(context); - final TelephonyManager tele = TelephonyManager.from(context); - - final boolean hasWimax = conn.isNetworkSupported(TYPE_WIMAX); - final boolean hasLte = (tele.getLteOnCdmaMode() == PhoneConstants.LTE_ON_CDMA_TRUE) - && hasReadyMobileRadio(context); - return hasWimax || hasLte; - } - - /** - * Test if device has a Wi-Fi data radio. - */ - public static boolean hasWifiRadio(Context context) { - if (TEST_RADIOS) { - return SystemProperties.get(TEST_RADIOS_PROP).contains("wifi"); - } - - final ConnectivityManager conn = ConnectivityManager.from(context); - return conn.isNetworkSupported(TYPE_WIFI); - } - - /** - * Test if device has an ethernet network connection. - */ - public boolean hasEthernet(Context context) { - if (TEST_RADIOS) { - return SystemProperties.get(TEST_RADIOS_PROP).contains("ethernet"); - } - - final ConnectivityManager conn = ConnectivityManager.from(context); - final boolean hasEthernet = conn.isNetworkSupported(TYPE_ETHERNET); - - final long ethernetBytes; - if (mStatsSession != null) { - try { - ethernetBytes = mStatsSession.getSummaryForNetwork( - NetworkTemplate.buildTemplateEthernet(), Long.MIN_VALUE, Long.MAX_VALUE) - .getTotalBytes(); - } catch (RemoteException e) { - throw new RuntimeException(e); - } - } else { - ethernetBytes = 0; - } - - // only show ethernet when both hardware present and traffic has occurred - return hasEthernet && ethernetBytes > 0; - } - - /** - * Inflate a {@link Preference} style layout, adding the given {@link View} - * widget into {@link android.R.id#widget_frame}. - */ - private static View inflatePreference(LayoutInflater inflater, ViewGroup root, View widget) { - final View view = inflater.inflate(R.layout.preference, root, false); - final LinearLayout widgetFrame = (LinearLayout) view.findViewById( - android.R.id.widget_frame); - widgetFrame.addView(widget, new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); - return view; - } - - /** - * Test if any networks are currently limited. - */ - private boolean hasLimitedNetworks() { - return !buildLimitedNetworksList().isEmpty(); - } - - /** - * Build string describing currently limited networks, which defines when - * background data is restricted. - */ - @Deprecated - private CharSequence buildLimitedNetworksString() { - final List<CharSequence> limited = buildLimitedNetworksList(); - - // handle case where no networks limited - if (limited.isEmpty()) { - limited.add(getText(R.string.data_usage_list_none)); - } - - final ICUResourceBundle icuBundle = (ICUResourceBundle) UResourceBundle. - getBundleInstance(ICUResourceBundle.ICU_BASE_NAME); - final String listMiddlePattern = - icuBundle.getStringWithFallback("listPattern/standard/middle"); - // The returned pattern is something like "{0}, {1}", from which we want - // to extract the ", " part. - final int firstClosingBrace = listMiddlePattern.indexOf('}'); - final int lastOpeningBrace = listMiddlePattern.lastIndexOf('{'); - final CharSequence delimiter = listMiddlePattern.substring( - firstClosingBrace+1, lastOpeningBrace); - - return TextUtils.join(delimiter, limited); - } - - /** - * Build list of currently limited networks, which defines when background - * data is restricted. - */ - @Deprecated - private List<CharSequence> buildLimitedNetworksList() { - final Context context = getActivity(); - - // build combined list of all limited networks - final ArrayList<CharSequence> limited = Lists.newArrayList(); - - final TelephonyManager tele = TelephonyManager.from(context); - if (tele.getSimState() == SIM_STATE_READY) { - final String subscriberId = getActiveSubscriberId(context); - if (mPolicyEditor.hasLimitedPolicy(buildTemplateMobileAll(subscriberId))) { - limited.add(getText(R.string.data_usage_list_mobile)); - } - if (mPolicyEditor.hasLimitedPolicy(buildTemplateMobile3gLower(subscriberId))) { - limited.add(getText(R.string.data_usage_tab_3g)); - } - if (mPolicyEditor.hasLimitedPolicy(buildTemplateMobile4g(subscriberId))) { - limited.add(getText(R.string.data_usage_tab_4g)); - } - } - - if (mPolicyEditor.hasLimitedPolicy(buildTemplateWifiWildcard())) { - limited.add(getText(R.string.data_usage_tab_wifi)); - } - if (mPolicyEditor.hasLimitedPolicy(buildTemplateEthernet())) { - limited.add(getText(R.string.data_usage_tab_ethernet)); - } - - return limited; - } - - /** - * Inset both selector and divider {@link Drawable} on the given - * {@link ListView} by the requested dimensions. - */ - private static void insetListViewDrawables(ListView view, int insetSide) { - final Drawable selector = view.getSelector(); - final Drawable divider = view.getDivider(); - - // fully unregister these drawables so callbacks can be maintained after - // wrapping below. - final Drawable stub = new ColorDrawable(Color.TRANSPARENT); - view.setSelector(stub); - view.setDivider(stub); - - view.setSelector(new InsetBoundsDrawable(selector, insetSide)); - view.setDivider(new InsetBoundsDrawable(divider, insetSide)); - } - - /** - * Set {@link android.R.id#title} for a preference view inflated with - * {@link #inflatePreference(LayoutInflater, ViewGroup, View)}. - */ - private static void setPreferenceTitle(View parent, int resId) { - final TextView title = (TextView) parent.findViewById(android.R.id.title); - title.setText(resId); - } - - /** - * Set {@link android.R.id#summary} for a preference view inflated with - * {@link #inflatePreference(LayoutInflater, ViewGroup, View)}. - */ - private static void setPreferenceSummary(View parent, CharSequence string) { - final TextView summary = (TextView) parent.findViewById(android.R.id.summary); - summary.setVisibility(View.VISIBLE); - summary.setText(string); - } - - private void addMobileTab(Context context, SubscriptionInfo subInfo, boolean isMultiSim) { - if (subInfo != null && mMobileTagMap != null) { - if (hasReadyMobileRadio(context, subInfo.getSubscriptionId())) { - if (isMultiSim) { - mTabHost.addTab(buildTabSpec(mMobileTagMap.get(subInfo.getSubscriptionId()), - subInfo.getDisplayName())); - } else { - mTabHost.addTab(buildTabSpec(mMobileTagMap.get(subInfo.getSubscriptionId()), - R.string.data_usage_tab_mobile)); - } - } - } else { - if (LOGD) Log.d(TAG, "addMobileTab: subInfoList is null"); - } - } - - private SubscriptionInfo getCurrentTabSubInfo(Context context) { - if (mSubInfoList != null && mTabHost != null) { - final int currentTagIndex = mTabHost.getCurrentTab(); - int i = 0; - for (SubscriptionInfo subInfo : mSubInfoList) { - if (hasReadyMobileRadio(context, subInfo.getSubscriptionId())) { - if (i++ == currentTagIndex) { - return subInfo; - } - } - } - } - return null; - } - - /** - * Init a map with subId key and mobile tag name - * @param subInfoList The subscription Info List - * @return The map or null if no activated subscription - */ - private Map<Integer, String> initMobileTabTag(List<SubscriptionInfo> subInfoList) { - Map<Integer, String> map = null; - if (subInfoList != null) { - String mobileTag; - map = new HashMap<Integer, String>(); - for (SubscriptionInfo subInfo : subInfoList) { - mobileTag = TAB_MOBILE + String.valueOf(subInfo.getSubscriptionId()); - map.put(subInfo.getSubscriptionId(), mobileTag); - } - } - return map; - } - - private static boolean isMobileTab(String currentTab) { - return currentTab != null ? currentTab.contains(TAB_MOBILE) : false; - } - - private int getSubId(String currentTab) { - if (mMobileTagMap != null) { - Set<Integer> set = mMobileTagMap.keySet(); - for (Integer subId : set) { - if (mMobileTagMap.get(subId).equals(currentTab)) { - return subId; - } - } - } - Log.e(TAG, "currentTab = " + currentTab + " non mobile tab called this function"); - return -1; - } - - private boolean isMobileDataAvailable(int subId) { - return mSubscriptionManager.getActiveSubscriptionInfo(subId) != null; - } - - private static class SummaryProvider - implements SummaryLoader.SummaryProvider { - - private final Activity mActivity; - private final SummaryLoader mSummaryLoader; - private final MobileDataController mDataController; - - public SummaryProvider(Activity activity, SummaryLoader summaryLoader) { - mActivity = activity; - mSummaryLoader = summaryLoader; - mDataController = new MobileDataController(activity); - } - - @Override - public void setListening(boolean listening) { - if (listening) { - MobileDataController.DataUsageInfo info = mDataController.getDataUsageInfo(); - String used; - if (info == null) { - used = Formatter.formatFileSize(mActivity, 0); - } else if (info.limitLevel <= 0) { - used = Formatter.formatFileSize(mActivity, info.usageLevel); - } else { - used = Utils.formatPercentage(info.usageLevel, info.limitLevel); - } - mSummaryLoader.setSummary(this, - mActivity.getString(R.string.data_usage_summary_format, used)); - } - } - } - - 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); - } - }; - - /** - * For search - */ - public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = - new BaseSearchIndexProvider() { - @Override - public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) { - final List<SearchIndexableRaw> result = new ArrayList<SearchIndexableRaw>(); - - final Resources res = context.getResources(); - - // Add fragment title - SearchIndexableRaw data = new SearchIndexableRaw(context); - data.title = res.getString(R.string.data_usage_summary_title); - data.screenTitle = res.getString(R.string.data_usage_summary_title); - result.add(data); - - // Mobile data - data = new SearchIndexableRaw(context); - data.key = DATA_USAGE_ENABLE_MOBILE_KEY; - data.title = res.getString(R.string.data_usage_enable_mobile); - data.screenTitle = res.getString(R.string.data_usage_summary_title); - result.add(data); - - // Set mobile data limit - data = new SearchIndexableRaw(context); - data.key = DATA_USAGE_DISABLE_MOBILE_LIMIT_KEY; - data.title = res.getString(R.string.data_usage_disable_mobile_limit); - data.screenTitle = res.getString(R.string.data_usage_summary_title); - result.add(data); - - // Data usage cycle - data = new SearchIndexableRaw(context); - data.key = DATA_USAGE_CYCLE_KEY; - data.title = res.getString(R.string.data_usage_cycle); - data.screenTitle = res.getString(R.string.data_usage_summary_title); - result.add(data); - - return result; - } - }; -} diff --git a/src/com/android/settings/HighlightingFragment.java b/src/com/android/settings/HighlightingFragment.java deleted file mode 100644 index 2d305e7d4af..00000000000 --- a/src/com/android/settings/HighlightingFragment.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.settings; - -import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.text.TextUtils; -import android.view.View; -import android.view.ViewGroup; - -public abstract class HighlightingFragment extends InstrumentedFragment { - - private static final String TAG = "HighlightSettingsFragment"; - - private static final int DELAY_HIGHLIGHT_DURATION_MILLIS = 400; - private static final String SAVE_HIGHLIGHTED_KEY = "android:view_highlighted"; - - private String mViewKey; - private boolean mViewHighlighted = false; - private Drawable mHighlightDrawable; - - @Override - public void onCreate(Bundle icicle) { - super.onCreate(icicle); - - if (icicle != null) { - mViewHighlighted = icicle.getBoolean(SAVE_HIGHLIGHTED_KEY); - } - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - - outState.putBoolean(SAVE_HIGHLIGHTED_KEY, mViewHighlighted); - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - final Bundle args = getArguments(); - if (args != null) { - mViewKey = args.getString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY); - highlightViewIfNeeded(); - } - } - - public void highlightViewIfNeeded() { - if (!mViewHighlighted &&!TextUtils.isEmpty(mViewKey)) { - highlightView(mViewKey); - } - } - - private Drawable getHighlightDrawable() { - if (mHighlightDrawable == null) { - mHighlightDrawable = getActivity().getDrawable(R.drawable.preference_highlight); - } - return mHighlightDrawable; - } - - private void highlightView(String key) { - final Drawable highlight = getHighlightDrawable(); - - // Try locating the View thru its Tag / Key - final View view = findViewForKey(getView(), key); - if (view != null ) { - view.setBackground(highlight); - - getView().postDelayed(new Runnable() { - @Override - public void run() { - final int centerX = view.getWidth() / 2; - final int centerY = view.getHeight() / 2; - highlight.setHotspot(centerX, centerY); - view.setPressed(true); - view.setPressed(false); - } - }, DELAY_HIGHLIGHT_DURATION_MILLIS); - - mViewHighlighted = true; - } - } - - private View findViewForKey(View root, String key) { - if (checkTag(root, key)) { - return root; - } - if (root instanceof ViewGroup) { - final ViewGroup group = (ViewGroup) root; - final int count = group.getChildCount(); - for (int n = 0; n < count; n++) { - final View child = group.getChildAt(n); - final View view = findViewForKey(child, key); - if (view != null) { - return view; - } - } - } - return null; - } - - private boolean checkTag(View view, String key) { - final Object tag = view.getTag(R.id.preference_highlight_key); - if (tag == null || !(tag instanceof String)) { - return false; - } - final String viewKey = (String) tag; - return (!TextUtils.isEmpty(viewKey) && viewKey.equals(key)); - } -} diff --git a/src/com/android/settings/InstrumentedFragment.java b/src/com/android/settings/InstrumentedFragment.java index 2d39961e464..58b8eb0b81d 100644 --- a/src/com/android/settings/InstrumentedFragment.java +++ b/src/com/android/settings/InstrumentedFragment.java @@ -34,6 +34,9 @@ public abstract class InstrumentedFragment extends PreferenceFragment { public static final int CONFIGURE_WIFI = UNDECLARED + 4; public static final int DISPLAY_SCREEN_ZOOM = UNDECLARED + 5; public static final int ACCESSIBILITY_FONT_SIZE = UNDECLARED + 6; + public static final int DATA_USAGE_LIST = UNDECLARED + 7; + public static final int BILLING_CYCLE = UNDECLARED + 8; + public static final int APP_DATA_USAGE = UNDECLARED + 9; /** * Declare the view of this category. diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java index de66aea7d6b..dab2cb7607c 100644 --- a/src/com/android/settings/SettingsActivity.java +++ b/src/com/android/settings/SettingsActivity.java @@ -68,6 +68,7 @@ import com.android.settings.applications.WriteSettingsDetails; import com.android.settings.bluetooth.BluetoothSettings; import com.android.settings.dashboard.DashboardSummary; import com.android.settings.dashboard.SearchResultsSummary; +import com.android.settings.datausage.DataUsageSummary; import com.android.settings.deviceinfo.PrivateVolumeForget; import com.android.settings.deviceinfo.PrivateVolumeSettings; import com.android.settings.deviceinfo.PublicVolumeSettings; @@ -85,9 +86,9 @@ import com.android.settings.nfc.PaymentSettings; import com.android.settings.notification.AppNotificationSettings; import com.android.settings.notification.ConfigureNotificationSettings; import com.android.settings.notification.NotificationAccessSettings; -import com.android.settings.notification.SoundSettings; import com.android.settings.notification.NotificationStation; import com.android.settings.notification.OtherSoundSettings; +import com.android.settings.notification.SoundSettings; import com.android.settings.notification.ZenAccessSettings; import com.android.settings.notification.ZenModeAutomationSettings; import com.android.settings.notification.ZenModeEventRuleSettings; diff --git a/src/com/android/settings/SettingsPreferenceFragment.java b/src/com/android/settings/SettingsPreferenceFragment.java index f79def99ee4..2ba49ce618b 100644 --- a/src/com/android/settings/SettingsPreferenceFragment.java +++ b/src/com/android/settings/SettingsPreferenceFragment.java @@ -24,7 +24,6 @@ import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; import android.content.pm.PackageManager; -import android.graphics.drawable.Drawable; import android.os.Bundle; import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceGroup; @@ -187,6 +186,11 @@ public abstract class SettingsPreferenceFragment extends InstrumentedPreferenceF setEmptyView(loading); } + public void setLoading(boolean loading, boolean animate) { + View loading_container = getView().findViewById(R.id.loading_container); + Utils.handleLoadingContainer(loading_container, getListView(), !loading, animate); + } + public void registerObserverIfNeeded() { if (!mIsDataSetObserverRegistered) { if (mCurrentRootAdapter != null) { diff --git a/src/com/android/settings/SummaryPreference.java b/src/com/android/settings/SummaryPreference.java new file mode 100644 index 00000000000..0943a2bd6a7 --- /dev/null +++ b/src/com/android/settings/SummaryPreference.java @@ -0,0 +1,99 @@ +/* + * 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; + +import android.content.Context; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceViewHolder; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.View; +import android.widget.TextView; +import com.android.settings.applications.LinearColorBar; + +/** + * Provides a summary of a setting page in a preference. Such as memory or data usage. + */ +public class SummaryPreference extends Preference { + + private static final String TAG = "SummaryPreference"; + private String mAmount; + private String mUnits; + + private int mLeft, mMiddle, mRight; + private float mLeftRatio, mMiddleRatio, mRightRatio; + private String mStartLabel; + private String mEndLabel; + + public SummaryPreference(Context context, AttributeSet attrs) { + super(context, attrs); + setLayoutResource(R.layout.settings_summary_preference); + mLeft = context.getColor(R.color.summary_default_start); + mRight = context.getColor(R.color.summary_default_end); + } + + public void setAmount(String amount) { + mAmount = amount; + if (mAmount != null && mUnits != null) { + setTitle(TextUtils.expandTemplate(getContext().getText(R.string.storage_size_large), + mAmount, mUnits)); + } + } + + public void setUnits(String units) { + mUnits = units; + if (mAmount != null && mUnits != null) { + setTitle(TextUtils.expandTemplate(getContext().getText(R.string.storage_size_large), + mAmount, mUnits)); + } + } + + public void setLabels(String start, String end) { + mStartLabel = start; + mEndLabel = end; + notifyChanged(); + } + + public void setRatios(float left, float middle, float right) { + mLeftRatio = left; + mMiddleRatio = middle; + mRightRatio = right; + notifyChanged(); + } + + public void setColors(int left, int middle, int right) { + mLeft = left; + mMiddle = middle; + mRight = right; + notifyChanged(); + } + + @Override + public void onBindViewHolder(PreferenceViewHolder holder) { + super.onBindViewHolder(holder); + + LinearColorBar colorBar = (LinearColorBar) holder.itemView.findViewById(R.id.color_bar); + colorBar.setRatios(mLeftRatio, mMiddleRatio, mRightRatio); + colorBar.setColors(mLeft, mMiddle, mRight); + + if (!TextUtils.isEmpty(mStartLabel) || !TextUtils.isEmpty(mEndLabel)) { + holder.findViewById(R.id.label_bar).setVisibility(View.VISIBLE); + ((TextView) holder.findViewById(android.R.id.text1)).setText(mStartLabel); + ((TextView) holder.findViewById(android.R.id.text2)).setText(mEndLabel); + } else { + holder.findViewById(R.id.label_bar).setVisibility(View.GONE); + } + } +} diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java index 4afa1d96fa3..96d0a37a602 100644 --- a/src/com/android/settings/Utils.java +++ b/src/com/android/settings/Utils.java @@ -67,6 +67,7 @@ import android.telephony.TelephonyManager; import android.text.Spannable; import android.text.SpannableString; import android.text.TextUtils; +import android.text.format.DateUtils; import android.text.style.TtsSpan; import android.util.ArraySet; import android.util.Log; @@ -81,6 +82,7 @@ import android.view.animation.AnimationUtils; import android.widget.ListView; import android.widget.TabWidget; import com.android.internal.util.UserIcons; +import com.android.settings.datausage.DataUsageList; import java.io.IOException; import java.io.InputStream; @@ -91,6 +93,8 @@ import java.util.List; import java.util.Locale; import static android.content.Intent.EXTRA_USER; +import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH; +import static android.text.format.DateUtils.FORMAT_SHOW_DATE; public final class Utils extends com.android.settingslib.Utils { @@ -978,5 +982,19 @@ public final class Utils extends com.android.settingslib.Utils { context.getTheme().resolveAttribute(attr, value, true); return value.resourceId; } + + private static final StringBuilder sBuilder = new StringBuilder(50); + private static final java.util.Formatter sFormatter = new java.util.Formatter( + sBuilder, Locale.getDefault()); + + public static String formatDateRange(Context context, long start, long end) { + final int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_MONTH; + + synchronized (sBuilder) { + sBuilder.setLength(0); + return DateUtils.formatDateRange(context, sFormatter, start, end, flags, null) + .toString(); + } + } } diff --git a/src/com/android/settings/applications/InstalledAppDetails.java b/src/com/android/settings/applications/InstalledAppDetails.java index 7d8a7f09b16..42d8e469850 100755 --- a/src/com/android/settings/applications/InstalledAppDetails.java +++ b/src/com/android/settings/applications/InstalledAppDetails.java @@ -21,7 +21,6 @@ import android.app.ActivityManager; import android.app.AlertDialog; import android.app.LoaderManager.LoaderCallbacks; import android.app.admin.DevicePolicyManager; -import android.icu.text.ListFormatter; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -37,6 +36,7 @@ import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; import android.content.res.Resources; import android.graphics.drawable.Drawable; +import android.icu.text.ListFormatter; import android.net.INetworkStatsService; import android.net.INetworkStatsSession; import android.net.NetworkTemplate; @@ -63,16 +63,17 @@ import android.view.ViewGroup; import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; - import com.android.internal.logging.MetricsLogger; import com.android.internal.os.BatterySipper; import com.android.internal.os.BatteryStatsHelper; import com.android.settings.AppHeader; -import com.android.settings.DataUsageSummary; import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.Utils; import com.android.settings.applications.PermissionsSummaryHelper.PermissionsResultCallback; +import com.android.settings.datausage.AppDataUsage; +import com.android.settings.datausage.DataUsageList; +import com.android.settings.datausage.DataUsageSummary; import com.android.settings.fuelgauge.BatteryEntry; import com.android.settings.fuelgauge.PowerUsageDetail; import com.android.settings.notification.AppNotificationSettings; @@ -753,13 +754,7 @@ public class InstalledAppDetails extends AppInfoBase ProcessStatsBase.launchMemoryDetail((SettingsActivity) getActivity(), mStatsManager.getMemInfo(), mStats, false); } else if (preference == mDataPreference) { - Bundle args = new Bundle(); - args.putString(DataUsageSummary.EXTRA_SHOW_APP_IMMEDIATE_PKG, - mAppEntry.info.packageName); - - SettingsActivity sa = (SettingsActivity) getActivity(); - sa.startPreferencePanel(DataUsageSummary.class.getName(), args, -1, - getString(R.string.app_data_usage), this, SUB_INFO_FRAGMENT); + startAppInfoFragment(AppDataUsage.class, getString(R.string.app_data_usage)); } else if (preference == mBatteryPreference) { BatteryEntry entry = new BatteryEntry(getActivity(), null, mUserManager, mSipper); PowerUsageDetail.startBatteryDetailPage((SettingsActivity) getActivity(), @@ -794,7 +789,7 @@ public class InstalledAppDetails extends AppInfoBase } public static NetworkTemplate getTemplate(Context context) { - if (DataUsageSummary.hasReadyMobileRadio(context)) { + if (DataUsageList.hasReadyMobileRadio(context)) { return NetworkTemplate.buildTemplateMobileWildcard(); } if (DataUsageSummary.hasWifiRadio(context)) { diff --git a/src/com/android/settings/applications/ProcessStatsDetail.java b/src/com/android/settings/applications/ProcessStatsDetail.java index 9db79bcbaa4..4ad480ee589 100644 --- a/src/com/android/settings/applications/ProcessStatsDetail.java +++ b/src/com/android/settings/applications/ProcessStatsDetail.java @@ -40,14 +40,13 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; -import android.widget.TextView; - import com.android.internal.logging.MetricsLogger; import com.android.settings.AppHeader; import com.android.settings.CancellablePreference; import com.android.settings.CancellablePreference.OnCancelListener; import com.android.settings.R; import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.SummaryPreference; import com.android.settings.applications.ProcStatsEntry.Service; import java.util.ArrayList; @@ -87,8 +86,6 @@ public class ProcessStatsDetail extends SettingsPreferenceFragment { private long mTotalTime; private long mOnePercentTime; - private LinearColorBar mColorBar; - private double mMaxMemoryUsage; private double mTotalScale; @@ -177,20 +174,19 @@ public class ProcessStatsDetail extends SettingsPreferenceFragment { mProcGroup = (PreferenceCategory) findPreference(KEY_PROCS); fillProcessesSection(); - LayoutPreference headerLayout = (LayoutPreference) findPreference(KEY_DETAILS_HEADER); + SummaryPreference summaryPreference = (SummaryPreference) findPreference(KEY_DETAILS_HEADER); // TODO: Find way to share this code with ProcessStatsPreference. boolean statsForeground = mApp.mRunWeight > mApp.mBgWeight; double avgRam = (statsForeground ? mApp.mRunWeight : mApp.mBgWeight) * mWeightToRam; float avgRatio = (float) (avgRam / mMaxMemoryUsage); float remainingRatio = 1 - avgRatio; - mColorBar = (LinearColorBar) headerLayout.findViewById(R.id.color_bar); Context context = getActivity(); - mColorBar.setColors( context.getColor(R.color.memory_max_use), 0, - context.getColor(R.color.memory_remaining)); - mColorBar.setRatios(avgRatio, 0, remainingRatio); - ((TextView) headerLayout.findViewById(R.id.memory_state)).setText( - Formatter.formatShortFileSize(getContext(), (long) avgRam)); + summaryPreference.setRatios(avgRatio, 0, remainingRatio); + Formatter.BytesResult usedResult = Formatter.formatBytes(context.getResources(), + (long) avgRam, Formatter.FLAG_SHORTER); + summaryPreference.setAmount(usedResult.value); + summaryPreference.setUnits(usedResult.units); long duration = Math.max(mApp.mRunDuration, mApp.mBgDuration); CharSequence frequency = ProcStatsPackageEntry.getFrequency(duration diff --git a/src/com/android/settings/applications/ProcessStatsSummary.java b/src/com/android/settings/applications/ProcessStatsSummary.java index 09fea8947b8..28917c85067 100644 --- a/src/com/android/settings/applications/ProcessStatsSummary.java +++ b/src/com/android/settings/applications/ProcessStatsSummary.java @@ -16,18 +16,15 @@ package com.android.settings.applications; import android.app.Activity; -import android.content.BroadcastReceiver; import android.content.Context; import android.os.Bundle; import android.support.v7.preference.Preference; import android.support.v7.preference.Preference.OnPreferenceClickListener; -import android.text.TextUtils; import android.text.format.Formatter; import android.text.format.Formatter.BytesResult; -import android.widget.TextView; - import com.android.internal.logging.MetricsLogger; import com.android.settings.R; +import com.android.settings.SummaryPreference; import com.android.settings.Utils; import com.android.settings.applications.ProcStatsData.MemInfo; import com.android.settings.dashboard.SummaryLoader; @@ -42,9 +39,7 @@ public class ProcessStatsSummary extends ProcessStatsBase implements OnPreferenc private static final String KEY_FREE = "free"; private static final String KEY_APP_LIST = "apps_list"; - private LinearColorBar mColors; - private LayoutPreference mHeader; - private TextView mMemStatus; + private SummaryPreference mSummaryPref; private Preference mPerformance; private Preference mTotalMemory; @@ -57,9 +52,10 @@ public class ProcessStatsSummary extends ProcessStatsBase implements OnPreferenc super.onCreate(icicle); addPreferencesFromResource(R.xml.process_stats_summary); - mHeader = (LayoutPreference) findPreference(KEY_STATUS_HEADER); - mMemStatus = (TextView) mHeader.findViewById(R.id.memory_state); - mColors = (LinearColorBar) mHeader.findViewById(R.id.color_bar); + mSummaryPref = (SummaryPreference) findPreference(KEY_STATUS_HEADER); + int memColor = getContext().getColor(R.color.running_processes_apps_ram); + mSummaryPref.setColors(memColor, memColor, + getContext().getColor(R.color.running_processes_free_ram)); mPerformance = findPreference(KEY_PERFORMANCE); mTotalMemory = findPreference(KEY_TOTAL_MEMORY); @@ -72,8 +68,6 @@ public class ProcessStatsSummary extends ProcessStatsBase implements OnPreferenc @Override public void refreshUi() { Context context = getContext(); - int memColor = context.getColor(R.color.running_processes_apps_ram); - mColors.setColors(memColor, memColor, context.getColor(R.color.running_processes_free_ram)); MemInfo memInfo = mStatsManager.getMemInfo(); @@ -92,10 +86,10 @@ public class ProcessStatsSummary extends ProcessStatsBase implements OnPreferenc } else { memString = memStatesStr[memStatesStr.length - 1]; } - mMemStatus.setText(TextUtils.expandTemplate(getText(R.string.storage_size_large), - usedResult.value, usedResult.units)); + mSummaryPref.setAmount(usedResult.value); + mSummaryPref.setUnits(usedResult.units); float usedRatio = (float)(usedRam / (freeRam + usedRam)); - mColors.setRatios(usedRatio, 0, 1 - usedRatio); + mSummaryPref.setRatios(usedRatio, 0, 1 - usedRatio); mPerformance.setSummary(memString); mTotalMemory.setSummary(totalString); diff --git a/src/com/android/settings/datausage/AppDataUsage.java b/src/com/android/settings/datausage/AppDataUsage.java new file mode 100644 index 00000000000..9ca066f002b --- /dev/null +++ b/src/com/android/settings/datausage/AppDataUsage.java @@ -0,0 +1,343 @@ +/* + * 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.datausage; + +import com.android.settings.AppHeader; +import com.android.settings.InstrumentedFragment; +import com.android.settings.R; +import com.android.settings.applications.AppInfoBase; +import com.android.settingslib.AppItem; +import com.android.settingslib.net.ChartData; +import com.android.settingslib.net.ChartDataLoader; + +import android.app.LoaderManager; +import android.content.Context; +import android.content.Intent; +import android.content.Loader; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.net.INetworkStatsSession; +import android.net.NetworkPolicy; +import android.net.NetworkStatsHistory; +import android.net.NetworkTemplate; +import android.net.TrafficStats; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.UserHandle; +import android.support.v14.preference.SwitchPreference; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceCategory; +import android.text.format.Formatter; +import android.util.ArraySet; +import android.util.Log; +import android.view.View; +import android.widget.AdapterView; +import android.widget.Spinner; + +import static android.net.NetworkPolicyManager.POLICY_NONE; +import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND; + +public class AppDataUsage extends DataUsageBase implements Preference.OnPreferenceChangeListener { + + public static final String ARG_APP_ITEM = "app_item"; + public static final String ARG_NETWORK_TEMPLATE = "network_template"; + + private static final String KEY_TOTAL_USAGE = "total_usage"; + private static final String KEY_FOREGROUND_USAGE = "foreground_usage"; + private static final String KEY_BACKGROUND_USAGE = "background_usage"; + private static final String KEY_APP_SETTINGS = "app_settings"; + private static final String KEY_RESTRICT_BACKGROUND = "restrict_background"; + private static final String KEY_APP_LIST = "app_list"; + + private static final int LOADER_CHART_DATA = 2; + + private final ArraySet<String> mPackages = new ArraySet<>(); + private Preference mTotalUsage; + private Preference mForegroundUsage; + private Preference mBackgroundUsage; + private Preference mAppSettings; + private SwitchPreference mRestrictBackground; + private PreferenceCategory mAppList; + + private Drawable mIcon; + private CharSequence mLabel; + private INetworkStatsSession mStatsSession; + private Spinner mCycleSpinner; + private CycleAdapter mCycleAdapter; + + private long mStart; + private long mEnd; + private ChartData mChartData; + private NetworkTemplate mTemplate; + private NetworkPolicy mPolicy; + private AppItem mAppItem; + private Intent mAppSettingsIntent; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + final Bundle args = getArguments(); + + try { + mStatsSession = services.mStatsService.openSession(); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + + mAppItem = (args != null) ? (AppItem) args.getParcelable(ARG_APP_ITEM) : null; + mTemplate = (args != null) ? (NetworkTemplate) args.getParcelable(ARG_NETWORK_TEMPLATE) + : null; + if (mTemplate == null) { + Context context = getContext(); + mTemplate = DataUsageSummary.getDefaultTemplate(context, + DataUsageSummary.getDefaultSubscriptionId(context)); + } + if (mAppItem == null) { + int uid = (args != null) ? args.getInt(AppInfoBase.ARG_PACKAGE_UID, -1) + : getActivity().getIntent().getIntExtra(AppInfoBase.ARG_PACKAGE_UID, -1); + if (uid == -1) { + // TODO: Log error. + getActivity().finish(); + } else { + addUid(uid); + mAppItem = new AppItem(uid); + mAppItem.addUid(uid); + } + } else { + for (int i = 0; i < mAppItem.uids.size(); i++) { + addUid(mAppItem.uids.keyAt(i)); + } + } + if (mPackages.size() != 0) { + PackageManager pm = getPackageManager(); + try { + ApplicationInfo info = pm.getApplicationInfo(mPackages.valueAt(0), 0); + mIcon = info.loadIcon(pm); + mLabel = info.loadLabel(pm); + } catch (PackageManager.NameNotFoundException e) { + } + } + addPreferencesFromResource(R.xml.app_data_usage); + + mTotalUsage = findPreference(KEY_TOTAL_USAGE); + mForegroundUsage = findPreference(KEY_FOREGROUND_USAGE); + mBackgroundUsage = findPreference(KEY_BACKGROUND_USAGE); + + if (UserHandle.isApp(mAppItem.key)) { + mRestrictBackground = (SwitchPreference) findPreference(KEY_RESTRICT_BACKGROUND); + mRestrictBackground.setOnPreferenceChangeListener(this); + mAppSettings = findPreference(KEY_APP_SETTINGS); + + mAppSettingsIntent = new Intent(Intent.ACTION_MANAGE_NETWORK_USAGE); + mAppSettingsIntent.addCategory(Intent.CATEGORY_DEFAULT); + + PackageManager pm = getPackageManager(); + boolean matchFound = false; + for (String packageName : mPackages) { + mAppSettingsIntent.setPackage(packageName); + if (pm.resolveActivity(mAppSettingsIntent, 0) != null) { + matchFound = true; + break; + } + } + if (!matchFound) { + removePreference(KEY_APP_SETTINGS); + mAppSettings = null; + } + + if (mPackages.size() > 1) { + mAppList = (PreferenceCategory) findPreference(KEY_APP_LIST); + for (int i = 1 ; i < mPackages.size(); i++) { + new AppPrefLoader().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, + mPackages.valueAt(i)); + } + } else { + removePreference(KEY_APP_LIST); + } + } else { + removePreference(KEY_APP_SETTINGS); + removePreference(KEY_RESTRICT_BACKGROUND); + removePreference(KEY_APP_LIST); + } + } + + @Override + public void onDestroy() { + TrafficStats.closeQuietly(mStatsSession); + super.onDestroy(); + } + + @Override + public void onResume() { + super.onResume(); + mPolicy = services.mPolicyEditor.getPolicy(mTemplate); + getLoaderManager().restartLoader(LOADER_CHART_DATA, + ChartDataLoader.buildArgs(mTemplate, mAppItem), mChartDataCallbacks); + updatePrefs(); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (preference == mRestrictBackground) { + setAppRestrictBackground((Boolean) newValue); + return true; + } + return false; + } + + @Override + public boolean onPreferenceTreeClick(Preference preference) { + if (preference == mAppSettings) { + // TODO: target towards entire UID instead of just first package + getActivity().startActivityAsUser(mAppSettingsIntent, new UserHandle( + UserHandle.getUserId(mAppItem.key))); + return true; + } + return super.onPreferenceTreeClick(preference); + } + + private void updatePrefs() { + if (mRestrictBackground != null) { + mRestrictBackground.setChecked(getAppRestrictBackground()); + } + } + + private void addUid(int uid) { + String[] packages = getPackageManager().getPackagesForUid(uid); + if (packages != null) { + for (int i = 0; i < packages.length; i++) { + mPackages.add(packages[i]); + } + } + } + + private void bindData() { + if (mChartData == null || mStart == 0) { + return; + } + final Context context = getContext(); + final long now = System.currentTimeMillis(); + + NetworkStatsHistory.Entry entry = null; + entry = mChartData.detailDefault.getValues(mStart, mEnd, now, entry); + final long backgroundBytes = entry.rxBytes + entry.txBytes; + entry = mChartData.detailForeground.getValues(mStart, mEnd, now, entry); + final long foregroundBytes = entry.rxBytes + entry.txBytes; + final long totalBytes = backgroundBytes + foregroundBytes; + + mTotalUsage.setSummary(Formatter.formatFileSize(context, totalBytes)); + mForegroundUsage.setSummary(Formatter.formatFileSize(context, foregroundBytes)); + mBackgroundUsage.setSummary(Formatter.formatFileSize(context, backgroundBytes)); + } + + private boolean getAppRestrictBackground() { + final int uid = mAppItem.key; + final int uidPolicy = services.mPolicyManager.getUidPolicy(uid); + return (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0; + } + + private void setAppRestrictBackground(boolean restrictBackground) { + final int uid = mAppItem.key; + services.mPolicyManager.setUidPolicy( + uid, restrictBackground ? POLICY_REJECT_METERED_BACKGROUND : POLICY_NONE); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + View header = setPinnedHeaderView(R.layout.data_usage_app_header); + String pkg = mPackages.size() != 0 ? mPackages.valueAt(0) : null; + int uid = 0; + try { + uid = pkg != null ? getPackageManager().getPackageUid(pkg, 0) : 0; + } catch (PackageManager.NameNotFoundException e) { + } + AppHeader.setupHeaderView(getActivity(), mIcon, mLabel, + pkg, uid, AppHeader.includeAppInfo(this), 0, header); + + mCycleSpinner = (Spinner) header.findViewById(R.id.filter_spinner); + mCycleAdapter = new CycleAdapter(getContext(), mCycleSpinner, mCycleListener); + } + + @Override + protected int getMetricsCategory() { + return InstrumentedFragment.APP_DATA_USAGE; + } + + private AdapterView.OnItemSelectedListener mCycleListener = + new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { + final CycleAdapter.CycleItem cycle = + (CycleAdapter.CycleItem) parent.getItemAtPosition(position); + + mStart = cycle.start; + mEnd = cycle.end; + bindData(); + } + + @Override + public void onNothingSelected(AdapterView<?> parent) { + // ignored + } + }; + + private final LoaderManager.LoaderCallbacks<ChartData> mChartDataCallbacks = + new LoaderManager.LoaderCallbacks<ChartData>() { + @Override + public Loader<ChartData> onCreateLoader(int id, Bundle args) { + return new ChartDataLoader(getActivity(), mStatsSession, args); + } + + @Override + public void onLoadFinished(Loader<ChartData> loader, ChartData data) { + mChartData = data; + mCycleAdapter.updateCycleList(mPolicy, mChartData); + bindData(); + } + + @Override + public void onLoaderReset(Loader<ChartData> loader) { + } + }; + + private class AppPrefLoader extends AsyncTask<String, Void, Preference> { + @Override + protected Preference doInBackground(String... params) { + PackageManager pm = getPackageManager(); + String pkg = params[0]; + try { + ApplicationInfo info = pm.getApplicationInfo(pkg, 0); + Preference preference = new Preference(getPrefContext()); + preference.setIcon(info.loadIcon(pm)); + preference.setTitle(info.loadLabel(pm)); + preference.setSelectable(false); + return preference; + } catch (PackageManager.NameNotFoundException e) { + } + return null; + } + + @Override + protected void onPostExecute(Preference pref) { + if (pref != null && mAppList != null) { + mAppList.addPreference(pref); + } + } + } +} diff --git a/src/com/android/settings/datausage/AppDataUsagePreference.java b/src/com/android/settings/datausage/AppDataUsagePreference.java new file mode 100644 index 00000000000..04e2b6dc466 --- /dev/null +++ b/src/com/android/settings/datausage/AppDataUsagePreference.java @@ -0,0 +1,123 @@ +/* + * 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.datausage; + +import android.content.Context; +import android.os.AsyncTask; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceViewHolder; +import android.text.format.Formatter; +import android.view.View; +import android.widget.ProgressBar; +import com.android.settingslib.AppItem; +import com.android.settingslib.net.UidDetail; +import com.android.settingslib.net.UidDetailProvider; + +import static com.android.internal.util.Preconditions.checkNotNull; + +public class AppDataUsagePreference extends Preference { + + private final AppItem mItem; + private final int mPercent; + + public AppDataUsagePreference(Context context, AppItem item, int percent, + UidDetailProvider provider) { + super(context); + mItem = item; + mPercent = percent; + setLayoutResource(com.android.settings.R.layout.data_usage_item); + setWidgetLayoutResource(com.android.settings.R.layout.widget_progress_bar); + if (item.restricted && item.total <= 0) { + setSummary(com.android.settings.R.string.data_usage_app_restricted); + } else { + setSummary(Formatter.formatFileSize(context, item.total)); + } + + // kick off async load of app details + UidDetailTask.bindView(provider, item, this); + } + + @Override + public void onBindViewHolder(PreferenceViewHolder holder) { + super.onBindViewHolder(holder); + + final ProgressBar progress = (ProgressBar) holder.findViewById( + android.R.id.progress); + + if (mItem.restricted && mItem.total <= 0) { + progress.setVisibility(View.GONE); + } else { + progress.setVisibility(View.VISIBLE); + } + progress.setProgress(mPercent); + } + + public AppItem getItem() { + return mItem; + } + + /** + * Background task that loads {@link UidDetail}, binding to + * {@link DataUsageAdapter} row item when finished. + */ + private static class UidDetailTask extends AsyncTask<Void, Void, UidDetail> { + private final UidDetailProvider mProvider; + private final AppItem mItem; + private final AppDataUsagePreference mTarget; + + private UidDetailTask(UidDetailProvider provider, AppItem item, + AppDataUsagePreference target) { + mProvider = checkNotNull(provider); + mItem = checkNotNull(item); + mTarget = checkNotNull(target); + } + + public static void bindView(UidDetailProvider provider, AppItem item, + AppDataUsagePreference target) { + final UidDetail cachedDetail = provider.getUidDetail(item.key, false); + if (cachedDetail != null) { + bindView(cachedDetail, target); + } else { + new UidDetailTask(provider, item, target).executeOnExecutor( + AsyncTask.THREAD_POOL_EXECUTOR); + } + } + + private static void bindView(UidDetail detail, Preference target) { + if (detail != null) { + target.setIcon(detail.icon); + target.setTitle(detail.label); + } else { + target.setIcon(null); + target.setTitle(null); + } + } + + @Override + protected void onPreExecute() { + bindView(null, mTarget); + } + + @Override + protected UidDetail doInBackground(Void... params) { + return mProvider.getUidDetail(mItem.key, true); + } + + @Override + protected void onPostExecute(UidDetail result) { + bindView(result, mTarget); + } + } +} diff --git a/src/com/android/settings/datausage/BillingCyclePreference.java b/src/com/android/settings/datausage/BillingCyclePreference.java new file mode 100644 index 00000000000..84eaabb90ba --- /dev/null +++ b/src/com/android/settings/datausage/BillingCyclePreference.java @@ -0,0 +1,90 @@ +/* + * 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.datausage; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.NetworkPolicy; +import android.net.NetworkTemplate; +import android.os.Bundle; +import android.os.RemoteException; +import android.support.v7.preference.Preference; +import android.util.AttributeSet; +import com.android.settings.R; +import com.android.settings.Utils; + +public class BillingCyclePreference extends Preference implements TemplatePreference { + + private NetworkTemplate mTemplate; + private NetworkServices mServices; + private NetworkPolicy mPolicy; + private int mSubId; + + public BillingCyclePreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public void onAttached() { + super.onAttached(); + getContext().registerReceiver(mReceiver, new IntentFilter( + CellDataPreference.ACTION_DATA_ENABLED_CHANGED)); + } + + @Override + public void onDetached() { + getContext().unregisterReceiver(mReceiver); + super.onDetached(); + } + + @Override + public void setTemplate(NetworkTemplate template, int subId, + NetworkServices services) { + mTemplate = template; + mSubId = subId; + mServices = services; + mPolicy = services.mPolicyEditor.getPolicy(mTemplate); + setSummary(getContext().getString(R.string.billing_cycle_fragment_summary, + mPolicy != null ? mPolicy.cycleDay : 1)); + setIntent(getIntent()); + } + + private void updateEnabled() { + try { + setEnabled(mPolicy != null && mServices.mNetworkService.isBandwidthControlEnabled() + && mServices.mTelephonyManager.getDataEnabled(mSubId) + && mServices.mUserManager.isAdminUser()); + } catch (RemoteException e) { + setEnabled(false); + } + } + + @Override + public Intent getIntent() { + Bundle args = new Bundle(); + args.putParcelable(DataUsageList.EXTRA_NETWORK_TEMPLATE, mTemplate); + return Utils.onBuildStartFragmentIntent(getContext(), BillingCycleSettings.class.getName(), + args, null, 0, getTitle(), false); + } + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + updateEnabled(); + } + }; +} diff --git a/src/com/android/settings/datausage/BillingCycleSettings.java b/src/com/android/settings/datausage/BillingCycleSettings.java new file mode 100644 index 00000000000..58079925e45 --- /dev/null +++ b/src/com/android/settings/datausage/BillingCycleSettings.java @@ -0,0 +1,357 @@ +/* + * 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.datausage; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.Context; +import android.content.DialogInterface; +import android.content.res.Resources; +import android.net.NetworkPolicy; +import android.net.NetworkTemplate; +import android.os.Bundle; +import android.support.v14.preference.SwitchPreference; +import android.support.v7.preference.Preference; +import android.text.format.Formatter; +import android.text.format.Time; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.NumberPicker; +import com.android.settings.InstrumentedFragment; +import com.android.settings.R; +import com.android.settingslib.NetworkPolicyEditor; +import com.android.settingslib.net.DataUsageController; + +import static android.net.NetworkPolicy.LIMIT_DISABLED; +import static android.net.NetworkPolicy.WARNING_DISABLED; +import static android.net.TrafficStats.GB_IN_BYTES; +import static android.net.TrafficStats.MB_IN_BYTES; + +public class BillingCycleSettings extends DataUsageBase implements + Preference.OnPreferenceChangeListener { + + private static final String TAG = "BillingCycleSettings"; + private static final boolean LOGD = false; + + private static final String TAG_CONFIRM_LIMIT = "confirmLimit"; + private static final String TAG_CYCLE_EDITOR = "cycleEditor"; + private static final String TAG_WARNING_EDITOR = "warningEditor"; + + private static final String KEY_BILLING_CYCLE = "billing_cycle"; + private static final String KEY_DATA_WARNING = "data_warning"; + private static final String KEY_SET_DATA_LIMIT = "set_data_limit"; + private static final String KEY_DATA_LIMIT = "data_limit"; + + private NetworkTemplate mNetworkTemplate; + private Preference mBillingCycle; + private Preference mDataWarning; + private SwitchPreference mEnableDataLimit; + private Preference mDataLimit; + private DataUsageController mDataUsageController; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + mDataUsageController = new DataUsageController(getContext()); + + Bundle args = getArguments(); + mNetworkTemplate = args.getParcelable(DataUsageList.EXTRA_NETWORK_TEMPLATE); + + addPreferencesFromResource(R.xml.billing_cycle); + mBillingCycle = findPreference(KEY_BILLING_CYCLE); + mDataWarning = findPreference(KEY_DATA_WARNING); + mEnableDataLimit = (SwitchPreference) findPreference(KEY_SET_DATA_LIMIT); + mEnableDataLimit.setOnPreferenceChangeListener(this); + mDataLimit = findPreference(KEY_DATA_LIMIT); + } + + @Override + public void onResume() { + super.onResume(); + updatePrefs(); + } + + private void updatePrefs() { + NetworkPolicy policy = services.mPolicyEditor.getPolicy(mNetworkTemplate); + mBillingCycle.setSummary(getString(R.string.billing_cycle_summary, policy != null ? + policy.cycleDay : 1)); + mDataWarning.setSummary(Formatter.formatFileSize(getContext(), policy != null + ? policy.warningBytes : DataUsageController.DEFAULT_WARNING_LEVEL)); + if (policy != null && policy.limitBytes != LIMIT_DISABLED) { + mDataLimit.setSummary(Formatter.formatFileSize(getContext(), policy.limitBytes)); + mDataLimit.setEnabled(true); + mEnableDataLimit.setChecked(true); + } else { + mDataLimit.setSummary(null); + mDataLimit.setEnabled(false); + mEnableDataLimit.setChecked(false); + } + } + + @Override + public boolean onPreferenceTreeClick(Preference preference) { + if (preference == mBillingCycle) { + CycleEditorFragment.show(this); + return true; + } else if (preference == mDataWarning) { + BytesEditorFragment.show(this, false); + return true; + } else if (preference == mDataLimit) { + BytesEditorFragment.show(this, true); + return true; + } + return super.onPreferenceTreeClick(preference); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (mEnableDataLimit == preference) { + boolean enabled = (Boolean) newValue; + if (enabled) { + ConfirmLimitFragment.show(this); + } else { + setPolicyLimitBytes(LIMIT_DISABLED); + } + return true; + } + return false; + } + + @Override + protected int getMetricsCategory() { + return InstrumentedFragment.BILLING_CYCLE; + } + + private void setPolicyLimitBytes(long limitBytes) { + if (LOGD) Log.d(TAG, "setPolicyLimitBytes()"); + services.mPolicyEditor.setPolicyLimitBytes(mNetworkTemplate, limitBytes); + updatePrefs(); + } + + /** + * Dialog to edit {@link NetworkPolicy#warningBytes}. + */ + public static class BytesEditorFragment extends DialogFragment + implements DialogInterface.OnClickListener{ + private static final String EXTRA_TEMPLATE = "template"; + private static final String EXTRA_LIMIT = "limit"; + private View mView; + + public static void show(BillingCycleSettings parent, boolean isLimit) { + if (!parent.isAdded()) return; + + final Bundle args = new Bundle(); + args.putParcelable(EXTRA_TEMPLATE, parent.mNetworkTemplate); + args.putBoolean(EXTRA_LIMIT, isLimit); + + final BytesEditorFragment dialog = new BytesEditorFragment(); + dialog.setArguments(args); + dialog.setTargetFragment(parent, 0); + dialog.show(parent.getFragmentManager(), TAG_WARNING_EDITOR); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final Context context = getActivity(); + + + final LayoutInflater dialogInflater = LayoutInflater.from(context); + mView = dialogInflater.inflate(R.layout.data_usage_bytes_editor, null, false); + setupPicker((NumberPicker) mView.findViewById(R.id.bytes)); + return new AlertDialog.Builder(context) + .setTitle(R.string.data_usage_warning_editor_title) + .setView(mView) + .setPositiveButton(R.string.data_usage_cycle_editor_positive, this) + .create(); + } + + private void setupPicker(NumberPicker bytesPicker) { + final BillingCycleSettings target = (BillingCycleSettings) getTargetFragment(); + final NetworkPolicyEditor editor = target.services.mPolicyEditor; + + final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE); + final boolean isLimit = getArguments().getBoolean(EXTRA_LIMIT); + final long warningBytes = editor.getPolicyWarningBytes(template); + final long limitBytes = editor.getPolicyLimitBytes(template); + + if (isLimit) { + bytesPicker.setMaxValue(Integer.MAX_VALUE); + if (warningBytes != WARNING_DISABLED) { + bytesPicker.setMinValue((int) (warningBytes / MB_IN_BYTES) + 1); + } else { + bytesPicker.setMinValue(0); + } + } else { + bytesPicker.setMinValue(0); + if (limitBytes != LIMIT_DISABLED) { + bytesPicker.setMaxValue((int) (limitBytes / MB_IN_BYTES) - 1); + } else { + bytesPicker.setMaxValue(Integer.MAX_VALUE); + } + } + bytesPicker.setValue((int) ((isLimit ? limitBytes : warningBytes) / MB_IN_BYTES)); + bytesPicker.setWrapSelectorWheel(false); + } + + @Override + public void onClick(DialogInterface dialog, int which) { + if (which != DialogInterface.BUTTON_POSITIVE) { + return; + } + final BillingCycleSettings target = (BillingCycleSettings) getTargetFragment(); + final NetworkPolicyEditor editor = target.services.mPolicyEditor; + + final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE); + final boolean isLimit = getArguments().getBoolean(EXTRA_LIMIT); + NumberPicker bytesPicker = (NumberPicker) mView.findViewById(R.id.bytes); + // clear focus to finish pending text edits + bytesPicker.clearFocus(); + + final long bytes = bytesPicker.getValue() * MB_IN_BYTES; + if (isLimit) { + editor.setPolicyLimitBytes(template, bytes); + } else { + editor.setPolicyWarningBytes(template, bytes); + } + target.updatePrefs(); + } + } + + /** + * Dialog to edit {@link NetworkPolicy#cycleDay}. + */ + public static class CycleEditorFragment extends DialogFragment implements + DialogInterface.OnClickListener{ + private static final String EXTRA_TEMPLATE = "template"; + private NumberPicker mCycleDayPicker; + + public static void show(BillingCycleSettings parent) { + if (!parent.isAdded()) return; + + final Bundle args = new Bundle(); + args.putParcelable(EXTRA_TEMPLATE, parent.mNetworkTemplate); + + final CycleEditorFragment dialog = new CycleEditorFragment(); + dialog.setArguments(args); + dialog.setTargetFragment(parent, 0); + dialog.show(parent.getFragmentManager(), TAG_CYCLE_EDITOR); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final Context context = getActivity(); + final BillingCycleSettings target = (BillingCycleSettings) getTargetFragment(); + final NetworkPolicyEditor editor = target.services.mPolicyEditor; + + final AlertDialog.Builder builder = new AlertDialog.Builder(context); + final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext()); + + final View view = dialogInflater.inflate(R.layout.data_usage_cycle_editor, null, false); + mCycleDayPicker = (NumberPicker) view.findViewById(R.id.cycle_day); + + final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE); + final int cycleDay = editor.getPolicyCycleDay(template); + + mCycleDayPicker.setMinValue(1); + mCycleDayPicker.setMaxValue(31); + mCycleDayPicker.setValue(cycleDay); + mCycleDayPicker.setWrapSelectorWheel(true); + + return builder.setTitle(R.string.data_usage_cycle_editor_title) + .setView(view) + .setPositiveButton(R.string.data_usage_cycle_editor_positive, this) + .create(); + } + + @Override + public void onClick(DialogInterface dialog, int which) { + final NetworkTemplate template = getArguments().getParcelable(EXTRA_TEMPLATE); + final BillingCycleSettings target = (BillingCycleSettings) getTargetFragment(); + final NetworkPolicyEditor editor = target.services.mPolicyEditor; + + // clear focus to finish pending text edits + mCycleDayPicker.clearFocus(); + + final int cycleDay = mCycleDayPicker.getValue(); + final String cycleTimezone = new Time().timezone; + editor.setPolicyCycleDay(template, cycleDay, cycleTimezone); + target.updatePrefs(); + } + } + + /** + * Dialog to request user confirmation before setting + * {@link NetworkPolicy#limitBytes}. + */ + public static class ConfirmLimitFragment extends DialogFragment implements + DialogInterface.OnClickListener{ + private static final String EXTRA_MESSAGE = "message"; + private static final String EXTRA_LIMIT_BYTES = "limitBytes"; + public static final float FLOAT = 1.2f; + + public static void show(BillingCycleSettings parent) { + if (!parent.isAdded()) return; + + final NetworkPolicy policy = parent.services.mPolicyEditor + .getPolicy(parent.mNetworkTemplate); + if (policy == null) return; + + final Resources res = parent.getResources(); + final CharSequence message; + final long minLimitBytes = (long) (policy.warningBytes * FLOAT); + final long limitBytes; + + // TODO: customize default limits based on network template + message = res.getString(R.string.data_usage_limit_dialog_mobile); + limitBytes = Math.max(5 * GB_IN_BYTES, minLimitBytes); + + final Bundle args = new Bundle(); + args.putCharSequence(EXTRA_MESSAGE, message); + args.putLong(EXTRA_LIMIT_BYTES, limitBytes); + + final ConfirmLimitFragment dialog = new ConfirmLimitFragment(); + dialog.setArguments(args); + dialog.setTargetFragment(parent, 0); + dialog.show(parent.getFragmentManager(), TAG_CONFIRM_LIMIT); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final Context context = getActivity(); + + final CharSequence message = getArguments().getCharSequence(EXTRA_MESSAGE); + + return new AlertDialog.Builder(context) + .setTitle(R.string.data_usage_limit_dialog_title) + .setMessage(message) + .setPositiveButton(android.R.string.ok, this) + .setNegativeButton(android.R.string.cancel, null) + .create(); + } + + @Override + public void onClick(DialogInterface dialog, int which) { + if (which != DialogInterface.BUTTON_POSITIVE) return; + final long limitBytes = getArguments().getLong(EXTRA_LIMIT_BYTES); + final BillingCycleSettings target = (BillingCycleSettings) getTargetFragment(); + if (target != null) { + target.setPolicyLimitBytes(limitBytes); + } + } + } +} diff --git a/src/com/android/settings/datausage/CellDataPreference.java b/src/com/android/settings/datausage/CellDataPreference.java new file mode 100644 index 00000000000..0a91be213de --- /dev/null +++ b/src/com/android/settings/datausage/CellDataPreference.java @@ -0,0 +1,276 @@ +/* + * 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.datausage; + +import android.app.AlertDialog; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.NetworkTemplate; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.v7.preference.PreferenceViewHolder; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.widget.Checkable; +import com.android.internal.logging.MetricsLogger; +import com.android.settings.CustomDialogPreference; +import com.android.settings.R; +import com.android.settings.Utils; + +import java.util.List; + +public class CellDataPreference extends CustomDialogPreference implements TemplatePreference { + + // TODO: Get Telephony to broadcast when this changes, and remove this. + static final String ACTION_DATA_ENABLED_CHANGED = + "com.android.settings.action.DATA_ENABLED_CHANGED"; + + private static final String TAG = "CellDataPreference"; + + public int mSubId; + public boolean mChecked; + public boolean mMultiSimDialog; + private TelephonyManager mTelephonyManager; + private SubscriptionManager mSubscriptionManager; + + public CellDataPreference(Context context, AttributeSet attrs) { + super(context, attrs, android.R.attr.switchPreferenceStyle); + } + + @Override + protected void onRestoreInstanceState(Parcelable s) { + CellDataState state = (CellDataState) s; + super.onRestoreInstanceState(state.getSuperState()); + mTelephonyManager = TelephonyManager.from(getContext()); + mChecked = state.mChecked; + mMultiSimDialog = state.mMultiSimDialog; + mSubId = state.mSubId; + setKey(getKey() + mSubId); + notifyChanged(); + } + + @Override + protected Parcelable onSaveInstanceState() { + CellDataState state = new CellDataState(super.onSaveInstanceState()); + state.mChecked = mChecked; + state.mMultiSimDialog = mMultiSimDialog; + state.mSubId = mSubId; + return state; + } + + @Override + public void onAttached() { + super.onAttached(); + getContext().registerReceiver(mReceiver, new IntentFilter(ACTION_DATA_ENABLED_CHANGED)); + } + + @Override + public void onDetached() { + getContext().unregisterReceiver(mReceiver); + super.onDetached(); + } + + @Override + public void setTemplate(NetworkTemplate template, int subId, NetworkServices services) { + if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { + throw new IllegalArgumentException("CellDataPreference needs a SubscriptionInfo"); + } + mTelephonyManager = TelephonyManager.from(getContext()); + if (mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { + mSubId = subId; + setKey(getKey() + subId); + } + updateChecked(); + } + + private void updateChecked() { + setChecked(mTelephonyManager.getDataEnabled(mSubId)); + } + + @Override + protected void performClick(View view) { + super.performClick(view); + MetricsLogger.action(getContext(), MetricsLogger.ACTION_CELL_DATA_TOGGLE, !mChecked); + if (mChecked) { + // disabling data; show confirmation dialog which eventually + // calls setMobileDataEnabled() once user confirms. + mMultiSimDialog = false; + super.performClick(view); + } else { + // If we are showing the Sim Card tile then we are a Multi-Sim device. + if (Utils.showSimCardTile(getContext())) { + mMultiSimDialog = true; + super.performClick(view); + } else { + setMobileDataEnabled(true); + } + } + } + + private void setMobileDataEnabled(boolean enabled) { + if (DataUsageSummary.LOGD) Log.d(TAG, "setMobileDataEnabled()"); + mTelephonyManager.setDataEnabled(mSubId, enabled); + setChecked(enabled); + getContext().sendBroadcast(new Intent(ACTION_DATA_ENABLED_CHANGED)); + } + + private void setChecked(boolean checked) { + if (mChecked == checked) return; + mChecked = checked; + notifyChanged(); + } + + @Override + public void onBindViewHolder(PreferenceViewHolder holder) { + super.onBindViewHolder(holder); + View switchView = holder.findViewById(android.R.id.switch_widget); + switchView.setClickable(false); + ((Checkable) switchView).setChecked(mChecked); + } + + @Override + protected void onPrepareDialogBuilder(AlertDialog.Builder builder, + DialogInterface.OnClickListener listener) { + if (mMultiSimDialog) { + showMultiSimDialog(builder, listener); + } else { + showDisableDialog(builder, listener); + } + } + + private void showDisableDialog(AlertDialog.Builder builder, + DialogInterface.OnClickListener listener) { + builder.setTitle(null) + .setMessage(R.string.data_usage_disable_mobile) + .setPositiveButton(android.R.string.ok, listener) + .setNegativeButton(android.R.string.cancel, null); + } + + private void showMultiSimDialog(AlertDialog.Builder builder, + DialogInterface.OnClickListener listener) { + mSubscriptionManager = SubscriptionManager.from(getContext()); + final SubscriptionInfo currentSir = mSubscriptionManager.getActiveSubscriptionInfo(mSubId); + + final SubscriptionInfo nextSir = mSubscriptionManager.getDefaultDataSubscriptionInfo(); + + // If the device is single SIM or is enabling data on the active data SIM then forgo + // the pop-up. + if (!Utils.showSimCardTile(getContext()) || + (nextSir != null && currentSir != null && + currentSir.getSubscriptionId() == nextSir.getSubscriptionId())) { + setMobileDataEnabled(true); + if (nextSir != null && currentSir != null && + currentSir.getSubscriptionId() == nextSir.getSubscriptionId()) { + disableDataForOtherSubscriptions(currentSir); + } + return; + } + + final String previousName = (nextSir == null) + ? getContext().getResources().getString(R.string.sim_selection_required_pref) + : nextSir.getDisplayName().toString(); + + builder.setTitle(R.string.sim_change_data_title); + builder.setMessage(getContext().getString(R.string.sim_change_data_message, + currentSir.getDisplayName(), previousName)); + + builder.setPositiveButton(R.string.okay, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + mSubscriptionManager.setDefaultDataSubId(currentSir.getSubscriptionId()); + setMobileDataEnabled(true); + disableDataForOtherSubscriptions(currentSir); + } + }); + builder.setNegativeButton(R.string.cancel, null); + + builder.create().show(); + } + + private void disableDataForOtherSubscriptions(SubscriptionInfo currentSir) { + List<SubscriptionInfo> subInfoList = mSubscriptionManager.getActiveSubscriptionInfoList(); + if (subInfoList != null) { + for (SubscriptionInfo subInfo : subInfoList) { + if (subInfo.getSubscriptionId() != currentSir.getSubscriptionId()) { + mTelephonyManager.setDataEnabled(subInfo.getSubscriptionId(), false); + } + } + } + getContext().sendBroadcast(new Intent(ACTION_DATA_ENABLED_CHANGED)); + } + + @Override + protected void onClick(DialogInterface dialog, int which) { + if (which != DialogInterface.BUTTON_POSITIVE) { + return; + } + if (mMultiSimDialog) { + } else { + // TODO: extend to modify policy enabled flag. + setMobileDataEnabled(false); + } + } + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + updateChecked(); + } + }; + + public static class CellDataState extends BaseSavedState { + public int mSubId; + public boolean mChecked; + public boolean mMultiSimDialog; + + public CellDataState(Parcelable base) { + super(base); + } + + public CellDataState(Parcel source) { + super(source); + mChecked = source.readByte() != 0; + mMultiSimDialog = source.readByte() != 0; + mSubId = source.readInt(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeByte((byte) (mChecked ? 1 : 0)); + dest.writeByte((byte) (mMultiSimDialog ? 1 : 0)); + dest.writeInt(mSubId); + } + + public static final Creator<CellDataState> CREATOR = new Creator<CellDataState>() { + @Override + public CellDataState createFromParcel(Parcel source) { + return new CellDataState(source); + } + + @Override + public CellDataState[] newArray(int size) { + return new CellDataState[size]; + } + }; + } +} diff --git a/src/com/android/settings/datausage/ChartDataUsagePreference.java b/src/com/android/settings/datausage/ChartDataUsagePreference.java new file mode 100644 index 00000000000..f328cf6ea33 --- /dev/null +++ b/src/com/android/settings/datausage/ChartDataUsagePreference.java @@ -0,0 +1,82 @@ +/* + * 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.datausage; + +import android.content.Context; +import android.graphics.Color; +import android.net.NetworkPolicy; +import android.net.NetworkStatsHistory; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceViewHolder; +import android.util.AttributeSet; +import com.android.settings.R; +import com.android.settings.widget.ChartDataUsageView; +import com.android.settings.widget.ChartNetworkSeriesView; + +public class ChartDataUsagePreference extends Preference { + + private NetworkPolicy mPolicy; + private long mStart; + private long mEnd; + private NetworkStatsHistory mNetwork; + private int mSecondaryColor; + private int mSeriesColor; + + public ChartDataUsagePreference(Context context, AttributeSet attrs) { + super(context, attrs); + setLayoutResource(R.layout.data_usage_chart); + } + + @Override + public void onBindViewHolder(PreferenceViewHolder holder) { + super.onBindViewHolder(holder); + ChartDataUsageView chart = (ChartDataUsageView) holder.itemView; + chart.setVisibleRange(mStart, mEnd); + chart.bindNetworkPolicy(mPolicy); + chart.bindNetworkStats(mNetwork); + ChartNetworkSeriesView series = (ChartNetworkSeriesView) holder.findViewById(R.id.series); + series.setChartColor(Color.BLACK, mSeriesColor, mSecondaryColor); + } + + public void bindNetworkPolicy(NetworkPolicy policy) { + mPolicy = policy; + notifyChanged(); + } + + public void setVisibleRange(long start, long end) { + mStart = start; + mEnd = end; + notifyChanged(); + } + + public long getInspectStart() { + return mStart; + } + + public long getInspectEnd() { + return mEnd; + } + + public void bindNetworkStats(NetworkStatsHistory network) { + mNetwork = network; + notifyChanged(); + } + + public void setColors(int seriesColor, int secondaryColor) { + mSeriesColor = seriesColor; + mSecondaryColor = secondaryColor; + notifyChanged(); + } +} diff --git a/src/com/android/settings/datausage/CycleAdapter.java b/src/com/android/settings/datausage/CycleAdapter.java new file mode 100644 index 00000000000..682cc8aca83 --- /dev/null +++ b/src/com/android/settings/datausage/CycleAdapter.java @@ -0,0 +1,188 @@ +/* + * 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.datausage; + +import com.android.settings.Utils; +import com.android.settingslib.net.ChartData; + +import android.content.Context; +import android.net.NetworkPolicy; +import android.net.NetworkStatsHistory; +import android.text.format.DateUtils; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Spinner; + +import libcore.util.Objects; + +import static android.net.NetworkPolicyManager.computeLastCycleBoundary; +import static android.net.NetworkPolicyManager.computeNextCycleBoundary; + +public class CycleAdapter extends ArrayAdapter<CycleAdapter.CycleItem> { + + private final Spinner mSpinner; + private final AdapterView.OnItemSelectedListener mListener; + + public CycleAdapter(Context context, Spinner spinner, + AdapterView.OnItemSelectedListener listener) { + super(context, com.android.settings.R.layout.filter_spinner_item); + setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + mSpinner = spinner; + mListener = listener; + mSpinner.setAdapter(this); + mSpinner.setOnItemSelectedListener(mListener); + } + + /** + * Find position of {@link CycleItem} in this adapter which is nearest + * the given {@link CycleItem}. + */ + public int findNearestPosition(CycleItem target) { + if (target != null) { + final int count = getCount(); + for (int i = count - 1; i >= 0; i--) { + final CycleItem item = getItem(i); + if (item.compareTo(target) >= 0) { + return i; + } + } + } + return 0; + } + + /** + * Rebuild list based on {@link NetworkPolicy#cycleDay} + * and available {@link NetworkStatsHistory} data. Always selects the newest + * item, updating the inspection range on chartData. + */ + public boolean updateCycleList(NetworkPolicy policy, ChartData chartData) { + // stash away currently selected cycle to try restoring below + final CycleAdapter.CycleItem previousItem = (CycleAdapter.CycleItem) + mSpinner.getSelectedItem(); + clear(); + + final Context context = mSpinner.getContext(); + NetworkStatsHistory.Entry entry = null; + + long historyStart = Long.MAX_VALUE; + long historyEnd = Long.MIN_VALUE; + if (chartData != null) { + historyStart = chartData.network.getStart(); + historyEnd = chartData.network.getEnd(); + } + + final long now = System.currentTimeMillis(); + if (historyStart == Long.MAX_VALUE) historyStart = now; + if (historyEnd == Long.MIN_VALUE) historyEnd = now + 1; + + boolean hasCycles = false; + if (policy != null) { + // find the next cycle boundary + long cycleEnd = computeNextCycleBoundary(historyEnd, policy); + + // walk backwards, generating all valid cycle ranges + while (cycleEnd > historyStart) { + final long cycleStart = computeLastCycleBoundary(cycleEnd, policy); + + final boolean includeCycle; + if (chartData != null) { + entry = chartData.network.getValues(cycleStart, cycleEnd, entry); + includeCycle = (entry.rxBytes + entry.txBytes) > 0; + } else { + includeCycle = true; + } + + if (includeCycle) { + add(new CycleAdapter.CycleItem(context, cycleStart, cycleEnd)); + hasCycles = true; + } + cycleEnd = cycleStart; + } + } + + if (!hasCycles) { + // no policy defined cycles; show entry for each four-week period + long cycleEnd = historyEnd; + while (cycleEnd > historyStart) { + final long cycleStart = cycleEnd - (DateUtils.WEEK_IN_MILLIS * 4); + + final boolean includeCycle; + if (chartData != null) { + entry = chartData.network.getValues(cycleStart, cycleEnd, entry); + includeCycle = (entry.rxBytes + entry.txBytes) > 0; + } else { + includeCycle = true; + } + + if (includeCycle) { + add(new CycleAdapter.CycleItem(context, cycleStart, cycleEnd)); + } + cycleEnd = cycleStart; + } + } + + // force pick the current cycle (first item) + if (getCount() > 0) { + final int position = findNearestPosition(previousItem); + mSpinner.setSelection(position); + + // only force-update cycle when changed; skipping preserves any + // user-defined inspection region. + final CycleAdapter.CycleItem selectedItem = getItem(position); + if (!Objects.equal(selectedItem, previousItem)) { + mListener.onItemSelected(mSpinner, null, position, 0); + return false; + } + } + return true; + } + + /** + * List item that reflects a specific data usage cycle. + */ + public static class CycleItem implements Comparable<CycleItem> { + public CharSequence label; + public long start; + public long end; + + public CycleItem(CharSequence label) { + this.label = label; + } + + public CycleItem(Context context, long start, long end) { + this.label = Utils.formatDateRange(context, start, end); + this.start = start; + this.end = end; + } + + @Override + public String toString() { + return label.toString(); + } + + @Override + public boolean equals(Object o) { + if (o instanceof CycleItem) { + final CycleItem another = (CycleItem) o; + return start == another.start && end == another.end; + } + return false; + } + + @Override + public int compareTo(CycleItem another) { + return Long.compare(start, another.start); + } + } +} diff --git a/src/com/android/settings/datausage/DataUsageBase.java b/src/com/android/settings/datausage/DataUsageBase.java new file mode 100644 index 00000000000..ef565a12690 --- /dev/null +++ b/src/com/android/settings/datausage/DataUsageBase.java @@ -0,0 +1,92 @@ +/* + * 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.datausage; + +import com.android.settings.SettingsPreferenceFragment; +import com.android.settingslib.NetworkPolicyEditor; + +import android.content.Context; +import android.net.INetworkStatsService; +import android.net.NetworkPolicy; +import android.net.NetworkPolicyManager; +import android.os.Bundle; +import android.os.INetworkManagementService; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.UserManager; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import android.util.Log; + +public abstract class DataUsageBase extends SettingsPreferenceFragment { + + private static final String TAG = "DataUsageBase"; + + protected final TemplatePreference.NetworkServices services = + new TemplatePreference.NetworkServices(); + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + final Context context = getActivity(); + + services.mNetworkService = INetworkManagementService.Stub.asInterface( + ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)); + services.mStatsService = INetworkStatsService.Stub.asInterface( + ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); + services.mPolicyManager = NetworkPolicyManager.from(context); + + services.mPolicyEditor = new NetworkPolicyEditor(services.mPolicyManager); + + services.mTelephonyManager = TelephonyManager.from(context); + services.mSubscriptionManager = SubscriptionManager.from(context); + services.mUserManager = UserManager.get(context); + } + + @Override + public void onResume() { + super.onResume(); + services.mPolicyEditor.read(); + } + + protected boolean isAdmin() { + return services.mUserManager.isAdminUser(); + } + + protected boolean isMobileDataAvailable(int subId) { + return services.mSubscriptionManager.getActiveSubscriptionInfo(subId) != null; + } + + protected boolean isNetworkPolicyModifiable(NetworkPolicy policy, int subId) { + return policy != null && isBandwidthControlEnabled() && services.mUserManager.isAdminUser() + && isDataEnabled(subId); + } + + private boolean isDataEnabled(int subId) { + if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { + return true; + } + return services.mTelephonyManager.getDataEnabled(subId); + } + + protected boolean isBandwidthControlEnabled() { + try { + return services.mNetworkService.isBandwidthControlEnabled(); + } catch (RemoteException e) { + Log.w(TAG, "problem talking with INetworkManagementService: " + e); + return false; + } + } +} diff --git a/src/com/android/settings/datausage/DataUsageList.java b/src/com/android/settings/datausage/DataUsageList.java new file mode 100644 index 00000000000..4aa52ba371a --- /dev/null +++ b/src/com/android/settings/datausage/DataUsageList.java @@ -0,0 +1,550 @@ +/* + * 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.datausage; + +import android.app.ActivityManager; +import android.app.LoaderManager.LoaderCallbacks; +import android.content.Context; +import android.content.Loader; +import android.content.pm.UserInfo; +import android.graphics.Color; +import android.net.ConnectivityManager; +import android.net.INetworkStatsSession; +import android.net.NetworkPolicy; +import android.net.NetworkStats; +import android.net.NetworkStatsHistory; +import android.net.NetworkTemplate; +import android.net.TrafficStats; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.os.UserManager; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceGroup; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import android.text.format.DateUtils; +import android.text.format.Formatter; +import android.util.Log; +import android.util.SparseArray; +import android.view.View; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.Spinner; +import com.android.settings.InstrumentedFragment; +import com.android.settings.R; +import com.android.settingslib.AppItem; +import com.android.settingslib.net.ChartData; +import com.android.settingslib.net.ChartDataLoader; +import com.android.settingslib.net.SummaryForAllUidLoader; +import com.android.settingslib.net.UidDetailProvider; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static android.net.ConnectivityManager.TYPE_MOBILE; +import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND; +import static android.net.TrafficStats.UID_REMOVED; +import static android.net.TrafficStats.UID_TETHERING; +import static android.telephony.TelephonyManager.SIM_STATE_READY; +import static com.android.settings.datausage.DataUsageSummary.TEST_RADIOS; +import static com.android.settings.datausage.DataUsageSummary.TEST_RADIOS_PROP; + +/** + * Panel showing data usage history across various networks, including options + * to inspect based on usage cycle and control through {@link NetworkPolicy}. + */ +public class DataUsageList extends DataUsageBase { + private static final String TAG = "DataUsage"; + private static final boolean LOGD = false; + + private static final String KEY_USAGE_AMOUNT = "usage_amount"; + private static final String KEY_CHART_DATA = "chart_data"; + private static final String KEY_APPS_GROUP = "apps_group"; + + private static final int LOADER_CHART_DATA = 2; + private static final int LOADER_SUMMARY = 3; + public static final String EXTRA_SUB_ID = "sub_id"; + public static final String EXTRA_NETWORK_TEMPLATE = "network_template"; + + private INetworkStatsSession mStatsSession; + + private ChartDataUsagePreference mChart; + + private NetworkTemplate mTemplate; + private int mSubId; + private ChartData mChartData; + + /** Flag used to ignore listeners during binding. */ + private boolean mBinding; + + private UidDetailProvider mUidDetailProvider; + + /** + * Local cache of data enabled for subId, used to work around delays. + */ + private final Map<String, Boolean> mMobileDataEnabled = new HashMap<String, Boolean>(); + private CycleAdapter mCycleAdapter; + private Spinner mCycleSpinner; + private Preference mUsageAmount; + private PreferenceGroup mApps; + private View mHeader; + + @Override + protected int getMetricsCategory() { + return InstrumentedFragment.DATA_USAGE_LIST; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + final Context context = getActivity(); + + if (!isBandwidthControlEnabled()) { + Log.w(TAG, "No bandwidth control; leaving"); + getActivity().finish(); + } + + try { + mStatsSession = services.mStatsService.openSession(); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + + mUidDetailProvider = new UidDetailProvider(context); + + addPreferencesFromResource(R.xml.data_usage_list); + mUsageAmount = findPreference(KEY_USAGE_AMOUNT); + mChart = (ChartDataUsagePreference) findPreference(KEY_CHART_DATA); + mApps = (PreferenceGroup) findPreference(KEY_APPS_GROUP); + + final Bundle args = getArguments(); + mSubId = args.getInt(EXTRA_SUB_ID, SubscriptionManager.INVALID_SUBSCRIPTION_ID); + mTemplate = args.getParcelable(EXTRA_NETWORK_TEMPLATE); + } + + @Override + public void onViewCreated(View v, Bundle savedInstanceState) { + super.onViewCreated(v, savedInstanceState); + + mHeader = setPinnedHeaderView(R.layout.apps_filter_spinner); + mCycleSpinner = (Spinner) mHeader.findViewById(R.id.filter_spinner); + mCycleAdapter = new CycleAdapter(getContext(), mCycleSpinner, mCycleListener); + setLoading(true, false); + } + + @Override + public void onResume() { + super.onResume(); + + updateBody(); + + // kick off background task to update stats + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... params) { + try { + // wait a few seconds before kicking off + Thread.sleep(2 * DateUtils.SECOND_IN_MILLIS); + services.mStatsService.forceUpdate(); + } catch (InterruptedException e) { + } catch (RemoteException e) { + } + return null; + } + + @Override + protected void onPostExecute(Void result) { + if (isAdded()) { + updateBody(); + } + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + @Override + public void onDestroy() { + mUidDetailProvider.clearCache(); + mUidDetailProvider = null; + + TrafficStats.closeQuietly(mStatsSession); + + super.onDestroy(); + } + + /** + * Update body content based on current tab. Loads + * {@link NetworkStatsHistory} and {@link NetworkPolicy} from system, and + * binds them to visible controls. + */ + private void updateBody() { + mBinding = true; + if (!isAdded()) return; + + final Context context = getActivity(); + + // kick off loader for network history + // TODO: consider chaining two loaders together instead of reloading + // network history when showing app detail. + getLoaderManager().restartLoader(LOADER_CHART_DATA, + ChartDataLoader.buildArgs(mTemplate, null), mChartDataCallbacks); + + // detail mode can change visible menus, invalidate + getActivity().invalidateOptionsMenu(); + + mBinding = false; + + int seriesColor = context.getColor(R.color.sim_noitification); + if (mSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID){ + final SubscriptionInfo sir = services.mSubscriptionManager + .getActiveSubscriptionInfo(mSubId); + + if (sir != null) { + seriesColor = sir.getIconTint(); + } + } + + final int secondaryColor = Color.argb(127, Color.red(seriesColor), Color.green(seriesColor), + Color.blue(seriesColor)); + mChart.setColors(seriesColor, secondaryColor); + } + + /** + * Update chart sweeps and cycle list to reflect {@link NetworkPolicy} for + * current {@link #mTemplate}. + */ + private void updatePolicy(boolean refreshCycle) { + final NetworkPolicy policy = services.mPolicyEditor.getPolicy(mTemplate); + //SUB SELECT + if (isNetworkPolicyModifiable(policy, mSubId) && isMobileDataAvailable(mSubId)) { + mChart.bindNetworkPolicy(policy); + mHeader.findViewById(R.id.filter_settings).setVisibility(View.VISIBLE); + mHeader.findViewById(R.id.filter_settings).setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + Bundle args = new Bundle(); + args.putParcelable(DataUsageList.EXTRA_NETWORK_TEMPLATE, mTemplate); + startFragment(DataUsageList.this, BillingCycleSettings.class.getName(), + R.string.billing_cycle, 0, args); + } + }); + } else { + // controls are disabled; don't bind warning/limit sweeps + mChart.bindNetworkPolicy(null); + mHeader.findViewById(R.id.filter_settings).setVisibility(View.GONE); + } + + if (refreshCycle) { + // generate cycle list based on policy and available history + if (mCycleAdapter.updateCycleList(policy, mChartData)) { + updateDetailData(); + } + } + } + + /** + * Update details based on {@link #mChart} inspection range depending on + * current mode. Updates {@link #mAdapter} with sorted list + * of applications data usage. + */ + private void updateDetailData() { + if (LOGD) Log.d(TAG, "updateDetailData()"); + + final long start = mChart.getInspectStart(); + final long end = mChart.getInspectEnd(); + final long now = System.currentTimeMillis(); + + final Context context = getActivity(); + + NetworkStatsHistory.Entry entry = null; + if (mChartData != null) { + entry = mChartData.network.getValues(start, end, now, null); + } + + // kick off loader for detailed stats + getLoaderManager().restartLoader(LOADER_SUMMARY, + SummaryForAllUidLoader.buildArgs(mTemplate, start, end), mSummaryCallbacks); + + final long totalBytes = entry != null ? entry.rxBytes + entry.txBytes : 0; + final String totalPhrase = Formatter.formatFileSize(context, totalBytes); + mUsageAmount.setTitle(getString(R.string.data_used_template, totalPhrase)); + } + + /** + * Bind the given {@link NetworkStats}, or {@code null} to clear list. + */ + public void bindStats(NetworkStats stats, int[] restrictedUids) { + ArrayList<AppItem> items = new ArrayList<>(); + long largest = 0; + + final int currentUserId = ActivityManager.getCurrentUser(); + UserManager userManager = UserManager.get(getContext()); + final List<UserHandle> profiles = userManager.getUserProfiles(); + final SparseArray<AppItem> knownItems = new SparseArray<AppItem>(); + + NetworkStats.Entry entry = null; + final int size = stats != null ? stats.size() : 0; + for (int i = 0; i < size; i++) { + entry = stats.getValues(i, entry); + + // Decide how to collapse items together + final int uid = entry.uid; + + final int collapseKey; + final int category; + final int userId = UserHandle.getUserId(uid); + if (UserHandle.isApp(uid)) { + if (profiles.contains(new UserHandle(userId))) { + if (userId != currentUserId) { + // Add to a managed user item. + final int managedKey = UidDetailProvider.buildKeyForUser(userId); + largest = accumulate(managedKey, knownItems, entry, AppItem.CATEGORY_USER, + items, largest); + } + // Add to app item. + collapseKey = uid; + category = AppItem.CATEGORY_APP; + } else { + // If it is a removed user add it to the removed users' key + final UserInfo info = userManager.getUserInfo(userId); + if (info == null) { + collapseKey = UID_REMOVED; + category = AppItem.CATEGORY_APP; + } else { + // Add to other user item. + collapseKey = UidDetailProvider.buildKeyForUser(userId); + category = AppItem.CATEGORY_USER; + } + } + } else if (uid == UID_REMOVED || uid == UID_TETHERING) { + collapseKey = uid; + category = AppItem.CATEGORY_APP; + } else { + collapseKey = android.os.Process.SYSTEM_UID; + category = AppItem.CATEGORY_APP; + } + largest = accumulate(collapseKey, knownItems, entry, category, items, largest); + } + + final int restrictedUidsMax = restrictedUids.length; + for (int i = 0; i < restrictedUidsMax; ++i) { + final int uid = restrictedUids[i]; + // Only splice in restricted state for current user or managed users + if (!profiles.contains(new UserHandle(UserHandle.getUserId(uid)))) { + continue; + } + + AppItem item = knownItems.get(uid); + if (item == null) { + item = new AppItem(uid); + item.total = -1; + items.add(item); + knownItems.put(item.key, item); + } + item.restricted = true; + } + + Collections.sort(items); + mApps.removeAll(); + for (int i = 0; i < items.size(); i++) { + final int percentTotal = largest != 0 ? (int) (items.get(i).total * 100 / largest) : 0; + AppDataUsagePreference preference = new AppDataUsagePreference(getContext(), + items.get(i), percentTotal, mUidDetailProvider); + preference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + AppDataUsagePreference pref = (AppDataUsagePreference) preference; + AppItem item = pref.getItem(); + startAppDataUsage(item); + return true; + } + }); + mApps.addPreference(preference); + } + } + + private void startAppDataUsage(AppItem item) { + Bundle args = new Bundle(); + args.putParcelable(AppDataUsage.ARG_APP_ITEM, item); + args.putParcelable(AppDataUsage.ARG_NETWORK_TEMPLATE, mTemplate); + startFragment(this, AppDataUsage.class.getName(), R.string.app_data_usage, 0, args); + } + + /** + * Accumulate data usage of a network stats entry for the item mapped by the collapse key. + * Creates the item if needed. + * @param collapseKey the collapse key used to map the item. + * @param knownItems collection of known (already existing) items. + * @param entry the network stats entry to extract data usage from. + * @param itemCategory the item is categorized on the list view by this category. Must be + */ + private static long accumulate(int collapseKey, final SparseArray<AppItem> knownItems, + NetworkStats.Entry entry, int itemCategory, ArrayList<AppItem> items, long largest) { + final int uid = entry.uid; + AppItem item = knownItems.get(collapseKey); + if (item == null) { + item = new AppItem(collapseKey); + item.category = itemCategory; + items.add(item); + knownItems.put(item.key, item); + } + item.addUid(uid); + item.total += entry.rxBytes + entry.txBytes; + return Math.max(largest, item.total); + } + + /** + * Test if device has a mobile data radio with SIM in ready state. + */ + public static boolean hasReadyMobileRadio(Context context) { + if (TEST_RADIOS) { + return SystemProperties.get(TEST_RADIOS_PROP).contains("mobile"); + } + + final ConnectivityManager conn = ConnectivityManager.from(context); + final TelephonyManager tele = TelephonyManager.from(context); + + final List<SubscriptionInfo> subInfoList = + SubscriptionManager.from(context).getActiveSubscriptionInfoList(); + // No activated Subscriptions + if (subInfoList == null) { + if (LOGD) Log.d(TAG, "hasReadyMobileRadio: subInfoList=null"); + return false; + } + // require both supported network and ready SIM + boolean isReady = true; + for (SubscriptionInfo subInfo : subInfoList) { + isReady = isReady & tele.getSimState(subInfo.getSimSlotIndex()) == SIM_STATE_READY; + if (LOGD) Log.d(TAG, "hasReadyMobileRadio: subInfo=" + subInfo); + } + boolean retVal = conn.isNetworkSupported(TYPE_MOBILE) && isReady; + if (LOGD) { + Log.d(TAG, "hasReadyMobileRadio:" + + " conn.isNetworkSupported(TYPE_MOBILE)=" + + conn.isNetworkSupported(TYPE_MOBILE) + + " isReady=" + isReady); + } + return retVal; + } + + /* + * TODO: consider adding to TelephonyManager or SubscriptionManager. + */ + public static boolean hasReadyMobileRadio(Context context, int subId) { + if (TEST_RADIOS) { + return SystemProperties.get(TEST_RADIOS_PROP).contains("mobile"); + } + + final ConnectivityManager conn = ConnectivityManager.from(context); + final TelephonyManager tele = TelephonyManager.from(context); + final int slotId = SubscriptionManager.getSlotId(subId); + final boolean isReady = tele.getSimState(slotId) == SIM_STATE_READY; + + boolean retVal = conn.isNetworkSupported(TYPE_MOBILE) && isReady; + if (LOGD) Log.d(TAG, "hasReadyMobileRadio: subId=" + subId + + " conn.isNetworkSupported(TYPE_MOBILE)=" + conn.isNetworkSupported(TYPE_MOBILE) + + " isReady=" + isReady); + return retVal; + } + + private OnItemSelectedListener mCycleListener = new OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { + final CycleAdapter.CycleItem cycle = (CycleAdapter.CycleItem) + parent.getItemAtPosition(position); + + if (LOGD) { + Log.d(TAG, "showing cycle " + cycle + ", start=" + cycle.start + ", end=" + + cycle.end + "]"); + } + + // update chart to show selected cycle, and update detail data + // to match updated sweep bounds. + mChart.setVisibleRange(cycle.start, cycle.end); + + updateDetailData(); + } + + @Override + public void onNothingSelected(AdapterView<?> parent) { + // ignored + } + }; + + private final LoaderCallbacks<ChartData> mChartDataCallbacks = new LoaderCallbacks< + ChartData>() { + @Override + public Loader<ChartData> onCreateLoader(int id, Bundle args) { + return new ChartDataLoader(getActivity(), mStatsSession, args); + } + + @Override + public void onLoadFinished(Loader<ChartData> loader, ChartData data) { + setLoading(false, true); + mChartData = data; + mChart.bindNetworkStats(mChartData.network); + + // calcuate policy cycles based on available data + updatePolicy(true); + } + + @Override + public void onLoaderReset(Loader<ChartData> loader) { + mChartData = null; + mChart.bindNetworkStats(null); + } + }; + + private final LoaderCallbacks<NetworkStats> mSummaryCallbacks = new LoaderCallbacks< + NetworkStats>() { + @Override + public Loader<NetworkStats> onCreateLoader(int id, Bundle args) { + return new SummaryForAllUidLoader(getActivity(), mStatsSession, args); + } + + @Override + public void onLoadFinished(Loader<NetworkStats> loader, NetworkStats data) { + final int[] restrictedUids = services.mPolicyManager.getUidsWithPolicy( + POLICY_REJECT_METERED_BACKGROUND); + bindStats(data, restrictedUids); + updateEmptyVisible(); + } + + @Override + public void onLoaderReset(Loader<NetworkStats> loader) { + bindStats(null, new int[0]); + updateEmptyVisible(); + } + + private void updateEmptyVisible() { + if ((mApps.getPreferenceCount() != 0) != + (getPreferenceScreen().getPreferenceCount() != 0)) { + if (mApps.getPreferenceCount() != 0) { + getPreferenceScreen().addPreference(mUsageAmount); + getPreferenceScreen().addPreference(mApps); + } else { + getPreferenceScreen().removeAll(); + } + } + } + }; +} diff --git a/src/com/android/settings/net/DataUsageMeteredSettings.java b/src/com/android/settings/datausage/DataUsageMeteredSettings.java index ef3a70588e4..97907048ab0 100644 --- a/src/com/android/settings/net/DataUsageMeteredSettings.java +++ b/src/com/android/settings/datausage/DataUsageMeteredSettings.java @@ -1,20 +1,18 @@ /* - * Copyright (C) 2012 The Android Open Source Project + * 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 + * 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. + * 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.net; +package com.android.settings.datausage; import android.content.Context; import android.content.res.Resources; @@ -42,8 +40,8 @@ import java.util.List; import static android.net.NetworkPolicy.LIMIT_DISABLED; import static android.net.wifi.WifiInfo.removeDoubleQuotes; -import static com.android.settings.DataUsageSummary.hasReadyMobileRadio; -import static com.android.settings.DataUsageSummary.hasWifiRadio; +import static com.android.settings.datausage.DataUsageList.hasReadyMobileRadio; +import static com.android.settings.datausage.DataUsageSummary.hasWifiRadio; /** * Panel to configure {@link NetworkPolicy#metered} for networks. diff --git a/src/com/android/settings/datausage/DataUsagePreference.java b/src/com/android/settings/datausage/DataUsagePreference.java new file mode 100644 index 00000000000..bd865b5cc37 --- /dev/null +++ b/src/com/android/settings/datausage/DataUsagePreference.java @@ -0,0 +1,58 @@ +/* + * 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.datausage; + +import android.content.Context; +import android.content.Intent; +import android.net.NetworkTemplate; +import android.os.Bundle; +import android.support.v7.preference.Preference; +import android.telephony.SubscriptionManager; +import android.text.format.Formatter; +import android.util.AttributeSet; +import com.android.settings.Utils; +import com.android.settingslib.net.DataUsageController; +import com.android.settings.R; + +public class DataUsagePreference extends Preference implements TemplatePreference { + + private NetworkTemplate mTemplate; + private int mSubId; + + public DataUsagePreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public void setTemplate(NetworkTemplate template, int subId, + NetworkServices services) { + mTemplate = template; + mSubId = subId; + DataUsageController controller = new DataUsageController(getContext()); + DataUsageController.DataUsageInfo usageInfo = controller.getDataUsageInfo(mTemplate); + setSummary(getContext().getString(R.string.data_usage_template, + Formatter.formatFileSize(getContext(), usageInfo.usageLevel), usageInfo.period)); + setIntent(getIntent()); + } + + @Override + public Intent getIntent() { + Bundle args = new Bundle(); + args.putParcelable(DataUsageList.EXTRA_NETWORK_TEMPLATE, mTemplate); + args.putInt(DataUsageList.EXTRA_SUB_ID, mSubId); + return Utils.onBuildStartFragmentIntent(getContext(), DataUsageList.class.getName(), args, + getContext().getPackageName(), 0, getTitle(), false); + } +} diff --git a/src/com/android/settings/datausage/DataUsageSummary.java b/src/com/android/settings/datausage/DataUsageSummary.java new file mode 100644 index 00000000000..de98008b477 --- /dev/null +++ b/src/com/android/settings/datausage/DataUsageSummary.java @@ -0,0 +1,375 @@ +/* + * 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.datausage; + +import android.app.Activity; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.net.ConnectivityManager; +import android.net.INetworkStatsSession; +import android.net.NetworkTemplate; +import android.net.TrafficStats; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.SystemProperties; +import android.provider.SearchIndexableResource; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import android.text.format.Formatter; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import com.android.internal.logging.MetricsLogger; +import com.android.settings.R; +import com.android.settings.SummaryPreference; +import com.android.settings.Utils; +import com.android.settings.dashboard.SummaryLoader; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settings.search.Indexable; +import com.android.settingslib.net.DataUsageController; + +import java.util.ArrayList; +import java.util.List; + +import static android.net.ConnectivityManager.TYPE_ETHERNET; +import static android.net.ConnectivityManager.TYPE_WIFI; + +public class DataUsageSummary extends DataUsageBase implements Indexable { + + private static final String TAG = "DataUsageSummary"; + static final boolean LOGD = false; + + public static final boolean TEST_RADIOS = false; + public static final String TEST_RADIOS_PROP = "test.radios"; + + private static final String KEY_STATUS_HEADER = "status_header"; + private static final String KEY_LIMIT_SUMMARY = "limit_summary"; + private static final String KEY_RESTRICT_BACKGROUND = "restrict_background"; + + private DataUsageController mDataUsageController; + private SummaryPreference mSummaryPreference; + private Preference mLimitPreference; + private NetworkTemplate mDefaultTemplate; + private int mDataUsageTemplate; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + boolean hasMobileData = hasMobileData(getContext()); + mDataUsageController = new DataUsageController(getContext()); + addPreferencesFromResource(R.xml.data_usage); + + int defaultSubId = getDefaultSubscriptionId(getContext()); + mDefaultTemplate = getDefaultTemplate(getContext(), defaultSubId); + if (hasMobileData) { + mLimitPreference = findPreference(KEY_LIMIT_SUMMARY); + } else { + removePreference(KEY_LIMIT_SUMMARY); + } + if (!hasMobileData || !isAdmin()) { + removePreference(KEY_RESTRICT_BACKGROUND); + } + if (hasMobileData) { + List<SubscriptionInfo> subscriptions = + services.mSubscriptionManager.getActiveSubscriptionInfoList(); + if (subscriptions.size() == 0) { + addMobileSection(defaultSubId); + } + for (int i = 0; i < subscriptions.size(); i++) { + addMobileSection(subscriptions.get(i).getSubscriptionId()); + } + } + boolean hasWifiRadio = hasWifiRadio(getContext()); + if (hasWifiRadio) { + addWifiSection(); + } + if (hasEthernet(getContext())) { + addEthernetSection(); + } + mDataUsageTemplate = hasMobileData ? R.string.cell_data_template + : hasWifiRadio ? R.string.wifi_data_template + : R.string.ethernet_data_template; + + mSummaryPreference = (SummaryPreference) findPreference(KEY_STATUS_HEADER); + setHasOptionsMenu(true); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.data_usage, menu); + super.onCreateOptionsMenu(menu, inflater); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.data_usage_menu_cellular_networks: { + final Intent intent = new Intent(Intent.ACTION_MAIN); + intent.setComponent(new ComponentName("com.android.phone", + "com.android.phone.MobileNetworkSettings")); + startActivity(intent); + return true; + } + } + return false; + } + + private void addMobileSection(int subId) { + TemplatePreferenceCategory category = (TemplatePreferenceCategory) + inflatePreferences(R.xml.data_usage_cellular); + category.setTemplate(getNetworkTemplate(subId), subId, services); + } + + private void addWifiSection() { + TemplatePreferenceCategory category = (TemplatePreferenceCategory) + inflatePreferences(R.xml.data_usage_wifi); + category.setTemplate(NetworkTemplate.buildTemplateWifiWildcard(), 0, services); + } + + private void addEthernetSection() { + TemplatePreferenceCategory category = (TemplatePreferenceCategory) + inflatePreferences(R.xml.data_usage_ethernet); + category.setTemplate(NetworkTemplate.buildTemplateEthernet(), 0, services); + } + + private Preference inflatePreferences(int resId) { + PreferenceScreen rootPreferences = getPreferenceManager().inflateFromResource( + getPrefContext(), resId, null); + Preference pref = rootPreferences.getPreference(0); + rootPreferences.removeAll(); + + PreferenceScreen screen = getPreferenceScreen(); + pref.setOrder(screen.getPreferenceCount()); + screen.addPreference(pref); + + return pref; + } + + private NetworkTemplate getNetworkTemplate(int subscriptionId) { + NetworkTemplate mobileAll = NetworkTemplate.buildTemplateMobileAll( + services.mTelephonyManager.getSubscriberId(subscriptionId)); + return NetworkTemplate.normalize(mobileAll, + services.mTelephonyManager.getMergedSubscriberIds()); + } + + @Override + public void onResume() { + super.onResume(); + updateState(); + } + + private void updateState() { + DataUsageController.DataUsageInfo info = mDataUsageController.getDataUsageInfo( + mDefaultTemplate); + Context context = getContext(); + if (mSummaryPreference != null) { + Formatter.BytesResult usedResult = Formatter.formatBytes(context.getResources(), + info.usageLevel, Formatter.FLAG_SHORTER); + mSummaryPreference.setAmount(usedResult.value); + mSummaryPreference.setUnits(getString(mDataUsageTemplate, usedResult.units)); + long limit = info.limitLevel; + if (limit <= 0) { + limit = info.warningLevel; + } + if (info.usageLevel > limit) { + limit = info.usageLevel; + } + mSummaryPreference.setSummary(info.period); + mSummaryPreference.setLabels(Formatter.formatFileSize(context, 0), + Formatter.formatFileSize(context, limit)); + mSummaryPreference.setRatios(info.usageLevel / (float) limit, 0, + (limit - info.usageLevel) / (float) limit); + } + if (mLimitPreference != null) { + String warning = Formatter.formatFileSize(context, info.warningLevel); + String limit = Formatter.formatFileSize(context, info.limitLevel); + mLimitPreference.setSummary(getString(info.limitLevel <= 0 ? R.string.cell_warning_only + : R.string.cell_warning_and_limit, warning, limit)); + } + + PreferenceScreen screen = getPreferenceScreen(); + for (int i = 1; i < screen.getPreferenceCount(); i++) { + ((TemplatePreferenceCategory) screen.getPreference(i)).pushTemplates(services); + } + } + + @Override + protected int getMetricsCategory() { + return MetricsLogger.DATA_USAGE_SUMMARY; + } + + /** + * Test if device has an ethernet network connection. + */ + public boolean hasEthernet(Context context) { + if (TEST_RADIOS) { + return SystemProperties.get(TEST_RADIOS_PROP).contains("ethernet"); + } + + final ConnectivityManager conn = ConnectivityManager.from(context); + final boolean hasEthernet = conn.isNetworkSupported(TYPE_ETHERNET); + + final long ethernetBytes; + try { + INetworkStatsSession statsSession = services.mStatsService.openSession(); + if (statsSession != null) { + ethernetBytes = statsSession.getSummaryForNetwork( + NetworkTemplate.buildTemplateEthernet(), Long.MIN_VALUE, Long.MAX_VALUE) + .getTotalBytes(); + TrafficStats.closeQuietly(statsSession); + } else { + ethernetBytes = 0; + } + } catch (RemoteException e) { + throw new RuntimeException(e); + } + + // only show ethernet when both hardware present and traffic has occurred + return hasEthernet && ethernetBytes > 0; + } + + public static boolean hasMobileData(Context context) { + return ConnectivityManager.from(context).isNetworkSupported( + ConnectivityManager.TYPE_MOBILE); + } + + /** + * Test if device has a Wi-Fi data radio. + */ + public static boolean hasWifiRadio(Context context) { + if (TEST_RADIOS) { + return SystemProperties.get(TEST_RADIOS_PROP).contains("wifi"); + } + + final ConnectivityManager conn = ConnectivityManager.from(context); + return conn.isNetworkSupported(TYPE_WIFI); + } + + public static int getDefaultSubscriptionId(Context context) { + SubscriptionManager subManager = SubscriptionManager.from(context); + if (subManager == null) { + return SubscriptionManager.INVALID_SUBSCRIPTION_ID; + } + SubscriptionInfo subscriptionInfo = subManager.getDefaultDataSubscriptionInfo(); + if (subscriptionInfo == null) { + List<SubscriptionInfo> list = subManager.getAllSubscriptionInfoList(); + if (list.size() == 0) { + return SubscriptionManager.INVALID_SUBSCRIPTION_ID; + } + subscriptionInfo = list.get(0); + } + return subscriptionInfo.getSubscriptionId(); + } + + public static NetworkTemplate getDefaultTemplate(Context context, int defaultSubId) { + if (hasMobileData(context)) { + TelephonyManager telephonyManager = TelephonyManager.from(context); + NetworkTemplate mobileAll = NetworkTemplate.buildTemplateMobileAll( + telephonyManager.getSubscriberId(defaultSubId)); + return NetworkTemplate.normalize(mobileAll, + telephonyManager.getMergedSubscriberIds()); + } else if (hasWifiRadio(context)) { + return NetworkTemplate.buildTemplateWifiWildcard(); + } else { + return NetworkTemplate.buildTemplateEthernet(); + } + } + + private static class SummaryProvider + implements SummaryLoader.SummaryProvider { + + private final Activity mActivity; + private final SummaryLoader mSummaryLoader; + private final DataUsageController mDataController; + + public SummaryProvider(Activity activity, SummaryLoader summaryLoader) { + mActivity = activity; + mSummaryLoader = summaryLoader; + mDataController = new DataUsageController(activity); + } + + @Override + public void setListening(boolean listening) { + if (listening) { + DataUsageController.DataUsageInfo info = mDataController.getDataUsageInfo(); + String used; + if (info == null) { + used = Formatter.formatFileSize(mActivity, 0); + } else if (info.limitLevel <= 0) { + used = Formatter.formatFileSize(mActivity, info.usageLevel); + } else { + used = Utils.formatPercentage(info.usageLevel, info.limitLevel); + } + mSummaryLoader.setSummary(this, + mActivity.getString(R.string.data_usage_summary_format, used)); + } + } + } + + 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); + } + }; + + /** + * For search + */ + public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider() { + + @Override + public List<SearchIndexableResource> getXmlResourcesToIndex(Context context, + boolean enabled) { + ArrayList<SearchIndexableResource> resources = new ArrayList<>(); + SearchIndexableResource resource = new SearchIndexableResource(context); + resource.xmlResId = R.xml.data_usage; + resources.add(resource); + + if (hasMobileData(context)) { + resource = new SearchIndexableResource(context); + resource.xmlResId = R.xml.data_usage_cellular; + resources.add(resource); + } + if (hasWifiRadio(context)) { + resource = new SearchIndexableResource(context); + resource.xmlResId = R.xml.data_usage_wifi; + resources.add(resource); + } + return resources; + } + + @Override + public List<String> getNonIndexableKeys(Context context) { + ArrayList<String> keys = new ArrayList<>(); + boolean hasMobileData = ConnectivityManager.from(context).isNetworkSupported( + ConnectivityManager.TYPE_MOBILE); + + if (hasMobileData) { + keys.add(KEY_RESTRICT_BACKGROUND); + } + + return keys; + } + }; +} diff --git a/src/com/android/settings/datausage/NetworkRestrictionsPreference.java b/src/com/android/settings/datausage/NetworkRestrictionsPreference.java new file mode 100644 index 00000000000..e2e9d1ebfee --- /dev/null +++ b/src/com/android/settings/datausage/NetworkRestrictionsPreference.java @@ -0,0 +1,33 @@ +/* + * 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.datausage; + +import android.content.Context; +import android.net.NetworkTemplate; +import android.support.v7.preference.Preference; +import android.util.AttributeSet; + +public class NetworkRestrictionsPreference extends Preference implements TemplatePreference { + + public NetworkRestrictionsPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public void setTemplate(NetworkTemplate template, int subId, + NetworkServices services) { + // TODO: Summary + } +} diff --git a/src/com/android/settings/datausage/RestrictBackgroundDataPreference.java b/src/com/android/settings/datausage/RestrictBackgroundDataPreference.java new file mode 100644 index 00000000000..67bce03e864 --- /dev/null +++ b/src/com/android/settings/datausage/RestrictBackgroundDataPreference.java @@ -0,0 +1,99 @@ +/* + * 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.datausage; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.net.NetworkPolicyManager; +import android.support.v7.preference.PreferenceViewHolder; +import android.util.AttributeSet; +import android.view.View; +import android.widget.Checkable; +import com.android.settings.CustomDialogPreference; +import com.android.settings.R; +import com.android.settings.Utils; +import com.android.settings.dashboard.conditional.BackgroundDataCondition; +import com.android.settings.dashboard.conditional.ConditionManager; + +public class RestrictBackgroundDataPreference extends CustomDialogPreference { + + private NetworkPolicyManager mPolicyManager; + private boolean mChecked; + + public RestrictBackgroundDataPreference(Context context, AttributeSet attrs) { + super(context, attrs, android.R.attr.switchPreferenceStyle); + } + + @Override + public void onAttached() { + super.onAttached(); + mPolicyManager = NetworkPolicyManager.from(getContext()); + setChecked(mPolicyManager.getRestrictBackground()); + } + + public void setRestrictBackground(boolean restrictBackground) { + mPolicyManager.setRestrictBackground(restrictBackground); + setChecked(restrictBackground); + ConditionManager.get(getContext()).getCondition(BackgroundDataCondition.class) + .refreshState(); + } + + private void setChecked(boolean checked) { + if (mChecked == checked) return; + mChecked = checked; + notifyChanged(); + } + + @Override + public void onBindViewHolder(PreferenceViewHolder holder) { + super.onBindViewHolder(holder); + View switchView = holder.findViewById(android.R.id.switch_widget); + switchView.setClickable(false); + ((Checkable) switchView).setChecked(mChecked); + } + + @Override + protected void performClick(View view) { + if (mChecked) { + setRestrictBackground(false); + } else { + super.performClick(view); + } + } + + @Override + protected void onPrepareDialogBuilder(AlertDialog.Builder builder, + DialogInterface.OnClickListener listener) { + super.onPrepareDialogBuilder(builder, listener); + builder.setTitle(R.string.data_usage_restrict_background_title); + if (Utils.hasMultipleUsers(getContext())) { + builder.setMessage(R.string.data_usage_restrict_background_multiuser); + } else { + builder.setMessage(R.string.data_usage_restrict_background); + } + + builder.setPositiveButton(android.R.string.ok, listener); + builder.setNegativeButton(android.R.string.cancel, null); + } + + @Override + protected void onClick(DialogInterface dialog, int which) { + if (which != DialogInterface.BUTTON_POSITIVE) { + return; + } + setRestrictBackground(true); + } +} diff --git a/src/com/android/settings/datausage/TemplatePreference.java b/src/com/android/settings/datausage/TemplatePreference.java new file mode 100644 index 00000000000..4b1cd0cf828 --- /dev/null +++ b/src/com/android/settings/datausage/TemplatePreference.java @@ -0,0 +1,41 @@ +/* + * 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.datausage; + +import android.net.INetworkStatsService; +import android.net.NetworkPolicyManager; +import android.net.NetworkTemplate; +import android.os.INetworkManagementService; +import android.os.UserManager; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import com.android.settingslib.NetworkPolicyEditor; + +public interface TemplatePreference { + + void setTemplate(NetworkTemplate template, int subId, NetworkServices services); + + class NetworkServices { + INetworkManagementService mNetworkService; + INetworkStatsService mStatsService; + NetworkPolicyManager mPolicyManager; + TelephonyManager mTelephonyManager; + SubscriptionManager mSubscriptionManager; + UserManager mUserManager; + NetworkPolicyEditor mPolicyEditor; + } + +} diff --git a/src/com/android/settings/datausage/TemplatePreferenceCategory.java b/src/com/android/settings/datausage/TemplatePreferenceCategory.java new file mode 100644 index 00000000000..0be5c733b81 --- /dev/null +++ b/src/com/android/settings/datausage/TemplatePreferenceCategory.java @@ -0,0 +1,57 @@ +/* + * 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.datausage; + +import android.content.Context; +import android.net.NetworkTemplate; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceCategory; +import android.util.AttributeSet; + +public class TemplatePreferenceCategory extends PreferenceCategory implements TemplatePreference { + + private NetworkTemplate mTemplate; + private int mSubId; + + public TemplatePreferenceCategory(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public void setTemplate(NetworkTemplate template, int subId, + NetworkServices services) { + mTemplate = template; + mSubId = subId; + } + + @Override + public boolean addPreference(Preference preference) { + if (!(preference instanceof TemplatePreference)) { + throw new IllegalArgumentException( + "TemplatePreferenceCategories can only hold TemplatePreferences"); + } + return super.addPreference(preference); + } + + public void pushTemplates(NetworkServices services) { + if (mTemplate == null) { + throw new RuntimeException("null mTemplate for " + getKey()); + } + for (int i = 0; i < getPreferenceCount(); i++) { + ((TemplatePreference) getPreference(i)).setTemplate(mTemplate, mSubId, services); + } + } + +} diff --git a/src/com/android/settings/search/Ranking.java b/src/com/android/settings/search/Ranking.java index 3d5ff357deb..59f83ec9380 100644 --- a/src/com/android/settings/search/Ranking.java +++ b/src/com/android/settings/search/Ranking.java @@ -17,7 +17,6 @@ package com.android.settings.search; import com.android.settings.ChooseLockGeneric; -import com.android.settings.DataUsageSummary; import com.android.settings.DateTimeSettings; import com.android.settings.DevelopmentSettings; import com.android.settings.DeviceInfoSettings; @@ -35,6 +34,8 @@ import com.android.settings.accounts.AccountSettings; import com.android.settings.applications.AdvancedAppSettings; import com.android.settings.applications.ManageDefaultApps; import com.android.settings.bluetooth.BluetoothSettings; +import com.android.settings.datausage.DataUsageMeteredSettings; +import com.android.settings.datausage.DataUsageSummary; import com.android.settings.deviceinfo.StorageSettings; import com.android.settings.display.ScreenZoomSettings; import com.android.settings.fuelgauge.BatterySaverSettings; @@ -42,7 +43,6 @@ import com.android.settings.fuelgauge.PowerUsageSummary; import com.android.settings.inputmethod.InputMethodAndLanguageSettings; import com.android.settings.location.LocationSettings; import com.android.settings.location.ScanningSettings; -import com.android.settings.net.DataUsageMeteredSettings; import com.android.settings.notification.ConfigureNotificationSettings; import com.android.settings.notification.OtherSoundSettings; import com.android.settings.notification.SoundSettings; diff --git a/src/com/android/settings/search/SearchIndexableResources.java b/src/com/android/settings/search/SearchIndexableResources.java index f15ff638017..1d55b5534fc 100644 --- a/src/com/android/settings/search/SearchIndexableResources.java +++ b/src/com/android/settings/search/SearchIndexableResources.java @@ -16,9 +16,6 @@ package com.android.settings.search; -import android.provider.SearchIndexableResource; - -import com.android.settings.DataUsageSummary; import com.android.settings.DateTimeSettings; import com.android.settings.DevelopmentSettings; import com.android.settings.DeviceInfoSettings; @@ -37,6 +34,8 @@ import com.android.settings.accounts.AccountSettings; import com.android.settings.applications.AdvancedAppSettings; import com.android.settings.applications.ManageDefaultApps; import com.android.settings.bluetooth.BluetoothSettings; +import com.android.settings.datausage.DataUsageMeteredSettings; +import com.android.settings.datausage.DataUsageSummary; import com.android.settings.deviceinfo.StorageSettings; import com.android.settings.display.ScreenZoomSettings; import com.android.settings.fuelgauge.BatterySaverSettings; @@ -44,7 +43,6 @@ import com.android.settings.fuelgauge.PowerUsageSummary; import com.android.settings.inputmethod.InputMethodAndLanguageSettings; import com.android.settings.location.LocationSettings; import com.android.settings.location.ScanningSettings; -import com.android.settings.net.DataUsageMeteredSettings; import com.android.settings.notification.ConfigureNotificationSettings; import com.android.settings.notification.OtherSoundSettings; import com.android.settings.notification.SoundSettings; @@ -58,6 +56,8 @@ import com.android.settings.wifi.AdvancedWifiSettings; import com.android.settings.wifi.SavedAccessPointsWifiSettings; import com.android.settings.wifi.WifiSettings; +import android.provider.SearchIndexableResource; + import java.util.Collection; import java.util.HashMap; diff --git a/src/com/android/settings/widget/ChartGridView.java b/src/com/android/settings/widget/ChartGridView.java index c85d4fcb86a..2091719c101 100644 --- a/src/com/android/settings/widget/ChartGridView.java +++ b/src/com/android/settings/widget/ChartGridView.java @@ -32,7 +32,7 @@ import android.view.View; import com.android.internal.util.Preconditions; import com.android.settings.R; -import static com.android.settings.DataUsageSummary.formatDateRange; +import static com.android.settings.Utils.formatDateRange; /** * Background of {@link ChartView} that renders grid lines as requested by diff --git a/src/com/android/settings/widget/ChartSweepView.java b/src/com/android/settings/widget/ChartSweepView.java index e8e24aa0027..cdcd0a749fc 100644 --- a/src/com/android/settings/widget/ChartSweepView.java +++ b/src/com/android/settings/widget/ChartSweepView.java @@ -143,7 +143,6 @@ public class ChartSweepView extends View { a.recycle(); setClickable(true); - setFocusable(true); setOnClickListener(mClickListener); setWillNotDraw(false); |