DeskClock.java revision 3f5052618fc262c22b9e00137014329bcc660524
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 mWeatherCurrentTemperatureString = String.format("%d\u00b0", 420 (cur.getInt(cur.getColumnIndexOrThrow("temperature")))); 421 mWeatherHighTemperatureString = String.format("%d\u00b0", 422 (cur.getInt(cur.getColumnIndexOrThrow("highTemperature")))); 423 mWeatherLowTemperatureString = String.format("%d\u00b0", 424 (cur.getInt(cur.getColumnIndexOrThrow("lowTemperature")))); 425 mWeatherLocationString = cur.getString( 426 cur.getColumnIndexOrThrow("location")); 427 } else { 428 Log.w(LOG_TAG, "No weather information available (cur=" 429 + cur +")"); 430 mWeatherIconDrawable = null; 431 mWeatherHighTemperatureString = ""; 432 mWeatherLowTemperatureString = ""; 433 mWeatherLocationString = getString(R.string.weather_fetch_failure); 434 } 435 436 mHandy.sendEmptyMessage(UPDATE_WEATHER_DISPLAY_MSG); 437 } 438 439 private void refreshWeather() { 440 if (supportsWeather()) 441 scheduleWeatherQueryDelayed(0); 442 updateWeatherDisplay(); // in case we have it cached 443 } 444 445 private void updateWeatherDisplay() { 446 if (mWeatherCurrentTemperature == null) return; 447 448 mWeatherCurrentTemperature.setText(mWeatherCurrentTemperatureString); 449 mWeatherHighTemperature.setText(mWeatherHighTemperatureString); 450 mWeatherLowTemperature.setText(mWeatherLowTemperatureString); 451 mWeatherLocation.setText(mWeatherLocationString); 452 mWeatherIcon.setImageDrawable(mWeatherIconDrawable); 453 } 454 455 // Adapted from KeyguardUpdateMonitor.java 456 private void handleBatteryUpdate(int plugStatus, int batteryLevel) { 457 final boolean pluggedIn = (plugStatus == BATTERY_STATUS_CHARGING || plugStatus == BATTERY_STATUS_FULL); 458 if (pluggedIn != mPluggedIn) { 459 setWakeLock(pluggedIn); 460 461 if (pluggedIn) { 462 // policy: update weather info when attaching to power 463 requestWeatherDataFetch(); 464 } 465 } 466 if (pluggedIn != mPluggedIn || batteryLevel != mBatteryLevel) { 467 mBatteryLevel = batteryLevel; 468 mPluggedIn = pluggedIn; 469 refreshBattery(); 470 } 471 } 472 473 private void refreshBattery() { 474 if (mBatteryDisplay == null) return; 475 476 if (mPluggedIn /* || mBatteryLevel < LOW_BATTERY_THRESHOLD */) { 477 mBatteryDisplay.setCompoundDrawablesWithIntrinsicBounds( 478 0, 0, android.R.drawable.ic_lock_idle_charging, 0); 479 mBatteryDisplay.setText( 480 getString(R.string.battery_charging_level, mBatteryLevel)); 481 mBatteryDisplay.setVisibility(View.VISIBLE); 482 } else { 483 mBatteryDisplay.setVisibility(View.INVISIBLE); 484 } 485 } 486 487 private void refreshDate() { 488 final Date now = new Date(); 489 if (DEBUG) Log.d(LOG_TAG, "refreshing date..." + now); 490 mDate.setText(DateFormat.format(mDateFormat, now)); 491 } 492 493 private void refreshAlarm() { 494 if (mNextAlarm == null) return; 495 496 String nextAlarm = Settings.System.getString(getContentResolver(), 497 Settings.System.NEXT_ALARM_FORMATTED); 498 if (!TextUtils.isEmpty(nextAlarm)) { 499 mNextAlarm.setText(nextAlarm); 500 //mNextAlarm.setCompoundDrawablesWithIntrinsicBounds( 501 // android.R.drawable.ic_lock_idle_alarm, 0, 0, 0); 502 mNextAlarm.setVisibility(View.VISIBLE); 503 } else { 504 mNextAlarm.setVisibility(View.INVISIBLE); 505 } 506 } 507 508 private void refreshAll() { 509 refreshDate(); 510 refreshAlarm(); 511 refreshBattery(); 512 refreshWeather(); 513 } 514 515 private void doDim(boolean fade) { 516 View tintView = findViewById(R.id.window_tint); 517 if (tintView == null) return; 518 519 Window win = getWindow(); 520 WindowManager.LayoutParams winParams = win.getAttributes(); 521 522 winParams.flags |= (WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN); 523 winParams.flags |= (WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); 524 525 // dim the wallpaper somewhat (how much is determined below) 526 winParams.flags |= (WindowManager.LayoutParams.FLAG_DIM_BEHIND); 527 528 if (mDimmed) { 529 winParams.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN; 530 winParams.dimAmount = DIM_BEHIND_AMOUNT_DIMMED; 531 winParams.buttonBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_OFF; 532 533 // show the window tint 534 tintView.startAnimation(AnimationUtils.loadAnimation(this, 535 fade ? R.anim.dim 536 : R.anim.dim_instant)); 537 } else { 538 winParams.flags &= (~WindowManager.LayoutParams.FLAG_FULLSCREEN); 539 winParams.dimAmount = DIM_BEHIND_AMOUNT_NORMAL; 540 winParams.buttonBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE; 541 542 // hide the window tint 543 tintView.startAnimation(AnimationUtils.loadAnimation(this, 544 fade ? R.anim.undim 545 : R.anim.undim_instant)); 546 } 547 548 win.setAttributes(winParams); 549 } 550 551 @Override 552 public void onNewIntent(Intent newIntent) { 553 super.onNewIntent(newIntent); 554 if (DEBUG) Log.d(LOG_TAG, "onNewIntent with intent: " + newIntent); 555 556 // update our intent so that we can consult it to determine whether or 557 // not the most recent launch was via a dock event 558 setIntent(newIntent); 559 } 560 561 @Override 562 public void onStart() { 563 super.onStart(); 564 565 IntentFilter filter = new IntentFilter(); 566 filter.addAction(Intent.ACTION_DATE_CHANGED); 567 filter.addAction(Intent.ACTION_BATTERY_CHANGED); 568 filter.addAction(UiModeManager.ACTION_EXIT_DESK_MODE); 569 filter.addAction(ACTION_MIDNIGHT); 570 registerReceiver(mIntentReceiver, filter); 571 } 572 573 @Override 574 public void onStop() { 575 super.onStop(); 576 577 unregisterReceiver(mIntentReceiver); 578 } 579 580 @Override 581 public void onResume() { 582 super.onResume(); 583 if (DEBUG) Log.d(LOG_TAG, "onResume with intent: " + getIntent()); 584 585 // reload the date format in case the user has changed settings 586 // recently 587 mDateFormat = getString(R.string.full_wday_month_day_no_year); 588 589 // Listen for updates to weather data 590 Uri weatherNotificationUri = new Uri.Builder() 591 .scheme(android.content.ContentResolver.SCHEME_CONTENT) 592 .authority(WEATHER_CONTENT_AUTHORITY) 593 .path(WEATHER_CONTENT_PATH) 594 .build(); 595 getContentResolver().registerContentObserver( 596 weatherNotificationUri, true, mContentObserver); 597 598 // Elaborate mechanism to find out when the day rolls over 599 Calendar today = Calendar.getInstance(); 600 today.set(Calendar.HOUR_OF_DAY, 0); 601 today.set(Calendar.MINUTE, 0); 602 today.set(Calendar.SECOND, 0); 603 today.add(Calendar.DATE, 1); 604 long alarmTimeUTC = today.getTimeInMillis() + today.get(Calendar.ZONE_OFFSET); 605 mMidnightIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_MIDNIGHT), 0); 606 AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE); 607 am.setRepeating(AlarmManager.RTC, alarmTimeUTC, AlarmManager.INTERVAL_DAY, mMidnightIntent); 608 if (DEBUG) Log.d(LOG_TAG, "set repeating midnight event at " 609 + alarmTimeUTC + " repeating every " 610 + AlarmManager.INTERVAL_DAY + " with intent: " + mMidnightIntent); 611 612 // If we weren't previously visible but now we are, it's because we're 613 // being started from another activity. So it's OK to un-dim. 614 if (mTime != null && mTime.getWindowVisibility() != View.VISIBLE) { 615 mDimmed = false; 616 } 617 618 // Adjust the display to reflect the currently chosen dim mode. 619 doDim(false); 620 621 restoreScreen(); // disable screen saver 622 refreshAll(); // will schedule periodic weather fetch 623 624 setWakeLock(mPluggedIn); 625 626 scheduleScreenSaver(); 627 628 final boolean launchedFromDock 629 = getIntent().hasCategory(Intent.CATEGORY_DESK_DOCK); 630 631 if (supportsWeather() && launchedFromDock && !mLaunchedFromDock) { 632 // policy: fetch weather if launched via dock connection 633 if (DEBUG) Log.d(LOG_TAG, "Device now docked; forcing weather to refresh right now"); 634 requestWeatherDataFetch(); 635 } 636 637 mLaunchedFromDock = launchedFromDock; 638 } 639 640 @Override 641 public void onPause() { 642 if (DEBUG) Log.d(LOG_TAG, "onPause"); 643 644 // Turn off the screen saver and cancel any pending timeouts. 645 // (But don't un-dim.) 646 mHandy.removeMessages(SCREEN_SAVER_TIMEOUT_MSG); 647 restoreScreen(); 648 649 // Other things we don't want to be doing in the background. 650 // NB: we need to keep our broadcast receiver alive in case the dock 651 // is disconnected while the screen is off 652 getContentResolver().unregisterContentObserver(mContentObserver); 653 654 AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE); 655 am.cancel(mMidnightIntent); 656 unscheduleWeatherQuery(); 657 658 super.onPause(); 659 } 660 661 private void initViews() { 662 // give up any internal focus before we switch layouts 663 final View focused = getCurrentFocus(); 664 if (focused != null) focused.clearFocus(); 665 666 setContentView(R.layout.desk_clock); 667 668 mTime = (DigitalClock) findViewById(R.id.time); 669 mDate = (TextView) findViewById(R.id.date); 670 mBatteryDisplay = (TextView) findViewById(R.id.battery); 671 672 mTime.getRootView().requestFocus(); 673 674 mWeatherCurrentTemperature = (TextView) findViewById(R.id.weather_temperature); 675 mWeatherHighTemperature = (TextView) findViewById(R.id.weather_high_temperature); 676 mWeatherLowTemperature = (TextView) findViewById(R.id.weather_low_temperature); 677 mWeatherLocation = (TextView) findViewById(R.id.weather_location); 678 mWeatherIcon = (ImageView) findViewById(R.id.weather_icon); 679 680 final View.OnClickListener alarmClickListener = new View.OnClickListener() { 681 public void onClick(View v) { 682 startActivity(new Intent(DeskClock.this, AlarmClock.class)); 683 } 684 }; 685 686 mNextAlarm = (TextView) findViewById(R.id.nextAlarm); 687 mNextAlarm.setOnClickListener(alarmClickListener); 688 689 final ImageButton alarmButton = (ImageButton) findViewById(R.id.alarm_button); 690 alarmButton.setOnClickListener(alarmClickListener); 691 692 final ImageButton galleryButton = (ImageButton) findViewById(R.id.gallery_button); 693 galleryButton.setOnClickListener(new View.OnClickListener() { 694 public void onClick(View v) { 695 try { 696 startActivity(new Intent( 697 Intent.ACTION_VIEW, 698 android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI) 699 .putExtra("slideshow", true) 700 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TOP)); 701 } catch (android.content.ActivityNotFoundException e) { 702 Log.e(LOG_TAG, "Couldn't launch image browser", e); 703 } 704 } 705 }); 706 707 final ImageButton musicButton = (ImageButton) findViewById(R.id.music_button); 708 musicButton.setOnClickListener(new View.OnClickListener() { 709 public void onClick(View v) { 710 try { 711 startActivity(new Intent(MediaStore.INTENT_ACTION_MUSIC_PLAYER) 712 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TOP)); 713 714 } catch (android.content.ActivityNotFoundException e) { 715 Log.e(LOG_TAG, "Couldn't launch music browser", e); 716 } 717 } 718 }); 719 720 final ImageButton homeButton = (ImageButton) findViewById(R.id.home_button); 721 homeButton.setOnClickListener(new View.OnClickListener() { 722 public void onClick(View v) { 723 startActivity( 724 new Intent(Intent.ACTION_MAIN) 725 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TOP) 726 .addCategory(Intent.CATEGORY_HOME)); 727 } 728 }); 729 730 final ImageButton nightmodeButton = (ImageButton) findViewById(R.id.nightmode_button); 731 nightmodeButton.setOnClickListener(new View.OnClickListener() { 732 public void onClick(View v) { 733 mDimmed = ! mDimmed; 734 doDim(true); 735 } 736 }); 737 738 nightmodeButton.setOnLongClickListener(new View.OnLongClickListener() { 739 public boolean onLongClick(View v) { 740 saveScreen(); 741 return true; 742 } 743 }); 744 745 final View weatherView = findViewById(R.id.weather); 746 weatherView.setOnClickListener(new View.OnClickListener() { 747 public void onClick(View v) { 748 if (!supportsWeather()) return; 749 750 Intent genieAppQuery = getPackageManager() 751 .getLaunchIntentForPackage(GENIE_PACKAGE_ID) 752 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TOP); 753 if (genieAppQuery != null) { 754 startActivity(genieAppQuery); 755 } 756 } 757 }); 758 759 final View tintView = findViewById(R.id.window_tint); 760 tintView.setOnTouchListener(new View.OnTouchListener() { 761 public boolean onTouch(View v, MotionEvent event) { 762 if (mDimmed && event.getAction() == MotionEvent.ACTION_DOWN) { 763 // We want to un-dim the whole screen on tap. 764 // ...Unless the user is specifically tapping on the dim 765 // widget, in which case let it do the work. 766 Rect r = new Rect(); 767 nightmodeButton.getHitRect(r); 768 int[] gloc = new int[2]; 769 nightmodeButton.getLocationInWindow(gloc); 770 r.offsetTo(gloc[0], gloc[1]); // convert to window coords 771 772 if (!r.contains((int) event.getX(), (int) event.getY())) { 773 mDimmed = false; 774 doDim(true); 775 } 776 } 777 return false; // always pass the click through 778 } 779 }); 780 781 // Tidy up awkward focus behavior: the first view to be focused in 782 // trackball mode should be the alarms button 783 final ViewTreeObserver vto = alarmButton.getViewTreeObserver(); 784 vto.addOnGlobalFocusChangeListener(new ViewTreeObserver.OnGlobalFocusChangeListener() { 785 public void onGlobalFocusChanged(View oldFocus, View newFocus) { 786 if (oldFocus == null && newFocus == nightmodeButton) { 787 alarmButton.requestFocus(); 788 } 789 } 790 }); 791 } 792 793 @Override 794 public void onConfigurationChanged(Configuration newConfig) { 795 super.onConfigurationChanged(newConfig); 796 if (mScreenSaverMode) { 797 moveScreenSaver(); 798 } else { 799 initViews(); 800 doDim(false); 801 refreshAll(); 802 } 803 } 804 805 @Override 806 public boolean onOptionsItemSelected(MenuItem item) { 807 switch (item.getItemId()) { 808 case R.id.menu_item_alarms: 809 startActivity(new Intent(DeskClock.this, AlarmClock.class)); 810 return true; 811 case R.id.menu_item_add_alarm: 812 startActivity(new Intent(this, SetAlarm.class)); 813 return true; 814 case R.id.menu_item_dock_settings: 815 startActivity(new Intent(DOCK_SETTINGS_ACTION)); 816 return true; 817 default: 818 return false; 819 } 820 } 821 822 @Override 823 public boolean onCreateOptionsMenu(Menu menu) { 824 MenuInflater inflater = getMenuInflater(); 825 inflater.inflate(R.menu.desk_clock_menu, menu); 826 return true; 827 } 828 829 @Override 830 protected void onCreate(Bundle icicle) { 831 super.onCreate(icicle); 832 833 mRNG = new Random(); 834 835 try { 836 mGenieResources = getPackageManager().getResourcesForApplication(GENIE_PACKAGE_ID); 837 } catch (PackageManager.NameNotFoundException e) { 838 // no weather info available 839 Log.w(LOG_TAG, "Can't find "+GENIE_PACKAGE_ID+". Weather forecast will not be available."); 840 } 841 842 initViews(); 843 } 844 845} 846