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 android.support.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 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 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 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 */ 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