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