DeskClock.java revision 9654c843a0b41beb94a5dc8151ab5aef8d262b3d
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 17package com.android.deskclock; 18 19import android.app.Activity; 20import android.app.AlarmManager; 21import android.app.AlertDialog; 22import android.app.PendingIntent; 23import android.app.UiModeManager; 24import android.content.BroadcastReceiver; 25import android.content.Context; 26import android.content.DialogInterface; 27import android.content.Intent; 28import android.content.IntentFilter; 29import android.content.SharedPreferences; 30import android.content.pm.PackageManager; 31import android.content.res.Configuration; 32import android.content.res.Resources; 33import android.database.ContentObserver; 34import android.database.Cursor; 35import android.graphics.Rect; 36import android.graphics.drawable.BitmapDrawable; 37import android.graphics.drawable.ColorDrawable; 38import android.graphics.drawable.Drawable; 39import android.net.Uri; 40import android.os.Bundle; 41import android.os.Handler; 42import android.os.Message; 43import android.os.SystemClock; 44import android.os.PowerManager; 45import android.provider.Settings; 46import android.provider.MediaStore; 47import android.text.TextUtils; 48import android.text.format.DateFormat; 49import android.util.DisplayMetrics; 50import android.util.Log; 51import android.view.ContextMenu.ContextMenuInfo; 52import android.view.ContextMenu; 53import android.view.LayoutInflater; 54import android.view.Menu; 55import android.view.MenuInflater; 56import android.view.MenuItem; 57import android.view.MotionEvent; 58import android.view.View.OnClickListener; 59import android.view.View.OnCreateContextMenuListener; 60import android.view.View; 61import android.view.ViewGroup; 62import android.view.ViewTreeObserver; 63import android.view.ViewTreeObserver.OnGlobalFocusChangeListener; 64import android.view.Window; 65import android.view.WindowManager; 66import android.view.animation.Animation; 67import android.view.animation.AnimationUtils; 68import android.view.animation.TranslateAnimation; 69import android.widget.AbsoluteLayout; 70import android.widget.AdapterView.AdapterContextMenuInfo; 71import android.widget.AdapterView.OnItemClickListener; 72import android.widget.AdapterView; 73import android.widget.Button; 74import android.widget.CheckBox; 75import android.widget.ImageButton; 76import android.widget.ImageView; 77import android.widget.TextView; 78 79import static android.os.BatteryManager.BATTERY_STATUS_CHARGING; 80import static android.os.BatteryManager.BATTERY_STATUS_FULL; 81import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN; 82 83import java.io.IOException; 84import java.io.InputStream; 85import java.util.Calendar; 86import java.util.Date; 87import java.util.Locale; 88import java.util.Random; 89 90/** 91 * DeskClock clock view for desk docks. 92 */ 93public class DeskClock extends Activity { 94 private static final boolean DEBUG = false; 95 96 private static final String LOG_TAG = "DeskClock"; 97 98 // Alarm action for midnight (so we can update the date display). 99 private static final String ACTION_MIDNIGHT = "com.android.deskclock.MIDNIGHT"; 100 101 // Interval between forced polls of the weather widget. 102 private final long QUERY_WEATHER_DELAY = 60 * 60 * 1000; // 1 hr 103 104 // Intent to broadcast for dock settings. 105 private static final String DOCK_SETTINGS_ACTION = "com.android.settings.DOCK_SETTINGS"; 106 107 // Delay before engaging the burn-in protection mode (green-on-black). 108 private final long SCREEN_SAVER_TIMEOUT = 5 * 60 * 1000; // 5 min 109 110 // Repositioning delay in screen saver. 111 private final long SCREEN_SAVER_MOVE_DELAY = 60 * 1000; // 1 min 112 113 // Color to use for text & graphics in screen saver mode. 114 private final int SCREEN_SAVER_COLOR = 0xFF308030; 115 private final int SCREEN_SAVER_COLOR_DIM = 0xFF183018; 116 117 // Opacity of black layer between clock display and wallpaper. 118 private final float DIM_BEHIND_AMOUNT_NORMAL = 0.4f; 119 private final float DIM_BEHIND_AMOUNT_DIMMED = 0.8f; // higher contrast when display dimmed 120 121 // Internal message IDs. 122 private final int QUERY_WEATHER_DATA_MSG = 0x1000; 123 private final int UPDATE_WEATHER_DISPLAY_MSG = 0x1001; 124 private final int SCREEN_SAVER_TIMEOUT_MSG = 0x2000; 125 private final int SCREEN_SAVER_MOVE_MSG = 0x2001; 126 127 // Weather widget query information. 128 private static final String GENIE_PACKAGE_ID = "com.google.android.apps.genie.geniewidget"; 129 private static final String WEATHER_CONTENT_AUTHORITY = GENIE_PACKAGE_ID + ".weather"; 130 private static final String WEATHER_CONTENT_PATH = "/weather/current"; 131 private static final String[] WEATHER_CONTENT_COLUMNS = new String[] { 132 "location", 133 "timestamp", 134 "temperature", 135 "highTemperature", 136 "lowTemperature", 137 "iconUrl", 138 "iconResId", 139 "description", 140 }; 141 142 private static final String ACTION_GENIE_REFRESH = "com.google.android.apps.genie.REFRESH"; 143 144 // State variables follow. 145 private DigitalClock mTime; 146 private TextView mDate; 147 148 private TextView mNextAlarm = null; 149 private TextView mBatteryDisplay; 150 151 private TextView mWeatherCurrentTemperature; 152 private TextView mWeatherHighTemperature; 153 private TextView mWeatherLowTemperature; 154 private TextView mWeatherLocation; 155 private ImageView mWeatherIcon; 156 157 private String mWeatherCurrentTemperatureString; 158 private String mWeatherHighTemperatureString; 159 private String mWeatherLowTemperatureString; 160 private String mWeatherLocationString; 161 private Drawable mWeatherIconDrawable; 162 163 private Resources mGenieResources = null; 164 165 private boolean mDimmed = false; 166 private boolean mScreenSaverMode = false; 167 168 private String mDateFormat; 169 170 private int mBatteryLevel = -1; 171 private boolean mPluggedIn = false; 172 173 private boolean mLaunchedFromDock = false; 174 175 private Random mRNG; 176 177 private PendingIntent mMidnightIntent; 178 179 private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { 180 @Override 181 public void onReceive(Context context, Intent intent) { 182 final String action = intent.getAction(); 183 if (DEBUG) Log.d(LOG_TAG, "mIntentReceiver.onReceive: action=" + action + ", intent=" + intent); 184 if (Intent.ACTION_DATE_CHANGED.equals(action) || ACTION_MIDNIGHT.equals(action)) { 185 refreshDate(); 186 } else if (Intent.ACTION_BATTERY_CHANGED.equals(action)) { 187 handleBatteryUpdate( 188 intent.getIntExtra("status", BATTERY_STATUS_UNKNOWN), 189 intent.getIntExtra("level", 0)); 190 } else if (UiModeManager.ACTION_EXIT_DESK_MODE.equals(action)) { 191 if (mLaunchedFromDock) { 192 // moveTaskToBack(false); 193 finish(); 194 } 195 mLaunchedFromDock = false; 196 } 197 } 198 }; 199 200 private final Handler mHandy = new Handler() { 201 @Override 202 public void handleMessage(Message m) { 203 if (m.what == QUERY_WEATHER_DATA_MSG) { 204 new Thread() { public void run() { queryWeatherData(); } }.start(); 205 scheduleWeatherQueryDelayed(QUERY_WEATHER_DELAY); 206 } else if (m.what == UPDATE_WEATHER_DISPLAY_MSG) { 207 updateWeatherDisplay(); 208 } else if (m.what == SCREEN_SAVER_TIMEOUT_MSG) { 209 saveScreen(); 210 } else if (m.what == SCREEN_SAVER_MOVE_MSG) { 211 moveScreenSaver(); 212 } 213 } 214 }; 215 216 private final ContentObserver mContentObserver = new ContentObserver(mHandy) { 217 @Override 218 public void onChange(boolean selfChange) { 219 if (DEBUG) Log.d(LOG_TAG, "content observer notified that weather changed"); 220 refreshWeather(); 221 } 222 }; 223 224 225 private void moveScreenSaver() { 226 moveScreenSaverTo(-1,-1); 227 } 228 private void moveScreenSaverTo(int x, int y) { 229 if (!mScreenSaverMode) return; 230 231 final View saver_view = findViewById(R.id.saver_view); 232 233 DisplayMetrics metrics = new DisplayMetrics(); 234 getWindowManager().getDefaultDisplay().getMetrics(metrics); 235 236 if (x < 0 || y < 0) { 237 int myWidth = saver_view.getMeasuredWidth(); 238 int myHeight = saver_view.getMeasuredHeight(); 239 x = (int)(mRNG.nextFloat()*(metrics.widthPixels - myWidth)); 240 y = (int)(mRNG.nextFloat()*(metrics.heightPixels - myHeight)); 241 } 242 243 if (DEBUG) Log.d(LOG_TAG, String.format("screen saver: %d: jumping to (%d,%d)", 244 System.currentTimeMillis(), x, y)); 245 246 saver_view.setLayoutParams(new AbsoluteLayout.LayoutParams( 247 ViewGroup.LayoutParams.WRAP_CONTENT, 248 ViewGroup.LayoutParams.WRAP_CONTENT, 249 x, 250 y)); 251 252 // Synchronize our jumping so that it happens exactly on the second. 253 mHandy.sendEmptyMessageDelayed(SCREEN_SAVER_MOVE_MSG, 254 SCREEN_SAVER_MOVE_DELAY + 255 (1000 - (System.currentTimeMillis() % 1000))); 256 } 257 258 private void setWakeLock(boolean hold) { 259 if (DEBUG) Log.d(LOG_TAG, (hold ? "hold" : " releas") + "ing wake lock"); 260 Window win = getWindow(); 261 WindowManager.LayoutParams winParams = win.getAttributes(); 262 winParams.flags |= (WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD 263 | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED 264 | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON 265 | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); 266 if (hold) 267 winParams.flags |= WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; 268 else 269 winParams.flags &= (~WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 270 win.setAttributes(winParams); 271 } 272 273 private void scheduleScreenSaver() { 274 // reschedule screen saver 275 mHandy.removeMessages(SCREEN_SAVER_TIMEOUT_MSG); 276 mHandy.sendMessageDelayed( 277 Message.obtain(mHandy, SCREEN_SAVER_TIMEOUT_MSG), 278 SCREEN_SAVER_TIMEOUT); 279 } 280 281 private void restoreScreen() { 282 if (!mScreenSaverMode) return; 283 if (DEBUG) Log.d(LOG_TAG, "restoreScreen"); 284 mScreenSaverMode = false; 285 initViews(); 286 doDim(false); // restores previous dim mode 287 // policy: update weather info when returning from screen saver 288 if (mPluggedIn) requestWeatherDataFetch(); 289 290 scheduleScreenSaver(); 291 292 refreshAll(); 293 } 294 295 // Special screen-saver mode for OLED displays that burn in quickly 296 private void saveScreen() { 297 if (mScreenSaverMode) return; 298 if (DEBUG) Log.d(LOG_TAG, "saveScreen"); 299 300 // quickly stash away the x/y of the current date 301 final View oldTimeDate = findViewById(R.id.time_date); 302 int oldLoc[] = new int[2]; 303 oldTimeDate.getLocationOnScreen(oldLoc); 304 305 mScreenSaverMode = true; 306 Window win = getWindow(); 307 WindowManager.LayoutParams winParams = win.getAttributes(); 308 winParams.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN; 309 win.setAttributes(winParams); 310 311 // give up any internal focus before we switch layouts 312 final View focused = getCurrentFocus(); 313 if (focused != null) focused.clearFocus(); 314 315 setContentView(R.layout.desk_clock_saver); 316 317 mTime = (DigitalClock) findViewById(R.id.time); 318 mDate = (TextView) findViewById(R.id.date); 319 mNextAlarm = (TextView) findViewById(R.id.nextAlarm); 320 321 final int color = mDimmed ? SCREEN_SAVER_COLOR_DIM : SCREEN_SAVER_COLOR; 322 323 ((TextView)findViewById(R.id.timeDisplay)).setTextColor(color); 324 ((TextView)findViewById(R.id.am_pm)).setTextColor(color); 325 mDate.setTextColor(color); 326 mNextAlarm.setTextColor(color); 327 mNextAlarm.setCompoundDrawablesWithIntrinsicBounds( 328 getResources().getDrawable(mDimmed 329 ? R.drawable.ic_lock_idle_alarm_saver_dim 330 : R.drawable.ic_lock_idle_alarm_saver), 331 null, null, null); 332 333 mBatteryDisplay = 334 mWeatherCurrentTemperature = 335 mWeatherHighTemperature = 336 mWeatherLowTemperature = 337 mWeatherLocation = null; 338 mWeatherIcon = null; 339 340 refreshDate(); 341 refreshAlarm(); 342 343 moveScreenSaverTo(oldLoc[0], oldLoc[1]); 344 } 345 346 @Override 347 public void onUserInteraction() { 348 if (mScreenSaverMode) 349 restoreScreen(); 350 } 351 352 // Tell the Genie widget to load new data from the network. 353 private void requestWeatherDataFetch() { 354 if (DEBUG) Log.d(LOG_TAG, "forcing the Genie widget to update weather now..."); 355 sendBroadcast(new Intent(ACTION_GENIE_REFRESH).putExtra("requestWeather", true)); 356 // we expect the result to show up in our content observer 357 } 358 359 private boolean supportsWeather() { 360 return (mGenieResources != null); 361 } 362 363 private void scheduleWeatherQueryDelayed(long delay) { 364 // cancel any existing scheduled queries 365 unscheduleWeatherQuery(); 366 367 if (DEBUG) Log.d(LOG_TAG, "scheduling weather fetch message for " + delay + "ms from now"); 368 369 mHandy.sendEmptyMessageDelayed(QUERY_WEATHER_DATA_MSG, delay); 370 } 371 372 private void unscheduleWeatherQuery() { 373 mHandy.removeMessages(QUERY_WEATHER_DATA_MSG); 374 } 375 376 private void queryWeatherData() { 377 // if we couldn't load the weather widget's resources, we simply 378 // assume it's not present on the device. 379 if (mGenieResources == null) return; 380 381 Uri queryUri = new Uri.Builder() 382 .scheme(android.content.ContentResolver.SCHEME_CONTENT) 383 .authority(WEATHER_CONTENT_AUTHORITY) 384 .path(WEATHER_CONTENT_PATH) 385 .appendPath(new Long(System.currentTimeMillis()).toString()) 386 .build(); 387 388 if (DEBUG) Log.d(LOG_TAG, "querying genie: " + queryUri); 389 390 Cursor cur; 391 try { 392 cur = managedQuery( 393 queryUri, 394 WEATHER_CONTENT_COLUMNS, 395 null, 396 null, 397 null); 398 } catch (RuntimeException e) { 399 Log.e(LOG_TAG, "Weather query failed", e); 400 cur = null; 401 } 402 403 if (cur != null && cur.moveToFirst()) { 404 if (DEBUG) { 405 java.lang.StringBuilder sb = 406 new java.lang.StringBuilder("Weather query result: {"); 407 for(int i=0; i<cur.getColumnCount(); i++) { 408 if (i>0) sb.append(", "); 409 sb.append(cur.getColumnName(i)) 410 .append("=") 411 .append(cur.getString(i)); 412 } 413 sb.append("}"); 414 Log.d(LOG_TAG, sb.toString()); 415 } 416 417 mWeatherIconDrawable = mGenieResources.getDrawable(cur.getInt( 418 cur.getColumnIndexOrThrow("iconResId"))); 419 420 mWeatherLocationString = cur.getString( 421 cur.getColumnIndexOrThrow("location")); 422 423 // any of these may be NULL 424 final int colTemp = cur.getColumnIndexOrThrow("temperature"); 425 final int colHigh = cur.getColumnIndexOrThrow("highTemperature"); 426 final int colLow = cur.getColumnIndexOrThrow("lowTemperature"); 427 428 mWeatherCurrentTemperatureString = 429 cur.isNull(colTemp) 430 ? "\u2014" 431 : String.format("%d\u00b0", cur.getInt(colTemp)); 432 mWeatherHighTemperatureString = 433 cur.isNull(colHigh) 434 ? "\u2014" 435 : String.format("%d\u00b0", cur.getInt(colHigh)); 436 mWeatherLowTemperatureString = 437 cur.isNull(colLow) 438 ? "\u2014" 439 : String.format("%d\u00b0", cur.getInt(colLow)); 440 } else { 441 Log.w(LOG_TAG, "No weather information available (cur=" 442 + cur +")"); 443 mWeatherIconDrawable = null; 444 mWeatherLocationString = getString(R.string.weather_fetch_failure); 445 mWeatherCurrentTemperatureString = 446 mWeatherHighTemperatureString = 447 mWeatherLowTemperatureString = ""; 448 } 449 450 mHandy.sendEmptyMessage(UPDATE_WEATHER_DISPLAY_MSG); 451 } 452 453 private void refreshWeather() { 454 if (supportsWeather()) 455 scheduleWeatherQueryDelayed(0); 456 updateWeatherDisplay(); // in case we have it cached 457 } 458 459 private void updateWeatherDisplay() { 460 if (mWeatherCurrentTemperature == null) return; 461 462 mWeatherCurrentTemperature.setText(mWeatherCurrentTemperatureString); 463 mWeatherHighTemperature.setText(mWeatherHighTemperatureString); 464 mWeatherLowTemperature.setText(mWeatherLowTemperatureString); 465 mWeatherLocation.setText(mWeatherLocationString); 466 mWeatherIcon.setImageDrawable(mWeatherIconDrawable); 467 } 468 469 // Adapted from KeyguardUpdateMonitor.java 470 private void handleBatteryUpdate(int plugStatus, int batteryLevel) { 471 final boolean pluggedIn = (plugStatus == BATTERY_STATUS_CHARGING || plugStatus == BATTERY_STATUS_FULL); 472 if (pluggedIn != mPluggedIn) { 473 setWakeLock(pluggedIn); 474 475 if (pluggedIn) { 476 // policy: update weather info when attaching to power 477 requestWeatherDataFetch(); 478 } 479 } 480 if (pluggedIn != mPluggedIn || batteryLevel != mBatteryLevel) { 481 mBatteryLevel = batteryLevel; 482 mPluggedIn = pluggedIn; 483 refreshBattery(); 484 } 485 } 486 487 private void refreshBattery() { 488 if (mBatteryDisplay == null) return; 489 490 if (mPluggedIn /* || mBatteryLevel < LOW_BATTERY_THRESHOLD */) { 491 mBatteryDisplay.setCompoundDrawablesWithIntrinsicBounds( 492 0, 0, android.R.drawable.ic_lock_idle_charging, 0); 493 mBatteryDisplay.setText( 494 getString(R.string.battery_charging_level, mBatteryLevel)); 495 mBatteryDisplay.setVisibility(View.VISIBLE); 496 } else { 497 mBatteryDisplay.setVisibility(View.INVISIBLE); 498 } 499 } 500 501 private void refreshDate() { 502 final Date now = new Date(); 503 if (DEBUG) Log.d(LOG_TAG, "refreshing date..." + now); 504 mDate.setText(DateFormat.format(mDateFormat, now)); 505 } 506 507 private void refreshAlarm() { 508 if (mNextAlarm == null) return; 509 510 String nextAlarm = Settings.System.getString(getContentResolver(), 511 Settings.System.NEXT_ALARM_FORMATTED); 512 if (!TextUtils.isEmpty(nextAlarm)) { 513 mNextAlarm.setText(nextAlarm); 514 //mNextAlarm.setCompoundDrawablesWithIntrinsicBounds( 515 // android.R.drawable.ic_lock_idle_alarm, 0, 0, 0); 516 mNextAlarm.setVisibility(View.VISIBLE); 517 } else { 518 mNextAlarm.setVisibility(View.INVISIBLE); 519 } 520 } 521 522 private void refreshAll() { 523 refreshDate(); 524 refreshAlarm(); 525 refreshBattery(); 526 refreshWeather(); 527 } 528 529 private void doDim(boolean fade) { 530 View tintView = findViewById(R.id.window_tint); 531 if (tintView == null) return; 532 533 Window win = getWindow(); 534 WindowManager.LayoutParams winParams = win.getAttributes(); 535 536 winParams.flags |= (WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN); 537 winParams.flags |= (WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); 538 539 // dim the wallpaper somewhat (how much is determined below) 540 winParams.flags |= (WindowManager.LayoutParams.FLAG_DIM_BEHIND); 541 542 if (mDimmed) { 543 winParams.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN; 544 winParams.dimAmount = DIM_BEHIND_AMOUNT_DIMMED; 545 winParams.buttonBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_OFF; 546 547 // show the window tint 548 tintView.startAnimation(AnimationUtils.loadAnimation(this, 549 fade ? R.anim.dim 550 : R.anim.dim_instant)); 551 } else { 552 winParams.flags &= (~WindowManager.LayoutParams.FLAG_FULLSCREEN); 553 winParams.dimAmount = DIM_BEHIND_AMOUNT_NORMAL; 554 winParams.buttonBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE; 555 556 // hide the window tint 557 tintView.startAnimation(AnimationUtils.loadAnimation(this, 558 fade ? R.anim.undim 559 : R.anim.undim_instant)); 560 } 561 562 win.setAttributes(winParams); 563 } 564 565 @Override 566 public void onNewIntent(Intent newIntent) { 567 super.onNewIntent(newIntent); 568 if (DEBUG) Log.d(LOG_TAG, "onNewIntent with intent: " + newIntent); 569 570 // update our intent so that we can consult it to determine whether or 571 // not the most recent launch was via a dock event 572 setIntent(newIntent); 573 } 574 575 @Override 576 public void onStart() { 577 super.onStart(); 578 579 IntentFilter filter = new IntentFilter(); 580 filter.addAction(Intent.ACTION_DATE_CHANGED); 581 filter.addAction(Intent.ACTION_BATTERY_CHANGED); 582 filter.addAction(UiModeManager.ACTION_EXIT_DESK_MODE); 583 filter.addAction(ACTION_MIDNIGHT); 584 registerReceiver(mIntentReceiver, filter); 585 } 586 587 @Override 588 public void onStop() { 589 super.onStop(); 590 591 unregisterReceiver(mIntentReceiver); 592 } 593 594 @Override 595 public void onResume() { 596 super.onResume(); 597 if (DEBUG) Log.d(LOG_TAG, "onResume with intent: " + getIntent()); 598 599 // reload the date format in case the user has changed settings 600 // recently 601 mDateFormat = getString(R.string.full_wday_month_day_no_year); 602 603 // Listen for updates to weather data 604 Uri weatherNotificationUri = new Uri.Builder() 605 .scheme(android.content.ContentResolver.SCHEME_CONTENT) 606 .authority(WEATHER_CONTENT_AUTHORITY) 607 .path(WEATHER_CONTENT_PATH) 608 .build(); 609 getContentResolver().registerContentObserver( 610 weatherNotificationUri, true, mContentObserver); 611 612 // Elaborate mechanism to find out when the day rolls over 613 Calendar today = Calendar.getInstance(); 614 today.set(Calendar.HOUR_OF_DAY, 0); 615 today.set(Calendar.MINUTE, 0); 616 today.set(Calendar.SECOND, 0); 617 today.add(Calendar.DATE, 1); 618 long alarmTimeUTC = today.getTimeInMillis() + today.get(Calendar.ZONE_OFFSET); 619 mMidnightIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_MIDNIGHT), 0); 620 AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE); 621 am.setRepeating(AlarmManager.RTC, alarmTimeUTC, AlarmManager.INTERVAL_DAY, mMidnightIntent); 622 if (DEBUG) Log.d(LOG_TAG, "set repeating midnight event at " 623 + alarmTimeUTC + " repeating every " 624 + AlarmManager.INTERVAL_DAY + " with intent: " + mMidnightIntent); 625 626 // If we weren't previously visible but now we are, it's because we're 627 // being started from another activity. So it's OK to un-dim. 628 if (mTime != null && mTime.getWindowVisibility() != View.VISIBLE) { 629 mDimmed = false; 630 } 631 632 // Adjust the display to reflect the currently chosen dim mode. 633 doDim(false); 634 635 restoreScreen(); // disable screen saver 636 refreshAll(); // will schedule periodic weather fetch 637 638 setWakeLock(mPluggedIn); 639 640 scheduleScreenSaver(); 641 642 final boolean launchedFromDock 643 = getIntent().hasCategory(Intent.CATEGORY_DESK_DOCK); 644 645 if (supportsWeather() && launchedFromDock && !mLaunchedFromDock) { 646 // policy: fetch weather if launched via dock connection 647 if (DEBUG) Log.d(LOG_TAG, "Device now docked; forcing weather to refresh right now"); 648 requestWeatherDataFetch(); 649 } 650 651 mLaunchedFromDock = launchedFromDock; 652 } 653 654 @Override 655 public void onPause() { 656 if (DEBUG) Log.d(LOG_TAG, "onPause"); 657 658 // Turn off the screen saver and cancel any pending timeouts. 659 // (But don't un-dim.) 660 mHandy.removeMessages(SCREEN_SAVER_TIMEOUT_MSG); 661 restoreScreen(); 662 663 // Other things we don't want to be doing in the background. 664 // NB: we need to keep our broadcast receiver alive in case the dock 665 // is disconnected while the screen is off 666 getContentResolver().unregisterContentObserver(mContentObserver); 667 668 AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE); 669 am.cancel(mMidnightIntent); 670 unscheduleWeatherQuery(); 671 672 super.onPause(); 673 } 674 675 private void initViews() { 676 // give up any internal focus before we switch layouts 677 final View focused = getCurrentFocus(); 678 if (focused != null) focused.clearFocus(); 679 680 setContentView(R.layout.desk_clock); 681 682 mTime = (DigitalClock) findViewById(R.id.time); 683 mDate = (TextView) findViewById(R.id.date); 684 mBatteryDisplay = (TextView) findViewById(R.id.battery); 685 686 mTime.getRootView().requestFocus(); 687 688 mWeatherCurrentTemperature = (TextView) findViewById(R.id.weather_temperature); 689 mWeatherHighTemperature = (TextView) findViewById(R.id.weather_high_temperature); 690 mWeatherLowTemperature = (TextView) findViewById(R.id.weather_low_temperature); 691 mWeatherLocation = (TextView) findViewById(R.id.weather_location); 692 mWeatherIcon = (ImageView) findViewById(R.id.weather_icon); 693 694 final View.OnClickListener alarmClickListener = new View.OnClickListener() { 695 public void onClick(View v) { 696 startActivity(new Intent(DeskClock.this, AlarmClock.class)); 697 } 698 }; 699 700 mNextAlarm = (TextView) findViewById(R.id.nextAlarm); 701 mNextAlarm.setOnClickListener(alarmClickListener); 702 703 final ImageButton alarmButton = (ImageButton) findViewById(R.id.alarm_button); 704 alarmButton.setOnClickListener(alarmClickListener); 705 706 final ImageButton galleryButton = (ImageButton) findViewById(R.id.gallery_button); 707 galleryButton.setOnClickListener(new View.OnClickListener() { 708 public void onClick(View v) { 709 try { 710 startActivity(new Intent( 711 Intent.ACTION_VIEW, 712 android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI) 713 .putExtra("slideshow", true) 714 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TOP)); 715 } catch (android.content.ActivityNotFoundException e) { 716 Log.e(LOG_TAG, "Couldn't launch image browser", e); 717 } 718 } 719 }); 720 721 final ImageButton musicButton = (ImageButton) findViewById(R.id.music_button); 722 musicButton.setOnClickListener(new View.OnClickListener() { 723 public void onClick(View v) { 724 try { 725 startActivity(new Intent(MediaStore.INTENT_ACTION_MUSIC_PLAYER) 726 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TOP)); 727 728 } catch (android.content.ActivityNotFoundException e) { 729 Log.e(LOG_TAG, "Couldn't launch music browser", e); 730 } 731 } 732 }); 733 734 final ImageButton homeButton = (ImageButton) findViewById(R.id.home_button); 735 homeButton.setOnClickListener(new View.OnClickListener() { 736 public void onClick(View v) { 737 startActivity( 738 new Intent(Intent.ACTION_MAIN) 739 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TOP) 740 .addCategory(Intent.CATEGORY_HOME)); 741 } 742 }); 743 744 final ImageButton nightmodeButton = (ImageButton) findViewById(R.id.nightmode_button); 745 nightmodeButton.setOnClickListener(new View.OnClickListener() { 746 public void onClick(View v) { 747 mDimmed = ! mDimmed; 748 doDim(true); 749 } 750 }); 751 752 nightmodeButton.setOnLongClickListener(new View.OnLongClickListener() { 753 public boolean onLongClick(View v) { 754 saveScreen(); 755 return true; 756 } 757 }); 758 759 final View weatherView = findViewById(R.id.weather); 760 weatherView.setOnClickListener(new View.OnClickListener() { 761 public void onClick(View v) { 762 if (!supportsWeather()) return; 763 764 Intent genieAppQuery = getPackageManager() 765 .getLaunchIntentForPackage(GENIE_PACKAGE_ID) 766 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TOP); 767 if (genieAppQuery != null) { 768 startActivity(genieAppQuery); 769 } 770 } 771 }); 772 773 final View tintView = findViewById(R.id.window_tint); 774 tintView.setOnTouchListener(new View.OnTouchListener() { 775 public boolean onTouch(View v, MotionEvent event) { 776 if (mDimmed && event.getAction() == MotionEvent.ACTION_DOWN) { 777 // We want to un-dim the whole screen on tap. 778 // ...Unless the user is specifically tapping on the dim 779 // widget, in which case let it do the work. 780 Rect r = new Rect(); 781 nightmodeButton.getHitRect(r); 782 int[] gloc = new int[2]; 783 nightmodeButton.getLocationInWindow(gloc); 784 r.offsetTo(gloc[0], gloc[1]); // convert to window coords 785 786 if (!r.contains((int) event.getX(), (int) event.getY())) { 787 mDimmed = false; 788 doDim(true); 789 } 790 } 791 return false; // always pass the click through 792 } 793 }); 794 795 // Tidy up awkward focus behavior: the first view to be focused in 796 // trackball mode should be the alarms button 797 final ViewTreeObserver vto = alarmButton.getViewTreeObserver(); 798 vto.addOnGlobalFocusChangeListener(new ViewTreeObserver.OnGlobalFocusChangeListener() { 799 public void onGlobalFocusChanged(View oldFocus, View newFocus) { 800 if (oldFocus == null && newFocus == nightmodeButton) { 801 alarmButton.requestFocus(); 802 } 803 } 804 }); 805 } 806 807 @Override 808 public void onConfigurationChanged(Configuration newConfig) { 809 super.onConfigurationChanged(newConfig); 810 if (mScreenSaverMode) { 811 moveScreenSaver(); 812 } else { 813 initViews(); 814 doDim(false); 815 refreshAll(); 816 } 817 } 818 819 @Override 820 public boolean onOptionsItemSelected(MenuItem item) { 821 switch (item.getItemId()) { 822 case R.id.menu_item_alarms: 823 startActivity(new Intent(DeskClock.this, AlarmClock.class)); 824 return true; 825 case R.id.menu_item_add_alarm: 826 startActivity(new Intent(this, SetAlarm.class)); 827 return true; 828 case R.id.menu_item_dock_settings: 829 startActivity(new Intent(DOCK_SETTINGS_ACTION)); 830 return true; 831 default: 832 return false; 833 } 834 } 835 836 @Override 837 public boolean onCreateOptionsMenu(Menu menu) { 838 MenuInflater inflater = getMenuInflater(); 839 inflater.inflate(R.menu.desk_clock_menu, menu); 840 return true; 841 } 842 843 @Override 844 protected void onCreate(Bundle icicle) { 845 super.onCreate(icicle); 846 847 mRNG = new Random(); 848 849 try { 850 mGenieResources = getPackageManager().getResourcesForApplication(GENIE_PACKAGE_ID); 851 } catch (PackageManager.NameNotFoundException e) { 852 // no weather info available 853 Log.w(LOG_TAG, "Can't find "+GENIE_PACKAGE_ID+". Weather forecast will not be available."); 854 } 855 856 initViews(); 857 } 858 859} 860