OSDN Git Service

a50cf41a385d673aa073856486666526fed39657
[android-x86/packages-apps-Settings.git] / src / com / android / settings / homepage / contextualcards / ContextualCardLoader.java
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package com.android.settings.homepage.contextualcards;
18
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;
22
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;
31
32 import androidx.annotation.NonNull;
33 import androidx.annotation.VisibleForTesting;
34
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;
40
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;
49
50 public class ContextualCardLoader extends AsyncLoaderCompat<List<ContextualCard>> {
51
52     @VisibleForTesting
53     static final int DEFAULT_CARD_COUNT = 2;
54     static final int CARD_CONTENT_LOADER_ID = 1;
55
56     private static final String TAG = "ContextualCardLoader";
57     private static final long ELIGIBILITY_CHECKER_TIMEOUT_MS = 250;
58
59     private final ExecutorService mExecutorService;
60     private final ContentObserver mObserver = new ContentObserver(
61             new Handler(Looper.getMainLooper())) {
62         @Override
63         public void onChange(boolean selfChange, Uri uri) {
64             if (isStarted()) {
65                 mNotifyUri = uri;
66                 forceLoad();
67             }
68         }
69     };
70
71     @VisibleForTesting
72     Uri mNotifyUri;
73
74     private final Context mContext;
75
76     ContextualCardLoader(Context context) {
77         super(context);
78         mContext = context.getApplicationContext();
79         mExecutorService = Executors.newCachedThreadPool();
80     }
81
82     @Override
83     protected void onStartLoading() {
84         super.onStartLoading();
85         mNotifyUri = null;
86         mContext.getContentResolver().registerContentObserver(CardContentProvider.REFRESH_CARD_URI,
87                 false /*notifyForDescendants*/, mObserver);
88         mContext.getContentResolver().registerContentObserver(CardContentProvider.DELETE_CARD_URI,
89                 false /*notifyForDescendants*/, mObserver);
90     }
91
92     @Override
93     protected void onStopLoading() {
94         super.onStopLoading();
95         mContext.getContentResolver().unregisterContentObserver(mObserver);
96     }
97
98     @Override
99     protected void onDiscardResult(List<ContextualCard> result) {
100
101     }
102
103     @NonNull
104     @Override
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");
109             return result;
110         }
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());
119                     } else {
120                         result.add(card);
121                     }
122                 }
123             }
124         }
125         return getDisplayableCards(result);
126     }
127
128     // Get final displayed cards and log what cards will be displayed/hidden
129     @VisibleForTesting
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<>();
134
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));
139             } else {
140                 hiddenCards.add(eligibleCards.get(i));
141             }
142         }
143
144         if (!CardContentProvider.DELETE_CARD_URI.equals(mNotifyUri)) {
145             final MetricsFeatureProvider metricsFeatureProvider =
146                     FeatureFactory.getFactory(mContext).getMetricsFeatureProvider();
147
148             metricsFeatureProvider.action(mContext,
149                     SettingsEnums.ACTION_CONTEXTUAL_CARD_SHOW,
150                     ContextualCardLogUtils.buildCardListLog(visibleCards));
151
152             metricsFeatureProvider.action(mContext,
153                     SettingsEnums.ACTION_CONTEXTUAL_CARD_NOT_SHOW,
154                     ContextualCardLogUtils.buildCardListLog(hiddenCards));
155         }
156         return visibleCards;
157     }
158
159     @VisibleForTesting
160     Cursor getContextualCardsFromProvider() {
161         return CardDatabaseHelper.getInstance(mContext).getContextualCards();
162     }
163
164     @VisibleForTesting
165     List<ContextualCard> filterEligibleCards(List<ContextualCard> candidates) {
166         final List<ContextualCard> cards = new ArrayList<>();
167         final List<Future<ContextualCard>> eligibleCards = new ArrayList<>();
168
169         for (ContextualCard card : candidates) {
170             final EligibleCardChecker future = new EligibleCardChecker(mContext, card);
171             eligibleCards.add(mExecutorService.submit(future));
172         }
173         // Collect future and eligible cards
174         for (Future<ContextualCard> cardFuture : eligibleCards) {
175             try {
176                 final ContextualCard card = cardFuture.get(ELIGIBILITY_CHECKER_TIMEOUT_MS,
177                         TimeUnit.MILLISECONDS);
178                 if (card != null) {
179                     cards.add(card);
180                 }
181             } catch (ExecutionException | InterruptedException | TimeoutException e) {
182                 Log.w(TAG, "Failed to get eligible state for card, likely timeout. Skipping", e);
183             }
184         }
185         return cards;
186     }
187
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);
192     }
193
194     public interface CardContentLoaderListener {
195         void onFinishCardLoading(List<ContextualCard> contextualCards);
196     }
197 }