NotificationController.java revision 9f6d7f00138afbb634fabf04d8dd67f6e86b60f5
1/* 2 * Copyright (C) 2016 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 17package com.android.storagemanager.automatic; 18 19import android.app.Notification; 20import android.app.NotificationManager; 21import android.app.PendingIntent; 22import android.content.BroadcastReceiver; 23import android.content.Context; 24import android.content.Intent; 25import android.content.SharedPreferences; 26import android.content.res.Resources; 27import android.os.SystemProperties; 28import android.provider.Settings; 29 30import com.android.storagemanager.R; 31import com.android.storagemanager.automatic.WarningDialogActivity; 32import com.android.storagemanager.overlay.FeatureFactory; 33 34import java.util.concurrent.TimeUnit; 35 36/** 37 * NotificationController handles the responses to the Automatic Storage Management low storage 38 * notification. 39 */ 40public class NotificationController extends BroadcastReceiver { 41 /** 42 * Intent action for if the user taps "Turn on" for the automatic storage manager. 43 */ 44 public static final String INTENT_ACTION_ACTIVATE_ASM = 45 "com.android.storagemanager.automatic.ACTIVATE"; 46 47 /** 48 * Intent action for if the user swipes the notification away. 49 */ 50 public static final String INTENT_ACTION_DISMISS = 51 "com.android.storagemanager.automatic.DISMISS"; 52 53 /** 54 * Intent action for if the user explicitly hits "No thanks" on the notification. 55 */ 56 public static final String INTENT_ACTION_NO_THANKS = 57 "com.android.storagemanager.automatic.NO_THANKS"; 58 59 /** 60 * Intent action to maybe show the ASM upsell notification. 61 */ 62 public static final String INTENT_ACTION_SHOW_NOTIFICATION = 63 "com.android.storagemanager.automatic.show_notification"; 64 65 /** 66 * Intent action for forcefully showing the notification, even if the conditions are not valid. 67 */ 68 private static final String INTENT_ACTION_DEBUG_NOTIFICATION = 69 "com.android.storagemanager.automatic.DEBUG_SHOW_NOTIFICATION"; 70 71 /** 72 * Intent action for if the user taps on the notification. 73 */ 74 private static final String INTENT_ACTION_TAP = 75 "com.android.storagemanager.automatic.SHOW_SETTINGS"; 76 77 /** 78 * Intent extra for the notification id. 79 */ 80 public static final String INTENT_EXTRA_ID = "id"; 81 82 private static final String SHARED_PREFERENCES_NAME = "NotificationController"; 83 private static final String NOTIFICATION_NEXT_SHOW_TIME = "notification_next_show_time"; 84 private static final String NOTIFICATION_SHOWN_COUNT = "notification_shown_count"; 85 private static final String NOTIFICATION_DISMISS_COUNT = "notification_dismiss_count"; 86 private static final String STORAGE_MANAGER_PROPERTY = "ro.storage_manager.enabled"; 87 88 private static final long DISMISS_DELAY = TimeUnit.DAYS.toMillis(15); 89 private static final long NO_THANKS_DELAY = TimeUnit.DAYS.toMillis(90); 90 private static final long MAXIMUM_SHOWN_COUNT = 4; 91 private static final long MAXIMUM_DISMISS_COUNT = 9; 92 private static final int NOTIFICATION_ID = 0; 93 94 // Keeps the time for test purposes. 95 private Clock mClock; 96 97 @Override 98 public void onReceive(Context context, Intent intent) { 99 switch (intent.getAction()) { 100 case INTENT_ACTION_ACTIVATE_ASM: 101 Settings.Secure.putInt(context.getContentResolver(), 102 Settings.Secure.AUTOMATIC_STORAGE_MANAGER_ENABLED, 103 1); 104 // Provide a warning if storage manager is not defaulted on. 105 if (!SystemProperties.getBoolean(STORAGE_MANAGER_PROPERTY, false)) { 106 Intent warningIntent = new Intent(context, WarningDialogActivity.class); 107 context.startActivity(warningIntent); 108 } 109 break; 110 case INTENT_ACTION_NO_THANKS: 111 delayNextNotification(context, NO_THANKS_DELAY); 112 break; 113 case INTENT_ACTION_DISMISS: 114 delayNextNotification(context, DISMISS_DELAY); 115 break; 116 case INTENT_ACTION_SHOW_NOTIFICATION: 117 maybeShowNotification(context); 118 return; 119 case INTENT_ACTION_DEBUG_NOTIFICATION: 120 showNotification(context); 121 return; 122 case INTENT_ACTION_TAP: 123 Intent storageIntent = new Intent(Settings.ACTION_STORAGE_MANAGER_SETTINGS); 124 storageIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 125 context.startActivity(storageIntent); 126 break; 127 } 128 cancelNotification(context, intent); 129 } 130 131 /** 132 * Sets a time provider for the controller. 133 * @param clock The time provider. 134 */ 135 protected void setClock(Clock clock) { 136 mClock = clock; 137 } 138 139 /** 140 * If the conditions for showing the activation notification are met, show the activation 141 * notification. 142 * @param context Context to use for getting resources and to display the notification. 143 */ 144 private void maybeShowNotification(Context context) { 145 if (shouldShowNotification(context)) { 146 showNotification(context); 147 } 148 } 149 150 private boolean shouldShowNotification(Context context) { 151 SharedPreferences sp = context.getSharedPreferences( 152 SHARED_PREFERENCES_NAME, 153 Context.MODE_PRIVATE); 154 int timesShown = sp.getInt(NOTIFICATION_SHOWN_COUNT, 0); 155 int timesDismissed = sp.getInt(NOTIFICATION_DISMISS_COUNT, 0); 156 if (timesShown >= MAXIMUM_SHOWN_COUNT || timesDismissed >= MAXIMUM_DISMISS_COUNT) { 157 return false; 158 } 159 160 long nextTimeToShow = sp.getLong(NOTIFICATION_NEXT_SHOW_TIME, 0); 161 162 return getCurrentTime() >= nextTimeToShow; 163 } 164 165 private void showNotification(Context context) { 166 Resources res = context.getResources(); 167 Intent noThanksIntent = new Intent(INTENT_ACTION_NO_THANKS); 168 noThanksIntent.putExtra(INTENT_EXTRA_ID, NOTIFICATION_ID); 169 Notification.Action.Builder cancelAction = new Notification.Action.Builder(null, 170 res.getString(R.string.automatic_storage_manager_cancel_button), 171 PendingIntent.getBroadcast(context, 0, noThanksIntent, 172 PendingIntent.FLAG_UPDATE_CURRENT)); 173 174 175 Intent activateIntent = new Intent(INTENT_ACTION_ACTIVATE_ASM); 176 activateIntent.putExtra(INTENT_EXTRA_ID, NOTIFICATION_ID); 177 Notification.Action.Builder activateAutomaticAction = new Notification.Action.Builder(null, 178 res.getString(R.string.automatic_storage_manager_activate_button), 179 PendingIntent.getBroadcast(context, 0, activateIntent, 180 PendingIntent.FLAG_UPDATE_CURRENT)); 181 182 Intent dismissIntent = new Intent(INTENT_ACTION_DISMISS); 183 dismissIntent.putExtra(INTENT_EXTRA_ID, NOTIFICATION_ID); 184 PendingIntent deleteIntent = PendingIntent.getBroadcast(context, 0, 185 dismissIntent, 186 PendingIntent.FLAG_ONE_SHOT); 187 188 Intent contentIntent = new Intent(INTENT_ACTION_TAP); 189 contentIntent.putExtra(INTENT_EXTRA_ID, NOTIFICATION_ID); 190 PendingIntent tapIntent = PendingIntent.getBroadcast(context, 0, contentIntent, 191 PendingIntent.FLAG_ONE_SHOT); 192 193 Notification.Builder builder = new Notification.Builder(context) 194 .setSmallIcon(R.drawable.ic_settings_24dp) 195 .setContentTitle( 196 res.getString(R.string.automatic_storage_manager_notification_title)) 197 .setContentText( 198 res.getString(R.string.automatic_storage_manager_notification_summary)) 199 .setStyle(new Notification.BigTextStyle().bigText( 200 res.getString(R.string.automatic_storage_manager_notification_summary))) 201 .addAction(cancelAction.build()) 202 .addAction(activateAutomaticAction.build()) 203 .setContentIntent(tapIntent) 204 .setDeleteIntent(deleteIntent); 205 206 NotificationManager manager = 207 ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE)); 208 manager.notify(NOTIFICATION_ID, builder.build()); 209 } 210 211 private void cancelNotification(Context context, Intent intent) { 212 if (intent.getAction() == INTENT_ACTION_DISMISS) { 213 incrementNotificationDismissedCount(context); 214 } else { 215 incrementNotificationShownCount(context); 216 } 217 218 int id = intent.getIntExtra(INTENT_EXTRA_ID, -1); 219 if (id == -1) { 220 return; 221 } 222 NotificationManager manager = (NotificationManager) context 223 .getSystemService(Context.NOTIFICATION_SERVICE); 224 manager.cancel(id); 225 } 226 227 private void incrementNotificationShownCount(Context context) { 228 SharedPreferences sp = context.getSharedPreferences(SHARED_PREFERENCES_NAME, 229 Context.MODE_PRIVATE); 230 SharedPreferences.Editor editor = sp.edit(); 231 int shownCount = sp.getInt(NotificationController.NOTIFICATION_SHOWN_COUNT, 0) + 1; 232 editor.putInt(NotificationController.NOTIFICATION_SHOWN_COUNT, shownCount); 233 editor.apply(); 234 } 235 236 private void incrementNotificationDismissedCount(Context context) { 237 SharedPreferences sp = context.getSharedPreferences(SHARED_PREFERENCES_NAME, 238 Context.MODE_PRIVATE); 239 SharedPreferences.Editor editor = sp.edit(); 240 int dismissCount = sp.getInt(NOTIFICATION_DISMISS_COUNT, 0) + 1; 241 editor.putInt(NOTIFICATION_DISMISS_COUNT, dismissCount); 242 editor.apply(); 243 } 244 245 private void delayNextNotification(Context context, long timeInMillis) { 246 SharedPreferences sp = context.getSharedPreferences(SHARED_PREFERENCES_NAME, 247 Context.MODE_PRIVATE); 248 SharedPreferences.Editor editor = sp.edit(); 249 editor.putLong(NOTIFICATION_NEXT_SHOW_TIME, 250 getCurrentTime() + timeInMillis); 251 editor.apply(); 252 } 253 254 private long getCurrentTime() { 255 if (mClock == null) { 256 mClock = new Clock(); 257 } 258 259 return mClock.currentTimeMillis(); 260 } 261 262 /** 263 * Clock provides the current time. 264 */ 265 protected static class Clock { 266 /** 267 * Returns the current time in milliseconds. 268 */ 269 public long currentTimeMillis() { 270 return System.currentTimeMillis(); 271 } 272 } 273} 274