OSDN Git Service

Add vertical and horizontal dividers for large cards of homepage
[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 android.app.slice.Slice.HINT_ERROR;
20
21 import static androidx.slice.widget.SliceLiveData.SUPPORTED_SPECS;
22
23 import static com.android.settings.slices.CustomSliceRegistry.BLUETOOTH_DEVICES_SLICE_URI;
24 import static com.android.settings.slices.CustomSliceRegistry.WIFI_SLICE_URI;
25
26 import android.content.ContentProviderClient;
27 import android.content.ContentResolver;
28 import android.content.Context;
29 import android.database.ContentObserver;
30 import android.database.Cursor;
31 import android.net.Uri;
32 import android.os.Handler;
33 import android.os.Looper;
34 import android.util.Log;
35
36 import androidx.annotation.NonNull;
37 import androidx.annotation.VisibleForTesting;
38 import androidx.slice.Slice;
39
40 import com.android.settings.overlay.FeatureFactory;
41 import com.android.settingslib.utils.AsyncLoaderCompat;
42
43 import java.util.ArrayList;
44 import java.util.List;
45 import java.util.stream.Collectors;
46
47 public class ContextualCardLoader extends AsyncLoaderCompat<List<ContextualCard>> {
48
49     @VisibleForTesting
50     static final int DEFAULT_CARD_COUNT = 4;
51     static final int CARD_CONTENT_LOADER_ID = 1;
52
53     private static final String TAG = "ContextualCardLoader";
54
55     private final ContentObserver mObserver = new ContentObserver(
56             new Handler(Looper.getMainLooper())) {
57         @Override
58         public void onChange(boolean selfChange) {
59             if (isStarted()) {
60                 forceLoad();
61             }
62         }
63     };
64
65     private Context mContext;
66
67     ContextualCardLoader(Context context) {
68         super(context);
69         mContext = context.getApplicationContext();
70     }
71
72     @Override
73     protected void onStartLoading() {
74         super.onStartLoading();
75         mContext.getContentResolver().registerContentObserver(CardContentProvider.URI,
76                 false /*notifyForDescendants*/, mObserver);
77     }
78
79     @Override
80     protected void onStopLoading() {
81         super.onStopLoading();
82         mContext.getContentResolver().unregisterContentObserver(mObserver);
83     }
84
85     @Override
86     protected void onDiscardResult(List<ContextualCard> result) {
87
88     }
89
90     @NonNull
91     @Override
92     public List<ContextualCard> loadInBackground() {
93         final List<ContextualCard> result = new ArrayList<>();
94         try (Cursor cursor = getContextualCardsFromProvider()) {
95             if (cursor.getCount() > 0) {
96                 for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
97                     final ContextualCard card = new ContextualCard(cursor);
98                     if (card.isCustomCard()) {
99                         //TODO(b/114688391): Load and generate custom card,then add into list
100                     } else if (isLargeCard(card)) {
101                         result.add(card.mutate().setIsLargeCard(true).build());
102                     } else {
103                         result.add(card);
104                     }
105                 }
106             }
107         }
108         return getFinalDisplayableCards(result);
109     }
110
111     // Get final displayed cards and log what cards will be displayed/hidden
112     @VisibleForTesting
113     List<ContextualCard> getFinalDisplayableCards(List<ContextualCard> candidates) {
114         final List<ContextualCard> eligibleCards = filterEligibleCards(candidates);
115         final List<ContextualCard> visibleCards = new ArrayList<>();
116         final List<ContextualCard> hiddenCards = new ArrayList<>();
117
118         final int size = eligibleCards.size();
119         for (int i = 0; i < size; i++) {
120             if (i < DEFAULT_CARD_COUNT) {
121                 visibleCards.add(eligibleCards.get(i));
122             } else {
123                 hiddenCards.add(eligibleCards.get(i));
124             }
125         }
126
127         try {
128             // The maximum cards are four small cards OR
129             // one large card with two small cards OR
130             // two large cards
131             if (visibleCards.size() <= 2 || getNumberOfLargeCard(visibleCards) == 0) {
132                 // four small cards
133                 return visibleCards;
134             }
135
136             if (visibleCards.size() == DEFAULT_CARD_COUNT) {
137                 hiddenCards.add(visibleCards.remove(visibleCards.size() - 1));
138             }
139
140             if (getNumberOfLargeCard(visibleCards) == 1) {
141                 // One large card with two small cards
142                 return visibleCards;
143             }
144
145             hiddenCards.add(visibleCards.remove(visibleCards.size() - 1));
146
147             // Two large cards
148             return visibleCards;
149         } finally {
150             final ContextualCardFeatureProvider contextualCardFeatureProvider =
151                     FeatureFactory.getFactory(mContext).getContextualCardFeatureProvider();
152             contextualCardFeatureProvider.logContextualCardDisplay(mContext, visibleCards,
153                     hiddenCards);
154         }
155     }
156
157     @VisibleForTesting
158     Cursor getContextualCardsFromProvider() {
159         return CardDatabaseHelper.getInstance(mContext).getContextualCards();
160     }
161
162     @VisibleForTesting
163     List<ContextualCard> filterEligibleCards(List<ContextualCard> candidates) {
164         return candidates.stream().filter(card -> isCardEligibleToDisplay(card))
165                 .collect(Collectors.toList());
166     }
167
168     @VisibleForTesting
169     boolean isCardEligibleToDisplay(ContextualCard card) {
170         if (card.isCustomCard()) {
171             return true;
172         }
173
174         final Uri uri = card.getSliceUri();
175
176         if (!ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
177             return false;
178         }
179
180         //check if the uri has a provider associated with.
181         final ContentProviderClient provider =
182                 mContext.getContentResolver().acquireContentProviderClient(uri);
183         if (provider == null) {
184             return false;
185         }
186         //release contentProviderClient to prevent from memory leak.
187         provider.release();
188
189         final Slice slice = Slice.bindSlice(mContext, uri, SUPPORTED_SPECS);
190         if (slice == null || slice.hasHint(HINT_ERROR)) {
191             Log.w(TAG, "Failed to bind slice, not eligible for display " + uri);
192             return false;
193         }
194
195         return true;
196     }
197
198     private int getNumberOfLargeCard(List<ContextualCard> cards) {
199         return (int) cards.stream()
200                 .filter(card -> isLargeCard(card))
201                 .count();
202     }
203
204     private boolean isLargeCard(ContextualCard card) {
205         return card.getSliceUri().equals(WIFI_SLICE_URI)
206                 || card.getSliceUri().equals(BLUETOOTH_DEVICES_SLICE_URI);
207     }
208
209     public interface CardContentLoaderListener {
210         void onFinishCardLoading(List<ContextualCard> contextualCards);
211     }
212 }