DeskClock.java revision 11c38e64f05bbcfa4503edb1680f255946ff6350
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.ActionBar; 20import android.app.ActionBar.Tab; 21import android.app.Activity; 22import android.app.Fragment; 23import android.app.FragmentManager; 24import android.app.FragmentTransaction; 25import android.content.ActivityNotFoundException; 26import android.content.Context; 27import android.content.Intent; 28import android.content.SharedPreferences; 29import android.content.res.Configuration; 30import android.os.Bundle; 31import android.preference.PreferenceManager; 32import android.support.v13.app.FragmentPagerAdapter; 33import android.support.v4.view.ViewPager; 34import android.text.TextUtils; 35import android.util.Log; 36import android.view.Menu; 37import android.view.MenuItem; 38import android.view.MotionEvent; 39import android.view.View; 40import android.view.View.OnTouchListener; 41import android.widget.PopupMenu; 42import android.widget.TextView; 43 44import com.android.deskclock.alarms.AlarmStateManager; 45import com.android.deskclock.provider.Alarm; 46import com.android.deskclock.stopwatch.StopwatchFragment; 47import com.android.deskclock.stopwatch.StopwatchService; 48import com.android.deskclock.stopwatch.Stopwatches; 49import com.android.deskclock.timer.TimerFragment; 50import com.android.deskclock.timer.TimerObj; 51import com.android.deskclock.timer.Timers; 52import com.android.deskclock.worldclock.CitiesActivity; 53 54import java.util.ArrayList; 55import java.util.HashSet; 56import java.util.Locale; 57import java.util.TimeZone; 58 59/** 60 * DeskClock clock view for desk docks. 61 */ 62public class DeskClock extends Activity implements LabelDialogFragment.TimerLabelDialogHandler, 63 LabelDialogFragment.AlarmLabelDialogHandler{ 64 private static final boolean DEBUG = false; 65 66 private static final String LOG_TAG = "DeskClock"; 67 68 // Alarm action for midnight (so we can update the date display). 69 private static final String KEY_SELECTED_TAB = "selected_tab"; 70 private static final String KEY_CLOCK_STATE = "clock_state"; 71 72 public static final String SELECT_TAB_INTENT_EXTRA = "deskclock.select.tab"; 73 74 private ActionBar mActionBar; 75 private Tab mAlarmTab; 76 private Tab mClockTab; 77 private Tab mTimerTab; 78 private Tab mStopwatchTab; 79 private Menu mMenu; 80 81 private ViewPager mViewPager; 82 private TabsAdapter mTabsAdapter; 83 84 public static final int ALARM_TAB_INDEX = 0; 85 public static final int CLOCK_TAB_INDEX = 1; 86 public static final int TIMER_TAB_INDEX = 2; 87 public static final int STOPWATCH_TAB_INDEX = 3; 88 // Tabs indices are switched for right-to-left since there is no 89 // native support for RTL in the ViewPager. 90 public static final int RTL_ALARM_TAB_INDEX = 3; 91 public static final int RTL_CLOCK_TAB_INDEX = 2; 92 public static final int RTL_TIMER_TAB_INDEX = 1; 93 public static final int RTL_STOPWATCH_TAB_INDEX = 0; 94 95 private int mSelectedTab; 96 97 @Override 98 public void onNewIntent(Intent newIntent) { 99 super.onNewIntent(newIntent); 100 if (DEBUG) Log.d(LOG_TAG, "onNewIntent with intent: " + newIntent); 101 102 // update our intent so that we can consult it to determine whether or 103 // not the most recent launch was via a dock event 104 setIntent(newIntent); 105 106 // Timer receiver may ask to go to the timers fragment if a timer expired. 107 int tab = newIntent.getIntExtra(SELECT_TAB_INTENT_EXTRA, -1); 108 if (tab != -1) { 109 if (mActionBar != null) { 110 mActionBar.setSelectedNavigationItem(tab); 111 } 112 } 113 } 114 115 private void initViews() { 116 if (mTabsAdapter == null) { 117 mViewPager = new ViewPager(this); 118 mViewPager.setId(R.id.desk_clock_pager); 119 // Keep all four tabs to minimize jank. 120 mViewPager.setOffscreenPageLimit(3); 121 mTabsAdapter = new TabsAdapter(this, mViewPager); 122 createTabs(mSelectedTab); 123 } 124 setContentView(mViewPager); 125 mActionBar.setSelectedNavigationItem(mSelectedTab); 126 } 127 128 private void createTabs(int selectedIndex) { 129 mActionBar = getActionBar(); 130 131 if (mActionBar != null) { 132 mActionBar.setDisplayOptions(0); 133 mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); 134 135 mAlarmTab = mActionBar.newTab(); 136 mAlarmTab.setIcon(R.drawable.alarm_tab); 137 mAlarmTab.setContentDescription(R.string.menu_alarm); 138 mTabsAdapter.addTab(mAlarmTab, AlarmClockFragment.class, ALARM_TAB_INDEX); 139 140 mClockTab = mActionBar.newTab(); 141 mClockTab.setIcon(R.drawable.clock_tab); 142 mClockTab.setContentDescription(R.string.menu_clock); 143 mTabsAdapter.addTab(mClockTab, ClockFragment.class, CLOCK_TAB_INDEX); 144 145 mTimerTab = mActionBar.newTab(); 146 mTimerTab.setIcon(R.drawable.timer_tab); 147 mTimerTab.setContentDescription(R.string.menu_timer); 148 mTabsAdapter.addTab(mTimerTab, TimerFragment.class, TIMER_TAB_INDEX); 149 150 mStopwatchTab = mActionBar.newTab(); 151 mStopwatchTab.setIcon(R.drawable.stopwatch_tab); 152 mStopwatchTab.setContentDescription(R.string.menu_stopwatch); 153 mTabsAdapter.addTab(mStopwatchTab, StopwatchFragment.class,STOPWATCH_TAB_INDEX); 154 155 mActionBar.setSelectedNavigationItem(selectedIndex); 156 mTabsAdapter.notifySelectedPage(selectedIndex); 157 } 158 } 159 160 @Override 161 protected void onCreate(Bundle icicle) { 162 super.onCreate(icicle); 163 164 mSelectedTab = CLOCK_TAB_INDEX; 165 if (icicle != null) { 166 mSelectedTab = icicle.getInt(KEY_SELECTED_TAB, CLOCK_TAB_INDEX); 167 } 168 169 // Timer receiver may ask the app to go to the timer fragment if a timer expired 170 Intent i = getIntent(); 171 if (i != null) { 172 int tab = i.getIntExtra(SELECT_TAB_INTENT_EXTRA, -1); 173 if (tab != -1) { 174 mSelectedTab = tab; 175 } 176 } 177 initViews(); 178 setHomeTimeZone(); 179 180 // We need to update the system next alarm time on app startup because the 181 // user might have clear our data. 182 AlarmStateManager.updateNextAlarm(this); 183 ExtensionsFactory.init(getAssets()); 184 } 185 186 @Override 187 protected void onResume() { 188 super.onResume(); 189 190 // We only want to show notifications for stopwatch/timer when the app is closed so 191 // that we don't have to worry about keeping the notifications in perfect sync with 192 // the app. 193 Intent stopwatchIntent = new Intent(getApplicationContext(), StopwatchService.class); 194 stopwatchIntent.setAction(Stopwatches.KILL_NOTIF); 195 startService(stopwatchIntent); 196 197 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 198 SharedPreferences.Editor editor = prefs.edit(); 199 editor.putBoolean(Timers.NOTIF_APP_OPEN, true); 200 editor.apply(); 201 Intent timerIntent = new Intent(); 202 timerIntent.setAction(Timers.NOTIF_IN_USE_CANCEL); 203 sendBroadcast(timerIntent); 204 } 205 206 @Override 207 public void onPause() { 208 Intent intent = new Intent(getApplicationContext(), StopwatchService.class); 209 intent.setAction(Stopwatches.SHOW_NOTIF); 210 startService(intent); 211 212 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 213 SharedPreferences.Editor editor = prefs.edit(); 214 editor.putBoolean(Timers.NOTIF_APP_OPEN, false); 215 editor.apply(); 216 Utils.showInUseNotifications(this); 217 218 super.onPause(); 219 } 220 221 @Override 222 protected void onSaveInstanceState(Bundle outState) { 223 super.onSaveInstanceState(outState); 224 outState.putInt(KEY_SELECTED_TAB, mActionBar.getSelectedNavigationIndex()); 225 } 226 227 public void clockButtonsOnClick(View v) { 228 if (v == null) { 229 return; 230 } 231 switch (v.getId()) { 232 case R.id.cities_button: 233 startActivity(new Intent(this, CitiesActivity.class)); 234 break; 235 default: 236 break; 237 } 238 } 239 240 @Override 241 public boolean onCreateOptionsMenu(Menu menu) { 242 // We only want to show it as a menu in landscape, and only for clock/alarm fragment. 243 mMenu = menu; 244 if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { 245 if (mActionBar.getSelectedNavigationIndex() == ALARM_TAB_INDEX || 246 mActionBar.getSelectedNavigationIndex() == CLOCK_TAB_INDEX) { 247 // Clear the menu so that it doesn't get duplicate items in case onCreateOptionsMenu 248 // was called multiple times. 249 menu.clear(); 250 getMenuInflater().inflate(R.menu.desk_clock_menu, menu); 251 } 252 // Always return true for landscape, regardless of whether we've inflated the menu, so 253 // that when we switch tabs this method will get called and we can inflate the menu. 254 return true; 255 } 256 return false; 257 } 258 259 @Override 260 public boolean onPrepareOptionsMenu(Menu menu) { 261 updateMenu(menu); 262 return true; 263 } 264 265 private void updateMenu(Menu menu) { 266 // Hide "help" if we don't have a URI for it. 267 MenuItem help = menu.findItem(R.id.menu_item_help); 268 if (help != null) { 269 Utils.prepareHelpMenuItem(this, help); 270 } 271 272 // Hide "lights out" for timer. 273 MenuItem nightMode = menu.findItem(R.id.menu_item_night_mode); 274 if (mActionBar.getSelectedNavigationIndex() == ALARM_TAB_INDEX) { 275 nightMode.setVisible(false); 276 } else if (mActionBar.getSelectedNavigationIndex() == CLOCK_TAB_INDEX) { 277 nightMode.setVisible(true); 278 } 279 } 280 281 @Override 282 public boolean onOptionsItemSelected(MenuItem item) { 283 if (processMenuClick(item)) { 284 return true; 285 } 286 287 return super.onOptionsItemSelected(item); 288 } 289 290 private boolean processMenuClick(MenuItem item) { 291 switch (item.getItemId()) { 292 case R.id.menu_item_settings: 293 startActivity(new Intent(DeskClock.this, SettingsActivity.class)); 294 return true; 295 case R.id.menu_item_help: 296 Intent i = item.getIntent(); 297 if (i != null) { 298 try { 299 startActivity(i); 300 } catch (ActivityNotFoundException e) { 301 // No activity found to match the intent - ignore 302 } 303 } 304 return true; 305 case R.id.menu_item_night_mode: 306 startActivity(new Intent(DeskClock.this, ScreensaverActivity.class)); 307 default: 308 break; 309 } 310 return true; 311 } 312 313 /*** 314 * Insert the local time zone as the Home Time Zone if one is not set 315 */ 316 private void setHomeTimeZone() { 317 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 318 String homeTimeZone = prefs.getString(SettingsActivity.KEY_HOME_TZ, ""); 319 if (!homeTimeZone.isEmpty()) { 320 return; 321 } 322 homeTimeZone = TimeZone.getDefault().getID(); 323 SharedPreferences.Editor editor = prefs.edit(); 324 editor.putString(SettingsActivity.KEY_HOME_TZ, homeTimeZone); 325 editor.apply(); 326 Log.v(LOG_TAG, "Setting home time zone to " + homeTimeZone); 327 } 328 329 public void registerPageChangedListener(DeskClockFragment frag) { 330 if (mTabsAdapter != null) { 331 mTabsAdapter.registerPageChangedListener(frag); 332 } 333 } 334 335 public void unregisterPageChangedListener(DeskClockFragment frag) { 336 if (mTabsAdapter != null) { 337 mTabsAdapter.unregisterPageChangedListener(frag); 338 } 339 } 340 341 342 /*** 343 * Adapter for wrapping together the ActionBar's tab with the ViewPager 344 */ 345 346 private class TabsAdapter extends FragmentPagerAdapter 347 implements ActionBar.TabListener, ViewPager.OnPageChangeListener { 348 349 private static final String KEY_TAB_POSITION = "tab_position"; 350 351 final class TabInfo { 352 private final Class<?> clss; 353 private final Bundle args; 354 355 TabInfo(Class<?> _class, int position) { 356 clss = _class; 357 args = new Bundle(); 358 args.putInt(KEY_TAB_POSITION, position); 359 } 360 361 public int getPosition() { 362 return args.getInt(KEY_TAB_POSITION, 0); 363 } 364 } 365 366 private final ArrayList<TabInfo> mTabs = new ArrayList <TabInfo>(); 367 ActionBar mMainActionBar; 368 Context mContext; 369 ViewPager mPager; 370 // Used for doing callbacks to fragments. 371 HashSet<String> mFragmentTags = new HashSet<String>(); 372 373 public TabsAdapter(Activity activity, ViewPager pager) { 374 super(activity.getFragmentManager()); 375 mContext = activity; 376 mMainActionBar = activity.getActionBar(); 377 mPager = pager; 378 mPager.setAdapter(this); 379 mPager.setOnPageChangeListener(this); 380 } 381 382 @Override 383 public Fragment getItem(int position) { 384 TabInfo info = mTabs.get(getRtlPosition(position)); 385 DeskClockFragment f = (DeskClockFragment) Fragment.instantiate( 386 mContext, info.clss.getName(), info.args); 387 return f; 388 } 389 390 @Override 391 public int getCount() { 392 return mTabs.size(); 393 } 394 395 public void addTab(ActionBar.Tab tab, Class<?> clss, int position) { 396 TabInfo info = new TabInfo(clss, position); 397 tab.setTag(info); 398 tab.setTabListener(this); 399 mTabs.add(info); 400 mMainActionBar.addTab(tab); 401 notifyDataSetChanged(); 402 } 403 404 @Override 405 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 406 // Do nothing 407 } 408 409 @Override 410 public void onPageSelected(int position) { 411 // Set the page before doing the menu so that onCreateOptionsMenu knows what page it is. 412 mMainActionBar.setSelectedNavigationItem(getRtlPosition(position)); 413 notifyPageChanged(position); 414 415 // Only show the overflow menu for alarm and world clock. 416 if (mMenu != null) { 417 // Make sure the menu's been initialized. 418 if (position == ALARM_TAB_INDEX || position == CLOCK_TAB_INDEX) { 419 mMenu.setGroupVisible(R.id.menu_items, true); 420 onCreateOptionsMenu(mMenu); 421 } else { 422 mMenu.setGroupVisible(R.id.menu_items, false); 423 } 424 } 425 } 426 427 @Override 428 public void onPageScrollStateChanged(int state) { 429 // Do nothing 430 } 431 432 @Override 433 public void onTabReselected(Tab arg0, FragmentTransaction arg1) { 434 // Do nothing 435 } 436 437 @Override 438 public void onTabSelected(Tab tab, FragmentTransaction ft) { 439 TabInfo info = (TabInfo)tab.getTag(); 440 int position = info.getPosition(); 441 mPager.setCurrentItem(getRtlPosition(position)); 442 } 443 444 @Override 445 public void onTabUnselected(Tab arg0, FragmentTransaction arg1) { 446 // Do nothing 447 } 448 449 public void notifySelectedPage(int page) { 450 notifyPageChanged(page); 451 } 452 453 private void notifyPageChanged(int newPage) { 454 for (String tag : mFragmentTags) { 455 final FragmentManager fm = getFragmentManager(); 456 DeskClockFragment f = (DeskClockFragment) fm.findFragmentByTag(tag); 457 if (f != null) { 458 f.onPageChanged(newPage); 459 } 460 } 461 } 462 463 public void registerPageChangedListener(DeskClockFragment frag) { 464 String tag = frag.getTag(); 465 if (mFragmentTags.contains(tag)) { 466 Log.wtf(LOG_TAG, "Trying to add an existing fragment " + tag); 467 } else { 468 mFragmentTags.add(frag.getTag()); 469 } 470 // Since registering a listener by the fragment is done sometimes after the page 471 // was already changed, make sure the fragment gets the current page 472 frag.onPageChanged(mMainActionBar.getSelectedNavigationIndex()); 473 } 474 475 public void unregisterPageChangedListener(DeskClockFragment frag) { 476 mFragmentTags.remove(frag.getTag()); 477 } 478 479 private boolean isRtl() { 480 return TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) == 481 View.LAYOUT_DIRECTION_RTL; 482 } 483 484 private int getRtlPosition(int position) { 485 if (isRtl()) { 486 switch (position) { 487 case TIMER_TAB_INDEX: 488 return RTL_TIMER_TAB_INDEX; 489 case CLOCK_TAB_INDEX: 490 return RTL_CLOCK_TAB_INDEX; 491 case STOPWATCH_TAB_INDEX: 492 return RTL_STOPWATCH_TAB_INDEX; 493 case ALARM_TAB_INDEX: 494 return RTL_ALARM_TAB_INDEX; 495 default: 496 break; 497 } 498 } 499 return position; 500 } 501 } 502 503 public static abstract class OnTapListener implements OnTouchListener { 504 private float mLastTouchX; 505 private float mLastTouchY; 506 private long mLastTouchTime; 507 private final TextView mMakePressedTextView; 508 private final int mPressedColor, mGrayColor; 509 private final float MAX_MOVEMENT_ALLOWED = 20; 510 private final long MAX_TIME_ALLOWED = 500; 511 512 public OnTapListener(Activity activity, TextView makePressedView) { 513 mMakePressedTextView = makePressedView; 514 mPressedColor = activity.getResources().getColor(Utils.getPressedColorId()); 515 mGrayColor = activity.getResources().getColor(Utils.getGrayColorId()); 516 } 517 518 @Override 519 public boolean onTouch(View v, MotionEvent e) { 520 switch (e.getAction()) { 521 case (MotionEvent.ACTION_DOWN): 522 mLastTouchTime = Utils.getTimeNow(); 523 mLastTouchX = e.getX(); 524 mLastTouchY = e.getY(); 525 if (mMakePressedTextView != null) { 526 mMakePressedTextView.setTextColor(mPressedColor); 527 } 528 break; 529 case (MotionEvent.ACTION_UP): 530 float xDiff = Math.abs(e.getX()-mLastTouchX); 531 float yDiff = Math.abs(e.getY()-mLastTouchY); 532 long timeDiff = (Utils.getTimeNow() - mLastTouchTime); 533 if (xDiff < MAX_MOVEMENT_ALLOWED && yDiff < MAX_MOVEMENT_ALLOWED 534 && timeDiff < MAX_TIME_ALLOWED) { 535 if (mMakePressedTextView != null) { 536 v = mMakePressedTextView; 537 } 538 processClick(v); 539 resetValues(); 540 return true; 541 } 542 resetValues(); 543 break; 544 case (MotionEvent.ACTION_MOVE): 545 xDiff = Math.abs(e.getX()-mLastTouchX); 546 yDiff = Math.abs(e.getY()-mLastTouchY); 547 if (xDiff >= MAX_MOVEMENT_ALLOWED || yDiff >= MAX_MOVEMENT_ALLOWED) { 548 resetValues(); 549 } 550 break; 551 default: 552 resetValues(); 553 } 554 return false; 555 } 556 557 private void resetValues() { 558 mLastTouchX = -1*MAX_MOVEMENT_ALLOWED + 1; 559 mLastTouchY = -1*MAX_MOVEMENT_ALLOWED + 1; 560 mLastTouchTime = -1*MAX_TIME_ALLOWED + 1; 561 if (mMakePressedTextView != null) { 562 mMakePressedTextView.setTextColor(mGrayColor); 563 } 564 } 565 566 protected abstract void processClick(View v); 567 } 568 569 /** Called by the LabelDialogFormat class after the dialog is finished. **/ 570 @Override 571 public void onDialogLabelSet(TimerObj timer, String label, String tag) { 572 Fragment frag = getFragmentManager().findFragmentByTag(tag); 573 if (frag instanceof TimerFragment) { 574 ((TimerFragment) frag).setLabel(timer, label); 575 } 576 } 577 578 /** Called by the LabelDialogFormat class after the dialog is finished. **/ 579 @Override 580 public void onDialogLabelSet(Alarm alarm, String label, String tag) { 581 Fragment frag = getFragmentManager().findFragmentByTag(tag); 582 if (frag instanceof AlarmClockFragment) { 583 ((AlarmClockFragment) frag).setLabel(alarm, label); 584 } 585 } 586} 587