Home | History | Annotate | Download | only in deskclock
      1 /*
      2  * Copyright (C) 2009 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.deskclock;
     18 
     19 import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
     20 
     21 import android.app.Activity;
     22 import android.app.AlarmManager;
     23 import android.app.PendingIntent;
     24 import android.app.UiModeManager;
     25 import android.content.BroadcastReceiver;
     26 import android.content.Context;
     27 import android.content.Intent;
     28 import android.content.IntentFilter;
     29 import android.content.pm.PackageManager;
     30 import android.content.res.Configuration;
     31 import android.content.res.Resources;
     32 import android.database.ContentObserver;
     33 import android.database.Cursor;
     34 import android.graphics.Rect;
     35 import android.graphics.drawable.Drawable;
     36 import android.net.Uri;
     37 import android.os.BatteryManager;
     38 import android.os.Bundle;
     39 import android.os.Handler;
     40 import android.os.Message;
     41 import android.provider.MediaStore;
     42 import android.provider.Settings;
     43 import android.text.TextUtils;
     44 import android.text.format.DateFormat;
     45 import android.util.DisplayMetrics;
     46 import android.util.Log;
     47 import android.view.GestureDetector;
     48 import android.view.Menu;
     49 import android.view.MenuInflater;
     50 import android.view.MenuItem;
     51 import android.view.MotionEvent;
     52 import android.view.View;
     53 import android.view.ViewGroup;
     54 import android.view.ViewTreeObserver;
     55 import android.view.Window;
     56 import android.view.WindowManager;
     57 import android.view.animation.AnimationUtils;
     58 import android.widget.AbsoluteLayout;
     59 import android.widget.ImageButton;
     60 import android.widget.ImageView;
     61 import android.widget.TextView;
     62 
     63 import java.util.Calendar;
     64 import java.util.Date;
     65 import java.util.Random;
     66 
     67 /**
     68  * DeskClock clock view for desk docks.
     69  */
     70 public class DeskClock extends Activity {
     71     private static final boolean DEBUG = false;
     72 
     73     private static final String LOG_TAG = "DeskClock";
     74 
     75     // Alarm action for midnight (so we can update the date display).
     76     private static final String ACTION_MIDNIGHT = "com.android.deskclock.MIDNIGHT";
     77 
     78     // This controls whether or not we will show a battery display when plugged
     79     // in.
     80     private static final boolean USE_BATTERY_DISPLAY = false;
     81 
     82     // Intent to broadcast for dock settings.
     83     private static final String DOCK_SETTINGS_ACTION = "com.android.settings.DOCK_SETTINGS";
     84 
     85     // Delay before engaging the burn-in protection mode (green-on-black).
     86     private final long SCREEN_SAVER_TIMEOUT = 5 * 60 * 1000; // 5 min
     87 
     88     // Repositioning delay in screen saver.
     89     public static final long SCREEN_SAVER_MOVE_DELAY = 60 * 1000; // 1 min
     90 
     91     // Color to use for text & graphics in screen saver mode.
     92     private int SCREEN_SAVER_COLOR = 0xFF006688;
     93     private int SCREEN_SAVER_COLOR_DIM = 0xFF001634;
     94 
     95     // Opacity of black layer between clock display and wallpaper.
     96     private final float DIM_BEHIND_AMOUNT_NORMAL = 0.4f;
     97     private final float DIM_BEHIND_AMOUNT_DIMMED = 0.8f; // higher contrast when display dimmed
     98 
     99     private final int SCREEN_SAVER_TIMEOUT_MSG   = 0x2000;
    100     private final int SCREEN_SAVER_MOVE_MSG      = 0x2001;
    101 
    102     // State variables follow.
    103     private DigitalClock mTime;
    104     private TextView mDate;
    105 
    106     private TextView mNextAlarm = null;
    107     private TextView mBatteryDisplay;
    108 
    109     private boolean mDimmed = false;
    110     private boolean mScreenSaverMode = false;
    111 
    112     private String mDateFormat;
    113 
    114     private int mBatteryLevel = -1;
    115     private boolean mPluggedIn = false;
    116 
    117     private boolean mLaunchedFromDock = false;
    118 
    119     private Random mRNG;
    120 
    121     private PendingIntent mMidnightIntent;
    122 
    123     private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
    124         @Override
    125         public void onReceive(Context context, Intent intent) {
    126             final String action = intent.getAction();
    127             if (DEBUG) Log.d(LOG_TAG, "mIntentReceiver.onReceive: action=" + action + ", intent=" + intent);
    128             if (Intent.ACTION_DATE_CHANGED.equals(action) || ACTION_MIDNIGHT.equals(action)) {
    129                 refreshDate();
    130             } else if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
    131                 handleBatteryUpdate(
    132                     intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0),
    133                     intent.getIntExtra(BatteryManager.EXTRA_STATUS, BATTERY_STATUS_UNKNOWN),
    134                     intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0));
    135             } else if (UiModeManager.ACTION_EXIT_DESK_MODE.equals(action)) {
    136                 if (mLaunchedFromDock) {
    137                     // moveTaskToBack(false);
    138                     finish();
    139                 }
    140                 mLaunchedFromDock = false;
    141             }
    142         }
    143     };
    144 
    145     private final Handler mHandy = new Handler() {
    146         @Override
    147         public void handleMessage(Message m) {
    148             if (m.what == SCREEN_SAVER_TIMEOUT_MSG) {
    149                 saveScreen();
    150             } else if (m.what == SCREEN_SAVER_MOVE_MSG) {
    151                 moveScreenSaver();
    152             }
    153         }
    154     };
    155 
    156     private View mAlarmButton;
    157 
    158     private void moveScreenSaver() {
    159         moveScreenSaverTo(-1,-1);
    160     }
    161     private void moveScreenSaverTo(int x, int y) {
    162         if (!mScreenSaverMode) return;
    163 
    164         final View saver_view = findViewById(R.id.saver_view);
    165 
    166         DisplayMetrics metrics = new DisplayMetrics();
    167         getWindowManager().getDefaultDisplay().getMetrics(metrics);
    168 
    169         if (x < 0 || y < 0) {
    170             int myWidth = saver_view.getMeasuredWidth();
    171             int myHeight = saver_view.getMeasuredHeight();
    172             x = (int)(mRNG.nextFloat()*(metrics.widthPixels - myWidth));
    173             y = (int)(mRNG.nextFloat()*(metrics.heightPixels - myHeight));
    174         }
    175 
    176         if (DEBUG) Log.d(LOG_TAG, String.format("screen saver: %d: jumping to (%d,%d)",
    177                 System.currentTimeMillis(), x, y));
    178 
    179         saver_view.setLayoutParams(new AbsoluteLayout.LayoutParams(
    180             ViewGroup.LayoutParams.WRAP_CONTENT,
    181             ViewGroup.LayoutParams.WRAP_CONTENT,
    182             x,
    183             y));
    184 
    185         // Synchronize our jumping so that it happens exactly on the second.
    186         mHandy.sendEmptyMessageDelayed(SCREEN_SAVER_MOVE_MSG,
    187             SCREEN_SAVER_MOVE_DELAY +
    188             (1000 - (System.currentTimeMillis() % 1000)));
    189     }
    190 
    191     private void setWakeLock(boolean hold) {
    192         if (DEBUG) Log.d(LOG_TAG, (hold ? "hold" : " releas") + "ing wake lock");
    193         Window win = getWindow();
    194         WindowManager.LayoutParams winParams = win.getAttributes();
    195         winParams.flags |= (WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
    196                 | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
    197                 | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
    198                 | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
    199         if (hold)
    200             winParams.flags |= WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
    201         else
    202             winParams.flags &= (~WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    203         win.setAttributes(winParams);
    204     }
    205 
    206     private void scheduleScreenSaver() {
    207         if (!getResources().getBoolean(R.bool.config_requiresScreenSaver)) {
    208             return;
    209         }
    210 
    211         // reschedule screen saver
    212         mHandy.removeMessages(SCREEN_SAVER_TIMEOUT_MSG);
    213         mHandy.sendMessageDelayed(
    214             Message.obtain(mHandy, SCREEN_SAVER_TIMEOUT_MSG),
    215             SCREEN_SAVER_TIMEOUT);
    216     }
    217 
    218     private void restoreScreen() {
    219         if (!mScreenSaverMode) return;
    220         if (DEBUG) Log.d(LOG_TAG, "restoreScreen");
    221         mScreenSaverMode = false;
    222 
    223         initViews();
    224         doDim(false); // restores previous dim mode
    225 
    226         scheduleScreenSaver();
    227 
    228         refreshAll();
    229     }
    230 
    231     // Special screen-saver mode for OLED displays that burn in quickly
    232     private void saveScreen() {
    233         if (mScreenSaverMode) return;
    234         if (DEBUG) Log.d(LOG_TAG, "saveScreen");
    235 
    236         // quickly stash away the x/y of the current date
    237         final View oldTimeDate = findViewById(R.id.time_date);
    238         int oldLoc[] = new int[2];
    239         oldTimeDate.getLocationOnScreen(oldLoc);
    240 
    241         mScreenSaverMode = true;
    242         Window win = getWindow();
    243         WindowManager.LayoutParams winParams = win.getAttributes();
    244         winParams.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN;
    245         win.setAttributes(winParams);
    246 
    247         // give up any internal focus before we switch layouts
    248         final View focused = getCurrentFocus();
    249         if (focused != null) focused.clearFocus();
    250 
    251         setContentView(R.layout.desk_clock_saver);
    252 
    253         mTime = (DigitalClock) findViewById(R.id.time);
    254         mDate = (TextView) findViewById(R.id.date);
    255 
    256         final int color = mDimmed ? SCREEN_SAVER_COLOR_DIM : SCREEN_SAVER_COLOR;
    257 
    258         ((AndroidClockTextView)findViewById(R.id.timeDisplay)).setTextColor(color);
    259         ((AndroidClockTextView)findViewById(R.id.am_pm)).setTextColor(color);
    260         mDate.setTextColor(color);
    261 
    262         mTime.setSystemUiVisibility(View.STATUS_BAR_HIDDEN);
    263 
    264         mBatteryDisplay = null;
    265 
    266         refreshDate();
    267         refreshAlarm();
    268 
    269         moveScreenSaverTo(oldLoc[0], oldLoc[1]);
    270     }
    271 
    272     @Override
    273     public void onUserInteraction() {
    274         if (mScreenSaverMode)
    275             restoreScreen();
    276     }
    277 
    278     // Adapted from KeyguardUpdateMonitor.java
    279     private void handleBatteryUpdate(int plugged, int status, int level) {
    280         final boolean pluggedIn = (plugged != 0);
    281         if (pluggedIn != mPluggedIn) {
    282             setWakeLock(pluggedIn);
    283         }
    284         if (pluggedIn != mPluggedIn || level != mBatteryLevel) {
    285             mBatteryLevel = level;
    286             mPluggedIn = pluggedIn;
    287             refreshBattery();
    288         }
    289     }
    290 
    291     private void refreshBattery() {
    292         // UX wants the battery level removed. This makes it not visible but
    293         // allows it to be easily turned back on if they change their mind.
    294         if (!USE_BATTERY_DISPLAY)
    295             return;
    296         if (mBatteryDisplay == null) return;
    297 
    298         if (mPluggedIn /* || mBatteryLevel < LOW_BATTERY_THRESHOLD */) {
    299             mBatteryDisplay.setCompoundDrawablesWithIntrinsicBounds(
    300                 0, 0, android.R.drawable.ic_lock_idle_charging, 0);
    301             mBatteryDisplay.setText(
    302                 getString(R.string.battery_charging_level, mBatteryLevel));
    303             mBatteryDisplay.setVisibility(View.VISIBLE);
    304         } else {
    305             mBatteryDisplay.setVisibility(View.INVISIBLE);
    306         }
    307     }
    308 
    309     private void refreshDate() {
    310         final Date now = new Date();
    311         if (DEBUG) Log.d(LOG_TAG, "refreshing date..." + now);
    312         mDate.setText(DateFormat.format(mDateFormat, now));
    313     }
    314 
    315     private void refreshAlarm() {
    316         if (mNextAlarm == null) return;
    317 
    318         String nextAlarm = Settings.System.getString(getContentResolver(),
    319                 Settings.System.NEXT_ALARM_FORMATTED);
    320         if (!TextUtils.isEmpty(nextAlarm)) {
    321             mNextAlarm.setText(getString(R.string.control_set_alarm_with_existing, nextAlarm));
    322             mNextAlarm.setVisibility(View.VISIBLE);
    323         } else if (mAlarmButton != null) {
    324             mNextAlarm.setVisibility(View.INVISIBLE);
    325         } else {
    326             mNextAlarm.setText(R.string.control_set_alarm);
    327             mNextAlarm.setVisibility(View.VISIBLE);
    328         }
    329     }
    330 
    331     private void refreshAll() {
    332         refreshDate();
    333         refreshAlarm();
    334         refreshBattery();
    335     }
    336 
    337     private void doDim(boolean fade) {
    338         View tintView = findViewById(R.id.window_tint);
    339         if (tintView == null) return;
    340 
    341         mTime.setSystemUiVisibility(mDimmed ? View.SYSTEM_UI_FLAG_LOW_PROFILE
    342                 : View.SYSTEM_UI_FLAG_VISIBLE);
    343 
    344         Window win = getWindow();
    345         WindowManager.LayoutParams winParams = win.getAttributes();
    346 
    347         winParams.flags |= (WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN);
    348         winParams.flags |= (WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
    349 
    350         // dim the wallpaper somewhat (how much is determined below)
    351         winParams.flags |= (WindowManager.LayoutParams.FLAG_DIM_BEHIND);
    352 
    353         if (mDimmed) {
    354             winParams.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN;
    355             winParams.dimAmount = DIM_BEHIND_AMOUNT_DIMMED;
    356             winParams.buttonBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_OFF;
    357 
    358             // show the window tint
    359             tintView.startAnimation(AnimationUtils.loadAnimation(this,
    360                 fade ? R.anim.dim
    361                      : R.anim.dim_instant));
    362         } else {
    363             winParams.flags &= (~WindowManager.LayoutParams.FLAG_FULLSCREEN);
    364             winParams.dimAmount = DIM_BEHIND_AMOUNT_NORMAL;
    365             winParams.buttonBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE;
    366 
    367             // hide the window tint
    368             tintView.startAnimation(AnimationUtils.loadAnimation(this,
    369                 fade ? R.anim.undim
    370                      : R.anim.undim_instant));
    371         }
    372 
    373         win.setAttributes(winParams);
    374     }
    375 
    376     @Override
    377     public void onNewIntent(Intent newIntent) {
    378         super.onNewIntent(newIntent);
    379         if (DEBUG) Log.d(LOG_TAG, "onNewIntent with intent: " + newIntent);
    380 
    381         // update our intent so that we can consult it to determine whether or
    382         // not the most recent launch was via a dock event
    383         setIntent(newIntent);
    384     }
    385 
    386     @Override
    387     public void onStart() {
    388         super.onStart();
    389 
    390         SCREEN_SAVER_COLOR = getResources().getColor(R.color.screen_saver_color);
    391         SCREEN_SAVER_COLOR_DIM = getResources().getColor(R.color.screen_saver_dim_color);
    392 
    393         IntentFilter filter = new IntentFilter();
    394         filter.addAction(Intent.ACTION_DATE_CHANGED);
    395         filter.addAction(Intent.ACTION_BATTERY_CHANGED);
    396         filter.addAction(UiModeManager.ACTION_EXIT_DESK_MODE);
    397         filter.addAction(ACTION_MIDNIGHT);
    398         registerReceiver(mIntentReceiver, filter);
    399     }
    400 
    401     @Override
    402     public void onStop() {
    403         super.onStop();
    404 
    405         unregisterReceiver(mIntentReceiver);
    406     }
    407 
    408     @Override
    409     public void onResume() {
    410         super.onResume();
    411         if (DEBUG) Log.d(LOG_TAG, "onResume with intent: " + getIntent());
    412 
    413         // reload the date format in case the user has changed settings
    414         // recently
    415         mDateFormat = getString(R.string.full_wday_month_day_no_year);
    416 
    417         // Elaborate mechanism to find out when the day rolls over
    418         Calendar today = Calendar.getInstance();
    419         today.set(Calendar.HOUR_OF_DAY, 0);
    420         today.set(Calendar.MINUTE, 0);
    421         today.set(Calendar.SECOND, 0);
    422         today.add(Calendar.DATE, 1);
    423         long alarmTimeUTC = today.getTimeInMillis();
    424 
    425         mMidnightIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_MIDNIGHT), 0);
    426         AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
    427         am.setRepeating(AlarmManager.RTC, alarmTimeUTC, AlarmManager.INTERVAL_DAY, mMidnightIntent);
    428         if (DEBUG) Log.d(LOG_TAG, "set repeating midnight event at UTC: "
    429             + alarmTimeUTC + " ("
    430             + (alarmTimeUTC - System.currentTimeMillis())
    431             + " ms from now) repeating every "
    432             + AlarmManager.INTERVAL_DAY + " with intent: " + mMidnightIntent);
    433 
    434         // If we weren't previously visible but now we are, it's because we're
    435         // being started from another activity. So it's OK to un-dim.
    436         if (mTime != null && mTime.getWindowVisibility() != View.VISIBLE) {
    437             mDimmed = false;
    438         }
    439 
    440         // Adjust the display to reflect the currently chosen dim mode.
    441         doDim(false);
    442 
    443         restoreScreen(); // disable screen saver
    444         refreshAll(); // will schedule periodic weather fetch
    445 
    446         setWakeLock(mPluggedIn);
    447 
    448         scheduleScreenSaver();
    449 
    450         final boolean launchedFromDock
    451             = getIntent().hasCategory(Intent.CATEGORY_DESK_DOCK);
    452 
    453         mLaunchedFromDock = launchedFromDock;
    454     }
    455 
    456     @Override
    457     public void onPause() {
    458         if (DEBUG) Log.d(LOG_TAG, "onPause");
    459 
    460         // Turn off the screen saver and cancel any pending timeouts.
    461         // (But don't un-dim.)
    462         mHandy.removeMessages(SCREEN_SAVER_TIMEOUT_MSG);
    463         restoreScreen();
    464 
    465         AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
    466         am.cancel(mMidnightIntent);
    467 
    468         super.onPause();
    469     }
    470 
    471     private void initViews() {
    472         // give up any internal focus before we switch layouts
    473         final View focused = getCurrentFocus();
    474         if (focused != null) focused.clearFocus();
    475 
    476         setContentView(R.layout.desk_clock);
    477 
    478         mTime = (DigitalClock) findViewById(R.id.time);
    479         mDate = (TextView) findViewById(R.id.date);
    480         mBatteryDisplay = (TextView) findViewById(R.id.battery);
    481 
    482         mTime.setSystemUiVisibility(View.STATUS_BAR_VISIBLE);
    483         mTime.getRootView().requestFocus();
    484 
    485         final View.OnClickListener alarmClickListener = new View.OnClickListener() {
    486             @Override
    487             public void onClick(View v) {
    488                 mDimmed = false;
    489                 doDim(true);
    490                 startActivity(new Intent(DeskClock.this, AlarmClock.class));
    491             }
    492         };
    493 
    494         mNextAlarm = (TextView) findViewById(R.id.nextAlarm);
    495         mNextAlarm.setOnClickListener(alarmClickListener);
    496 
    497         mAlarmButton = findViewById(R.id.alarm_button);
    498         View alarmControl = mAlarmButton != null ? mAlarmButton : findViewById(R.id.nextAlarm);
    499         alarmControl.setOnClickListener(alarmClickListener);
    500 
    501         View touchView = findViewById(R.id.window_touch);
    502         touchView.setOnClickListener(new View.OnClickListener() {
    503             @Override
    504             public void onClick(View v) {
    505                 // If the screen saver is on let onUserInteraction handle it
    506                 if (!mScreenSaverMode) {
    507                     mDimmed = !mDimmed;
    508                     doDim(true);
    509                 }
    510             }
    511         });
    512         touchView.setOnLongClickListener(new View.OnLongClickListener() {
    513             @Override
    514             public boolean onLongClick(View v) {
    515                 saveScreen();
    516                 return true;
    517             }
    518         });
    519     }
    520 
    521     @Override
    522     public void onConfigurationChanged(Configuration newConfig) {
    523         super.onConfigurationChanged(newConfig);
    524         if (mScreenSaverMode) {
    525             moveScreenSaver();
    526         } else {
    527             initViews();
    528             doDim(false);
    529             refreshAll();
    530         }
    531     }
    532 
    533     @Override
    534     public boolean onOptionsItemSelected(MenuItem item) {
    535         switch (item.getItemId()) {
    536             case R.id.menu_item_dock_settings:
    537                 startActivity(new Intent(DOCK_SETTINGS_ACTION));
    538                 return true;
    539             default:
    540                 return false;
    541         }
    542     }
    543 
    544     @Override
    545     public boolean onCreateOptionsMenu(Menu menu) {
    546         MenuInflater inflater = getMenuInflater();
    547         inflater.inflate(R.menu.desk_clock_menu, menu);
    548         return true;
    549     }
    550 
    551     @Override
    552     public boolean onPrepareOptionsMenu(Menu menu) {
    553         // Only show the "Dock settings" menu item if the device supports it.
    554         boolean isDockSupported =
    555                 (getPackageManager().resolveActivity(new Intent(DOCK_SETTINGS_ACTION), 0) != null);
    556         menu.findItem(R.id.menu_item_dock_settings).setVisible(isDockSupported);
    557         return super.onPrepareOptionsMenu(menu);
    558     }
    559 
    560     @Override
    561     protected void onCreate(Bundle icicle) {
    562         super.onCreate(icicle);
    563 
    564         mRNG = new Random();
    565 
    566         initViews();
    567     }
    568 }
    569