• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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 android.app.settings.SettingsEnums;
22 import android.content.ContentResolver;
23 import android.content.Context;
24 import android.net.Uri;
25 import android.util.Log;
26 
27 import androidx.annotation.VisibleForTesting;
28 import androidx.slice.Slice;
29 import androidx.slice.SliceMetadata;
30 import androidx.slice.SliceViewManager;
31 import androidx.slice.core.SliceAction;
32 
33 import com.android.settings.overlay.FeatureFactory;
34 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
35 
36 import java.util.List;
37 import java.util.concurrent.Callable;
38 import java.util.concurrent.CountDownLatch;
39 import java.util.concurrent.TimeUnit;
40 
41 public class EligibleCardChecker implements Callable<ContextualCard> {
42 
43     private static final String TAG = "EligibleCardChecker";
44     private static final long LATCH_TIMEOUT_MS = 200;
45 
46     private final Context mContext;
47 
48     @VisibleForTesting
49     ContextualCard mCard;
50 
EligibleCardChecker(Context context, ContextualCard card)51     EligibleCardChecker(Context context, ContextualCard card) {
52         mContext = context;
53         mCard = card;
54     }
55 
56     @Override
call()57     public ContextualCard call() throws Exception {
58         final long startTime = System.currentTimeMillis();
59         final MetricsFeatureProvider metricsFeatureProvider =
60                 FeatureFactory.getFactory(mContext).getMetricsFeatureProvider();
61         ContextualCard result;
62 
63         if (isCardEligibleToDisplay(mCard)) {
64             metricsFeatureProvider.action(SettingsEnums.PAGE_UNKNOWN,
65                     SettingsEnums.ACTION_CONTEXTUAL_CARD_ELIGIBILITY,
66                     SettingsEnums.SETTINGS_HOMEPAGE,
67                     mCard.getTextSliceUri() /* key */, 1 /* true */);
68             result = mCard;
69         } else {
70             metricsFeatureProvider.action(SettingsEnums.PAGE_UNKNOWN,
71                     SettingsEnums.ACTION_CONTEXTUAL_CARD_ELIGIBILITY,
72                     SettingsEnums.SETTINGS_HOMEPAGE,
73                     mCard.getTextSliceUri() /* key */, 0 /* false */);
74             result = null;
75         }
76         // Log individual card loading time
77         metricsFeatureProvider.action(SettingsEnums.PAGE_UNKNOWN,
78                 SettingsEnums.ACTION_CONTEXTUAL_CARD_LOAD,
79                 SettingsEnums.SETTINGS_HOMEPAGE,
80                 mCard.getTextSliceUri() /* key */,
81                 (int) (System.currentTimeMillis() - startTime) /* value */);
82 
83         return result;
84     }
85 
86     @VisibleForTesting
isCardEligibleToDisplay(ContextualCard card)87     boolean isCardEligibleToDisplay(ContextualCard card) {
88         if (card.getRankingScore() < 0) {
89             return false;
90         }
91         if (card.isCustomCard()) {
92             return true;
93         }
94 
95         final Uri uri = card.getSliceUri();
96         if (!ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
97             return false;
98         }
99 
100         final Slice slice = bindSlice(uri);
101 
102         if (isSliceToggleable(slice)) {
103             mCard = card.mutate().setHasInlineAction(true).build();
104         }
105 
106         if (slice == null || slice.hasHint(HINT_ERROR)) {
107             Log.w(TAG, "Failed to bind slice, not eligible for display " + uri);
108             return false;
109         }
110         return true;
111     }
112 
113     @VisibleForTesting
bindSlice(Uri uri)114     Slice bindSlice(Uri uri) {
115         final SliceViewManager manager = SliceViewManager.getInstance(mContext);
116         final Slice[] returnSlice = new Slice[1];
117         final CountDownLatch latch = new CountDownLatch(1);
118         final SliceViewManager.SliceCallback callback =
119                 new SliceViewManager.SliceCallback() {
120                     @Override
121                     public void onSliceUpdated(Slice slice) {
122                         try {
123                             // We are just making sure the existence of the slice, so ignore
124                             // slice loading state here.
125                             returnSlice[0] = slice;
126                             latch.countDown();
127                         } catch (Exception e) {
128                             Log.w(TAG, uri + " cannot be indexed", e);
129                         } finally {
130                             manager.unregisterSliceCallback(uri, this);
131                         }
132                     }
133                 };
134         // Register a callback until we get a loaded slice.
135         manager.registerSliceCallback(uri, callback);
136         // Trigger the binding.
137         callback.onSliceUpdated(manager.bindSlice(uri));
138         try {
139             latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
140         } catch (InterruptedException e) {
141             Log.w(TAG, "Error waiting for slice binding for uri" + uri, e);
142             manager.unregisterSliceCallback(uri, callback);
143         }
144         return returnSlice[0];
145     }
146 
147     @VisibleForTesting
isSliceToggleable(Slice slice)148     boolean isSliceToggleable(Slice slice) {
149         final SliceMetadata metadata = SliceMetadata.from(mContext, slice);
150         final List<SliceAction> toggles = metadata.getToggles();
151 
152         return !toggles.isEmpty();
153     }
154 }
155