diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 762d84da36e..4117b2e36f0 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -866,6 +866,7 @@ @@ -1451,7 +1452,8 @@ + android:noHistory="true" + android:permission="android.permission.QUERY_ALL_PACKAGES"> @@ -1521,6 +1523,7 @@ @@ -1534,7 +1537,8 @@ + android:exported="true" + android:permission="android.permission.QUERY_ALL_PACKAGES"> @@ -1905,7 +1909,8 @@ + android:label="@string/usage_access_title" + android:permission="android.permission.QUERY_ALL_PACKAGES"> diff --git a/res/drawable/ic_settings_safety_center.xml b/res/drawable/ic_settings_safety_center.xml index f43359fefd4..e817b7194bb 100644 --- a/res/drawable/ic_settings_safety_center.xml +++ b/res/drawable/ic_settings_safety_center.xml @@ -13,17 +13,19 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - + android:viewportHeight="24" + android:tint="?android:attr/textColorPrimary"> + + android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" /> + android:fillColor="#000000" + android:pathData="M12,4.14l6,2.25v4.71c0,2.12-0.62,4-1.88,5.74c-1.13,1.55-2.48,2.56-4.12,3.09c-1.64-0.52-2.99-1.54-4.12-3.09 C6.62,15.1,6,13.22,6,11.1V6.39L12,4.14 M12,2L4,5v6.1c0,2.53,0.75,4.84,2.26,6.91C7.77,20.09,9.68,21.42,12,22 c2.32-0.58,4.23-1.91,5.74-3.99C19.25,15.94,20,13.63,20,11.1V5L12,2L12,2z" /> + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index cc66b3b432b..e333969e471 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -10195,18 +10195,18 @@ When an app is pinned, the pinned app may open other apps and personal data may be accessible. \n\nTo use app pinning: - \t\n1.\tTurn on app pinning - \t\n2.\tOpen Overview - \t\n3.\tTap the app icon at the top of the screen, then tap Pin + \t\n1. Turn on app pinning + \t\n2. Open Overview + \t\n3. Tap the app icon at the top of the screen, then tap Pin When an app is pinned, the pinned app may open other apps and personal data may be accessible. \n\nIf you want to securely share your device with someone, try using a guest user instead. \n\nTo use app pinning: - \t\n1.\tTurn on app pinning - \t\n2.\tOpen Overview - \t\n3.\tTap the app icon at the top of the screen, then tap Pin + \t\n1. Turn on app pinning + \t\n2. Open Overview + \t\n3. Tap the app icon at the top of the screen, then tap Pin diff --git a/res/xml/app_data_usage.xml b/res/xml/app_data_usage.xml index e64a1c5cc63..e94c4ff4ca2 100644 --- a/res/xml/app_data_usage.xml +++ b/res/xml/app_data_usage.xml @@ -21,8 +21,7 @@ android:title="@string/data_usage_app_summary_title"> + android:key="cycle" /> @@ -31,19 +30,22 @@ android:key="total_usage" android:title="@string/total_size_label" android:selectable="false" - android:layout="@layout/horizontal_preference" /> + android:layout="@layout/horizontal_preference" + android:summary="@string/summary_placeholder" /> + android:layout="@layout/horizontal_preference" + android:summary="@string/summary_placeholder" /> + android:layout="@layout/horizontal_preference" + android:summary="@string/summary_placeholder" /> diff --git a/src/com/android/settings/applications/manageapplications/ManageApplications.java b/src/com/android/settings/applications/manageapplications/ManageApplications.java index 3eae8855959..062bfc576b2 100644 --- a/src/com/android/settings/applications/manageapplications/ManageApplications.java +++ b/src/com/android/settings/applications/manageapplications/ManageApplications.java @@ -330,8 +330,8 @@ public class ManageApplications extends InstrumentedFragment if (className.equals(Settings.NotificationReviewPermissionsActivity.class.getName())) { // Special-case for a case where a user is directed to the all apps notification // preferences page via a notification prompt to review permissions settings. - android.provider.Settings.Secure.putInt(getContext().getContentResolver(), - android.provider.Settings.Secure.REVIEW_PERMISSIONS_NOTIFICATION_STATE, + android.provider.Settings.Global.putInt(getContext().getContentResolver(), + android.provider.Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, 1); // USER_INTERACTED } } else if (className.equals(AppLocaleDetails.class.getName())) { diff --git a/src/com/android/settings/datausage/AppDataUsage.java b/src/com/android/settings/datausage/AppDataUsage.java index 460f3909f74..531a341c95a 100644 --- a/src/com/android/settings/datausage/AppDataUsage.java +++ b/src/com/android/settings/datausage/AppDataUsage.java @@ -144,8 +144,7 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC mForegroundUsage = findPreference(KEY_FOREGROUND_USAGE); mBackgroundUsage = findPreference(KEY_BACKGROUND_USAGE); - mCycle = findPreference(KEY_CYCLE); - mCycleAdapter = new CycleAdapter(mContext, mCycle, mCycleListener); + initCycle(); final UidDetailProvider uidDetailProvider = getUidDetailProvider(); @@ -211,6 +210,8 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC removePreference(KEY_RESTRICT_BACKGROUND); removePreference(KEY_APP_LIST); } + + addEntityHeader(); } @Override @@ -276,6 +277,17 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC return new UidDetailProvider(mContext); } + private void initCycle() { + mCycle = findPreference(KEY_CYCLE); + mCycleAdapter = new CycleAdapter(mContext, mCycle, mCycleListener); + if (mCycles != null) { + // If coming from a page like DataUsageList where already has a selected cycle, display + // that before loading to reduce flicker. + mCycleAdapter.setInitialCycleList(mCycles, mSelectedCycle); + mCycle.setHasCycles(true); + } + } + private void updatePrefs(boolean restrictBackground, boolean unrestrictData) { final EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfMeteredDataRestricted( mContext, mPackageName, UserHandle.getUserId(mAppItem.key)); @@ -308,9 +320,9 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC final long backgroundBytes, foregroundBytes; if (mUsageData == null || position >= mUsageData.size()) { backgroundBytes = foregroundBytes = 0; - mCycle.setVisible(false); + mCycle.setHasCycles(false); } else { - mCycle.setVisible(true); + mCycle.setHasCycles(true); final NetworkCycleDataForUid data = mUsageData.get(position); backgroundBytes = data.getBackgroudUsage(); foregroundBytes = data.getForegroudUsage(); @@ -335,10 +347,8 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC return false; } - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - + @VisibleForTesting + void addEntityHeader() { String pkg = mPackages.size() != 0 ? mPackages.valueAt(0) : null; int uid = 0; if (pkg != null) { diff --git a/src/com/android/settings/datausage/CycleAdapter.java b/src/com/android/settings/datausage/CycleAdapter.java index 2cabd8de452..b41b6aad91b 100644 --- a/src/com/android/settings/datausage/CycleAdapter.java +++ b/src/com/android/settings/datausage/CycleAdapter.java @@ -13,24 +13,13 @@ */ package com.android.settings.datausage; -import android.annotation.NonNull; -import android.app.usage.NetworkStats; import android.content.Context; -import android.net.NetworkPolicy; -import android.net.NetworkPolicyManager; -import android.text.format.DateUtils; -import android.util.Pair; -import android.util.Range; import android.widget.AdapterView; -import com.android.net.module.util.NetworkStatsUtils; import com.android.settings.Utils; -import com.android.settingslib.net.ChartData; import com.android.settingslib.net.NetworkCycleData; import com.android.settingslib.widget.SettingsSpinnerAdapter; -import java.time.ZonedDateTime; -import java.util.Iterator; import java.util.List; import java.util.Objects; @@ -45,7 +34,6 @@ public class CycleAdapter extends SettingsSpinnerAdapter mSpinner = spinner; mListener = listener; mSpinner.setAdapter(this); - mSpinner.setOnItemSelectedListener(mListener); } /** @@ -65,128 +53,14 @@ public class CycleAdapter extends SettingsSpinnerAdapter return 0; } - protected static long getTotalBytesForTimeRange(List stats, - Range range) { - long bytes = 0L; - for (NetworkStats.Bucket bucket : stats) { - final Range bucketSpan = new Range<>( - bucket.getStartTimeStamp(), bucket.getEndTimeStamp()); - // Only record bytes that overlapped with the given time range. For partially - // overlapped bucket, record rational bytes assuming the traffic is uniform - // distributed within the bucket. - try { - final Range overlapped = range.intersect(bucketSpan); - final long totalOfBucket = bucket.getRxBytes() + bucket.getTxBytes(); - bytes += NetworkStatsUtils.multiplySafeByRational(totalOfBucket, - overlapped.getUpper() - overlapped.getLower(), - bucketSpan.getUpper() - bucketSpan.getLower()); - } catch (IllegalArgumentException e) { - // Range disjoint, ignore. - continue; - } - } - return bytes; - } - - @NonNull - private Range getTimeRangeOf(@NonNull List stats) { - long start = Long.MAX_VALUE; - long end = Long.MIN_VALUE; - for (NetworkStats.Bucket bucket : stats) { - start = Math.min(start, bucket.getStartTimeStamp()); - end = Math.max(end, bucket.getEndTimeStamp()); - } - return new Range(start, end); - } - - /** - * Rebuild list based on {@link NetworkPolicy} and available - * {@link List} data. Always selects the newest item, - * updating the inspection range on chartData. - */ - @Deprecated - public boolean updateCycleList(NetworkPolicy policy, ChartData chartData) { - // stash away currently selected cycle to try restoring below - final CycleAdapter.CycleItem previousItem = (CycleAdapter.CycleItem) - mSpinner.getSelectedItem(); + void setInitialCycleList(List cycles, long selectedCycle) { clear(); - - final Context context = getContext(); - - long historyStart; - long historyEnd; - try { - final Range historyTimeRange = getTimeRangeOf(chartData.network); - historyStart = historyTimeRange.getLower(); - historyEnd = historyTimeRange.getUpper(); - } catch (IllegalArgumentException e) { - // Empty history. - final long now = System.currentTimeMillis(); - historyStart = now; - historyEnd = now + 1; - } - - boolean hasCycles = false; - if (policy != null) { - final Iterator> it = NetworkPolicyManager - .cycleIterator(policy); - while (it.hasNext()) { - final Pair cycle = it.next(); - final long cycleStart = cycle.first.toInstant().toEpochMilli(); - final long cycleEnd = cycle.second.toInstant().toEpochMilli(); - - final boolean includeCycle; - if (chartData != null) { - final long bytesInCycle = getTotalBytesForTimeRange(chartData.network, - new Range<>(cycleStart, cycleEnd)); - includeCycle = bytesInCycle > 0; - } else { - includeCycle = true; - } - - if (includeCycle) { - add(new CycleAdapter.CycleItem(context, cycleStart, cycleEnd)); - hasCycles = true; - } + for (int i = 0; i < cycles.size() - 1; i++) { + add(new CycleAdapter.CycleItem(getContext(), cycles.get(i + 1), cycles.get(i))); + if (cycles.get(i) == selectedCycle) { + mSpinner.setSelection(i); } } - - 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) { - final long bytesInCycle = getTotalBytesForTimeRange(chartData.network, - new Range<>(cycleStart, cycleEnd)); - includeCycle = bytesInCycle > 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.equals(selectedItem, previousItem)) { - mListener.onItemSelected(null, null, position, 0); - return false; - } - } - return true; } /** @@ -194,6 +68,7 @@ public class CycleAdapter extends SettingsSpinnerAdapter * updating the inspection range on chartData. */ public boolean updateCycleList(List cycleData) { + mSpinner.setOnItemSelectedListener(mListener); // stash away currently selected cycle to try restoring below final CycleAdapter.CycleItem previousItem = (CycleAdapter.CycleItem) mSpinner.getSelectedItem(); @@ -228,10 +103,6 @@ public class CycleAdapter extends SettingsSpinnerAdapter 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; diff --git a/src/com/android/settings/datausage/DataUsageList.java b/src/com/android/settings/datausage/DataUsageList.java index dc945f1fc42..4ee653017e4 100644 --- a/src/com/android/settings/datausage/DataUsageList.java +++ b/src/com/android/settings/datausage/DataUsageList.java @@ -48,6 +48,7 @@ import android.widget.ImageView; import android.widget.Spinner; import androidx.annotation.VisibleForTesting; +import androidx.lifecycle.Lifecycle; import androidx.loader.app.LoaderManager.LoaderCallbacks; import androidx.loader.content.Loader; import androidx.preference.Preference; @@ -500,6 +501,17 @@ public class DataUsageList extends DataUsageBaseFragment + cycle.end + "]"); } + // Avoid from updating UI after #onStop. + if (!getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) { + return; + } + + // Avoid from updating UI when async query still on-going. + // This could happen when a request from #onMobileDataEnabledChange. + if (mCycleData == null) { + return; + } + // update chart to show selected cycle, and update detail data // to match updated sweep bounds. mChart.setNetworkCycleData(mCycleData.get(position)); diff --git a/src/com/android/settings/datausage/SpinnerPreference.java b/src/com/android/settings/datausage/SpinnerPreference.java index 867930baa97..c4b7a4e18da 100644 --- a/src/com/android/settings/datausage/SpinnerPreference.java +++ b/src/com/android/settings/datausage/SpinnerPreference.java @@ -31,6 +31,8 @@ public class SpinnerPreference extends Preference implements CycleAdapter.Spinne private AdapterView.OnItemSelectedListener mListener; private Object mCurrentObject; private int mPosition; + private View mItemView; + private boolean mItemViewVisible = false; public SpinnerPreference(Context context, AttributeSet attrs) { super(context, attrs); @@ -63,12 +65,24 @@ public class SpinnerPreference extends Preference implements CycleAdapter.Spinne @Override public void onBindViewHolder(PreferenceViewHolder holder) { super.onBindViewHolder(holder); + mItemView = holder.itemView; + mItemView.setVisibility(mItemViewVisible ? View.VISIBLE : View.INVISIBLE); Spinner spinner = (Spinner) holder.findViewById(R.id.cycles_spinner); spinner.setAdapter(mAdapter); spinner.setSelection(mPosition); spinner.setOnItemSelectedListener(mOnSelectedListener); } + void setHasCycles(boolean hasData) { + setVisible(hasData); + if (hasData) { + mItemViewVisible = true; + if (mItemView != null) { + mItemView.setVisibility(View.VISIBLE); + } + } + } + @Override protected void performClick(View view) { view.findViewById(R.id.cycles_spinner).performClick(); diff --git a/src/com/android/settings/fuelgauge/BatteryAppListPreferenceController.java b/src/com/android/settings/fuelgauge/BatteryAppListPreferenceController.java index 8fb0d1e0f64..db4cc536579 100644 --- a/src/com/android/settings/fuelgauge/BatteryAppListPreferenceController.java +++ b/src/com/android/settings/fuelgauge/BatteryAppListPreferenceController.java @@ -424,6 +424,9 @@ public class BatteryAppListPreferenceController extends AbstractPreferenceContro @VisibleForTesting void setUsageSummary(Preference preference, BatteryEntry entry) { + if (entry.getUid() == Process.SYSTEM_UID) { + return; + } // Only show summary when usage time is longer than one minute final long usageTimeMs = entry.getTimeInForegroundMs(); if (shouldShowSummary(entry) && usageTimeMs >= DateUtils.MINUTE_IN_MILLIS) { diff --git a/src/com/android/settings/localepicker/AppLocalePickerActivity.java b/src/com/android/settings/localepicker/AppLocalePickerActivity.java index 4700f3f5383..1ed6f5be3b1 100644 --- a/src/com/android/settings/localepicker/AppLocalePickerActivity.java +++ b/src/com/android/settings/localepicker/AppLocalePickerActivity.java @@ -30,7 +30,6 @@ import android.view.View; import android.widget.FrameLayout; import android.widget.ListView; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.LocalePickerWithRegion; import com.android.internal.app.LocaleStore; import com.android.settings.R; @@ -38,10 +37,6 @@ import com.android.settings.applications.AppInfoBase; import com.android.settings.applications.appinfo.AppLocaleDetails; import com.android.settings.core.SettingsBaseActivity; -/** - * TODO(b/223503670): Add unit test for AppLocalePickerActivity. - * A activity to show the locale picker and information page. - */ public class AppLocalePickerActivity extends SettingsBaseActivity implements LocalePickerWithRegion.LocaleSelectedListener { private static final String TAG = AppLocalePickerActivity.class.getSimpleName(); @@ -128,8 +123,7 @@ public class AppLocalePickerActivity extends SettingsBaseActivity return appLocaleDetailsContainer; } - @VisibleForTesting - void launchLocalePickerPage() { + private void launchLocalePickerPage() { // LocalePickerWithRegion use android.app.ListFragment. Thus, it can not use // getSupportFragmentManager() to add this into container. android.app.FragmentManager fragmentManager = getFragmentManager(); diff --git a/src/com/android/settings/network/telephony/MobileNetworkSettings.java b/src/com/android/settings/network/telephony/MobileNetworkSettings.java index 763962c492f..4eb8a857232 100644 --- a/src/com/android/settings/network/telephony/MobileNetworkSettings.java +++ b/src/com/android/settings/network/telephony/MobileNetworkSettings.java @@ -37,6 +37,7 @@ import androidx.preference.Preference; import com.android.settings.R; import com.android.settings.Settings.MobileNetworkActivity; +import com.android.settings.SettingsActivity; import com.android.settings.datausage.BillingCyclePreferenceController; import com.android.settings.datausage.DataUsageSummaryPreferenceController; import com.android.settings.network.ActiveSubscriptionsListener; @@ -54,6 +55,7 @@ import com.android.settingslib.utils.ThreadUtils; import java.util.Arrays; import java.util.List; +import java.util.function.Consumer; @SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC) public class MobileNetworkSettings extends AbstractMobileNetworkSettings { @@ -275,17 +277,37 @@ public class MobileNetworkSettings extends AbstractMobileNetworkSettings { Log.d(LOG_TAG, "Callback during onResume()"); return; } + + final SubscriptionInfo subInfo = SubscriptionUtil + .getSubscriptionOrDefault(getContext(), mSubId); + + if (subInfo != null) { + /** + * Update the title when SIM stats got changed + */ + final Consumer renameTitle = activity -> { + if (activity != null && !activity.isFinishing()) { + if (activity instanceof SettingsActivity) { + final CharSequence displayName = SubscriptionUtil + .getUniqueSubscriptionDisplayName(subInfo, activity); + ((SettingsActivity)activity).setTitle(displayName); + } + } + }; + + ThreadUtils.postOnMainThread(() -> renameTitle.accept(getActivity())); + } + mActiveSubscriptionsListenerCount++; if (mActiveSubscriptionsListenerCount != 1) { return; } - if (SubscriptionUtil.getSubscriptionOrDefault(getContext(), mSubId) == null) { - finishFragment(); - return; - } - ThreadUtils.postOnMainThread(() -> { + if (subInfo == null) { + finishFragment(); + return; + } mActiveSubscriptionsListenerCount = 0; redrawPreferenceControllers(); }); diff --git a/src/com/android/settings/notification/MediaVolumePreferenceController.java b/src/com/android/settings/notification/MediaVolumePreferenceController.java index 978a4b40237..c0d5610c932 100644 --- a/src/com/android/settings/notification/MediaVolumePreferenceController.java +++ b/src/com/android/settings/notification/MediaVolumePreferenceController.java @@ -35,6 +35,7 @@ import com.android.settings.media.MediaOutputIndicatorWorker; import com.android.settings.slices.CustomSliceRegistry; import com.android.settings.slices.SliceBackgroundWorker; import com.android.settingslib.media.MediaDevice; +import com.android.settingslib.media.MediaOutputConstants; public class MediaVolumePreferenceController extends VolumeSeekBarPreferenceController { private static final String TAG = "MediaVolumePreCtrl"; @@ -106,8 +107,10 @@ public class MediaVolumePreferenceController extends VolumeSeekBarPreferenceCont final Intent intent = new Intent(); if (getWorker().isDeviceBroadcasting()) { - // TODO(b/229577323) : Get the intent action for the Media Output Broadcast Dialog - // in SystemUI + intent.setPackage(MediaOutputConstants.SYSTEMUI_PACKAGE_NAME); + intent.setAction(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG); + intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, + getWorker().getActiveLocalMediaController().getPackageName()); } else { // TODO(b/229577518) : Get the intent action of the Bluetooth Broadcast Dialog // for user to choose the action diff --git a/src/com/android/settings/wifi/p2p/WifiP2pPreferenceController.java b/src/com/android/settings/wifi/p2p/WifiP2pPreferenceController.java index db150dab3b5..5d73fa464f1 100644 --- a/src/com/android/settings/wifi/p2p/WifiP2pPreferenceController.java +++ b/src/com/android/settings/wifi/p2p/WifiP2pPreferenceController.java @@ -15,18 +15,17 @@ */ package com.android.settings.wifi.p2p; -import android.app.Service; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.location.LocationManager; import android.net.wifi.WifiManager; import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; +import com.android.settings.R; import com.android.settings.core.PreferenceControllerMixin; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.lifecycle.Lifecycle; @@ -70,6 +69,9 @@ public class WifiP2pPreferenceController extends AbstractPreferenceController super.displayPreference(screen); mWifiDirectPref = screen.findPreference(KEY_WIFI_DIRECT); togglePreferences(); + if (!mIsWifiDirectAllow) { + mWifiDirectPref.setSummary(R.string.not_allowed_by_ent); + } } @Override diff --git a/src/com/android/settings/wifi/tether/WifiTetherPreferenceController.java b/src/com/android/settings/wifi/tether/WifiTetherPreferenceController.java index 94bd78a9c09..73ff31d4e47 100644 --- a/src/com/android/settings/wifi/tether/WifiTetherPreferenceController.java +++ b/src/com/android/settings/wifi/tether/WifiTetherPreferenceController.java @@ -46,12 +46,9 @@ public class WifiTetherPreferenceController extends AbstractPreferenceController private static final String WIFI_TETHER_SETTINGS = "wifi_tether"; - private final TetheringManager mTetheringManager; - private final String[] mWifiRegexs; - private final WifiManager mWifiManager; - private final Lifecycle mLifecycle; - @VisibleForTesting - boolean mIsWifiTetheringAllow; + private boolean mIsWifiTetherable; + private WifiManager mWifiManager; + private boolean mIsWifiTetheringAllow; private int mSoftApState; @VisibleForTesting Preference mPreference; @@ -59,18 +56,32 @@ public class WifiTetherPreferenceController extends AbstractPreferenceController WifiTetherSoftApManager mWifiTetherSoftApManager; public WifiTetherPreferenceController(Context context, Lifecycle lifecycle) { - this(context, lifecycle, true /* initSoftApManager */); + this(context, lifecycle, + context.getSystemService(WifiManager.class), + context.getSystemService(TetheringManager.class), + true /* initSoftApManager */, + WifiEnterpriseRestrictionUtils.isWifiTetheringAllowed(context)); } @VisibleForTesting - WifiTetherPreferenceController(Context context, Lifecycle lifecycle, - boolean initSoftApManager) { + WifiTetherPreferenceController( + Context context, + Lifecycle lifecycle, + WifiManager wifiManager, + TetheringManager tetheringManager, + boolean initSoftApManager, + boolean isWifiTetheringAllow) { super(context); - mTetheringManager = context.getSystemService(TetheringManager.class); - mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); - mWifiRegexs = mTetheringManager.getTetherableWifiRegexs(); - mIsWifiTetheringAllow = WifiEnterpriseRestrictionUtils.isWifiTetheringAllowed(context); - mLifecycle = lifecycle; + final String[] wifiRegexs = tetheringManager.getTetherableWifiRegexs(); + if (wifiRegexs != null && wifiRegexs.length != 0) { + mIsWifiTetherable = true; + } + + mIsWifiTetheringAllow = isWifiTetheringAllow; + if (!isWifiTetheringAllow) return; + + mWifiManager = wifiManager; + if (lifecycle != null) { lifecycle.addObserver(this); } @@ -81,9 +92,7 @@ public class WifiTetherPreferenceController extends AbstractPreferenceController @Override public boolean isAvailable() { - return mWifiRegexs != null - && mWifiRegexs.length != 0 - && !Utils.isMonkeyRunning(); + return mIsWifiTetherable && !Utils.isMonkeyRunning(); } @Override @@ -94,7 +103,10 @@ public class WifiTetherPreferenceController extends AbstractPreferenceController // unavailable return; } - mPreference.setEnabled(mIsWifiTetheringAllow); + if (!mIsWifiTetheringAllow && mPreference.isEnabled()) { + mPreference.setEnabled(false); + mPreference.setSummary(R.string.not_allowed_by_ent); + } } @Override diff --git a/tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java b/tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java index 09c57340d60..f043ec737e3 100644 --- a/tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java +++ b/tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java @@ -43,7 +43,6 @@ import android.os.Process; import android.telephony.SubscriptionManager; import android.text.format.DateUtils; import android.util.ArraySet; -import android.view.View; import androidx.fragment.app.FragmentActivity; import androidx.preference.Preference; @@ -96,6 +95,10 @@ public class AppDataUsageTest { @Before public void setUp() { MockitoAnnotations.initMocks(this); + + ShadowEntityHeaderController.setUseMock(mHeaderController); + when(mHeaderController.setRecyclerView(any(), any())).thenReturn(mHeaderController); + when(mHeaderController.setUid(anyInt())).thenReturn(mHeaderController); } @After @@ -163,10 +166,6 @@ public class AppDataUsageTest { @Test public void bindAppHeader_allWorkApps_shouldNotShowAppInfoLink() { - ShadowEntityHeaderController.setUseMock(mHeaderController); - when(mHeaderController.setRecyclerView(any(), any())).thenReturn(mHeaderController); - when(mHeaderController.setUid(anyInt())).thenReturn(mHeaderController); - mFragment = spy(new AppDataUsage()); when(mFragment.getPreferenceManager()) @@ -174,7 +173,7 @@ public class AppDataUsageTest { doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen(); ReflectionHelpers.setField(mFragment, "mAppItem", mock(AppItem.class)); - mFragment.onViewCreated(new View(RuntimeEnvironment.application), new Bundle()); + mFragment.addEntityHeader(); verify(mHeaderController).setHasAppInfoLink(false); } @@ -196,16 +195,13 @@ public class AppDataUsageTest { when(mPackageManager.getPackageUidAsUser(anyString(), anyInt())) .thenReturn(fakeUserId); - ShadowEntityHeaderController.setUseMock(mHeaderController); - when(mHeaderController.setRecyclerView(any(), any())).thenReturn(mHeaderController); - when(mHeaderController.setUid(fakeUserId)).thenReturn(mHeaderController); when(mHeaderController.setHasAppInfoLink(anyBoolean())).thenReturn(mHeaderController); when(mFragment.getPreferenceManager()) .thenReturn(mock(PreferenceManager.class, RETURNS_DEEP_STUBS)); doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen(); - mFragment.onViewCreated(new View(RuntimeEnvironment.application), new Bundle()); + mFragment.addEntityHeader(); verify(mHeaderController).setHasAppInfoLink(true); verify(mHeaderController).setUid(fakeUserId); @@ -268,7 +264,7 @@ public class AppDataUsageTest { mFragment.bindData(0 /* position */); - verify(cycle).setVisible(false); + verify(cycle).setHasCycles(false); } @Test @@ -293,7 +289,7 @@ public class AppDataUsageTest { mFragment.bindData(0 /* position */); - verify(cycle).setVisible(true); + verify(cycle).setHasCycles(true); verify(totalPref).setSummary( DataUsageUtils.formatDataUsage(context, backgroundBytes + foregroundBytes)); verify(backgroundPref).setSummary(DataUsageUtils.formatDataUsage(context, backgroundBytes)); diff --git a/tests/robotests/src/com/android/settings/localepicker/AppLocalePickerActivityTest.java b/tests/robotests/src/com/android/settings/localepicker/AppLocalePickerActivityTest.java new file mode 100644 index 00000000000..4d5e0b98172 --- /dev/null +++ b/tests/robotests/src/com/android/settings/localepicker/AppLocalePickerActivityTest.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2022 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.localepicker; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.Activity; +import android.app.ApplicationPackageManager; +import android.content.Context; +import android.content.Intent; +import android.content.pm.InstallSourceInfo; +import android.net.Uri; +import android.os.Process; +import android.os.UserHandle; +import android.telephony.TelephonyManager; + +import androidx.fragment.app.Fragment; + +import com.android.settings.R; +import com.android.settings.applications.AppInfoBase; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.Shadows; +import org.robolectric.android.controller.ActivityController; +import org.robolectric.annotation.Config; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.shadows.ShadowTelephonyManager; + +@RunWith(RobolectricTestRunner.class) +@Config( + shadows = { + AppLocalePickerActivityTest.ShadowApplicationPackageManager.class, + }) +public class AppLocalePickerActivityTest { + private static final String TEST_PACKAGE_NAME = "com.android.settings"; + private static final Uri TEST_PACKAGE_URI = Uri.parse("package:" + TEST_PACKAGE_NAME); + + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + + @Test + public void launchAppLocalePickerActivity_hasPackageName_success() { + ActivityController controller = + initActivityController(true); + + controller.create(); + + assertThat(controller.get().isFinishing()).isFalse(); + } + + @Test + public void launchAppLocalePickerActivity_intentWithoutPackageName_failed() { + ActivityController controller = + initActivityController(false); + + controller.create(); + + assertThat(controller.get().isFinishing()).isTrue(); + } + + private ActivityController initActivityController( + boolean hasPackageName) { + Intent data = new Intent(); + if (hasPackageName) { + data.setData(TEST_PACKAGE_URI); + } + data.putExtra(AppInfoBase.ARG_PACKAGE_UID, UserHandle.getUserId(Process.myUid())); + ActivityController activityController = + Robolectric.buildActivity(TestAppLocalePickerActivity.class, data); + Activity activity = activityController.get(); + + ShadowTelephonyManager shadowTelephonyManager = Shadows.shadowOf( + activity.getSystemService(TelephonyManager.class)); + shadowTelephonyManager.setSimCountryIso("US"); + shadowTelephonyManager.setNetworkCountryIso("US"); + + return activityController; + } + + private static class TestAppLocalePickerActivity extends AppLocalePickerActivity { + @Override + public Context createContextAsUser(UserHandle user, int flags) { + // return the current context as a work profile + return this; + } + } + + @Implements(ApplicationPackageManager.class) + public static class ShadowApplicationPackageManager extends + org.robolectric.shadows.ShadowApplicationPackageManager { + + @Implementation + protected Object getInstallSourceInfo(String packageName) { + return new InstallSourceInfo("", null, null, ""); + } + } +} diff --git a/tests/robotests/src/com/android/settings/wifi/p2p/WifiP2PPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/wifi/p2p/WifiP2PPreferenceControllerTest.java index a9d5611918d..e292f24e5ce 100644 --- a/tests/robotests/src/com/android/settings/wifi/p2p/WifiP2PPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/wifi/p2p/WifiP2PPreferenceControllerTest.java @@ -22,12 +22,10 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.app.Service; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -40,6 +38,7 @@ import androidx.lifecycle.LifecycleOwner; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; +import com.android.settings.R; import com.android.settingslib.core.lifecycle.Lifecycle; import org.junit.Before; @@ -142,6 +141,7 @@ public class WifiP2PPreferenceControllerTest { mController.displayPreference(mScreen); verify(mWifiDirectPreference).setEnabled(false); + verify(mWifiDirectPreference).setSummary(R.string.not_allowed_by_ent); } @Test diff --git a/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherPreferenceControllerTest.java index 7ce2bf3b32c..e8ee7c3b90e 100644 --- a/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherPreferenceControllerTest.java @@ -19,30 +19,29 @@ package com.android.settings.wifi.tether; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import android.content.Context; import android.net.TetheringManager; import android.net.wifi.SoftApConfiguration; import android.net.wifi.WifiManager; -import android.os.Bundle; -import android.os.UserManager; -import androidx.lifecycle.LifecycleOwner; import androidx.preference.PreferenceScreen; +import androidx.test.core.app.ApplicationProvider; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settingslib.PrimarySwitchPreference; import com.android.settingslib.core.lifecycle.Lifecycle; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; @@ -56,53 +55,44 @@ public class WifiTetherPreferenceControllerTest { private static final String SSID = "Pixel"; - private Context mContext; + @Rule + public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + @Spy + Context mContext = ApplicationProvider.getApplicationContext(); + @Mock + private Lifecycle mLifecycle; @Mock private TetheringManager mTetheringManager; @Mock private WifiManager mWifiManager; @Mock - private UserManager mUserManager; - @Mock - private Bundle mBundle; - @Mock private PreferenceScreen mScreen; private SoftApConfiguration mSoftApConfiguration; private WifiTetherPreferenceController mController; - private Lifecycle mLifecycle; - private LifecycleOwner mLifecycleOwner; private PrimarySwitchPreference mPreference; @Before public void setUp() { - MockitoAnnotations.initMocks(this); - - mContext = spy(RuntimeEnvironment.application); - mLifecycleOwner = () -> mLifecycle; - mLifecycle = new Lifecycle(mLifecycleOwner); FakeFeatureFactory.setupForTest(); - mPreference = new PrimarySwitchPreference(RuntimeEnvironment.application); + mPreference = new PrimarySwitchPreference(mContext); when(mContext.getSystemService(Context.TETHERING_SERVICE)).thenReturn(mTetheringManager); when(mContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mWifiManager); - when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager); - when(mUserManager.getUserRestrictions()).thenReturn(mBundle); when(mScreen.findPreference(anyString())).thenReturn(mPreference); mSoftApConfiguration = new SoftApConfiguration.Builder().setSsid(SSID).build(); when(mWifiManager.getSoftApConfiguration()).thenReturn(mSoftApConfiguration); when(mTetheringManager.getTetherableWifiRegexs()).thenReturn(new String[]{"1", "2"}); - mController = new WifiTetherPreferenceController(mContext, mLifecycle, - false /* initSoftApManager */); - mController.mIsWifiTetheringAllow = true; + mController = new WifiTetherPreferenceController(mContext, mLifecycle, mWifiManager, + mTetheringManager, false /* initSoftApManager */, true /* isWifiTetheringAllow */); mController.displayPreference(mScreen); } @Test public void isAvailable_noTetherRegex_shouldReturnFalse() { when(mTetheringManager.getTetherableWifiRegexs()).thenReturn(new String[]{}); - mController = new WifiTetherPreferenceController(mContext, mLifecycle, - false /* initSoftApManager */); + mController = new WifiTetherPreferenceController(mContext, mLifecycle, mWifiManager, + mTetheringManager, false /* initSoftApManager */, true /* isWifiTetheringAllow */); assertThat(mController.isAvailable()).isFalse(); } @@ -114,16 +104,19 @@ public class WifiTetherPreferenceControllerTest { @Test public void displayPreference_wifiTetheringNotAllowed_shouldDisable() { - mController.mIsWifiTetheringAllow = false; + mController = new WifiTetherPreferenceController(mContext, mLifecycle, mWifiManager, + mTetheringManager, false /* initSoftApManager */, false /* isWifiTetheringAllow */); mController.displayPreference(mScreen); assertThat(mPreference.isEnabled()).isFalse(); + assertThat(mPreference.getSummary()).isEqualTo("Not allowed by your organization"); } @Test public void displayPreference_wifiTetheringAllowed_shouldEnable() { - mController.mIsWifiTetheringAllow = true; + mController = new WifiTetherPreferenceController(mContext, mLifecycle, mWifiManager, + mTetheringManager, false /* initSoftApManager */, true /* isWifiTetheringAllow */); mController.displayPreference(mScreen);