From: Jason Chiu Date: Mon, 8 Oct 2018 04:06:26 +0000 (+0800) Subject: Slice background worker with Wi-Fi Slice X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=f17233ba7d4ae9d80995e526a530ae10771a98f2;p=android-x86%2Fpackages-apps-Settings.git Slice background worker with Wi-Fi Slice Test: manual Change-Id: Ic4fdc5713f511ff80f03728c99c68fda3d0cab02 --- diff --git a/src/com/android/settings/slices/CustomSliceable.java b/src/com/android/settings/slices/CustomSliceable.java index 48b5d161db..e09cc7386f 100644 --- a/src/com/android/settings/slices/CustomSliceable.java +++ b/src/com/android/settings/slices/CustomSliceable.java @@ -90,6 +90,16 @@ public interface CustomSliceable { } /** + * Settings Slices which can represent component lists that are updatable by the + * {@link SliceBackgroundWorker} returned here. + * + * @return a {@link SliceBackgroundWorker} for fetching the list of results in the background. + */ + default SliceBackgroundWorker getBackgroundWorker() { + return null; + } + + /** * Standardize the intents returned to indicate actions by the Slice. *

* The {@link PendingIntent} is linked to {@link SliceBroadcastReceiver} where the Intent diff --git a/src/com/android/settings/slices/SettingsSliceProvider.java b/src/com/android/settings/slices/SettingsSliceProvider.java index 8b975b4c51..395b878779 100644 --- a/src/com/android/settings/slices/SettingsSliceProvider.java +++ b/src/com/android/settings/slices/SettingsSliceProvider.java @@ -28,6 +28,7 @@ import android.os.StrictMode; import android.provider.Settings; import android.provider.SettingsSlicesContract; import android.text.TextUtils; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.KeyValueListParser; import android.util.Log; @@ -131,6 +132,8 @@ public class SettingsSliceProvider extends SliceProvider { final Set mRegisteredUris = new ArraySet<>(); + final Map mWorkerMap = new ArrayMap<>(); + public SettingsSliceProvider() { super(READ_SEARCH_INDEXABLES); } @@ -165,6 +168,7 @@ public class SettingsSliceProvider extends SliceProvider { if (filter != null) { registerIntentToUri(filter, sliceUri); } + startBackgroundWorker(sliceable); return; } @@ -191,6 +195,7 @@ public class SettingsSliceProvider extends SliceProvider { SliceBroadcastRelay.unregisterReceivers(getContext(), sliceUri); mRegisteredUris.remove(sliceUri); } + stopBackgroundWorker(sliceUri); mSliceDataCache.remove(sliceUri); } @@ -348,6 +353,31 @@ public class SettingsSliceProvider extends SliceProvider { } } + private void startBackgroundWorker(CustomSliceable sliceable) { + final SliceBackgroundWorker worker = sliceable.getBackgroundWorker(); + if (worker == null) { + return; + } + + final Uri uri = sliceable.getUri(); + Log.d(TAG, "Starting background worker for: " + uri); + if (mWorkerMap.containsKey(uri)) { + return; + } + + mWorkerMap.put(uri, worker); + worker.onSlicePinned(); + } + + private void stopBackgroundWorker(Uri uri) { + final SliceBackgroundWorker worker = mWorkerMap.get(uri); + if (worker != null) { + Log.d(TAG, "Stopping background worker for: " + uri); + worker.onSliceUnpinned(); + mWorkerMap.remove(uri); + } + } + private List buildUrisFromKeys(List keys, String authority) { final List descendants = new ArrayList<>(); diff --git a/src/com/android/settings/slices/SliceBackgroundWorker.java b/src/com/android/settings/slices/SliceBackgroundWorker.java new file mode 100644 index 0000000000..7178eadbdc --- /dev/null +++ b/src/com/android/settings/slices/SliceBackgroundWorker.java @@ -0,0 +1,85 @@ +/* + * 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.slices; + +import android.content.ContentResolver; +import android.net.Uri; + +import java.util.ArrayList; +import java.util.List; + +/** + * The Slice background worker is used to make Settings Slices be able to work with data that is + * changing continuously, e.g. available Wi-Fi networks. + * + * The background worker will be started at {@link SettingsSliceProvider#onSlicePinned(Uri)}, and be + * stopped at {@link SettingsSliceProvider#onSliceUnpinned(Uri)}. + * + * {@link SliceBackgroundWorker} caches the results, uses the cache to compare if there is any data + * changed, and then notifies the Slice {@link Uri} to update. + */ +public abstract class SliceBackgroundWorker { + + private final ContentResolver mContentResolver; + private final Uri mUri; + + private List mCachedResults; + + protected SliceBackgroundWorker(ContentResolver cr, Uri uri) { + mContentResolver = cr; + mUri = uri; + } + + /** + * Called when the Slice is pinned. This is the place to register callbacks or initialize scan + * tasks. + */ + protected abstract void onSlicePinned(); + + /** + * Called when the Slice is unpinned. This is the place to unregister callbacks or perform any + * final cleanup. + */ + protected abstract void onSliceUnpinned(); + + /** + * @return a {@link List} of cached results + */ + public final List getResults() { + return mCachedResults == null ? null : new ArrayList<>(mCachedResults); + } + + /** + * Update the results when data changes + */ + protected final void updateResults(List results) { + boolean needNotify = false; + + if (results == null) { + if (mCachedResults != null) { + needNotify = true; + } + } else { + needNotify = !results.equals(mCachedResults); + } + + if (needNotify) { + mCachedResults = results; + mContentResolver.notifyChange(mUri, null); + } + } +} diff --git a/src/com/android/settings/wifi/WifiDialogActivity.java b/src/com/android/settings/wifi/WifiDialogActivity.java index 63785e6902..e3a03ad55c 100644 --- a/src/com/android/settings/wifi/WifiDialogActivity.java +++ b/src/com/android/settings/wifi/WifiDialogActivity.java @@ -37,12 +37,6 @@ public class WifiDialogActivity extends Activity implements WifiDialog.WifiDialo private static final String TAG = "WifiDialogActivity"; - private static final int RESULT_CONNECTED = RESULT_FIRST_USER; - private static final int RESULT_FORGET = RESULT_FIRST_USER + 1; - - private static final String KEY_ACCESS_POINT_STATE = "access_point_state"; - private static final String KEY_WIFI_CONFIGURATION = "wifi_configuration"; - /** * Boolean extra indicating whether this activity should connect to an access point on the * caller's behalf. If this is set to false, the caller should check @@ -51,6 +45,11 @@ public class WifiDialogActivity extends Activity implements WifiDialog.WifiDialo */ @VisibleForTesting static final String KEY_CONNECT_FOR_CALLER = "connect_for_caller"; + static final String KEY_ACCESS_POINT_STATE = "access_point_state"; + private static final String KEY_WIFI_CONFIGURATION = "wifi_configuration"; + + private static final int RESULT_CONNECTED = RESULT_FIRST_USER; + private static final int RESULT_FORGET = RESULT_FIRST_USER + 1; private WifiDialog mDialog; diff --git a/src/com/android/settings/wifi/WifiSlice.java b/src/com/android/settings/wifi/WifiSlice.java index d0b14e383c..536be6f386 100644 --- a/src/com/android/settings/wifi/WifiSlice.java +++ b/src/com/android/settings/wifi/WifiSlice.java @@ -29,6 +29,9 @@ import android.net.Uri; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.net.wifi.WifiSsid; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; import android.provider.SettingsSlicesContract; import android.text.TextUtils; @@ -42,8 +45,16 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; import com.android.settings.SubSettings; import com.android.settings.Utils; +import com.android.settings.core.SubSettingLauncher; import com.android.settings.slices.CustomSliceable; +import com.android.settings.slices.SliceBackgroundWorker; import com.android.settings.slices.SliceBuilderUtils; +import com.android.settings.wifi.details.WifiNetworkDetailsFragment; +import com.android.settingslib.wifi.AccessPoint; +import com.android.settingslib.wifi.WifiTracker; + +import java.util.ArrayList; +import java.util.List; /** * Utility class to build a Wifi Slice, and handle all associated actions. @@ -96,14 +107,73 @@ public class WifiSlice implements CustomSliceable { final SliceAction toggleSliceAction = new SliceAction(toggleAction, null /* actionTitle */, isWifiEnabled); - return new ListBuilder(mContext, WIFI_URI, ListBuilder.INFINITY) + final ListBuilder listBuilder = new ListBuilder(mContext, WIFI_URI, ListBuilder.INFINITY) .setAccentColor(color) .addRow(new RowBuilder() .setTitle(title) .setSubtitle(summary) .addEndItem(toggleSliceAction) - .setPrimaryAction(primarySliceAction)) - .build(); + .setPrimaryAction(primarySliceAction)); + + if (isWifiEnabled) { + final List result = getBackgroundWorker().getResults(); + if (result != null && !result.isEmpty()) { + for (AccessPoint ap : result) { + listBuilder.addRow(getAccessPointRow(ap)); + } + listBuilder.setSeeMoreAction(primaryAction); + } + } + return listBuilder.build(); + } + + private RowBuilder getAccessPointRow(AccessPoint accessPoint) { + final String title = accessPoint.getConfigName(); + final IconCompat levelIcon = IconCompat.createWithResource(mContext, + com.android.settingslib.Utils.getWifiIconResource(accessPoint.getLevel())); + final RowBuilder rowBuilder = new RowBuilder() + .setTitleItem(levelIcon, ListBuilder.ICON_IMAGE) + .setTitle(title) + .setSubtitle(accessPoint.getSettingsSummary()) + .setPrimaryAction(new SliceAction( + getAccessPointAction(accessPoint), levelIcon, title)); + + final IconCompat endIcon = getEndIcon(accessPoint); + if (endIcon != null) { + rowBuilder.addEndItem(endIcon, ListBuilder.ICON_IMAGE); + } + return rowBuilder; + } + + private IconCompat getEndIcon(AccessPoint accessPoint) { + if (accessPoint.isActive()) { + return IconCompat.createWithResource(mContext, R.drawable.ic_settings); + } else if (accessPoint.getSecurity() != AccessPoint.SECURITY_NONE) { + return IconCompat.createWithResource(mContext, R.drawable.ic_friction_lock_closed); + } else if (accessPoint.isMetered()) { + return IconCompat.createWithResource(mContext, R.drawable.ic_friction_money); + } + return null; + } + + private PendingIntent getAccessPointAction(AccessPoint accessPoint) { + final Bundle extras = new Bundle(); + accessPoint.saveWifiState(extras); + + Intent intent; + if (accessPoint.isActive()) { + intent = new SubSettingLauncher(mContext) + .setTitleRes(R.string.pref_title_network_details) + .setDestination(WifiNetworkDetailsFragment.class.getName()) + .setArguments(extras) + .setSourceMetricsCategory(MetricsEvent.WIFI) + .toIntent(); + } else { + intent = new Intent(mContext, WifiDialogActivity.class); + intent.putExtra(WifiDialogActivity.KEY_ACCESS_POINT_STATE, extras); + } + return PendingIntent.getActivity(mContext, accessPoint.hashCode() /* requestCode */, + intent, 0 /* flags */); } /** @@ -176,4 +246,75 @@ public class WifiSlice implements CustomSliceable { return PendingIntent.getActivity(mContext, 0 /* requestCode */, intent, 0 /* flags */); } + + @Override + public SliceBackgroundWorker getBackgroundWorker() { + return WifiScanWorker.getInstance(mContext, WIFI_URI); + } + + private static class WifiScanWorker extends SliceBackgroundWorker + implements WifiTracker.WifiListener { + + private static WifiScanWorker mWifiScanWorker; + + private final Context mContext; + + private WifiTracker mWifiTracker; + private WifiManager mWifiManager; + + private WifiScanWorker(Context context, Uri uri) { + super(context.getContentResolver(), uri); + mContext = context; + } + + public static WifiScanWorker getInstance(Context context, Uri uri) { + if (mWifiScanWorker == null) { + mWifiScanWorker = new WifiScanWorker(context, uri); + } + return mWifiScanWorker; + } + + @Override + protected void onSlicePinned() { + new Handler(Looper.getMainLooper()).post(() -> { + mWifiTracker = new WifiTracker(mContext, this, true, true); + mWifiManager = mWifiTracker.getManager(); + mWifiTracker.onStart(); + onAccessPointsChanged(); + }); + } + + @Override + protected void onSliceUnpinned() { + mWifiTracker.onStop(); + mWifiTracker.onDestroy(); + mWifiScanWorker = null; + } + + @Override + public void onWifiStateChanged(int state) { + } + + @Override + public void onConnectedChanged() { + } + + @Override + public void onAccessPointsChanged() { + // in case state has changed + if (!mWifiManager.isWifiEnabled()) { + updateResults(null); + return; + } + // AccessPoints are sorted by the WifiTracker + final List accessPoints = mWifiTracker.getAccessPoints(); + final List resultList = new ArrayList<>(); + for (AccessPoint ap : accessPoints) { + if (ap.isReachable()) { + resultList.add(ap); + } + } + updateResults(resultList); + } + } } diff --git a/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java b/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java index f7c6bba76e..19f3d329b1 100644 --- a/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java +++ b/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java @@ -118,7 +118,7 @@ public class SettingsSliceProviderTest { mProvider.mSliceWeakDataCache = new HashMap<>(); mProvider.mSliceDataCache = new HashMap<>(); mProvider.mSlicesDatabaseAccessor = new SlicesDatabaseAccessor(mContext); - mProvider.mCustomSliceManager = new CustomSliceManager(mContext); + mProvider.mCustomSliceManager = spy(new CustomSliceManager(mContext)); when(mProvider.getContext()).thenReturn(mContext); mDb = SlicesDatabaseHelper.getInstance(mContext).getWritableDatabase(); @@ -481,6 +481,44 @@ public class SettingsSliceProviderTest { mProvider.onSlicePinned(uri); } + private SliceBackgroundWorker initBackgroundWorker(Uri uri) { + final SliceBackgroundWorker worker = spy(new SliceBackgroundWorker( + mContext.getContentResolver(), uri) { + @Override + public void onSlicePinned() { + } + + @Override + public void onSliceUnpinned() { + } + }); + final WifiSlice wifiSlice = spy(new WifiSlice(mContext)); + when(wifiSlice.getBackgroundWorker()).thenReturn(worker); + when(mProvider.mCustomSliceManager.getSliceableFromUri(uri)).thenReturn(wifiSlice); + return worker; + } + + @Test + public void onSlicePinned_backgroundWorker_started() { + final Uri uri = WifiSlice.WIFI_URI; + final SliceBackgroundWorker worker = initBackgroundWorker(uri); + + mProvider.onSlicePinned(uri); + + verify(worker).onSlicePinned(); + } + + @Test + public void onSlicePinned_backgroundWorker_stopped() { + final Uri uri = WifiSlice.WIFI_URI; + final SliceBackgroundWorker worker = initBackgroundWorker(uri); + + mProvider.onSlicePinned(uri); + mProvider.onSliceUnpinned(uri); + + verify(worker).onSliceUnpinned(); + } + @Test public void grantWhitelistedPackagePermissions_noWhitelist_shouldNotGrant() { final List uris = new ArrayList<>();