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.os.AsyncTask; 26 import android.util.Log; 27 28 import androidx.annotation.VisibleForTesting; 29 import androidx.slice.Slice; 30 import androidx.slice.SliceMetadata; 31 import androidx.slice.SliceViewManager; 32 import androidx.slice.core.SliceAction; 33 34 import com.android.settings.overlay.FeatureFactory; 35 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; 36 import com.android.settingslib.utils.ThreadUtils; 37 38 import java.util.List; 39 import java.util.concurrent.Callable; 40 41 public class EligibleCardChecker implements Callable<ContextualCard> { 42 43 private static final String TAG = "EligibleCardChecker"; 44 45 private final Context mContext; 46 47 @VisibleForTesting 48 ContextualCard mCard; 49 EligibleCardChecker(Context context, ContextualCard card)50 EligibleCardChecker(Context context, ContextualCard card) { 51 mContext = context; 52 mCard = card; 53 } 54 55 @Override call()56 public ContextualCard call() { 57 final long startTime = System.currentTimeMillis(); 58 final MetricsFeatureProvider metricsFeatureProvider = 59 FeatureFactory.getFactory(mContext).getMetricsFeatureProvider(); 60 ContextualCard result; 61 62 if (isCardEligibleToDisplay(mCard)) { 63 metricsFeatureProvider.action(SettingsEnums.PAGE_UNKNOWN, 64 SettingsEnums.ACTION_CONTEXTUAL_CARD_ELIGIBILITY, 65 SettingsEnums.SETTINGS_HOMEPAGE, 66 mCard.getTextSliceUri() /* key */, 1 /* true */); 67 result = mCard; 68 } else { 69 metricsFeatureProvider.action(SettingsEnums.PAGE_UNKNOWN, 70 SettingsEnums.ACTION_CONTEXTUAL_CARD_ELIGIBILITY, 71 SettingsEnums.SETTINGS_HOMEPAGE, 72 mCard.getTextSliceUri() /* key */, 0 /* false */); 73 result = null; 74 } 75 // Log individual card loading time 76 metricsFeatureProvider.action(SettingsEnums.PAGE_UNKNOWN, 77 SettingsEnums.ACTION_CONTEXTUAL_CARD_LOAD, 78 SettingsEnums.SETTINGS_HOMEPAGE, 79 mCard.getTextSliceUri() /* key */, 80 (int) (System.currentTimeMillis() - startTime) /* value */); 81 82 return result; 83 } 84 85 @VisibleForTesting isCardEligibleToDisplay(ContextualCard card)86 boolean isCardEligibleToDisplay(ContextualCard card) { 87 if (card.getRankingScore() < 0) { 88 return false; 89 } 90 91 final Uri uri = card.getSliceUri(); 92 if (!ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) { 93 return false; 94 } 95 96 final Slice slice = bindSlice(uri); 97 98 if (slice == null || slice.hasHint(HINT_ERROR)) { 99 Log.w(TAG, "Failed to bind slice, not eligible for display " + uri); 100 return false; 101 } 102 103 mCard = card.mutate().setSlice(slice).build(); 104 105 if (isSliceToggleable(slice)) { 106 mCard = card.mutate().setHasInlineAction(true).build(); 107 } 108 109 return true; 110 } 111 112 @VisibleForTesting bindSlice(Uri uri)113 Slice bindSlice(Uri uri) { 114 final SliceViewManager manager = SliceViewManager.getInstance(mContext); 115 final SliceViewManager.SliceCallback callback = slice -> { }; 116 117 // Register a trivial callback to pin the slice 118 manager.registerSliceCallback(uri, callback); 119 final Slice slice = manager.bindSlice(uri); 120 121 // Workaround of unpinning slice in the same SerialExecutor of AsyncTask as SliceCallback's 122 // observer. 123 ThreadUtils.postOnMainThread(() -> AsyncTask.execute(() -> { 124 try { 125 manager.unregisterSliceCallback(uri, callback); 126 } catch (SecurityException e) { 127 Log.d(TAG, "No permission currently: " + e); 128 } 129 })); 130 131 return slice; 132 } 133 134 @VisibleForTesting isSliceToggleable(Slice slice)135 boolean isSliceToggleable(Slice slice) { 136 final SliceMetadata metadata = SliceMetadata.from(mContext, slice); 137 final List<SliceAction> toggles = metadata.getToggles(); 138 139 return !toggles.isEmpty(); 140 } 141 } 142