2 * Copyright (C) 2018 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.settings.homepage.contextualcards;
19 import static com.android.settings.slices.CustomSliceRegistry.BLUETOOTH_DEVICES_SLICE_URI;
20 import static com.android.settings.slices.CustomSliceRegistry.CONTEXTUAL_NOTIFICATION_CHANNEL_SLICE_URI;
21 import static com.android.settings.slices.CustomSliceRegistry.CONTEXTUAL_WIFI_SLICE_URI;
23 import android.app.settings.SettingsEnums;
24 import android.content.Context;
25 import android.database.ContentObserver;
26 import android.database.Cursor;
27 import android.net.Uri;
28 import android.os.Handler;
29 import android.os.Looper;
30 import android.util.Log;
32 import androidx.annotation.NonNull;
33 import androidx.annotation.VisibleForTesting;
35 import com.android.settings.R;
36 import com.android.settings.homepage.contextualcards.logging.ContextualCardLogUtils;
37 import com.android.settings.overlay.FeatureFactory;
38 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
39 import com.android.settingslib.utils.AsyncLoaderCompat;
41 import java.util.ArrayList;
42 import java.util.List;
43 import java.util.concurrent.ExecutionException;
44 import java.util.concurrent.ExecutorService;
45 import java.util.concurrent.Executors;
46 import java.util.concurrent.Future;
47 import java.util.concurrent.TimeUnit;
48 import java.util.concurrent.TimeoutException;
50 public class ContextualCardLoader extends AsyncLoaderCompat<List<ContextualCard>> {
53 static final int DEFAULT_CARD_COUNT = 2;
54 static final int CARD_CONTENT_LOADER_ID = 1;
56 private static final String TAG = "ContextualCardLoader";
57 private static final long ELIGIBILITY_CHECKER_TIMEOUT_MS = 250;
59 private final ExecutorService mExecutorService;
60 private final ContentObserver mObserver = new ContentObserver(
61 new Handler(Looper.getMainLooper())) {
63 public void onChange(boolean selfChange, Uri uri) {
74 private final Context mContext;
76 ContextualCardLoader(Context context) {
78 mContext = context.getApplicationContext();
79 mExecutorService = Executors.newCachedThreadPool();
83 protected void onStartLoading() {
84 super.onStartLoading();
86 mContext.getContentResolver().registerContentObserver(CardContentProvider.REFRESH_CARD_URI,
87 false /*notifyForDescendants*/, mObserver);
88 mContext.getContentResolver().registerContentObserver(CardContentProvider.DELETE_CARD_URI,
89 false /*notifyForDescendants*/, mObserver);
93 protected void onStopLoading() {
94 super.onStopLoading();
95 mContext.getContentResolver().unregisterContentObserver(mObserver);
99 protected void onDiscardResult(List<ContextualCard> result) {
105 public List<ContextualCard> loadInBackground() {
106 final List<ContextualCard> result = new ArrayList<>();
107 if (mContext.getResources().getBoolean(R.bool.config_use_legacy_suggestion)) {
108 Log.d(TAG, "Skipping - in legacy suggestion mode");
111 try (Cursor cursor = getContextualCardsFromProvider()) {
112 if (cursor.getCount() > 0) {
113 for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
114 final ContextualCard card = new ContextualCard(cursor);
115 if (card.isCustomCard()) {
116 //TODO(b/114688391): Load and generate custom card,then add into list
117 } else if (isLargeCard(card)) {
118 result.add(card.mutate().setIsLargeCard(true).build());
125 return getDisplayableCards(result);
128 // Get final displayed cards and log what cards will be displayed/hidden
130 List<ContextualCard> getDisplayableCards(List<ContextualCard> candidates) {
131 final List<ContextualCard> eligibleCards = filterEligibleCards(candidates);
132 final List<ContextualCard> visibleCards = new ArrayList<>();
133 final List<ContextualCard> hiddenCards = new ArrayList<>();
135 final int size = eligibleCards.size();
136 for (int i = 0; i < size; i++) {
137 if (i < DEFAULT_CARD_COUNT) {
138 visibleCards.add(eligibleCards.get(i));
140 hiddenCards.add(eligibleCards.get(i));
144 if (!CardContentProvider.DELETE_CARD_URI.equals(mNotifyUri)) {
145 final MetricsFeatureProvider metricsFeatureProvider =
146 FeatureFactory.getFactory(mContext).getMetricsFeatureProvider();
148 metricsFeatureProvider.action(mContext,
149 SettingsEnums.ACTION_CONTEXTUAL_CARD_SHOW,
150 ContextualCardLogUtils.buildCardListLog(visibleCards));
152 metricsFeatureProvider.action(mContext,
153 SettingsEnums.ACTION_CONTEXTUAL_CARD_NOT_SHOW,
154 ContextualCardLogUtils.buildCardListLog(hiddenCards));
160 Cursor getContextualCardsFromProvider() {
161 return CardDatabaseHelper.getInstance(mContext).getContextualCards();
165 List<ContextualCard> filterEligibleCards(List<ContextualCard> candidates) {
166 final List<ContextualCard> cards = new ArrayList<>();
167 final List<Future<ContextualCard>> eligibleCards = new ArrayList<>();
169 for (ContextualCard card : candidates) {
170 final EligibleCardChecker future = new EligibleCardChecker(mContext, card);
171 eligibleCards.add(mExecutorService.submit(future));
173 // Collect future and eligible cards
174 for (Future<ContextualCard> cardFuture : eligibleCards) {
176 final ContextualCard card = cardFuture.get(ELIGIBILITY_CHECKER_TIMEOUT_MS,
177 TimeUnit.MILLISECONDS);
181 } catch (ExecutionException | InterruptedException | TimeoutException e) {
182 Log.w(TAG, "Failed to get eligible state for card, likely timeout. Skipping", e);
188 private boolean isLargeCard(ContextualCard card) {
189 return card.getSliceUri().equals(CONTEXTUAL_WIFI_SLICE_URI)
190 || card.getSliceUri().equals(BLUETOOTH_DEVICES_SLICE_URI)
191 || card.getSliceUri().equals(CONTEXTUAL_NOTIFICATION_CHANNEL_SLICE_URI);
194 public interface CardContentLoaderListener {
195 void onFinishCardLoading(List<ContextualCard> contextualCards);