com.stasbar.knowyourself.DeskClock.java Source code

Java tutorial

Introduction

Here is the source code for com.stasbar.knowyourself.DeskClock.java

Source

/*
 * Copyright (C) 2009 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.stasbar.knowyourself;

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.ContentObserver;
import android.media.AudioManager;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.provider.Settings;
import android.support.annotation.StringRes;
import android.support.design.widget.Snackbar;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;

import com.stasbar.knowyourself.actionbarmenu.MenuItemControllerFactory;
import com.stasbar.knowyourself.actionbarmenu.OptionsMenuManager;
import com.stasbar.knowyourself.actionbarmenu.SettingsMenuItemController;
import com.stasbar.knowyourself.alarms.AlarmStateManager;
import com.stasbar.knowyourself.data.DataModel;
import com.stasbar.knowyourself.widget.toast.SnackbarManager;

import static android.app.NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED;
import static android.app.NotificationManager.INTERRUPTION_FILTER_NONE;
import static android.media.AudioManager.FLAG_SHOW_UI;
import static android.media.AudioManager.STREAM_ALARM;
import static android.media.RingtoneManager.TYPE_ALARM;
import static android.provider.Settings.System.CONTENT_URI;
import static android.provider.Settings.System.DEFAULT_ALARM_ALERT_URI;
import static android.text.format.DateUtils.SECOND_IN_MILLIS;

/**
 * The main activity of the application which displays 4 different tabs contains alarms, world
 * clocks, timers and a stopwatch.
 */
public class DeskClock extends BaseActivity {

    /**
     * The Uri to the settings entry that stores alarm stream volume.
     */
    private static final Uri VOLUME_URI = Uri.withAppendedPath(CONTENT_URI, "volume_alarm_speaker");

    /**
     * The intent filter that identifies do-not-disturb change broadcasts.
     */
    @SuppressLint("NewApi")
    private static final IntentFilter DND_CHANGE_FILTER = new IntentFilter(ACTION_INTERRUPTION_FILTER_CHANGED);
    private static final String TAG = DeskClock.class.getName();

    /**
     * Coordinates handling of context menu items.
     */
    private final OptionsMenuManager mOptionsMenuManager = new OptionsMenuManager();

    /**
     * Displays a snackbar explaining that the system default alarm ringtone is silent.
     */
    private final Runnable mShowSilentAlarmSnackbarRunnable = new ShowSilentAlarmSnackbarRunnable();

    /**
     * Observes default alarm ringtone changes while the app is in the foreground.
     */
    private final ContentObserver mAlarmRingtoneChangeObserver = new AlarmRingtoneChangeObserver();

    /**
     * Displays a snackbar explaining that the alarm volume is muted.
     */
    private final Runnable mShowMutedVolumeSnackbarRunnable = new ShowMutedVolumeSnackbarRunnable();

    /**
     * Observes alarm volume changes while the app is in the foreground.
     */
    private final ContentObserver mAlarmVolumeChangeObserver = new AlarmVolumeChangeObserver();

    /**
     * Displays a snackbar explaining that do-not-disturb is blocking alarms.
     */
    private final Runnable mShowDNDBlockingSnackbarRunnable = new ShowDNDBlockingSnackbarRunnable();

    /**
     * Observes do-not-disturb changes while the app is in the foreground.
     */
    private final BroadcastReceiver mDoNotDisturbChangeReceiver = new DoNotDisturbChangeReceiver();

    /**
     * Used to query the alarm volume and display the system control to change the alarm volume.
     */
    private AudioManager mAudioManager;

    /**
     * Used to query the do-not-disturb setting value, also called "interruption filter".
     */
    private NotificationManager mNotificationManager;

    /**
     * {@code true} permits the muted alarm volume snackbar to show when starting this activity.
     */
    private boolean mShowSilencedAlarmsSnackbar;

    /**
     * The view to which snackbar items are anchored.
     */
    private View mSnackbarAnchor;

    /**
     * {@code true} when a settings change necessitates recreating this activity.
     */
    private boolean mRecreateActivity;

    @Override
    public void onNewIntent(Intent newIntent) {
        super.onNewIntent(newIntent);

        // Fragments may query the latest intent for information, so update the intent.
        setIntent(newIntent);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.desk_clock);

        mAudioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
        mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);

        // Don't show the volume muted snackbar on rotations.
        mShowSilencedAlarmsSnackbar = savedInstanceState == null;
        mSnackbarAnchor = findViewById(R.id.coordinator);

        // Configure the toolbar.
        final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        getSupportActionBar().setDisplayShowTitleEnabled(false);

        // Configure the menu item controllers add behavior to the toolbar.
        mOptionsMenuManager.addMenuItemController(new SettingsMenuItemController(this))
                .addMenuItemController(MenuItemControllerFactory.getInstance().buildMenuItemControllers(this));

        // Inflate the menu during creation to avoid a double layout pass. Otherwise, the menu
        // inflation occurs *after* the initial draw and a second layout pass adds in the menu.
        onCreateOptionsMenu(toolbar.getMenu());

        // Update the next alarm time on app startup because the user might have altered the data.
        AlarmStateManager.updateNextAlarm(this);

        if (savedInstanceState == null) {
            // Set the background color to initially match the theme value so that we can
            // smoothly transition to the dynamic color.
            final int backgroundColor = ContextCompat.getColor(this, R.color.default_background);
            adjustAppColor(backgroundColor, false /* animate */);
        }
    }

    @Override
    protected void onStart() {
        super.onStart();

        if (mShowSilencedAlarmsSnackbar) {
            if (isDoNotDisturbBlockingAlarms()) {
                mSnackbarAnchor.postDelayed(mShowDNDBlockingSnackbarRunnable, SECOND_IN_MILLIS);
            } else if (isAlarmStreamMuted()) {
                mSnackbarAnchor.postDelayed(mShowMutedVolumeSnackbarRunnable, SECOND_IN_MILLIS);
            } else if (isSystemAlarmRingtoneSilent()) {
                mSnackbarAnchor.postDelayed(mShowSilentAlarmSnackbarRunnable, SECOND_IN_MILLIS);
            }
        }

        // Subsequent starts of this activity should show the snackbar by default.
        mShowSilencedAlarmsSnackbar = true;

        final ContentResolver cr = getContentResolver();
        // Watch for system alarm ringtone changes while the app is in the foreground.
        cr.registerContentObserver(DEFAULT_ALARM_ALERT_URI, false, mAlarmRingtoneChangeObserver);

        // Watch for alarm volume changes while the app is in the foreground.
        cr.registerContentObserver(VOLUME_URI, false, mAlarmVolumeChangeObserver);

        if (Utils.isMOrLater()) {
            // Watch for do-not-disturb changes while the app is in the foreground.
            registerReceiver(mDoNotDisturbChangeReceiver, DND_CHANGE_FILTER);
        }

        DataModel.getDataModel().setApplicationInForeground(true);
    }

    @Override
    protected void onPostResume() {
        super.onPostResume();

        if (mRecreateActivity) {
            mRecreateActivity = false;
        }
    }

    @Override
    protected void onStop() {
        LogUtils.d(TAG, "onStop");
        if (!isChangingConfigurations()) {
            LogUtils.d(TAG, "onStop setApplicationInForeground -> FALSE");
            DataModel.getDataModel().setApplicationInForeground(false);
        }

        // Stop watching for system alarm ringtone changes while the app is in the background.
        getContentResolver().unregisterContentObserver(mAlarmRingtoneChangeObserver);

        // Stop watching for alarm volume changes while the app is in the background.
        getContentResolver().unregisterContentObserver(mAlarmVolumeChangeObserver);

        if (Utils.isMOrLater()) {
            // Stop watching for do-not-disturb changes while the app is in the background.
            unregisterReceiver(mDoNotDisturbChangeReceiver);
        }

        // Remove any scheduled work to show snackbars; it is no longer relevant.
        mSnackbarAnchor.removeCallbacks(mShowSilentAlarmSnackbarRunnable);
        mSnackbarAnchor.removeCallbacks(mShowDNDBlockingSnackbarRunnable);
        mSnackbarAnchor.removeCallbacks(mShowMutedVolumeSnackbarRunnable);
        super.onStop();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        mOptionsMenuManager.onCreateOptionsMenu(menu);
        return true;
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        super.onPrepareOptionsMenu(menu);
        mOptionsMenuManager.onPrepareOptionsMenu(menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        return mOptionsMenuManager.onOptionsItemSelected(item) || super.onOptionsItemSelected(item);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        // Recreate the activity if any settings have been changed
        if (requestCode == SettingsMenuItemController.REQUEST_CHANGE_SETTINGS && resultCode == RESULT_OK) {
            mRecreateActivity = true;
        }
    }

    private boolean isSystemAlarmRingtoneSilent() {
        return RingtoneManager.getActualDefaultRingtoneUri(this, TYPE_ALARM) == null;
    }

    private void showSilentRingtoneSnackbar() {
        final OnClickListener changeClickListener = new OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(Settings.ACTION_SOUND_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
            }
        };

        SnackbarManager.show(createSnackbar(R.string.silent_default_alarm_ringtone)
                .setAction(R.string.change_default_alarm_ringtone, changeClickListener));
    }

    private boolean isAlarmStreamMuted() {
        return mAudioManager.getStreamVolume(STREAM_ALARM) <= 0;
    }

    private void showAlarmVolumeMutedSnackbar() {
        final OnClickListener unmuteClickListener = new OnClickListener() {
            @Override
            public void onClick(View v) {
                // Set the alarm volume to ~30% of max and show the slider UI.
                final int index = mAudioManager.getStreamMaxVolume(STREAM_ALARM) / 3;
                mAudioManager.setStreamVolume(STREAM_ALARM, index, FLAG_SHOW_UI);
            }
        };

        SnackbarManager.show(createSnackbar(R.string.alarm_volume_muted).setAction(R.string.unmute_alarm_volume,
                unmuteClickListener));
    }

    @TargetApi(Build.VERSION_CODES.M)
    private boolean isDoNotDisturbBlockingAlarms() {
        if (!Utils.isMOrLater()) {
            return false;
        }
        return mNotificationManager.getCurrentInterruptionFilter() == INTERRUPTION_FILTER_NONE;
    }

    private void showDoNotDisturbIsBlockingAlarmsSnackbar() {
        SnackbarManager.show(createSnackbar(R.string.alarms_blocked_by_dnd));
    }

    /**
     * @return a Snackbar that displays the message with the given id for 5 seconds
     */
    private Snackbar createSnackbar(@StringRes int messageId) {
        return Snackbar.make(mSnackbarAnchor, messageId, 5000 /* duration */);
    }

    /**
     * Displays a snackbar that indicates the system default alarm ringtone currently silent and
     * offers an action that displays the system alarm ringtone setting to adjust it.
     */
    private final class ShowSilentAlarmSnackbarRunnable implements Runnable {
        @Override
        public void run() {
            showSilentRingtoneSnackbar();
        }
    }

    /**
     * Displays a snackbar that indicates the alarm volume is currently muted and offers an action
     * that displays the system volume control to adjust it.
     */
    private final class ShowMutedVolumeSnackbarRunnable implements Runnable {
        @Override
        public void run() {
            showAlarmVolumeMutedSnackbar();
        }
    }

    /**
     * Displays a snackbar that indicates the do-not-disturb setting is currently blocking alarms.
     */
    private final class ShowDNDBlockingSnackbarRunnable implements Runnable {
        @Override
        public void run() {
            showDoNotDisturbIsBlockingAlarmsSnackbar();
        }
    }

    /**
     * Observe changes to the system default alarm ringtone while the application is in the
     * foreground and show/hide the snackbar that warns when the ringtone is silent.
     */
    private final class AlarmRingtoneChangeObserver extends ContentObserver {
        private AlarmRingtoneChangeObserver() {
            super(new Handler());
        }

        @Override
        public void onChange(boolean selfChange) {
            if (isSystemAlarmRingtoneSilent()) {
                showSilentRingtoneSnackbar();
            } else {
                SnackbarManager.dismiss();
            }
        }
    }

    /**
     * Observe changes to the alarm stream volume while the application is in the foreground and
     * show/hide the snackbar that warns when the alarm volume is muted.
     */
    private final class AlarmVolumeChangeObserver extends ContentObserver {
        private AlarmVolumeChangeObserver() {
            super(new Handler());
        }

        @Override
        public void onChange(boolean selfChange) {
            if (isAlarmStreamMuted()) {
                showAlarmVolumeMutedSnackbar();
            } else {
                SnackbarManager.dismiss();
            }
        }
    }

    /**
     * Observe changes to the do-not-disturb setting while the application is in the foreground
     * and show/hide the snackbar that warns when the setting is blocking alarms.
     */
    private final class DoNotDisturbChangeReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (isDoNotDisturbBlockingAlarms()) {
                showDoNotDisturbIsBlockingAlarmsSnackbar();
            } else {
                SnackbarManager.dismiss();
            }
        }
    }

}