Add data saver to settings
Bug: 22817899 Change-Id: Ic3055aa6a5baae1653db350313366f180c049cc7
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
-keep class com.android.settings.wifi.*Settings
|
||||
-keep class com.android.settings.deviceinfo.*
|
||||
-keep class com.android.settings.bluetooth.*
|
||||
-keep class com.android.settings.datausage.*
|
||||
-keep class com.android.settings.applications.*
|
||||
-keep class com.android.settings.inputmethod.*
|
||||
-keep class com.android.settings.ResetNetwork
|
||||
|
28
res/drawable/ic_data_saver.xml
Normal file
28
res/drawable/ic_data_saver.xml
Normal file
@@ -0,0 +1,28 @@
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24.0dp"
|
||||
android:height="24.0dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="
|
||||
M9.0,16.0l2.0,0.0L11.0,8.0L9.0,8.0l0.0,8.0z
|
||||
m3.0,-14.0C6.48,2.0 2.0,6.48 2.0,12.0s4.48,10.0 10.0,10.0 10.0,-4.48 10.0,-10.0S17.52,2.0 12.0,2.0z
|
||||
m0.0,18.0c-4.41,0.0 -8.0,-3.59 -8.0,-8.0s3.59,-8.0 8.0,-8.0 8.0,3.59 8.0,8.0 -3.59,8.0 -8.0,8.0z
|
||||
m1.0,-4.0l2.0,0.0l0.0,-8.0l-2.0,0.0l0.0,8.0z"/>
|
||||
</vector>
|
@@ -17,7 +17,7 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?android:attr/listPreferredItemHeightSmall"
|
||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||
android:orientation="horizontal">
|
||||
|
@@ -4914,9 +4914,9 @@
|
||||
<!-- Button title for launching application-specific data usage settings. [CHAR LIMIT=32] -->
|
||||
<string name="data_usage_app_settings">App settings</string>
|
||||
<!-- Checkbox label that restricts background data usage of a specific application. [CHAR LIMIT=40] -->
|
||||
<string name="data_usage_app_restrict_background">Restrict app background data</string>
|
||||
<!-- Summary message for checkbox that restricts background data usage of a specific application. [CHAR LIMIT=64] -->
|
||||
<string name="data_usage_app_restrict_background_summary">Disable background data on cellular networks.</string>
|
||||
<string name="data_usage_app_restrict_background">Background data</string>
|
||||
<!-- Summary message for checkbox that restricts background data usage of a specific application. [CHAR LIMIT=NONE] -->
|
||||
<string name="data_usage_app_restrict_background_summary">Enable usage of cellular data in the background</string>
|
||||
<!-- Summary message for checkbox that restricts background data usage of a specific application when no networks have been limited. [CHAR LIMIT=84] -->
|
||||
<string name="data_usage_app_restrict_background_summary_disabled">To restrict background data for this app, first set a cellular data limit.</string>
|
||||
<!-- Title of dialog shown when user restricts background data usage of a specific application. [CHAR LIMIT=48] -->
|
||||
@@ -6870,7 +6870,7 @@
|
||||
<string name="condition_cellular_summary">Internet is available only via Wi-Fi</string>
|
||||
|
||||
<!-- Title of condition that background data is off [CHAR LIMIT=30] -->
|
||||
<string name="condition_bg_data_title">Background data is off</string>
|
||||
<string name="condition_bg_data_title">Data Saver is on</string>
|
||||
|
||||
<!-- Summary of condition that background data is off [CHAR LIMIT=NONE] -->
|
||||
<string name="condition_bg_data_summary">Background data is only available via Wi-Fi. This may affect some apps or services when Wi-Fi is not available.</string>
|
||||
@@ -6966,4 +6966,28 @@
|
||||
the code to do that -->
|
||||
<string name="data_usage_other_apps" translatable="false">Other apps included in usage</string>
|
||||
|
||||
<!-- Description of number of apps allowed to ignore data saver [CHAR LIMIT=NONE] -->
|
||||
<plurals name="data_saver_unrestricted_summary">
|
||||
<item quantity="one">1 app allowed to use unrestricted data when Data Saver is on</item>
|
||||
<item quantity="other"><xliff:g id="count" example="10">%1$d</xliff:g> apps allowed to use unrestricted data when Data Saver is on</item>
|
||||
</plurals>
|
||||
|
||||
<!-- Name of Data Saver screens [CHAR LIMIT=30] -->
|
||||
<string name="data_saver_title">Data Saver</string>
|
||||
|
||||
<!-- Button that leads to list of apps with unrestricted data access [CHAR LIMIT=60] -->
|
||||
<string name="unrestricted_data_saver">Unrestricted data access</string>
|
||||
|
||||
<!-- Summary for the data saver feature being on [CHAR LIMIT=NONE] -->
|
||||
<string name="data_saver_on">On</string>
|
||||
|
||||
<!-- Summary for the data saver feature being off [CHAR LIMIT=NONE] -->
|
||||
<string name="data_saver_off">Off</string>
|
||||
|
||||
<!-- Title for switch to allow app unrestricted data usage [CHAR LIMIT=30] -->
|
||||
<string name="unrestricted_app_title">Unrestricted data usage</string>
|
||||
|
||||
<!-- Title for switch to allow app unrestricted data usage [CHAR LIMIT=30] -->
|
||||
<string name="unrestricted_app_summary">Allow unrestricted data access when Data Saver is on</string>
|
||||
|
||||
</resources>
|
||||
|
@@ -17,6 +17,9 @@
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:title="@string/data_usage_summary_title">
|
||||
|
||||
<com.android.settings.datausage.SpinnerPreference
|
||||
android:key="cycle" />
|
||||
|
||||
<com.android.settings.applications.SpacePreference
|
||||
android:layout_height="8dp" />
|
||||
|
||||
@@ -50,6 +53,11 @@
|
||||
android:title="@string/data_usage_app_restrict_background"
|
||||
android:summary="@string/data_usage_app_restrict_background_summary" />
|
||||
|
||||
<SwitchPreference
|
||||
android:key="unrestricted_data_saver"
|
||||
android:title="@string/unrestricted_app_title"
|
||||
android:summary="@string/unrestricted_app_summary" />
|
||||
|
||||
<PreferenceCategory
|
||||
android:key="app_list"
|
||||
android:title="@string/data_usage_other_apps" />
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<!--
|
||||
Copyright (C) 2016 The Android Open Source Project
|
||||
<?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.
|
||||
@@ -14,18 +14,12 @@
|
||||
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">
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:title="@string/data_saver_title">
|
||||
|
||||
<include layout="@layout/app_header" />
|
||||
<Preference
|
||||
android:key="unrestricted_access"
|
||||
android:title="@string/unrestricted_data_saver"
|
||||
android:fragment="com.android.settings.datausage.UnrestrictedDataAccess" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height=".5dp"
|
||||
android:background="@android:color/white" />
|
||||
|
||||
<include layout="@layout/apps_filter_spinner" />
|
||||
|
||||
</LinearLayout>
|
||||
</PreferenceScreen>
|
@@ -29,9 +29,10 @@
|
||||
android:key="limit_summary"
|
||||
android:selectable="false" />
|
||||
|
||||
<com.android.settings.datausage.RestrictBackgroundDataPreference
|
||||
<com.android.settings.datausage.DataSaverPreference
|
||||
android:key="restrict_background"
|
||||
android:title="@string/data_usage_menu_restrict_background" />
|
||||
android:title="@string/data_saver_title"
|
||||
android:fragment="com.android.settings.datausage.DataSaverSummary" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
|
@@ -41,6 +41,8 @@ public abstract class InstrumentedFragment extends PreferenceFragment {
|
||||
public static final int VIRTUAL_KEYBOARDS = UNDECLARED + 11;
|
||||
public static final int PHYSICAL_KEYBOARDS = UNDECLARED + 12;
|
||||
public static final int ENABLE_VIRTUAL_KEYBOARDS = UNDECLARED + 13;
|
||||
public static final int DATA_SAVER_SUMMARY = UNDECLARED + 14;
|
||||
public static final int DATA_USAGE_UNRESTRICTED_ACCESS = UNDECLARED + 15;
|
||||
|
||||
/**
|
||||
* Declare the view of this category.
|
||||
|
@@ -34,7 +34,7 @@ public class BackgroundDataCondition extends Condition {
|
||||
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
return Icon.createWithResource(mManager.getContext(), R.drawable.ic_cellular_off);
|
||||
return Icon.createWithResource(mManager.getContext(), R.drawable.ic_data_saver);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -14,14 +14,6 @@
|
||||
|
||||
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;
|
||||
@@ -43,10 +35,15 @@ 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 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 static android.net.NetworkPolicyManager.POLICY_NONE;
|
||||
import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
|
||||
@@ -62,6 +59,8 @@ public class AppDataUsage extends DataUsageBase implements Preference.OnPreferen
|
||||
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 String KEY_CYCLE = "cycle";
|
||||
private static final String KEY_UNRESTRICTED_DATA = "unrestricted_data_saver";
|
||||
|
||||
private static final int LOADER_CHART_DATA = 2;
|
||||
|
||||
@@ -76,7 +75,6 @@ public class AppDataUsage extends DataUsageBase implements Preference.OnPreferen
|
||||
private Drawable mIcon;
|
||||
private CharSequence mLabel;
|
||||
private INetworkStatsSession mStatsSession;
|
||||
private Spinner mCycleSpinner;
|
||||
private CycleAdapter mCycleAdapter;
|
||||
|
||||
private long mStart;
|
||||
@@ -86,6 +84,9 @@ public class AppDataUsage extends DataUsageBase implements Preference.OnPreferen
|
||||
private NetworkPolicy mPolicy;
|
||||
private AppItem mAppItem;
|
||||
private Intent mAppSettingsIntent;
|
||||
private SpinnerPreference mCycle;
|
||||
private SwitchPreference mUnrestrictedData;
|
||||
private DataSaverBackend mDataSaverBackend;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
@@ -137,9 +138,15 @@ public class AppDataUsage extends DataUsageBase implements Preference.OnPreferen
|
||||
mForegroundUsage = findPreference(KEY_FOREGROUND_USAGE);
|
||||
mBackgroundUsage = findPreference(KEY_BACKGROUND_USAGE);
|
||||
|
||||
mCycle = (SpinnerPreference) findPreference(KEY_CYCLE);
|
||||
mCycleAdapter = new CycleAdapter(getContext(), mCycle, mCycleListener, false);
|
||||
|
||||
if (UserHandle.isApp(mAppItem.key)) {
|
||||
mRestrictBackground = (SwitchPreference) findPreference(KEY_RESTRICT_BACKGROUND);
|
||||
mRestrictBackground.setOnPreferenceChangeListener(this);
|
||||
mUnrestrictedData = (SwitchPreference) findPreference(KEY_UNRESTRICTED_DATA);
|
||||
mUnrestrictedData.setOnPreferenceChangeListener(this);
|
||||
mDataSaverBackend = new DataSaverBackend(getContext());
|
||||
mAppSettings = findPreference(KEY_APP_SETTINGS);
|
||||
|
||||
mAppSettingsIntent = new Intent(Intent.ACTION_MANAGE_NETWORK_USAGE);
|
||||
@@ -169,6 +176,7 @@ public class AppDataUsage extends DataUsageBase implements Preference.OnPreferen
|
||||
removePreference(KEY_APP_LIST);
|
||||
}
|
||||
} else {
|
||||
removePreference(KEY_UNRESTRICTED_DATA);
|
||||
removePreference(KEY_APP_SETTINGS);
|
||||
removePreference(KEY_RESTRICT_BACKGROUND);
|
||||
removePreference(KEY_APP_LIST);
|
||||
@@ -195,6 +203,9 @@ public class AppDataUsage extends DataUsageBase implements Preference.OnPreferen
|
||||
if (preference == mRestrictBackground) {
|
||||
setAppRestrictBackground((Boolean) newValue);
|
||||
return true;
|
||||
} else if (preference == mUnrestrictedData) {
|
||||
mDataSaverBackend.setIsWhitelisted(mAppItem.key, (Boolean) newValue);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -214,6 +225,9 @@ public class AppDataUsage extends DataUsageBase implements Preference.OnPreferen
|
||||
if (mRestrictBackground != null) {
|
||||
mRestrictBackground.setChecked(getAppRestrictBackground());
|
||||
}
|
||||
if (mUnrestrictedData != null) {
|
||||
mUnrestrictedData.setChecked(mDataSaverBackend.isWhitelisted(mAppItem.key));
|
||||
}
|
||||
}
|
||||
|
||||
private void addUid(int uid) {
|
||||
@@ -260,7 +274,7 @@ public class AppDataUsage extends DataUsageBase implements Preference.OnPreferen
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
View header = setPinnedHeaderView(R.layout.data_usage_app_header);
|
||||
View header = setPinnedHeaderView(R.layout.app_header);
|
||||
String pkg = mPackages.size() != 0 ? mPackages.valueAt(0) : null;
|
||||
int uid = 0;
|
||||
try {
|
||||
@@ -269,9 +283,6 @@ public class AppDataUsage extends DataUsageBase implements Preference.OnPreferen
|
||||
}
|
||||
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
|
||||
@@ -283,8 +294,7 @@ public class AppDataUsage extends DataUsageBase implements Preference.OnPreferen
|
||||
new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
final CycleAdapter.CycleItem cycle =
|
||||
(CycleAdapter.CycleItem) parent.getItemAtPosition(position);
|
||||
final CycleAdapter.CycleItem cycle = (CycleAdapter.CycleItem) mCycle.getSelectedItem();
|
||||
|
||||
mStart = cycle.start;
|
||||
mEnd = cycle.end;
|
||||
|
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.applications.AppStateBaseBridge;
|
||||
import com.android.settingslib.applications.ApplicationsState;
|
||||
import com.android.settingslib.applications.ApplicationsState.AppEntry;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class AppStateDataUsageBridge extends AppStateBaseBridge {
|
||||
|
||||
private static final String TAG = "AppStateDataUsageBridge";
|
||||
|
||||
private final DataSaverBackend mDataSaverBackend;
|
||||
|
||||
public AppStateDataUsageBridge(ApplicationsState appState, Callback callback,
|
||||
DataSaverBackend backend) {
|
||||
super(appState, callback);
|
||||
mDataSaverBackend = backend;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadAllExtraInfo() {
|
||||
ArrayList<AppEntry> apps = mAppSession.getAllApps();
|
||||
final int N = apps.size();
|
||||
for (int i = 0; i < N; i++) {
|
||||
AppEntry app = apps.get(i);
|
||||
app.extraInfo = new DataUsageState(mDataSaverBackend.isWhitelisted(app.info.uid));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateExtraInfo(AppEntry app, String pkg, int uid) {
|
||||
app.extraInfo = new DataUsageState(mDataSaverBackend.isWhitelisted(uid));
|
||||
}
|
||||
|
||||
public static class DataUsageState {
|
||||
public boolean isDataSaverWhitelisted;
|
||||
|
||||
public DataUsageState(boolean isDataSaverWhitelisted) {
|
||||
this.isDataSaverWhitelisted = isDataSaverWhitelisted;
|
||||
}
|
||||
}
|
||||
}
|
@@ -13,17 +13,15 @@
|
||||
*/
|
||||
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 com.android.settings.R;
|
||||
import com.android.settings.Utils;
|
||||
import com.android.settingslib.net.ChartData;
|
||||
import libcore.util.Objects;
|
||||
|
||||
import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
|
||||
@@ -31,12 +29,13 @@ import static android.net.NetworkPolicyManager.computeNextCycleBoundary;
|
||||
|
||||
public class CycleAdapter extends ArrayAdapter<CycleAdapter.CycleItem> {
|
||||
|
||||
private final Spinner mSpinner;
|
||||
private final SpinnerInterface 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);
|
||||
public CycleAdapter(Context context, SpinnerInterface spinner,
|
||||
AdapterView.OnItemSelectedListener listener, boolean isHeader) {
|
||||
super(context, isHeader ? R.layout.filter_spinner_item
|
||||
: R.layout.data_usage_cycle_item);
|
||||
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
mSpinner = spinner;
|
||||
mListener = listener;
|
||||
@@ -72,7 +71,7 @@ public class CycleAdapter extends ArrayAdapter<CycleAdapter.CycleItem> {
|
||||
mSpinner.getSelectedItem();
|
||||
clear();
|
||||
|
||||
final Context context = mSpinner.getContext();
|
||||
final Context context = getContext();
|
||||
NetworkStatsHistory.Entry entry = null;
|
||||
|
||||
long historyStart = Long.MAX_VALUE;
|
||||
@@ -141,7 +140,7 @@ public class CycleAdapter extends ArrayAdapter<CycleAdapter.CycleItem> {
|
||||
// user-defined inspection region.
|
||||
final CycleAdapter.CycleItem selectedItem = getItem(position);
|
||||
if (!Objects.equal(selectedItem, previousItem)) {
|
||||
mListener.onItemSelected(mSpinner, null, position, 0);
|
||||
mListener.onItemSelected(null, null, position, 0);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -185,4 +184,11 @@ public class CycleAdapter extends ArrayAdapter<CycleAdapter.CycleItem> {
|
||||
return Long.compare(start, another.start);
|
||||
}
|
||||
}
|
||||
|
||||
public interface SpinnerInterface {
|
||||
void setAdapter(CycleAdapter cycleAdapter);
|
||||
void setOnItemSelectedListener(AdapterView.OnItemSelectedListener listener);
|
||||
Object getSelectedItem();
|
||||
void setSelection(int position);
|
||||
}
|
||||
}
|
||||
|
148
src/com/android/settings/datausage/DataSaverBackend.java
Normal file
148
src/com/android/settings/datausage/DataSaverBackend.java
Normal file
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* 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.INetworkPolicyListener;
|
||||
import android.net.INetworkPolicyManager;
|
||||
import android.net.NetworkPolicyManager;
|
||||
import android.os.Handler;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.util.Log;
|
||||
import android.util.SparseBooleanArray;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class DataSaverBackend {
|
||||
|
||||
private static final String TAG = "DataSaverBackend";
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
private final Handler mHandler = new Handler();
|
||||
private final NetworkPolicyManager mPolicyManager;
|
||||
private final INetworkPolicyManager mIPolicyManager;
|
||||
private final ArrayList<Listener> mListeners = new ArrayList<>();
|
||||
private SparseBooleanArray mWhitelist;
|
||||
|
||||
// TODO: Staticize into only one.
|
||||
public DataSaverBackend(Context context) {
|
||||
mContext = context;
|
||||
mIPolicyManager = INetworkPolicyManager.Stub.asInterface(
|
||||
ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
|
||||
mPolicyManager = NetworkPolicyManager.from(context);
|
||||
}
|
||||
|
||||
public void addListener(Listener listener) {
|
||||
mListeners.add(listener);
|
||||
if (mListeners.size() == 1) {
|
||||
mPolicyManager.registerListener(mPolicyListener);
|
||||
}
|
||||
listener.onDataSaverChanged(isDataSaverEnabled());
|
||||
}
|
||||
|
||||
public void remListener(Listener listener) {
|
||||
mListeners.remove(listener);
|
||||
if (mListeners.size() == 0) {
|
||||
mPolicyManager.unregisterListener(mPolicyListener);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isDataSaverEnabled() {
|
||||
return mPolicyManager.getRestrictBackground();
|
||||
}
|
||||
|
||||
public void setDataSaverEnabled(boolean enabled) {
|
||||
mPolicyManager.setRestrictBackground(enabled);
|
||||
}
|
||||
|
||||
public void refreshWhitelist() {
|
||||
loadWhitelist();
|
||||
}
|
||||
|
||||
public void setIsWhitelisted(int uid, boolean whitelisted) {
|
||||
mWhitelist.put(uid, whitelisted);
|
||||
try {
|
||||
if (whitelisted) {
|
||||
mIPolicyManager.addRestrictBackgroundWhitelistedUid(uid);
|
||||
} else {
|
||||
mIPolicyManager.removeRestrictBackgroundWhitelistedUid(uid);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Can't reach policy manager", e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isWhitelisted(int uid) {
|
||||
if (mWhitelist == null) {
|
||||
loadWhitelist();
|
||||
}
|
||||
return mWhitelist.get(uid);
|
||||
}
|
||||
|
||||
public int getWhitelistedCount() {
|
||||
int count = 0;
|
||||
if (mWhitelist == null) {
|
||||
loadWhitelist();
|
||||
}
|
||||
for (int i = 0; i < mWhitelist.size(); i++) {
|
||||
if (mWhitelist.valueAt(i)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private void loadWhitelist() {
|
||||
mWhitelist = new SparseBooleanArray();
|
||||
try {
|
||||
for (int uid : mIPolicyManager.getRestrictBackgroundWhitelistedUids()) {
|
||||
mWhitelist.put(uid, true);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleRestrictBackgroundChanged(boolean isDataSaving) {
|
||||
for (int i = 0; i < mListeners.size(); i++) {
|
||||
mListeners.get(i).onDataSaverChanged(isDataSaving);
|
||||
}
|
||||
}
|
||||
|
||||
private final INetworkPolicyListener mPolicyListener = new INetworkPolicyListener.Stub() {
|
||||
@Override
|
||||
public void onUidRulesChanged(int i, int i1) throws RemoteException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMeteredIfacesChanged(String[] strings) throws RemoteException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRestrictBackgroundChanged(final boolean isDataSaving) throws RemoteException {
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
handleRestrictBackgroundChanged(isDataSaving);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
public interface Listener {
|
||||
void onDataSaverChanged(boolean isDataSaving);
|
||||
}
|
||||
}
|
47
src/com/android/settings/datausage/DataSaverPreference.java
Normal file
47
src/com/android/settings/datausage/DataSaverPreference.java
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.support.v7.preference.Preference;
|
||||
import android.util.AttributeSet;
|
||||
import com.android.settings.R;
|
||||
|
||||
public class DataSaverPreference extends Preference implements DataSaverBackend.Listener {
|
||||
|
||||
private final DataSaverBackend mDataSaverBackend;
|
||||
|
||||
public DataSaverPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mDataSaverBackend = new DataSaverBackend(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttached() {
|
||||
super.onAttached();
|
||||
mDataSaverBackend.addListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetached() {
|
||||
super.onDetached();
|
||||
mDataSaverBackend.addListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDataSaverChanged(boolean isDataSaving) {
|
||||
setSummary(isDataSaving ? R.string.data_saver_on : R.string.data_saver_off);
|
||||
}
|
||||
}
|
83
src/com/android/settings/datausage/DataSaverSummary.java
Normal file
83
src/com/android/settings/datausage/DataSaverSummary.java
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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.os.Bundle;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.util.Log;
|
||||
import android.widget.Switch;
|
||||
import com.android.settings.InstrumentedFragment;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.SettingsActivity;
|
||||
import com.android.settings.SettingsPreferenceFragment;
|
||||
import com.android.settings.widget.SwitchBar;
|
||||
|
||||
public class DataSaverSummary extends SettingsPreferenceFragment
|
||||
implements SwitchBar.OnSwitchChangeListener, DataSaverBackend.Listener {
|
||||
|
||||
private static final String KEY_UNRESTRICTED_ACCESS = "unrestricted_access";
|
||||
|
||||
private SwitchBar mSwitchBar;
|
||||
private DataSaverBackend mDataSaverBackend;
|
||||
private Preference mUnrestrictedAccess;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
|
||||
addPreferencesFromResource(R.xml.data_saver);
|
||||
mUnrestrictedAccess = findPreference(KEY_UNRESTRICTED_ACCESS);
|
||||
mDataSaverBackend = new DataSaverBackend(getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
mSwitchBar = ((SettingsActivity) getActivity()).getSwitchBar();
|
||||
mSwitchBar.show();
|
||||
mSwitchBar.addOnSwitchChangeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
mDataSaverBackend.addListener(this);
|
||||
mDataSaverBackend.refreshWhitelist();
|
||||
int count = mDataSaverBackend.getWhitelistedCount();
|
||||
mUnrestrictedAccess.setSummary(getResources().getQuantityString(
|
||||
R.plurals.data_saver_unrestricted_summary, count, count));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
mDataSaverBackend.remListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwitchChanged(Switch switchView, boolean isChecked) {
|
||||
mDataSaverBackend.setDataSaverEnabled(isChecked);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getMetricsCategory() {
|
||||
return InstrumentedFragment.DATA_SAVER_SUMMARY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDataSaverChanged(boolean isDataSaving) {
|
||||
mSwitchBar.setChecked(isDataSaving);
|
||||
}
|
||||
}
|
@@ -147,7 +147,27 @@ public class DataUsageList extends DataUsageBase {
|
||||
|
||||
mHeader = setPinnedHeaderView(R.layout.apps_filter_spinner);
|
||||
mCycleSpinner = (Spinner) mHeader.findViewById(R.id.filter_spinner);
|
||||
mCycleAdapter = new CycleAdapter(getContext(), mCycleSpinner, mCycleListener);
|
||||
mCycleAdapter = new CycleAdapter(getContext(), new CycleAdapter.SpinnerInterface() {
|
||||
@Override
|
||||
public void setAdapter(CycleAdapter cycleAdapter) {
|
||||
mCycleSpinner.setAdapter(cycleAdapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnItemSelectedListener(OnItemSelectedListener listener) {
|
||||
mCycleSpinner.setOnItemSelectedListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getSelectedItem() {
|
||||
return mCycleSpinner.getSelectedItem();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSelection(int position) {
|
||||
mCycleSpinner.setSelection(position);
|
||||
}
|
||||
}, mCycleListener, true);
|
||||
setLoading(true, false);
|
||||
}
|
||||
|
||||
@@ -470,7 +490,7 @@ public class DataUsageList extends DataUsageBase {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
final CycleAdapter.CycleItem cycle = (CycleAdapter.CycleItem)
|
||||
parent.getItemAtPosition(position);
|
||||
mCycleSpinner.getSelectedItem();
|
||||
|
||||
if (LOGD) {
|
||||
Log.d(TAG, "showing cycle " + cycle + ", start=" + cycle.start + ", end="
|
||||
|
90
src/com/android/settings/datausage/SpinnerPreference.java
Normal file
90
src/com/android/settings/datausage/SpinnerPreference.java
Normal file
@@ -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.Context;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.support.v7.preference.PreferenceViewHolder;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.Spinner;
|
||||
import com.android.settings.R;
|
||||
|
||||
public class SpinnerPreference extends Preference implements CycleAdapter.SpinnerInterface {
|
||||
|
||||
private CycleAdapter mAdapter;
|
||||
private AdapterView.OnItemSelectedListener mListener;
|
||||
private Object mCurrentObject;
|
||||
private int mPosition;
|
||||
|
||||
public SpinnerPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
setLayoutResource(R.layout.data_usage_cycles);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAdapter(CycleAdapter cycleAdapter) {
|
||||
mAdapter = cycleAdapter;
|
||||
notifyChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnItemSelectedListener(AdapterView.OnItemSelectedListener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getSelectedItem() {
|
||||
return mCurrentObject;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSelection(int position) {
|
||||
mPosition = position;
|
||||
mCurrentObject = mAdapter.getItem(mPosition);
|
||||
notifyChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(PreferenceViewHolder holder) {
|
||||
super.onBindViewHolder(holder);
|
||||
Spinner spinner = (Spinner) holder.findViewById(R.id.cycles_spinner);
|
||||
spinner.setAdapter(mAdapter);
|
||||
spinner.setSelection(mPosition);
|
||||
spinner.setOnItemSelectedListener(mOnSelectedListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void performClick(View view) {
|
||||
view.findViewById(R.id.cycles_spinner).performClick();
|
||||
}
|
||||
|
||||
private final AdapterView.OnItemSelectedListener mOnSelectedListener
|
||||
= new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
if (mPosition == position) return;
|
||||
mPosition = position;
|
||||
mCurrentObject = mAdapter.getItem(position);
|
||||
mListener.onItemSelected(parent, view, position, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
mListener.onNothingSelected(parent);
|
||||
}
|
||||
};
|
||||
}
|
184
src/com/android/settings/datausage/UnrestrictedDataAccess.java
Normal file
184
src/com/android/settings/datausage/UnrestrictedDataAccess.java
Normal file
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* 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.Application;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.v14.preference.SwitchPreference;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.support.v7.preference.PreferenceViewHolder;
|
||||
import android.view.View;
|
||||
import com.android.settings.InstrumentedFragment;
|
||||
import com.android.settings.SettingsPreferenceFragment;
|
||||
import com.android.settings.applications.AppStateBaseBridge;
|
||||
import com.android.settingslib.applications.ApplicationsState;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class UnrestrictedDataAccess extends SettingsPreferenceFragment
|
||||
implements ApplicationsState.Callbacks, AppStateBaseBridge.Callback, Preference.OnPreferenceChangeListener {
|
||||
|
||||
private ApplicationsState mApplicationsState;
|
||||
private AppStateDataUsageBridge mDataUsageBridge;
|
||||
private ApplicationsState.Session mSession;
|
||||
private DataSaverBackend mDataSaverBackend;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
setPreferenceScreen(getPreferenceManager().createPreferenceScreen(getContext()));
|
||||
getPreferenceScreen().setOrderingAsAdded(false);
|
||||
mApplicationsState = ApplicationsState.getInstance(
|
||||
(Application) getContext().getApplicationContext());
|
||||
mDataSaverBackend = new DataSaverBackend(getContext());
|
||||
mDataUsageBridge = new AppStateDataUsageBridge(mApplicationsState, this, mDataSaverBackend);
|
||||
mSession = mApplicationsState.newSession(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
setLoading(true, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
mSession.resume();
|
||||
mDataUsageBridge.resume();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
mDataUsageBridge.pause();
|
||||
mSession.pause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
mSession.release();
|
||||
mDataUsageBridge.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExtraInfoUpdated() {
|
||||
ArrayList<ApplicationsState.AppEntry> apps = mSession.getAllApps();
|
||||
final int N = apps.size();
|
||||
for (int i = 0; i < N; i++) {
|
||||
ApplicationsState.AppEntry entry = apps.get(i);
|
||||
String key = entry.info.packageName + "|" + entry.info.uid;
|
||||
AccessPreference preference = (AccessPreference) findPreference(key);
|
||||
if (preference == null) {
|
||||
preference = new AccessPreference(getContext(), entry);
|
||||
preference.setKey(key);
|
||||
preference.setOnPreferenceChangeListener(this);
|
||||
getPreferenceScreen().addPreference(preference);
|
||||
}
|
||||
AppStateDataUsageBridge.DataUsageState state =
|
||||
(AppStateDataUsageBridge.DataUsageState) entry.extraInfo;
|
||||
preference.setChecked(state.isDataSaverWhitelisted);
|
||||
}
|
||||
setLoading(false, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRunningStateChanged(boolean running) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPackageListChanged() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPackageIconChanged() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPackageSizeChanged(String packageName) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAllSizesComputed() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLauncherInfoChanged() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadEntriesCompleted() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getMetricsCategory() {
|
||||
return InstrumentedFragment.DATA_USAGE_UNRESTRICTED_ACCESS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
if (preference instanceof AccessPreference) {
|
||||
AccessPreference accessPreference = (AccessPreference) preference;
|
||||
boolean whitelisted = newValue == Boolean.TRUE;
|
||||
mDataSaverBackend.setIsWhitelisted(accessPreference.mEntry.info.uid, whitelisted);
|
||||
((AppStateDataUsageBridge.DataUsageState) accessPreference.mEntry.extraInfo)
|
||||
.isDataSaverWhitelisted = whitelisted;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private class AccessPreference extends SwitchPreference {
|
||||
private final ApplicationsState.AppEntry mEntry;
|
||||
|
||||
public AccessPreference(Context context, ApplicationsState.AppEntry entry) {
|
||||
super(context);
|
||||
mEntry = entry;
|
||||
mEntry.ensureLabel(getContext());
|
||||
setTitle(entry.label);
|
||||
setChecked(((AppStateDataUsageBridge.DataUsageState) entry.extraInfo)
|
||||
.isDataSaverWhitelisted);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(PreferenceViewHolder holder) {
|
||||
holder.itemView.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Ensure we have an icon before binding.
|
||||
mApplicationsState.ensureIcon(mEntry);
|
||||
// This might trigger us to bind again, but it gives an easy way to only load the icon
|
||||
// once its needed, so its probably worth it.
|
||||
setIcon(mEntry.icon);
|
||||
}
|
||||
});
|
||||
super.onBindViewHolder(holder);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user