diff --git a/res/xml/app_and_notification.xml b/res/xml/app_and_notification.xml index 5a55ba5dab0..c15df75afdc 100644 --- a/res/xml/app_and_notification.xml +++ b/res/xml/app_and_notification.xml @@ -34,18 +34,18 @@ android:title="@string/applications_settings" android:key="all_app_info" android:fragment="com.android.settings.applications.manageapplications.ManageApplications" - android:order="20" /> + android:order="20"/> + android:order="-190"/> + android:order="10"/> - + + android:targetClass="com.android.cellbroadcastreceiver.CellBroadcastSettings"/> + settings:controller="com.android.settings.applications.SpecialAppAccessPreferenceController"/> diff --git a/src/com/android/settings/applications/AppAndNotificationDashboardFragment.java b/src/com/android/settings/applications/AppAndNotificationDashboardFragment.java index 3eaed2dbdf2..9ac3ecc4c9e 100644 --- a/src/com/android/settings/applications/AppAndNotificationDashboardFragment.java +++ b/src/com/android/settings/applications/AppAndNotificationDashboardFragment.java @@ -21,6 +21,8 @@ import android.app.Application; import android.content.Context; import android.provider.SearchIndexableResource; +import androidx.fragment.app.Fragment; + import com.android.internal.logging.nano.MetricsProto; import com.android.settings.R; import com.android.settings.dashboard.DashboardFragment; @@ -33,8 +35,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import androidx.fragment.app.Fragment; - @SearchIndexable public class AppAndNotificationDashboardFragment extends DashboardFragment { @@ -60,6 +60,12 @@ public class AppAndNotificationDashboardFragment extends DashboardFragment { return R.xml.app_and_notification; } + @Override + public void onAttach(Context context) { + super.onAttach(context); + use(SpecialAppAccessPreferenceController.class).setSession(getSettingsLifecycle()); + } + @Override protected List createPreferenceControllers(Context context) { final Activity activity = getActivity(); @@ -77,7 +83,6 @@ public class AppAndNotificationDashboardFragment extends DashboardFragment { final List controllers = new ArrayList<>(); controllers.add(new EmergencyBroadcastPreferenceController(context, "app_and_notif_cell_broadcast_settings")); - controllers.add(new SpecialAppAccessPreferenceController(context)); controllers.add(new RecentAppsPreferenceController(context, app, host)); return controllers; } diff --git a/src/com/android/settings/applications/AppStateBaseBridge.java b/src/com/android/settings/applications/AppStateBaseBridge.java index 2329b4469e6..1a39483af9a 100644 --- a/src/com/android/settings/applications/AppStateBaseBridge.java +++ b/src/com/android/settings/applications/AppStateBaseBridge.java @@ -45,7 +45,7 @@ public abstract class AppStateBaseBridge implements ApplicationsState.Callbacks // the same time as us as well. mHandler = new BackgroundHandler(mAppState != null ? mAppState.getBackgroundLooper() : Looper.getMainLooper()); - mMainHandler = new MainHandler(); + mMainHandler = new MainHandler(Looper.getMainLooper()); } public void resume() { @@ -106,11 +106,16 @@ public abstract class AppStateBaseBridge implements ApplicationsState.Callbacks } protected abstract void loadAllExtraInfo(); + protected abstract void updateExtraInfo(AppEntry app, String pkg, int uid); private class MainHandler extends Handler { private static final int MSG_INFO_UPDATED = 1; + public MainHandler(Looper looper) { + super(looper); + } + @Override public void handleMessage(Message msg) { switch (msg.what) { diff --git a/src/com/android/settings/applications/SpecialAppAccessPreferenceController.java b/src/com/android/settings/applications/SpecialAppAccessPreferenceController.java index 16a6cab5d98..a395f9833f0 100644 --- a/src/com/android/settings/applications/SpecialAppAccessPreferenceController.java +++ b/src/com/android/settings/applications/SpecialAppAccessPreferenceController.java @@ -13,43 +13,140 @@ */ package com.android.settings.applications; +import android.app.Application; import android.content.Context; -import com.android.settings.R; -import com.android.settings.core.PreferenceControllerMixin; -import com.android.settings.datausage.DataSaverBackend; -import com.android.settingslib.core.AbstractPreferenceController; - +import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; -public class SpecialAppAccessPreferenceController extends AbstractPreferenceController - implements PreferenceControllerMixin { +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; +import com.android.settings.datausage.AppStateDataUsageBridge; +import com.android.settings.datausage.DataSaverBackend; +import com.android.settingslib.applications.ApplicationsState; +import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnDestroy; +import com.android.settingslib.core.lifecycle.events.OnStart; +import com.android.settingslib.core.lifecycle.events.OnStop; - private static final String KEY_SPECIAL_ACCESS = "special_access"; +import java.util.ArrayList; - private DataSaverBackend mDataSaverBackend; +public class SpecialAppAccessPreferenceController extends BasePreferenceController implements + AppStateBaseBridge.Callback, ApplicationsState.Callbacks, LifecycleObserver, OnStart, + OnStop, OnDestroy { - public SpecialAppAccessPreferenceController(Context context) { - super(context); + @VisibleForTesting + ApplicationsState.Session mSession; + + private final ApplicationsState mApplicationsState; + private final AppStateDataUsageBridge mDataUsageBridge; + private final DataSaverBackend mDataSaverBackend; + + private Preference mPreference; + private boolean mExtraLoaded; + + + public SpecialAppAccessPreferenceController(Context context, String key) { + super(context, key); + mApplicationsState = ApplicationsState.getInstance( + (Application) context.getApplicationContext()); + mDataSaverBackend = new DataSaverBackend(context); + mDataUsageBridge = new AppStateDataUsageBridge(mApplicationsState, this, mDataSaverBackend); + } + + public void setSession(Lifecycle lifecycle) { + mSession = mApplicationsState.newSession(this, lifecycle); } @Override - public boolean isAvailable() { - return true; + public int getAvailabilityStatus() { + return AVAILABLE_UNSEARCHABLE; } @Override - public String getPreferenceKey() { - return KEY_SPECIAL_ACCESS; + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreference = screen.findPreference(getPreferenceKey()); + } + + @Override + public void onStart() { + mDataUsageBridge.resume(); + } + + @Override + public void onStop() { + mDataUsageBridge.pause(); + } + + @Override + public void onDestroy() { + mDataUsageBridge.release(); } @Override public void updateState(Preference preference) { - if (mDataSaverBackend == null) { - mDataSaverBackend = new DataSaverBackend(mContext); - } - final int count = mDataSaverBackend.getWhitelistedCount(); - preference.setSummary(mContext.getResources().getQuantityString( - R.plurals.special_access_summary, count, count)); + updateSummary(); } + + @Override + public void onExtraInfoUpdated() { + mExtraLoaded = true; + updateSummary(); + } + + private void updateSummary() { + if (!mExtraLoaded || mPreference == null) { + return; + } + + final ArrayList allApps = mSession.getAllApps(); + int count = 0; + for (ApplicationsState.AppEntry entry : allApps) { + if (!ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER.filterApp(entry)) { + continue; + } + if (entry.extraInfo != null && ((AppStateDataUsageBridge.DataUsageState) + entry.extraInfo).isDataSaverWhitelisted) { + count++; + } + } + mPreference.setSummary(mContext.getResources().getQuantityString( + R.plurals.special_access_summary, count, count)); + } + + @Override + public void onRunningStateChanged(boolean running) { + } + + @Override + public void onPackageListChanged() { + } + + @Override + public void onRebuildComplete(ArrayList apps) { + } + + @Override + public void onPackageIconChanged() { + } + + @Override + public void onPackageSizeChanged(String packageName) { + } + + @Override + public void onAllSizesComputed() { + } + + @Override + public void onLauncherInfoChanged() { + } + + @Override + public void onLoadEntriesCompleted() { + } + } diff --git a/src/com/android/settings/datausage/DataSaverBackend.java b/src/com/android/settings/datausage/DataSaverBackend.java index b59da9d7ff8..ed2e6c9195c 100644 --- a/src/com/android/settings/datausage/DataSaverBackend.java +++ b/src/com/android/settings/datausage/DataSaverBackend.java @@ -95,19 +95,10 @@ public class DataSaverBackend { return mUidPolicies.get(uid, POLICY_NONE) == POLICY_ALLOW_METERED_BACKGROUND; } - public int getWhitelistedCount() { - int count = 0; - loadWhitelist(); - for (int i = 0; i < mUidPolicies.size(); i++) { - if (mUidPolicies.valueAt(i) == POLICY_ALLOW_METERED_BACKGROUND) { - count++; - } - } - return count; - } - private void loadWhitelist() { - if (mWhitelistInitialized) return; + if (mWhitelistInitialized) { + return; + } for (int uid : mPolicyManager.getUidsWithPolicy(POLICY_ALLOW_METERED_BACKGROUND)) { mUidPolicies.put(uid, POLICY_ALLOW_METERED_BACKGROUND); @@ -135,7 +126,9 @@ public class DataSaverBackend { } private void loadBlacklist() { - if (mBlacklistInitialized) return; + if (mBlacklistInitialized) { + return; + } for (int uid : mPolicyManager.getUidsWithPolicy(POLICY_REJECT_METERED_BACKGROUND)) { mUidPolicies.put(uid, POLICY_REJECT_METERED_BACKGROUND); } @@ -212,7 +205,9 @@ public class DataSaverBackend { public interface Listener { void onDataSaverChanged(boolean isDataSaving); + void onWhitelistStatusChanged(int uid, boolean isWhitelisted); + void onBlacklistStatusChanged(int uid, boolean isBlacklisted); } } diff --git a/tests/robotests/src/com/android/settings/applications/AppAndNotificationDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/applications/AppAndNotificationDashboardFragmentTest.java deleted file mode 100644 index c332c064da3..00000000000 --- a/tests/robotests/src/com/android/settings/applications/AppAndNotificationDashboardFragmentTest.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.settings.applications; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; - -import android.content.Context; -import android.os.UserManager; - -import com.android.settings.notification.EmergencyBroadcastPreferenceController; -import com.android.settings.testutils.SettingsRobolectricTestRunner; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.Config; -import org.robolectric.annotation.Implementation; -import org.robolectric.annotation.Implements; - -import java.util.List; - -@RunWith(SettingsRobolectricTestRunner.class) -public class AppAndNotificationDashboardFragmentTest { - - @Test - @Config(shadows = {ShadowEmergencyBroadcastPreferenceController.class}) - public void getNonIndexableKeys_shouldIncludeSpecialAppAccess() { - final Context context = spy(RuntimeEnvironment.application); - UserManager manager = mock(UserManager.class); - when(manager.isAdminUser()).thenReturn(true); - when(context.getSystemService(Context.USER_SERVICE)).thenReturn(manager); - final List niks = AppAndNotificationDashboardFragment.SEARCH_INDEX_DATA_PROVIDER - .getNonIndexableKeys(context); - - assertThat(niks).contains( - new SpecialAppAccessPreferenceController(context).getPreferenceKey()); - } - - @Implements(EmergencyBroadcastPreferenceController.class) - public static class ShadowEmergencyBroadcastPreferenceController { - - @Implementation - public boolean isAvailable() { - return true; - } - } -} diff --git a/tests/robotests/src/com/android/settings/applications/SpecialAppAccessPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/SpecialAppAccessPreferenceControllerTest.java index 224a8f9561b..b6429151b67 100644 --- a/tests/robotests/src/com/android/settings/applications/SpecialAppAccessPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/applications/SpecialAppAccessPreferenceControllerTest.java @@ -16,15 +16,25 @@ package com.android.settings.applications; +import static com.android.settings.core.BasePreferenceController.AVAILABLE_UNSEARCHABLE; + import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.verify; + +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.content.Context; +import android.content.pm.ApplicationInfo; + +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; import com.android.settings.R; -import com.android.settings.datausage.DataSaverBackend; +import com.android.settings.datausage.AppStateDataUsageBridge; import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.shadow.ShadowApplicationsState; +import com.android.settings.testutils.shadow.ShadowUserManager; +import com.android.settingslib.applications.ApplicationsState; import org.junit.Before; import org.junit.Test; @@ -32,48 +42,56 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; -import org.robolectric.util.ReflectionHelpers; +import org.robolectric.annotation.Config; -import androidx.preference.Preference; +import java.util.ArrayList; @RunWith(SettingsRobolectricTestRunner.class) +@Config(shadows = {ShadowUserManager.class, ShadowApplicationsState.class}) public class SpecialAppAccessPreferenceControllerTest { private Context mContext; @Mock - private DataSaverBackend mBackend; + private ApplicationsState.Session mSession; @Mock - private Preference mPreference; + private PreferenceScreen mScreen; private SpecialAppAccessPreferenceController mController; + private Preference mPreference; @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; - mController = new SpecialAppAccessPreferenceController(mContext); - ReflectionHelpers.setField(mController, "mDataSaverBackend", mBackend); + ShadowUserManager.getShadow().setProfileIdsWithDisabled(new int[]{0}); + mController = new SpecialAppAccessPreferenceController(mContext, "test_key"); + mPreference = new Preference(mContext); + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference); + + mController.mSession = mSession; } @Test - public void isAvailable_shouldAlwaysReturnTrue() { - assertThat(mController.isAvailable()).isTrue(); + public void getAvailabilityState_unsearchable() { + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE_UNSEARCHABLE); } @Test public void updateState_shouldSetSummary() { - when(mBackend.getWhitelistedCount()).thenReturn(0); + final ArrayList apps = new ArrayList<>(); + final ApplicationsState.AppEntry entry = mock(ApplicationsState.AppEntry.class); + entry.hasLauncherEntry = true; + entry.info = new ApplicationInfo(); + entry.extraInfo = new AppStateDataUsageBridge.DataUsageState( + true /* whitelisted */, false /* blacklisted */); + apps.add(entry); + when(mSession.getAllApps()).thenReturn(apps); - mController.updateState(mPreference); + mController.displayPreference(mScreen); + mController.onExtraInfoUpdated(); - verify(mPreference).setSummary(mContext.getResources() - .getQuantityString(R.plurals.special_access_summary, 0, 0)); - - when(mBackend.getWhitelistedCount()).thenReturn(1); - - mController.updateState(mPreference); - - verify(mPreference).setSummary(mContext.getResources() - .getQuantityString(R.plurals.special_access_summary, 1, 1)); + assertThat(mPreference.getSummary()) + .isEqualTo(mContext.getResources().getQuantityString( + R.plurals.special_access_summary, 1, 1)); } } diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowApplicationsState.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowApplicationsState.java new file mode 100644 index 00000000000..3ee4fcb1647 --- /dev/null +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowApplicationsState.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.testutils.shadow; + +import android.os.Looper; + +import com.android.settingslib.applications.ApplicationsState; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +@Implements(ApplicationsState.class) +public class ShadowApplicationsState { + @Implementation + public Looper getBackgroundLooper() { + return Looper.getMainLooper(); + } +}