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