Change AppPrefLoader from AsyncTask to AsyncLoader.
Showing the wifi data usage results in reaching the max number of
threads in the pool executor. Switching the loader implementation
from Async task to AsynLoader and use a single loader to get the
whole list of preferences instead of using a new task to create each
list element.
Change-Id: I0da19ca09031a7fa178970c192e12a99ab489145
Fix: 33820327
Test: make RunSettingsRoboTests
diff --git a/src/com/android/settings/datausage/AppDataUsage.java b/src/com/android/settings/datausage/AppDataUsage.java
index a448b5c..69343c3 100644
--- a/src/com/android/settings/datausage/AppDataUsage.java
+++ b/src/com/android/settings/datausage/AppDataUsage.java
@@ -30,7 +30,6 @@
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
import android.net.TrafficStats;
-import android.os.AsyncTask;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -55,11 +54,6 @@
import com.android.settingslib.net.ChartDataLoader;
import com.android.settingslib.net.UidDetailProvider;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-
public class AppDataUsage extends DataUsageBase implements Preference.OnPreferenceChangeListener,
DataSaverBackend.Listener {
@@ -78,6 +72,7 @@
private static final String KEY_UNRESTRICTED_DATA = "unrestricted_data_saver";
private static final int LOADER_CHART_DATA = 2;
+ private static final int LOADER_APP_PREF = 3;
private final ArraySet<String> mPackages = new ArraySet<>();
private Preference mTotalUsage;
@@ -104,12 +99,6 @@
private SwitchPreference mUnrestrictedData;
private DataSaverBackend mDataSaverBackend;
- // Parameters to construct an efficient ThreadPoolExecutor
- private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
- private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
- private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
- private static final int KEEP_ALIVE_SECONDS = 30;
-
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
@@ -196,14 +185,7 @@
if (mPackages.size() > 1) {
mAppList = (PreferenceCategory) findPreference(KEY_APP_LIST);
- final int packageSize = mPackages.size();
- final BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(packageSize);
- final ThreadPoolExecutor executor = new ThreadPoolExecutor(CORE_POOL_SIZE,
- MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS, workQueue);
- for (int i = 1; i < mPackages.size(); i++) {
- final AppPrefLoader loader = new AppPrefLoader();
- loader.executeOnExecutor(executor, mPackages.valueAt(i));
- }
+ getLoaderManager().initLoader(LOADER_APP_PREF, Bundle.EMPTY, mAppPrefCallbacks);
} else {
removePreference(KEY_APP_LIST);
}
@@ -408,30 +390,27 @@
}
};
- private class AppPrefLoader extends AsyncTask<String, Void, Preference> {
- @Override
- protected Preference doInBackground(String... params) {
- PackageManager pm = getPackageManager();
- String pkg = params[0];
- try {
- ApplicationInfo info = pm.getApplicationInfo(pkg, 0);
- Preference preference = new Preference(getPrefContext());
- preference.setIcon(info.loadIcon(pm));
- preference.setTitle(info.loadLabel(pm));
- preference.setSelectable(false);
- return preference;
- } catch (PackageManager.NameNotFoundException e) {
+ private final LoaderManager.LoaderCallbacks<ArraySet<Preference>> mAppPrefCallbacks =
+ new LoaderManager.LoaderCallbacks<ArraySet<Preference>>() {
+ @Override
+ public Loader<ArraySet<Preference>> onCreateLoader(int i, Bundle bundle) {
+ return new AppPrefLoader(getPrefContext(), mPackages, getPackageManager());
}
- return null;
- }
- @Override
- protected void onPostExecute(Preference pref) {
- if (pref != null && mAppList != null) {
- mAppList.addPreference(pref);
+ @Override
+ public void onLoadFinished(Loader<ArraySet<Preference>> loader,
+ ArraySet<Preference> preferences) {
+ if (preferences != null && mAppList != null) {
+ for (Preference preference : preferences) {
+ mAppList.addPreference(preference);
+ }
+ }
}
- }
- }
+
+ @Override
+ public void onLoaderReset(Loader<ArraySet<Preference>> loader) {
+ }
+ };
@Override
public void onDataSaverChanged(boolean isDataSaving) {
diff --git a/src/com/android/settings/datausage/AppPrefLoader.java b/src/com/android/settings/datausage/AppPrefLoader.java
new file mode 100644
index 0000000..30e30eb
--- /dev/null
+++ b/src/com/android/settings/datausage/AppPrefLoader.java
@@ -0,0 +1,58 @@
+/*
+ * 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.datausage;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.support.v7.preference.Preference;
+import android.util.ArraySet;
+import com.android.settings.utils.AsyncLoader;
+
+public class AppPrefLoader extends AsyncLoader<ArraySet<Preference>> {
+ private ArraySet<String> mPackages;
+ private PackageManager mPackageManager;
+ private Context mPrefContext;
+
+ public AppPrefLoader(Context prefContext, ArraySet<String> pkgs, PackageManager pm) {
+ super(prefContext);
+ mPackages = pkgs;
+ mPackageManager = pm;
+ mPrefContext = prefContext;
+ }
+
+ @Override
+ public ArraySet<Preference> loadInBackground() {
+ ArraySet<Preference> results = new ArraySet<>();
+ for (int i = 1, size = mPackages.size(); i < size; i++) {
+ try {
+ ApplicationInfo info = mPackageManager.getApplicationInfo(mPackages.valueAt(i), 0);
+ Preference preference = new Preference(mPrefContext);
+ preference.setIcon(info.loadIcon(mPackageManager));
+ preference.setTitle(info.loadLabel(mPackageManager));
+ preference.setSelectable(false);
+ results.add(preference);
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+ }
+ return results;
+ }
+
+ @Override
+ protected void onDiscardResult(ArraySet<Preference> result) {
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/datausage/AppPrefLoaderTest.java b/tests/robotests/src/com/android/settings/datausage/AppPrefLoaderTest.java
new file mode 100644
index 0000000..fd1c96b
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/datausage/AppPrefLoaderTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.datausage;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.graphics.drawable.Drawable;
+import android.support.v7.preference.Preference;
+
+import android.util.ArraySet;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class AppPrefLoaderTest {
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private Context mContext;
+ @Mock
+ private PackageManager mPackageManager;
+
+ private AppPrefLoader mLoader;
+
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ final ArraySet<String> pkgs = new ArraySet<>();
+ pkgs.add("pkg0");
+ pkgs.add("pkg1");
+ mLoader =
+ new AppPrefLoader(RuntimeEnvironment.application, pkgs, mPackageManager);
+ }
+
+ @Test
+ public void loadInBackground_packageNotFound_shouldReturnEmptySet()
+ throws NameNotFoundException {
+ when(mPackageManager.getApplicationInfo(anyString(), anyInt()))
+ .thenThrow(new NameNotFoundException());
+
+ ArraySet<Preference> preferences = mLoader.loadInBackground();
+ assertThat(preferences).isEmpty();
+ }
+
+ @Test
+ public void loadInBackground_shouldReturnPreference() throws NameNotFoundException {
+ ApplicationInfo info = mock(ApplicationInfo.class);
+ when(mPackageManager.getApplicationInfo(anyString(), anyInt())).thenReturn(info);
+ final Drawable drawable = mock(Drawable.class);
+ final String label = "Label1";
+ when(info.loadIcon(mPackageManager)).thenReturn(drawable);
+ when(info.loadLabel(mPackageManager)).thenReturn(label);
+
+ Preference preference = mLoader.loadInBackground().valueAt(0);
+ assertThat(preference.getTitle()).isEqualTo(label);
+ assertThat(preference.getIcon()).isEqualTo(drawable);
+ assertThat(preference.isSelectable()).isFalse();
+ }
+
+}