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.eligibility;
18 
19 import android.content.Context;
20 import android.content.SharedPreferences;
21 import android.content.pm.ResolveInfo;
22 import androidx.annotation.VisibleForTesting;
23 import android.text.format.DateUtils;
24 import android.util.Log;
25 
26 import com.android.settings.intelligence.overlay.FeatureFactory;
27 import com.android.settings.intelligence.suggestions.SuggestionFeatureProvider;
28 
29 public class DismissedChecker {
30 
31     /**
32      * Allows suggestions to appear after a certain number of days, and to re-appear if dismissed.
33      * For instance:
34      * 0,10
35      * Will appear immediately, the 10 is ignored.
36      *
37      * 10
38      * Will appear after 10 days
39      */
40     @VisibleForTesting
41     static final String META_DATA_DISMISS_CONTROL = "com.android.settings.dismiss";
42 
43     // Shared prefs keys for storing dismissed state.
44     // Index into current dismissed state.
45     @VisibleForTesting
46     static final String SETUP_TIME = "_setup_time";
47     // Default dismiss rule for suggestions.
48 
49     private static final int DEFAULT_FIRST_APPEAR_DAY = 0;
50 
51     private static final String TAG = "DismissedChecker";
52 
isEligible(Context context, String id, ResolveInfo info, boolean ignoreAppearRule)53     public static boolean isEligible(Context context, String id, ResolveInfo info,
54             boolean ignoreAppearRule) {
55         final SuggestionFeatureProvider featureProvider = FeatureFactory.get(context)
56                 .suggestionFeatureProvider();
57         final SharedPreferences prefs = featureProvider.getSharedPrefs(context);
58         final long currentTimeMs = System.currentTimeMillis();
59         final String keySetupTime = id + SETUP_TIME;
60         if (!prefs.contains(keySetupTime)) {
61             prefs.edit()
62                     .putLong(keySetupTime, currentTimeMs)
63                     .apply();
64         }
65 
66         // Check if it's already manually dismissed
67         final boolean isDismissed = featureProvider.isSuggestionDismissed(context, id);
68         if (isDismissed) {
69             return false;
70         }
71 
72         // Parse when suggestion should first appear. Hide suggestion before then.
73         int firstAppearDay = ignoreAppearRule
74                 ? DEFAULT_FIRST_APPEAR_DAY
75                 : parseAppearDay(info);
76         long setupTime = prefs.getLong(keySetupTime, 0);
77         if (setupTime > currentTimeMs) {
78             // SetupTime is the future, user's date/time is probably wrong at some point.
79             // Force setupTime to be now. So we get a more reasonable firstAppearDay.
80             setupTime = currentTimeMs;
81         }
82         final long firstAppearDayInMs = getFirstAppearTimeMillis(setupTime, firstAppearDay);
83         if (currentTimeMs >= firstAppearDayInMs) {
84             // Dismiss timeout has passed, undismiss it.
85             featureProvider.markSuggestionNotDismissed(context, id);
86             return true;
87         }
88         return false;
89     }
90 
91     /**
92      * Parse the first int from a string formatted as "0,1,2..."
93      * The value means suggestion should first appear on Day X.
94      */
parseAppearDay(ResolveInfo info)95     private static int parseAppearDay(ResolveInfo info) {
96         if (!info.activityInfo.metaData.containsKey(META_DATA_DISMISS_CONTROL)) {
97             return 0;
98         }
99 
100         final Object firstAppearRule = info.activityInfo.metaData
101                 .get(META_DATA_DISMISS_CONTROL);
102         if (firstAppearRule instanceof Integer) {
103             return (int) firstAppearRule;
104         } else {
105             try {
106                 final String[] days = ((String) firstAppearRule).split(",");
107                 return Integer.parseInt(days[0]);
108             } catch (Exception e) {
109                 Log.w(TAG, "Failed to parse appear/dismiss rule, fall back to 0");
110                 return 0;
111             }
112         }
113     }
114 
getFirstAppearTimeMillis(long setupTime, int daysDelay)115     private static long getFirstAppearTimeMillis(long setupTime, int daysDelay) {
116         long days = daysDelay * DateUtils.DAY_IN_MILLIS;
117         return setupTime + days;
118     }
119 }
120