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