| /* |
| * Copyright (C) 2015 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.deskclock.alarms; |
| |
| import android.content.ContentResolver; |
| import android.content.Context; |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.text.format.DateFormat; |
| import android.view.View; |
| import android.view.ViewGroup; |
| |
| import com.android.deskclock.AlarmUtils; |
| import com.android.deskclock.R; |
| import com.android.deskclock.events.Events; |
| import com.android.deskclock.provider.Alarm; |
| import com.android.deskclock.provider.AlarmInstance; |
| import com.android.deskclock.widget.toast.SnackbarManager; |
| import com.google.android.material.snackbar.Snackbar; |
| |
| import java.util.Calendar; |
| import java.util.List; |
| import java.util.concurrent.ExecutorService; |
| import java.util.concurrent.Executors; |
| |
| /** |
| * API for asynchronously mutating a single alarm. |
| */ |
| public final class AlarmUpdateHandler { |
| |
| private final Context mAppContext; |
| private final ScrollHandler mScrollHandler; |
| private final View mSnackbarAnchor; |
| |
| // For undo |
| private Alarm mDeletedAlarm; |
| |
| public AlarmUpdateHandler(Context context, ScrollHandler scrollHandler, |
| ViewGroup snackbarAnchor) { |
| mAppContext = context.getApplicationContext(); |
| mScrollHandler = scrollHandler; |
| mSnackbarAnchor = snackbarAnchor; |
| } |
| |
| /** |
| * Adds a new alarm on the background. |
| * |
| * @param alarm The alarm to be added. |
| */ |
| public void asyncAddAlarm(final Alarm alarm) { |
| ExecutorService executor = Executors.newSingleThreadExecutor(); |
| Handler handler = new Handler(Looper.getMainLooper()); |
| executor.execute(() -> { |
| AlarmInstance instance = null; |
| if (alarm != null) { |
| Events.sendAlarmEvent(R.string.action_create, R.string.label_deskclock); |
| ContentResolver cr = mAppContext.getContentResolver(); |
| |
| // Add alarm to db |
| Alarm newAlarm = Alarm.addAlarm(cr, alarm); |
| |
| // Be ready to scroll to this alarm on UI later. |
| mScrollHandler.setSmoothScrollStableId(newAlarm.id); |
| |
| // Create and add instance to db |
| if (newAlarm.enabled) { |
| instance = setupAlarmInstance(newAlarm); |
| } |
| } |
| |
| final AlarmInstance finalInstance = instance; |
| handler.post(() -> { |
| if (finalInstance != null) { |
| AlarmUtils.popAlarmSetSnackbar( |
| mSnackbarAnchor, finalInstance.getAlarmTime().getTimeInMillis()); |
| } |
| }); |
| }); |
| } |
| |
| /** |
| * Modifies an alarm on the background, and optionally show a toast when done. |
| * |
| * @param alarm The alarm to be modified. |
| * @param popToast whether or not a toast should be displayed when done. |
| * @param minorUpdate if true, don't affect any currently snoozed instances. |
| */ |
| public void asyncUpdateAlarm(final Alarm alarm, final boolean popToast, |
| final boolean minorUpdate) { |
| ExecutorService executor = Executors.newSingleThreadExecutor(); |
| Handler handler = new Handler(Looper.getMainLooper()); |
| executor.execute(() -> { |
| ContentResolver cr = mAppContext.getContentResolver(); |
| |
| // Update alarm |
| Alarm.updateAlarm(cr, alarm); |
| |
| if (minorUpdate) { |
| // just update the instance in the database and update notifications. |
| final List<AlarmInstance> instanceList = |
| AlarmInstance.getInstancesByAlarmId(cr, alarm.id); |
| for (AlarmInstance instance : instanceList) { |
| // Make a copy of the existing instance |
| final AlarmInstance newInstance = new AlarmInstance(instance); |
| // Copy over minor change data to the instance; we don't know |
| // exactly which minor field changed, so just copy them all. |
| newInstance.mVibrate = alarm.vibrate; |
| newInstance.mRingtone = alarm.alert; |
| newInstance.mLabel = alarm.label; |
| // Since we copied the mId of the old instance and the mId is used |
| // as the primary key in the AlarmInstance table, this will replace |
| // the existing instance. |
| AlarmInstance.updateInstance(cr, newInstance); |
| // Update the notification for this instance. |
| AlarmNotifications.updateNotification(mAppContext, newInstance); |
| } |
| return; |
| } |
| // Otherwise, this is a major update and we're going to re-create the alarm |
| AlarmStateManager.deleteAllInstances(mAppContext, alarm.id); |
| |
| final AlarmInstance finalInstance = alarm.enabled ? setupAlarmInstance(alarm) : null; |
| |
| handler.post(() -> { |
| if (popToast && finalInstance != null) { |
| AlarmUtils.popAlarmSetSnackbar( |
| mSnackbarAnchor, finalInstance.getAlarmTime().getTimeInMillis()); |
| } |
| }); |
| }); |
| } |
| |
| /** |
| * Deletes an alarm on the background. |
| * |
| * @param alarm The alarm to be deleted. |
| */ |
| public void asyncDeleteAlarm(final Alarm alarm) { |
| ExecutorService executor = Executors.newSingleThreadExecutor(); |
| Handler handler = new Handler(Looper.getMainLooper()); |
| executor.execute(() -> { |
| // Activity may be closed at this point , make sure data is still valid |
| if (alarm == null) { |
| // Nothing to do here, just return. |
| return; |
| } |
| AlarmStateManager.deleteAllInstances(mAppContext, alarm.id); |
| final boolean deleted = Alarm.deleteAlarm(mAppContext.getContentResolver(), alarm.id); |
| |
| handler.post(() -> { |
| if (deleted) { |
| mDeletedAlarm = alarm; |
| showUndoBar(); |
| } |
| }); |
| }); |
| } |
| |
| /** |
| * Show a toast when an alarm is predismissed. |
| * |
| * @param instance Instance being predismissed. |
| */ |
| public void showPredismissToast(AlarmInstance instance) { |
| final String time = DateFormat.getTimeFormat(mAppContext).format( |
| instance.getAlarmTime().getTime()); |
| final String text = mAppContext.getString(R.string.alarm_is_dismissed, time); |
| SnackbarManager.show(Snackbar.make(mSnackbarAnchor, text, Snackbar.LENGTH_SHORT)); |
| } |
| |
| /** |
| * Hides any undo toast. |
| */ |
| public void hideUndoBar() { |
| mDeletedAlarm = null; |
| SnackbarManager.dismiss(); |
| } |
| |
| private void showUndoBar() { |
| final Alarm deletedAlarm = mDeletedAlarm; |
| final Snackbar snackbar = Snackbar.make(mSnackbarAnchor, |
| mAppContext.getString(R.string.alarm_deleted), Snackbar.LENGTH_LONG) |
| .setAction(R.string.alarm_undo, v -> { |
| mDeletedAlarm = null; |
| asyncAddAlarm(deletedAlarm); |
| }); |
| SnackbarManager.show(snackbar); |
| } |
| |
| private AlarmInstance setupAlarmInstance(Alarm alarm) { |
| final ContentResolver cr = mAppContext.getContentResolver(); |
| AlarmInstance newInstance = alarm.createInstanceAfter(Calendar.getInstance()); |
| newInstance = AlarmInstance.addInstance(cr, newInstance); |
| // Register instance to state manager |
| AlarmStateManager.registerInstance(mAppContext, newInstance, true); |
| return newInstance; |
| } |
| } |