1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5  * except in compliance with the License. You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the
10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11  * KIND, either express or implied. See the License for the specific language governing
12  * permissions and limitations under the License.
13  */
14 
15 package com.android.storagemanager.deletionhelper;
16 
17 import android.app.Activity;
18 import android.app.AlertDialog;
19 import android.app.Dialog;
20 import android.app.DialogFragment;
21 import android.content.Context;
22 import android.content.DialogInterface;
23 import android.content.SharedPreferences;
24 import android.os.Bundle;
25 import android.provider.Settings;
26 import androidx.annotation.VisibleForTesting;
27 import android.text.format.Formatter;
28 
29 import com.android.settingslib.Utils;
30 
31 import com.android.storagemanager.R;
32 
33 import java.util.concurrent.TimeUnit;
34 
35 /**
36  * Fragment for activating the storage manager after a manual clear.
37  */
38 public class StorageManagerUpsellDialog extends DialogFragment
39         implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener {
40     public static final String TAG = "StorageManagerUpsellDialog";
41     private static final String SHARED_PREFERENCES_NAME = "StorageManagerUpsellDialog";
42     private static final String NEXT_SHOW_TIME = "next_show_time";
43     private static final String DISMISSED_COUNT = "dismissed_count";
44     private static final String NO_THANKS_COUNT = "no_thanks_count";
45 
46     private static final String ARGS_FREED_BYTES = "freed_bytes";
47 
48     private static final long NEVER = -1;
49     private static final long DISMISS_SHORT_DELAY = TimeUnit.DAYS.toMillis(14);
50     private static final long DISMISS_LONG_DELAY = TimeUnit.DAYS.toMillis(90);
51     private static final int DISMISS_LONG_THRESHOLD = 9;
52     private static final long NO_THANKS_SHORT_DELAY = TimeUnit.DAYS.toMillis(90);
53     private static final long NO_THANKS_LONG_DELAY = NEVER;
54     private static final int NO_THANKS_LONG_THRESHOLD = 3;
55 
56     private Clock mClock;
57 
newInstance(long freedBytes)58     public static StorageManagerUpsellDialog newInstance(long freedBytes) {
59         StorageManagerUpsellDialog dialog = new StorageManagerUpsellDialog();
60         Bundle args = new Bundle(1);
61         args.putLong(ARGS_FREED_BYTES, freedBytes);
62         dialog.setArguments(args);
63         return dialog;
64     }
65 
66     @VisibleForTesting(otherwise = VisibleForTesting.NONE)
setClock(Clock clock)67     protected void setClock(Clock clock) {
68         mClock = clock;
69     }
70 
71     @Override
onCreateDialog(Bundle savedInstanceState)72     public Dialog onCreateDialog(Bundle savedInstanceState) {
73         final Bundle args = getArguments();
74         long freedBytes = args.getLong(ARGS_FREED_BYTES);
75 
76         final Context context = getContext();
77         return new AlertDialog.Builder(context)
78                 .setTitle(context.getString(R.string.deletion_helper_upsell_title))
79                 .setMessage(context.getString(R.string.deletion_helper_upsell_summary,
80                         Formatter.formatFileSize(context, freedBytes)))
81                 .setPositiveButton(R.string.deletion_helper_upsell_activate, this)
82                 .setNegativeButton(R.string.deletion_helper_upsell_cancel, this)
83                 .create();
84     }
85 
86     @Override
onClick(DialogInterface dialog, int buttonId)87     public void onClick(DialogInterface dialog, int buttonId) {
88         if (buttonId == DialogInterface.BUTTON_POSITIVE) {
89             Settings.Secure.putInt(getActivity().getContentResolver(),
90                     Settings.Secure.AUTOMATIC_STORAGE_MANAGER_ENABLED, 1);
91         } else {
92             SharedPreferences sp = getSharedPreferences(getContext());
93             int noThanksCount = sp.getInt(NO_THANKS_COUNT, 0) + 1;
94             SharedPreferences.Editor editor = sp.edit();
95             editor.putInt(NO_THANKS_COUNT, noThanksCount);
96             long noThanksDelay = getNoThanksDelay(noThanksCount);
97             long nextShowTime = noThanksDelay == NEVER ? NEVER : getCurrentTime() + noThanksDelay;
98             editor.putLong(NEXT_SHOW_TIME, nextShowTime);
99             editor.apply();
100         }
101 
102         finishActivity();
103     }
104 
105     @Override
onCancel(DialogInterface dialog)106     public void onCancel(DialogInterface dialog) {
107         SharedPreferences sp = getSharedPreferences(getContext());
108         int dismissCount = sp.getInt(DISMISSED_COUNT, 0) + 1;
109         SharedPreferences.Editor editor = sp.edit();
110         editor.putInt(DISMISSED_COUNT, dismissCount);
111         editor.putLong(NEXT_SHOW_TIME, getCurrentTime() + getDismissDelay(dismissCount));
112         editor.apply();
113 
114         finishActivity();
115     }
116 
117     /**
118      * Returns if the dialog should be shown, given the delays between when it is shown.
119      *
120      * @param context Context to get shared preferences for determining the next show time.
121      * @param time The current time in millis.
122      */
shouldShow(Context context, long time)123     public static boolean shouldShow(Context context, long time) {
124         boolean isEnabled = Utils.isStorageManagerEnabled(context);
125         if (isEnabled) {
126             return false;
127         }
128 
129         long nextTimeToShow = getSharedPreferences(context).getLong(NEXT_SHOW_TIME, 0);
130         if (nextTimeToShow == NEVER) {
131             return false;
132         }
133 
134         return time >= nextTimeToShow;
135     }
136 
getSharedPreferences(Context context)137     private static SharedPreferences getSharedPreferences(Context context) {
138         return context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
139     }
140 
getNoThanksDelay(int noThanksCount)141     private static long getNoThanksDelay(int noThanksCount) {
142         return (noThanksCount > NO_THANKS_LONG_THRESHOLD)
143                 ? NO_THANKS_LONG_DELAY : NO_THANKS_SHORT_DELAY;
144     }
145 
getDismissDelay(int dismissCount)146     private static long getDismissDelay(int dismissCount) {
147         return (dismissCount > DISMISS_LONG_THRESHOLD)
148                 ? DISMISS_LONG_DELAY : DISMISS_SHORT_DELAY;
149     }
150 
finishActivity()151     private void finishActivity() {
152         Activity activity = getActivity();
153         if (activity != null) {
154             activity.finish();
155         }
156     }
157 
getCurrentTime()158     private long getCurrentTime() {
159         if (mClock == null) {
160             mClock = new Clock();
161         }
162 
163         return mClock.currentTimeMillis();
164     }
165 
166     /**
167      * Clock provides the current time.
168      */
169     protected static class Clock {
170         /**
171          * Returns the current time in milliseconds.
172          */
currentTimeMillis()173         public long currentTimeMillis() {
174             return System.currentTimeMillis();
175         }
176     }
177 }
178