Home | History | Annotate | Download | only in keyguard
      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.systemui.keyguard;
     18 
     19 import android.app.ActivityManager;
     20 import android.app.AlarmManager;
     21 import android.app.NotificationManager;
     22 import android.app.PendingIntent;
     23 import android.content.BroadcastReceiver;
     24 import android.content.ContentResolver;
     25 import android.content.Context;
     26 import android.content.Intent;
     27 import android.content.IntentFilter;
     28 import android.graphics.drawable.Icon;
     29 import android.icu.text.DateFormat;
     30 import android.icu.text.DisplayContext;
     31 import android.net.Uri;
     32 import android.os.Handler;
     33 import android.provider.Settings;
     34 import android.service.notification.ZenModeConfig;
     35 import android.text.TextUtils;
     36 
     37 import com.android.internal.annotations.VisibleForTesting;
     38 import com.android.systemui.R;
     39 import com.android.systemui.statusbar.policy.NextAlarmController;
     40 import com.android.systemui.statusbar.policy.NextAlarmControllerImpl;
     41 import com.android.systemui.statusbar.policy.ZenModeController;
     42 import com.android.systemui.statusbar.policy.ZenModeControllerImpl;
     43 
     44 import java.util.Date;
     45 import java.util.Locale;
     46 import java.util.concurrent.TimeUnit;
     47 
     48 import androidx.slice.Slice;
     49 import androidx.slice.SliceProvider;
     50 import androidx.slice.builders.ListBuilder;
     51 import androidx.slice.builders.ListBuilder.RowBuilder;
     52 import androidx.slice.builders.SliceAction;
     53 
     54 /**
     55  * Simple Slice provider that shows the current date.
     56  */
     57 public class KeyguardSliceProvider extends SliceProvider implements
     58         NextAlarmController.NextAlarmChangeCallback, ZenModeController.Callback {
     59 
     60     public static final String KEYGUARD_SLICE_URI = "content://com.android.systemui.keyguard/main";
     61     public static final String KEYGUARD_DATE_URI = "content://com.android.systemui.keyguard/date";
     62     public static final String KEYGUARD_NEXT_ALARM_URI =
     63             "content://com.android.systemui.keyguard/alarm";
     64     public static final String KEYGUARD_DND_URI = "content://com.android.systemui.keyguard/dnd";
     65     public static final String KEYGUARD_ACTION_URI =
     66             "content://com.android.systemui.keyguard/action";
     67 
     68     /**
     69      * Only show alarms that will ring within N hours.
     70      */
     71     @VisibleForTesting
     72     static final int ALARM_VISIBILITY_HOURS = 12;
     73 
     74     protected final Uri mSliceUri;
     75     protected final Uri mDateUri;
     76     protected final Uri mAlarmUri;
     77     protected final Uri mDndUri;
     78     private final Date mCurrentTime = new Date();
     79     private final Handler mHandler;
     80     private final AlarmManager.OnAlarmListener mUpdateNextAlarm = this::updateNextAlarm;
     81     private ZenModeController mZenModeController;
     82     private String mDatePattern;
     83     private DateFormat mDateFormat;
     84     private String mLastText;
     85     private boolean mRegistered;
     86     private String mNextAlarm;
     87     private NextAlarmController mNextAlarmController;
     88     protected AlarmManager mAlarmManager;
     89     protected ContentResolver mContentResolver;
     90     private AlarmManager.AlarmClockInfo mNextAlarmInfo;
     91 
     92     /**
     93      * Receiver responsible for time ticking and updating the date format.
     94      */
     95     @VisibleForTesting
     96     final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
     97         @Override
     98         public void onReceive(Context context, Intent intent) {
     99             final String action = intent.getAction();
    100             if (Intent.ACTION_TIME_TICK.equals(action)
    101                     || Intent.ACTION_DATE_CHANGED.equals(action)
    102                     || Intent.ACTION_TIME_CHANGED.equals(action)
    103                     || Intent.ACTION_TIMEZONE_CHANGED.equals(action)
    104                     || Intent.ACTION_LOCALE_CHANGED.equals(action)) {
    105                 if (Intent.ACTION_LOCALE_CHANGED.equals(action)
    106                         || Intent.ACTION_TIMEZONE_CHANGED.equals(action)) {
    107                     // need to get a fresh date format
    108                     mHandler.post(KeyguardSliceProvider.this::cleanDateFormat);
    109                 }
    110                 mHandler.post(KeyguardSliceProvider.this::updateClock);
    111             }
    112         }
    113     };
    114 
    115     public KeyguardSliceProvider() {
    116         this(new Handler());
    117     }
    118 
    119     @VisibleForTesting
    120     KeyguardSliceProvider(Handler handler) {
    121         mHandler = handler;
    122         mSliceUri = Uri.parse(KEYGUARD_SLICE_URI);
    123         mDateUri = Uri.parse(KEYGUARD_DATE_URI);
    124         mAlarmUri = Uri.parse(KEYGUARD_NEXT_ALARM_URI);
    125         mDndUri = Uri.parse(KEYGUARD_DND_URI);
    126     }
    127 
    128     @Override
    129     public Slice onBindSlice(Uri sliceUri) {
    130         ListBuilder builder = new ListBuilder(getContext(), mSliceUri);
    131         builder.addRow(new RowBuilder(builder, mDateUri).setTitle(mLastText));
    132         addNextAlarm(builder);
    133         addZenMode(builder);
    134         addPrimaryAction(builder);
    135         return builder.build();
    136     }
    137 
    138     protected void addPrimaryAction(ListBuilder builder) {
    139         // Add simple action because API requires it; Keyguard handles presenting
    140         // its own slices so this action + icon are actually never used.
    141         PendingIntent pi = PendingIntent.getActivity(getContext(), 0, new Intent(), 0);
    142         Icon icon = Icon.createWithResource(getContext(), R.drawable.ic_access_alarms_big);
    143         SliceAction action = new SliceAction(pi, icon, mLastText);
    144 
    145         RowBuilder primaryActionRow = new RowBuilder(builder, Uri.parse(KEYGUARD_ACTION_URI))
    146             .setPrimaryAction(action);
    147         builder.addRow(primaryActionRow);
    148     }
    149 
    150     protected void addNextAlarm(ListBuilder builder) {
    151         if (TextUtils.isEmpty(mNextAlarm)) {
    152             return;
    153         }
    154 
    155         Icon alarmIcon = Icon.createWithResource(getContext(), R.drawable.ic_access_alarms_big);
    156         RowBuilder alarmRowBuilder = new RowBuilder(builder, mAlarmUri)
    157                 .setTitle(mNextAlarm)
    158                 .addEndItem(alarmIcon);
    159         builder.addRow(alarmRowBuilder);
    160     }
    161 
    162     /**
    163      * Add zen mode (DND) icon to slice if it's enabled.
    164      * @param builder The slice builder.
    165      */
    166     protected void addZenMode(ListBuilder builder) {
    167         if (!isDndSuppressingNotifications()) {
    168             return;
    169         }
    170         RowBuilder dndBuilder = new RowBuilder(builder, mDndUri)
    171                 .setContentDescription(getContext().getResources()
    172                         .getString(R.string.accessibility_quick_settings_dnd))
    173                 .addEndItem(Icon.createWithResource(getContext(), R.drawable.stat_sys_dnd));
    174         builder.addRow(dndBuilder);
    175     }
    176 
    177     /**
    178      * Return true if DND is enabled suppressing notifications.
    179      */
    180     protected boolean isDndSuppressingNotifications() {
    181         boolean suppressingNotifications = (mZenModeController.getConfig().suppressedVisualEffects
    182                 & NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST) != 0;
    183         return mZenModeController.getZen() != Settings.Global.ZEN_MODE_OFF
    184                 && suppressingNotifications;
    185     }
    186 
    187     @Override
    188     public boolean onCreateSliceProvider() {
    189         mAlarmManager = getContext().getSystemService(AlarmManager.class);
    190         mContentResolver = getContext().getContentResolver();
    191         mNextAlarmController = new NextAlarmControllerImpl(getContext());
    192         mNextAlarmController.addCallback(this);
    193         mZenModeController = new ZenModeControllerImpl(getContext(), mHandler);
    194         mZenModeController.addCallback(this);
    195         mDatePattern = getContext().getString(R.string.system_ui_aod_date_pattern);
    196         registerClockUpdate();
    197         updateClock();
    198         return true;
    199     }
    200 
    201     @Override
    202     public void onZenChanged(int zen) {
    203         mContentResolver.notifyChange(mSliceUri, null /* observer */);
    204     }
    205 
    206     @Override
    207     public void onConfigChanged(ZenModeConfig config) {
    208         mContentResolver.notifyChange(mSliceUri, null /* observer */);
    209     }
    210 
    211     private void updateNextAlarm() {
    212         if (withinNHours(mNextAlarmInfo, ALARM_VISIBILITY_HOURS)) {
    213             String pattern = android.text.format.DateFormat.is24HourFormat(getContext(),
    214                     ActivityManager.getCurrentUser()) ? "H:mm" : "h:mm";
    215             mNextAlarm = android.text.format.DateFormat.format(pattern,
    216                     mNextAlarmInfo.getTriggerTime()).toString();
    217         } else {
    218             mNextAlarm = "";
    219         }
    220         mContentResolver.notifyChange(mSliceUri, null /* observer */);
    221     }
    222 
    223     private boolean withinNHours(AlarmManager.AlarmClockInfo alarmClockInfo, int hours) {
    224         if (alarmClockInfo == null) {
    225             return false;
    226         }
    227 
    228         long limit = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(hours);
    229         return mNextAlarmInfo.getTriggerTime() <= limit;
    230     }
    231 
    232     /**
    233      * Registers a broadcast receiver for clock updates, include date, time zone and manually
    234      * changing the date/time via the settings app.
    235      */
    236     private void registerClockUpdate() {
    237         if (mRegistered) {
    238             return;
    239         }
    240 
    241         IntentFilter filter = new IntentFilter();
    242         filter.addAction(Intent.ACTION_DATE_CHANGED);
    243         filter.addAction(Intent.ACTION_TIME_CHANGED);
    244         filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
    245         filter.addAction(Intent.ACTION_LOCALE_CHANGED);
    246         getContext().registerReceiver(mIntentReceiver, filter, null /* permission*/,
    247                 null /* scheduler */);
    248         mRegistered = true;
    249     }
    250 
    251     @VisibleForTesting
    252     boolean isRegistered() {
    253         return mRegistered;
    254     }
    255 
    256     protected void updateClock() {
    257         final String text = getFormattedDate();
    258         if (!text.equals(mLastText)) {
    259             mLastText = text;
    260             mContentResolver.notifyChange(mSliceUri, null /* observer */);
    261         }
    262     }
    263 
    264     protected String getFormattedDate() {
    265         if (mDateFormat == null) {
    266             final Locale l = Locale.getDefault();
    267             DateFormat format = DateFormat.getInstanceForSkeleton(mDatePattern, l);
    268             format.setContext(DisplayContext.CAPITALIZATION_FOR_STANDALONE);
    269             mDateFormat = format;
    270         }
    271         mCurrentTime.setTime(System.currentTimeMillis());
    272         return mDateFormat.format(mCurrentTime);
    273     }
    274 
    275     @VisibleForTesting
    276     void cleanDateFormat() {
    277         mDateFormat = null;
    278     }
    279 
    280     @Override
    281     public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) {
    282         mNextAlarmInfo = nextAlarm;
    283         mAlarmManager.cancel(mUpdateNextAlarm);
    284 
    285         long triggerAt = mNextAlarmInfo == null ? -1 : mNextAlarmInfo.getTriggerTime()
    286                 - TimeUnit.HOURS.toMillis(ALARM_VISIBILITY_HOURS);
    287         if (triggerAt > 0) {
    288             mAlarmManager.setExact(AlarmManager.RTC, triggerAt, "lock_screen_next_alarm",
    289                     mUpdateNextAlarm, mHandler);
    290         }
    291         updateNextAlarm();
    292     }
    293 }
    294