• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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.intelligence.suggestions;
18 
19 import static com.android.settings.intelligence.suggestions.model.SuggestionCategoryRegistry
20         .CATEGORIES;
21 
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.SharedPreferences;
25 import android.content.pm.PackageManager;
26 import android.content.pm.ResolveInfo;
27 import android.service.settings.suggestions.Suggestion;
28 import androidx.annotation.VisibleForTesting;
29 import android.text.format.DateUtils;
30 import android.util.ArrayMap;
31 import android.util.Log;
32 
33 import com.android.settings.intelligence.overlay.FeatureFactory;
34 import com.android.settings.intelligence.suggestions.eligibility.CandidateSuggestionFilter;
35 import com.android.settings.intelligence.suggestions.model.CandidateSuggestion;
36 import com.android.settings.intelligence.suggestions.model.SuggestionCategory;
37 import com.android.settings.intelligence.suggestions.model.SuggestionListBuilder;
38 
39 import java.util.ArrayList;
40 import java.util.List;
41 import java.util.Map;
42 
43 /**
44  * Main parser to get suggestions from all providers.
45  * <p/>
46  * Copied from framework/packages/SettingsLib/src/.../SuggestionParser
47  */
48 class SuggestionParser {
49 
50     private static final String TAG = "SuggestionParser";
51     private static final String SETUP_TIME = "_setup_time";
52 
53     private final Context mContext;
54     private final PackageManager mPackageManager;
55     private final SharedPreferences mSharedPrefs;
56     private final Map<String, Suggestion> mAddCache;
57 
58 
SuggestionParser(Context context)59     public SuggestionParser(Context context) {
60         mContext = context.getApplicationContext();
61         mPackageManager = context.getPackageManager();
62         mAddCache = new ArrayMap<>();
63         mSharedPrefs = FeatureFactory.get(mContext)
64                 .suggestionFeatureProvider().getSharedPrefs(mContext);
65     }
66 
getSuggestions()67     public List<Suggestion> getSuggestions() {
68         final SuggestionListBuilder suggestionBuilder = new SuggestionListBuilder();
69 
70         for (SuggestionCategory category : CATEGORIES) {
71             if (category.isExclusive() && !isExclusiveCategoryExpired(category)) {
72                 // If suggestions from an exclusive category are present, parsing is stopped
73                 // and only suggestions from that category are displayed. Note that subsequent
74                 // exclusive categories are also ignored.
75 
76                 // Read suggestion and force ignoreSuggestionDismissRule to be false so the rule
77                 // defined from each suggestion itself is used.
78                 final List<Suggestion> exclusiveSuggestions =
79                         readSuggestions(category, false /* ignoreDismissRule */);
80                 if (!exclusiveSuggestions.isEmpty()) {
81                     suggestionBuilder.addSuggestions(category, exclusiveSuggestions);
82                     return suggestionBuilder.build();
83                 }
84             } else {
85                 // Either the category is not exclusive, or the exclusiveness expired so we should
86                 // treat it as a normal category.
87                 final List<Suggestion> suggestions =
88                         readSuggestions(category, true /* ignoreDismissRule */);
89                 suggestionBuilder.addSuggestions(category, suggestions);
90             }
91         }
92         return suggestionBuilder.build();
93     }
94 
95     @VisibleForTesting
readSuggestions(SuggestionCategory category, boolean ignoreDismissRule)96     List<Suggestion> readSuggestions(SuggestionCategory category, boolean ignoreDismissRule) {
97         final List<Suggestion> suggestions = new ArrayList<>();
98         final Intent probe = new Intent(Intent.ACTION_MAIN);
99         probe.addCategory(category.getCategory());
100         List<ResolveInfo> results = mPackageManager
101                 .queryIntentActivities(probe, PackageManager.GET_META_DATA);
102 
103         // Build a list of eligible candidates
104         final List<CandidateSuggestion> eligibleCandidates = new ArrayList<>();
105         for (ResolveInfo resolved : results) {
106             final CandidateSuggestion candidate = new CandidateSuggestion(mContext, resolved,
107                     ignoreDismissRule);
108             if (!candidate.isEligible()) {
109                 continue;
110             }
111             eligibleCandidates.add(candidate);
112         }
113         // Then remove completed ones
114         final List<CandidateSuggestion> incompleteSuggestions = CandidateSuggestionFilter
115                 .getInstance()
116                 .filterCandidates(mContext, eligibleCandidates);
117 
118         // Convert the rest to suggestion.
119         for (CandidateSuggestion candidate : incompleteSuggestions) {
120             final String id = candidate.getId();
121             Suggestion suggestion = mAddCache.get(id);
122             if (suggestion == null) {
123                 suggestion = candidate.toSuggestion();
124                 mAddCache.put(id, suggestion);
125             }
126             if (!suggestions.contains(suggestion)) {
127                 suggestions.add(suggestion);
128             }
129         }
130         return suggestions;
131     }
132 
133     /**
134      * Whether or not the category's exclusiveness has expired.
135      */
isExclusiveCategoryExpired(SuggestionCategory category)136     private boolean isExclusiveCategoryExpired(SuggestionCategory category) {
137         final String keySetupTime = category.getCategory() + SETUP_TIME;
138         final long currentTime = System.currentTimeMillis();
139         if (!mSharedPrefs.contains(keySetupTime)) {
140             mSharedPrefs.edit()
141                     .putLong(keySetupTime, currentTime)
142                     .commit();
143         }
144         if (category.getExclusiveExpireDaysInMillis() < 0) {
145             // negative means never expires
146             return false;
147         }
148         final long setupTime = mSharedPrefs.getLong(keySetupTime, 0);
149         final long elapsedTime = currentTime - setupTime;
150         Log.d(TAG, "Day " + elapsedTime / DateUtils.DAY_IN_MILLIS + " for "
151                 + category.getCategory());
152         return elapsedTime > category.getExclusiveExpireDaysInMillis();
153     }
154 }
155