1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 * except in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 11 * KIND, either express or implied. See the License for the specific language governing 12 * permissions and limitations under the License. 13 */ 14 package com.android.settings.datausage; 15 16 import static com.android.settingslib.RestrictedLockUtilsInternal.checkIfMeteredDataUsageUserControlDisabled; 17 18 import android.content.Context; 19 import android.graphics.drawable.Drawable; 20 import android.os.UserHandle; 21 import android.view.View; 22 23 import androidx.annotation.NonNull; 24 import androidx.annotation.Nullable; 25 import androidx.annotation.VisibleForTesting; 26 import androidx.preference.PreferenceViewHolder; 27 28 import com.android.settings.R; 29 import com.android.settings.applications.appinfo.AppInfoDashboardFragment; 30 import com.android.settings.dashboard.DashboardFragment; 31 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; 32 import com.android.settingslib.RestrictedPreferenceHelper; 33 import com.android.settingslib.applications.AppUtils; 34 import com.android.settingslib.applications.ApplicationsState; 35 import com.android.settingslib.applications.ApplicationsState.AppEntry; 36 import com.android.settingslib.utils.ThreadUtils; 37 import com.android.settingslib.widget.AppSwitchPreference; 38 39 public class UnrestrictedDataAccessPreference extends AppSwitchPreference implements 40 DataSaverBackend.Listener { 41 private static final String ECM_SETTING_IDENTIFIER = "android:unrestricted_data_access"; 42 43 private final ApplicationsState mApplicationsState; 44 private final AppEntry mEntry; 45 private final AppStateDataUsageBridge.DataUsageState mDataUsageState; 46 private final DataSaverBackend mDataSaverBackend; 47 private final DashboardFragment mParentFragment; 48 private final RestrictedPreferenceHelper mHelper; 49 private Drawable mCacheIcon; 50 UnrestrictedDataAccessPreference(final Context context, AppEntry entry, ApplicationsState applicationsState, DataSaverBackend dataSaverBackend, DashboardFragment parentFragment)51 public UnrestrictedDataAccessPreference(final Context context, AppEntry entry, 52 ApplicationsState applicationsState, DataSaverBackend dataSaverBackend, 53 DashboardFragment parentFragment) { 54 super(context); 55 mHelper = new RestrictedPreferenceHelper(context, this, null); 56 mEntry = entry; 57 mDataUsageState = (AppStateDataUsageBridge.DataUsageState) mEntry.extraInfo; 58 mEntry.ensureLabel(context); 59 mApplicationsState = applicationsState; 60 mDataSaverBackend = dataSaverBackend; 61 mParentFragment = parentFragment; 62 setDisabledByAdmin(checkIfMeteredDataUsageUserControlDisabled( 63 context, entry.info.packageName, UserHandle.getUserId(entry.info.uid))); 64 mHelper.checkEcmRestrictionAndSetDisabled(ECM_SETTING_IDENTIFIER, entry.info.packageName); 65 updateState(); 66 setKey(generateKey(mEntry)); 67 68 mCacheIcon = AppUtils.getIconFromCache(mEntry); 69 if (mCacheIcon != null) { 70 setIcon(mCacheIcon); 71 } else { 72 // Set empty icon as default. 73 setIcon(R.drawable.empty_icon); 74 } 75 } 76 generateKey(final AppEntry entry)77 static String generateKey(final AppEntry entry) { 78 return entry.info.packageName + "|" + entry.info.uid; 79 } 80 81 @Override onAttached()82 public void onAttached() { 83 super.onAttached(); 84 mDataSaverBackend.addListener(this); 85 } 86 87 @Override onDetached()88 public void onDetached() { 89 mDataSaverBackend.remListener(this); 90 super.onDetached(); 91 } 92 93 @Override onClick()94 protected void onClick() { 95 if (mDataUsageState != null && mDataUsageState.isDataSaverDenylisted) { 96 // app is denylisted, launch App Data Usage screen 97 AppInfoDashboardFragment.startAppInfoFragment(AppDataUsage.class, 98 R.string.data_usage_app_summary_title, 99 null /* arguments */, 100 mParentFragment, 101 mEntry); 102 } else { 103 // app is not denylisted, let superclass handle toggle switch 104 super.onClick(); 105 } 106 } 107 108 @Override performClick()109 public void performClick() { 110 if (!mHelper.performClick()) { 111 super.performClick(); 112 } 113 } 114 115 @Override onBindViewHolder(PreferenceViewHolder holder)116 public void onBindViewHolder(PreferenceViewHolder holder) { 117 if (mCacheIcon == null) { 118 ThreadUtils.postOnBackgroundThread(() -> { 119 final Drawable icon = AppUtils.getIcon(getContext(), mEntry); 120 ThreadUtils.postOnMainThread(() -> { 121 setIcon(icon); 122 mCacheIcon = icon; 123 }); 124 }); 125 } 126 final boolean disabledByAdmin = isDisabledByAdmin(); 127 final View widgetFrame = holder.findViewById(android.R.id.widget_frame); 128 if (disabledByAdmin) { 129 widgetFrame.setVisibility(View.VISIBLE); 130 } else { 131 widgetFrame.setVisibility( 132 mDataUsageState != null && mDataUsageState.isDataSaverDenylisted 133 ? View.INVISIBLE : View.VISIBLE); 134 } 135 super.onBindViewHolder(holder); 136 137 mHelper.onBindViewHolder(holder); 138 } 139 140 @Override onDataSaverChanged(boolean isDataSaving)141 public void onDataSaverChanged(boolean isDataSaving) { 142 } 143 144 @Override onAllowlistStatusChanged(int uid, boolean isAllowlisted)145 public void onAllowlistStatusChanged(int uid, boolean isAllowlisted) { 146 if (mDataUsageState != null && mEntry.info.uid == uid) { 147 mDataUsageState.isDataSaverAllowlisted = isAllowlisted; 148 updateState(); 149 } 150 } 151 152 @Override onDenylistStatusChanged(int uid, boolean isDenylisted)153 public void onDenylistStatusChanged(int uid, boolean isDenylisted) { 154 if (mDataUsageState != null && mEntry.info.uid == uid) { 155 mDataUsageState.isDataSaverDenylisted = isDenylisted; 156 updateState(); 157 } 158 } 159 160 @Nullable getDataUsageState()161 public AppStateDataUsageBridge.DataUsageState getDataUsageState() { 162 return mDataUsageState; 163 } 164 getEntry()165 public AppEntry getEntry() { 166 return mEntry; 167 } 168 isDisabledByAdmin()169 public boolean isDisabledByAdmin() { 170 return mHelper.isDisabledByAdmin(); 171 } 172 173 @VisibleForTesting isDisabledByEcm()174 boolean isDisabledByEcm() { 175 return mHelper.isDisabledByEcm(); 176 } 177 setDisabledByAdmin(EnforcedAdmin admin)178 public void setDisabledByAdmin(EnforcedAdmin admin) { 179 mHelper.setDisabledByAdmin(admin); 180 } 181 182 /** 183 * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this 184 * package. Marks the preference as disabled if so. 185 * @param packageName the package to check the restriction for 186 */ checkEcmRestrictionAndSetDisabled(@onNull String packageName)187 public void checkEcmRestrictionAndSetDisabled(@NonNull String packageName) { 188 mHelper.checkEcmRestrictionAndSetDisabled(ECM_SETTING_IDENTIFIER, packageName); 189 } 190 191 // Sets UI state based on allowlist/denylist status. updateState()192 public void updateState() { 193 setTitle(mEntry.label); 194 if (mDataUsageState != null) { 195 setChecked(mDataUsageState.isDataSaverAllowlisted); 196 if (isDisabledByAdmin()) { 197 setSummary(com.android.settingslib.widget.restricted.R.string.disabled_by_admin); 198 } else if (mDataUsageState.isDataSaverDenylisted) { 199 setSummary(R.string.restrict_background_blocklisted); 200 // If disabled by ECM, the summary is set directly by the switch. 201 } else if (!isDisabledByEcm()) { 202 setSummary(""); 203 } 204 } 205 notifyChanged(); 206 } 207 } 208