/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License
 */

package com.android.systemui.keyguard;

import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.drawable.Icon;
import android.icu.text.DateFormat;
import android.icu.text.DisplayContext;
import android.net.Uri;
import android.os.Handler;
import android.provider.Settings;
import android.service.notification.ZenModeConfig;
import android.text.TextUtils;

import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
import com.android.systemui.statusbar.policy.NextAlarmController;
import com.android.systemui.statusbar.policy.NextAlarmControllerImpl;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.statusbar.policy.ZenModeControllerImpl;

import java.util.Date;
import java.util.Locale;
import java.util.concurrent.TimeUnit;

import androidx.slice.Slice;
import androidx.slice.SliceProvider;
import androidx.slice.builders.ListBuilder;
import androidx.slice.builders.ListBuilder.RowBuilder;
import androidx.slice.builders.SliceAction;

Simple Slice provider that shows the current date.
/** * Simple Slice provider that shows the current date. */
public class KeyguardSliceProvider extends SliceProvider implements NextAlarmController.NextAlarmChangeCallback, ZenModeController.Callback { public static final String KEYGUARD_SLICE_URI = "content://com.android.systemui.keyguard/main"; public static final String KEYGUARD_DATE_URI = "content://com.android.systemui.keyguard/date"; public static final String KEYGUARD_NEXT_ALARM_URI = "content://com.android.systemui.keyguard/alarm"; public static final String KEYGUARD_DND_URI = "content://com.android.systemui.keyguard/dnd"; public static final String KEYGUARD_ACTION_URI = "content://com.android.systemui.keyguard/action";
Only show alarms that will ring within N hours.
/** * Only show alarms that will ring within N hours. */
@VisibleForTesting static final int ALARM_VISIBILITY_HOURS = 12; protected final Uri mSliceUri; protected final Uri mDateUri; protected final Uri mAlarmUri; protected final Uri mDndUri; private final Date mCurrentTime = new Date(); private final Handler mHandler; private final AlarmManager.OnAlarmListener mUpdateNextAlarm = this::updateNextAlarm; private ZenModeController mZenModeController; private String mDatePattern; private DateFormat mDateFormat; private String mLastText; private boolean mRegistered; private String mNextAlarm; private NextAlarmController mNextAlarmController; protected AlarmManager mAlarmManager; protected ContentResolver mContentResolver; private AlarmManager.AlarmClockInfo mNextAlarmInfo;
Receiver responsible for time ticking and updating the date format.
/** * Receiver responsible for time ticking and updating the date format. */
@VisibleForTesting final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); if (Intent.ACTION_TIME_TICK.equals(action) || Intent.ACTION_DATE_CHANGED.equals(action) || Intent.ACTION_TIME_CHANGED.equals(action) || Intent.ACTION_TIMEZONE_CHANGED.equals(action) || Intent.ACTION_LOCALE_CHANGED.equals(action)) { if (Intent.ACTION_LOCALE_CHANGED.equals(action) || Intent.ACTION_TIMEZONE_CHANGED.equals(action)) { // need to get a fresh date format mHandler.post(KeyguardSliceProvider.this::cleanDateFormat); } mHandler.post(KeyguardSliceProvider.this::updateClock); } } }; public KeyguardSliceProvider() { this(new Handler()); } @VisibleForTesting KeyguardSliceProvider(Handler handler) { mHandler = handler; mSliceUri = Uri.parse(KEYGUARD_SLICE_URI); mDateUri = Uri.parse(KEYGUARD_DATE_URI); mAlarmUri = Uri.parse(KEYGUARD_NEXT_ALARM_URI); mDndUri = Uri.parse(KEYGUARD_DND_URI); } @Override public Slice onBindSlice(Uri sliceUri) { ListBuilder builder = new ListBuilder(getContext(), mSliceUri); builder.addRow(new RowBuilder(builder, mDateUri).setTitle(mLastText)); addNextAlarm(builder); addZenMode(builder); addPrimaryAction(builder); return builder.build(); } protected void addPrimaryAction(ListBuilder builder) { // Add simple action because API requires it; Keyguard handles presenting // its own slices so this action + icon are actually never used. PendingIntent pi = PendingIntent.getActivity(getContext(), 0, new Intent(), 0); Icon icon = Icon.createWithResource(getContext(), R.drawable.ic_access_alarms_big); SliceAction action = new SliceAction(pi, icon, mLastText); RowBuilder primaryActionRow = new RowBuilder(builder, Uri.parse(KEYGUARD_ACTION_URI)) .setPrimaryAction(action); builder.addRow(primaryActionRow); } protected void addNextAlarm(ListBuilder builder) { if (TextUtils.isEmpty(mNextAlarm)) { return; } Icon alarmIcon = Icon.createWithResource(getContext(), R.drawable.ic_access_alarms_big); RowBuilder alarmRowBuilder = new RowBuilder(builder, mAlarmUri) .setTitle(mNextAlarm) .addEndItem(alarmIcon); builder.addRow(alarmRowBuilder); }
Add zen mode (DND) icon to slice if it's enabled.
Params:
  • builder – The slice builder.
/** * Add zen mode (DND) icon to slice if it's enabled. * @param builder The slice builder. */
protected void addZenMode(ListBuilder builder) { if (!isDndSuppressingNotifications()) { return; } RowBuilder dndBuilder = new RowBuilder(builder, mDndUri) .setContentDescription(getContext().getResources() .getString(R.string.accessibility_quick_settings_dnd)) .addEndItem(Icon.createWithResource(getContext(), R.drawable.stat_sys_dnd)); builder.addRow(dndBuilder); }
Return true if DND is enabled suppressing notifications.
/** * Return true if DND is enabled suppressing notifications. */
protected boolean isDndSuppressingNotifications() { boolean suppressingNotifications = (mZenModeController.getConfig().suppressedVisualEffects & NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST) != 0; return mZenModeController.getZen() != Settings.Global.ZEN_MODE_OFF && suppressingNotifications; } @Override public boolean onCreateSliceProvider() { mAlarmManager = getContext().getSystemService(AlarmManager.class); mContentResolver = getContext().getContentResolver(); mNextAlarmController = new NextAlarmControllerImpl(getContext()); mNextAlarmController.addCallback(this); mZenModeController = new ZenModeControllerImpl(getContext(), mHandler); mZenModeController.addCallback(this); mDatePattern = getContext().getString(R.string.system_ui_aod_date_pattern); registerClockUpdate(); updateClock(); return true; } @Override public void onZenChanged(int zen) { mContentResolver.notifyChange(mSliceUri, null /* observer */); } @Override public void onConfigChanged(ZenModeConfig config) { mContentResolver.notifyChange(mSliceUri, null /* observer */); } private void updateNextAlarm() { if (withinNHours(mNextAlarmInfo, ALARM_VISIBILITY_HOURS)) { String pattern = android.text.format.DateFormat.is24HourFormat(getContext(), ActivityManager.getCurrentUser()) ? "H:mm" : "h:mm"; mNextAlarm = android.text.format.DateFormat.format(pattern, mNextAlarmInfo.getTriggerTime()).toString(); } else { mNextAlarm = ""; } mContentResolver.notifyChange(mSliceUri, null /* observer */); } private boolean withinNHours(AlarmManager.AlarmClockInfo alarmClockInfo, int hours) { if (alarmClockInfo == null) { return false; } long limit = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(hours); return mNextAlarmInfo.getTriggerTime() <= limit; }
Registers a broadcast receiver for clock updates, include date, time zone and manually changing the date/time via the settings app.
/** * Registers a broadcast receiver for clock updates, include date, time zone and manually * changing the date/time via the settings app. */
private void registerClockUpdate() { if (mRegistered) { return; } IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_DATE_CHANGED); filter.addAction(Intent.ACTION_TIME_CHANGED); filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); filter.addAction(Intent.ACTION_LOCALE_CHANGED); getContext().registerReceiver(mIntentReceiver, filter, null /* permission*/, null /* scheduler */); mRegistered = true; } @VisibleForTesting boolean isRegistered() { return mRegistered; } protected void updateClock() { final String text = getFormattedDate(); if (!text.equals(mLastText)) { mLastText = text; mContentResolver.notifyChange(mSliceUri, null /* observer */); } } protected String getFormattedDate() { if (mDateFormat == null) { final Locale l = Locale.getDefault(); DateFormat format = DateFormat.getInstanceForSkeleton(mDatePattern, l); format.setContext(DisplayContext.CAPITALIZATION_FOR_STANDALONE); mDateFormat = format; } mCurrentTime.setTime(System.currentTimeMillis()); return mDateFormat.format(mCurrentTime); } @VisibleForTesting void cleanDateFormat() { mDateFormat = null; } @Override public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) { mNextAlarmInfo = nextAlarm; mAlarmManager.cancel(mUpdateNextAlarm); long triggerAt = mNextAlarmInfo == null ? -1 : mNextAlarmInfo.getTriggerTime() - TimeUnit.HOURS.toMillis(ALARM_VISIBILITY_HOURS); if (triggerAt > 0) { mAlarmManager.setExact(AlarmManager.RTC, triggerAt, "lock_screen_next_alarm", mUpdateNextAlarm, mHandler); } updateNextAlarm(); } }