DeskClock.java revision 2ddf7a583828ecb3534e8fcac7392e6e2c7cf5b4
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.Context; 22import android.content.Intent; 23import android.content.SharedPreferences; 24import android.media.AudioManager; 25import android.os.Bundle; 26import android.preference.PreferenceManager; 27import android.support.annotation.VisibleForTesting; 28import android.support.design.widget.TabLayout; 29import android.support.design.widget.TabLayout.Tab; 30import android.support.design.widget.TabLayout.ViewPagerOnTabSelectedListener; 31import android.support.v13.app.FragmentPagerAdapter; 32import android.support.v4.view.ViewPager.OnPageChangeListener; 33import android.support.v7.app.AppCompatActivity; 34import android.support.v7.widget.Toolbar; 35import android.util.ArraySet; 36import android.view.Menu; 37import android.view.MenuItem; 38import android.view.View; 39import android.view.View.OnClickListener; 40import android.view.ViewGroup; 41import android.widget.ImageButton; 42import android.widget.ImageView; 43 44import com.android.deskclock.actionbarmenu.ActionBarMenuManager; 45import com.android.deskclock.actionbarmenu.MenuItemController; 46import com.android.deskclock.actionbarmenu.MenuItemControllerFactory; 47import com.android.deskclock.actionbarmenu.NightModeMenuItemController; 48import com.android.deskclock.actionbarmenu.SettingMenuItemController; 49import com.android.deskclock.alarms.AlarmStateManager; 50import com.android.deskclock.events.Events; 51import com.android.deskclock.provider.Alarm; 52import com.android.deskclock.settings.SettingsActivity; 53import com.android.deskclock.stopwatch.StopwatchFragment; 54import com.android.deskclock.stopwatch.StopwatchService; 55import com.android.deskclock.stopwatch.Stopwatches; 56import com.android.deskclock.timer.TimerFragment; 57import com.android.deskclock.timer.TimerObj; 58import com.android.deskclock.timer.Timers; 59import com.android.deskclock.widget.RtlViewPager; 60 61import java.util.ArrayList; 62import java.util.List; 63import java.util.Set; 64import java.util.TimeZone; 65 66/** 67 * DeskClock clock view for desk docks. 68 */ 69public class DeskClock extends BaseActivity 70 implements LabelDialogFragment.TimerLabelDialogHandler, 71 LabelDialogFragment.AlarmLabelDialogHandler { 72 73 private static final String TAG = "DeskClock"; 74 75 // Alarm action for midnight (so we can update the date display). 76 private static final String KEY_SELECTED_TAB = "selected_tab"; 77 public static final String SELECT_TAB_INTENT_EXTRA = "deskclock.select.tab"; 78 79 public static final int ALARM_TAB_INDEX = 0; 80 public static final int CLOCK_TAB_INDEX = 1; 81 public static final int TIMER_TAB_INDEX = 2; 82 public static final int STOPWATCH_TAB_INDEX = 3; 83 84 private final ActionBarMenuManager mActionBarMenuManager = new ActionBarMenuManager(this); 85 private final MenuItemController nightModeMenuItemController = 86 new NightModeMenuItemController(this); 87 88 private TabLayout mTabLayout; 89 private RtlViewPager mViewPager; 90 private ImageView mFab; 91 private ImageButton mLeftButton; 92 private ImageButton mRightButton; 93 94 private TabsAdapter mTabsAdapter; 95 private int mSelectedTab; 96 private boolean mActivityResumed; 97 98 @Override 99 public void onNewIntent(Intent newIntent) { 100 super.onNewIntent(newIntent); 101 LogUtils.d(TAG, "onNewIntent with intent: %s", newIntent); 102 103 // update our intent so that we can consult it to determine whether or 104 // not the most recent launch was via a dock event 105 setIntent(newIntent); 106 107 // Timer receiver may ask to go to the timers fragment if a timer expired. 108 int tab = newIntent.getIntExtra(SELECT_TAB_INTENT_EXTRA, -1); 109 if (tab != -1 && mTabLayout != null) { 110 mTabLayout.getTabAt(tab).select(); 111 mViewPager.setCurrentItem(tab); 112 } 113 } 114 115 private void initViews() { 116 setContentView(R.layout.desk_clock); 117 setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); 118 mTabLayout = (TabLayout) findViewById(R.id.sliding_tabs); 119 mFab = (ImageView) 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 = (RtlViewPager) 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(); 131 mTabLayout.setOnTabSelectedListener(new ViewPagerOnTabSelectedListener(mViewPager)); 132 } 133 134 mFab.setOnClickListener(new OnClickListener() { 135 @Override 136 public void onClick(View view) { 137 getSelectedFragment().onFabClick(view); 138 } 139 }); 140 mLeftButton.setOnClickListener(new OnClickListener() { 141 @Override 142 public void onClick(View view) { 143 getSelectedFragment().onLeftButtonClick(view); 144 } 145 }); 146 mRightButton.setOnClickListener(new OnClickListener() { 147 @Override 148 public void onClick(View view) { 149 getSelectedFragment().onRightButtonClick(view); 150 } 151 }); 152 } 153 154 @VisibleForTesting 155 DeskClockFragment getSelectedFragment() { 156 return (DeskClockFragment) mTabsAdapter.getItem(mSelectedTab); 157 } 158 159 private void createTabs() { 160 final TabLayout.Tab alarmTab = mTabLayout.newTab(); 161 alarmTab.setIcon(R.drawable.ic_tab_alarm).setContentDescription(R.string.menu_alarm); 162 mTabsAdapter.addTab(alarmTab, AlarmClockFragment.class, ALARM_TAB_INDEX); 163 164 final Tab clockTab = mTabLayout.newTab(); 165 clockTab.setIcon(R.drawable.ic_tab_clock).setContentDescription(R.string.menu_clock); 166 mTabsAdapter.addTab(clockTab, ClockFragment.class, CLOCK_TAB_INDEX); 167 168 final Tab timerTab = mTabLayout.newTab(); 169 timerTab.setIcon(R.drawable.ic_tab_timer).setContentDescription(R.string.menu_timer); 170 mTabsAdapter.addTab(timerTab, TimerFragment.class, TIMER_TAB_INDEX); 171 172 final Tab stopwatchTab = mTabLayout.newTab(); 173 stopwatchTab.setIcon(R.drawable.ic_tab_stopwatch) 174 .setContentDescription(R.string.menu_stopwatch); 175 mTabsAdapter.addTab(stopwatchTab, StopwatchFragment.class, STOPWATCH_TAB_INDEX); 176 177 mTabLayout.getTabAt(mSelectedTab).select(); 178 mViewPager.setCurrentItem(mSelectedTab); 179 mTabsAdapter.notifySelectedPage(mSelectedTab); 180 } 181 182 @Override 183 protected void onCreate(Bundle icicle) { 184 super.onCreate(icicle); 185 mActionBarMenuManager.addMenuItemController(new SettingMenuItemController(this)) 186 .addMenuItemController( 187 MenuItemControllerFactory.getInstance().buildHelpMenuItemController(this)) 188 .addMenuItemController(nightModeMenuItemController); 189 setVolumeControlStream(AudioManager.STREAM_ALARM); 190 191 if (icicle != null) { 192 mSelectedTab = icicle.getInt(KEY_SELECTED_TAB, CLOCK_TAB_INDEX); 193 } else { 194 mSelectedTab = CLOCK_TAB_INDEX; 195 196 // Set the background color to initially match the theme value so that we can 197 // smoothly transition to the dynamic color. 198 setBackgroundColor(getResources().getColor(R.color.default_background), 199 false /* animate */); 200 } 201 202 // Timer receiver may ask the app to go to the timer fragment if a timer expired 203 Intent i = getIntent(); 204 if (i != null) { 205 int tab = i.getIntExtra(SELECT_TAB_INTENT_EXTRA, -1); 206 if (tab != -1) { 207 mSelectedTab = tab; 208 } 209 } 210 initViews(); 211 setHomeTimeZone(); 212 213 // We need to update the system next alarm time on app startup because the 214 // user might have clear our data. 215 AlarmStateManager.updateNextAlarm(this); 216 } 217 218 @Override 219 protected void onResume() { 220 super.onResume(); 221 222 // We only want to show notifications for stopwatch/timer when the app is closed so 223 // that we don't have to worry about keeping the notifications in perfect sync with 224 // the app. 225 Intent stopwatchIntent = new Intent(getApplicationContext(), StopwatchService.class); 226 stopwatchIntent.setAction(Stopwatches.KILL_NOTIF); 227 startService(stopwatchIntent); 228 229 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 230 SharedPreferences.Editor editor = prefs.edit(); 231 editor.putBoolean(Timers.NOTIF_APP_OPEN, true); 232 editor.apply(); 233 Utils.cancelTimesUpNotifications(this); 234 Utils.updateTimesUpNotification(this); 235 sendBroadcast(new Intent(Timers.NOTIF_IN_USE_CANCEL)); 236 mActivityResumed = true; 237 } 238 239 @Override 240 public void onPause() { 241 mActivityResumed = false; 242 Intent intent = new Intent(getApplicationContext(), StopwatchService.class); 243 intent.setAction(Stopwatches.SHOW_NOTIF); 244 startService(intent); 245 246 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 247 SharedPreferences.Editor editor = prefs.edit(); 248 editor.putBoolean(Timers.NOTIF_APP_OPEN, false); 249 editor.apply(); 250 Utils.showInUseNotifications(this); 251 Utils.updateTimesUpNotification(this); 252 253 super.onPause(); 254 } 255 256 @Override 257 protected void onSaveInstanceState(Bundle outState) { 258 super.onSaveInstanceState(outState); 259 outState.putInt(KEY_SELECTED_TAB, mTabLayout.getSelectedTabPosition()); 260 } 261 262 @Override 263 public boolean onCreateOptionsMenu(Menu menu) { 264 mActionBarMenuManager.createOptionsMenu(menu, getMenuInflater()); 265 return true; 266 } 267 268 @Override 269 public boolean onPrepareOptionsMenu(Menu menu) { 270 super.onPrepareOptionsMenu(menu); 271 mActionBarMenuManager.prepareShowMenu(menu); 272 return true; 273 } 274 275 @Override 276 public boolean onOptionsItemSelected(MenuItem item) { 277 if (mActionBarMenuManager.handleMenuItemClick(item)) { 278 return true; 279 } 280 return super.onOptionsItemSelected(item); 281 } 282 283 @Override 284 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 285 // Recreate the activity if any settings have been changed 286 if (requestCode == SettingMenuItemController.REQUEST_CHANGE_SETTINGS 287 && resultCode == RESULT_OK) { 288 recreate(); 289 } 290 } 291 292 /** 293 * Insert the local time zone as the Home Time Zone if one is not set 294 */ 295 private void setHomeTimeZone() { 296 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 297 String homeTimeZone = prefs.getString(SettingsActivity.KEY_HOME_TZ, ""); 298 if (!homeTimeZone.isEmpty()) { 299 return; 300 } 301 homeTimeZone = TimeZone.getDefault().getID(); 302 SharedPreferences.Editor editor = prefs.edit(); 303 editor.putString(SettingsActivity.KEY_HOME_TZ, homeTimeZone); 304 editor.apply(); 305 LogUtils.v(TAG, "Setting home time zone to " + homeTimeZone); 306 } 307 308 public void registerPageChangedListener(DeskClockFragment frag) { 309 if (mTabsAdapter != null) { 310 mTabsAdapter.registerPageChangedListener(frag); 311 } 312 } 313 314 public void unregisterPageChangedListener(DeskClockFragment frag) { 315 if (mTabsAdapter != null) { 316 mTabsAdapter.unregisterPageChangedListener(frag); 317 } 318 } 319 320 /** 321 * Adapter for wrapping together the ActionBar's tab with the ViewPager 322 */ 323 private class TabsAdapter extends FragmentPagerAdapter implements OnPageChangeListener { 324 325 private static final String KEY_TAB_POSITION = "tab_position"; 326 327 final class TabInfo { 328 private final Class<?> clss; 329 private final Bundle args; 330 331 TabInfo(Class<?> _class, int position) { 332 clss = _class; 333 args = new Bundle(); 334 args.putInt(KEY_TAB_POSITION, position); 335 } 336 337 public int getPosition() { 338 return args.getInt(KEY_TAB_POSITION, 0); 339 } 340 } 341 342 private final List<TabInfo> mTabs = new ArrayList<>(4 /* number of fragments */); 343 private final Context mContext; 344 private final RtlViewPager mPager; 345 // Used for doing callbacks to fragments. 346 private final Set<String> mFragmentTags = new ArraySet<>(4 /* number of fragments */); 347 348 public TabsAdapter(AppCompatActivity activity, RtlViewPager pager) { 349 super(activity.getFragmentManager()); 350 mContext = activity; 351 mPager = pager; 352 mPager.setAdapter(this); 353 mPager.setOnRTLPageChangeListener(this); 354 } 355 356 @Override 357 public Object instantiateItem(ViewGroup container, int position) { 358 return super.instantiateItem(container, mViewPager.getRtlAwareIndex(position)); 359 } 360 361 @Override 362 public Fragment getItem(int position) { 363 // Because this public method is called outside many times, 364 // check if it exits first before creating a new one. 365 final String name = makeFragmentName(R.id.desk_clock_pager, position); 366 Fragment fragment = getFragmentManager().findFragmentByTag(name); 367 if (fragment == null) { 368 TabInfo info = mTabs.get(position); 369 fragment = Fragment.instantiate(mContext, info.clss.getName(), info.args); 370 if (fragment instanceof TimerFragment) { 371 ((TimerFragment) fragment).setFabAppearance(); 372 ((TimerFragment) fragment).setLeftRightButtonAppearance(); 373 } 374 } 375 return fragment; 376 } 377 378 /** 379 * Copied from: 380 * android/frameworks/support/v13/java/android/support/v13/app/FragmentPagerAdapter.java#94 381 * Create unique name for the fragment so fragment manager knows it exist. 382 */ 383 private String makeFragmentName(int viewId, int index) { 384 return "android:switcher:" + viewId + ":" + index; 385 } 386 387 @Override 388 public int getCount() { 389 return mTabs.size(); 390 } 391 392 public void addTab(TabLayout.Tab tab, Class<?> clss, int position) { 393 TabInfo info = new TabInfo(clss, position); 394 mTabs.add(info); 395 mTabLayout.addTab(tab); 396 notifyDataSetChanged(); 397 } 398 399 @Override 400 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 401 // Do nothing 402 } 403 404 @Override 405 public void onPageSelected(int position) { 406 // Set the page before doing the menu so that onCreateOptionsMenu knows what page it is. 407 mTabLayout.getTabAt(position).select(); 408 notifyPageChanged(position); 409 410 mSelectedTab = position; 411 nightModeMenuItemController.setEnabled(mSelectedTab == CLOCK_TAB_INDEX); 412 413 if (mActivityResumed) { 414 switch (mSelectedTab) { 415 case ALARM_TAB_INDEX: 416 Events.sendAlarmEvent(R.string.action_show, R.string.label_deskclock); 417 break; 418 case CLOCK_TAB_INDEX: 419 Events.sendClockEvent(R.string.action_show, R.string.label_deskclock); 420 break; 421 case TIMER_TAB_INDEX: 422 Events.sendTimerEvent(R.string.action_show, R.string.label_deskclock); 423 break; 424 case STOPWATCH_TAB_INDEX: 425 Events.sendStopwatchEvent(R.string.action_show, R.string.label_deskclock); 426 break; 427 } 428 } 429 430 final DeskClockFragment f = (DeskClockFragment) getItem(position); 431 if (f != null) { 432 f.setFabAppearance(); 433 f.setLeftRightButtonAppearance(); 434 } 435 } 436 437 @Override 438 public void onPageScrollStateChanged(int state) { 439 // Do nothing 440 } 441 442 public void notifySelectedPage(int page) { 443 notifyPageChanged(page); 444 } 445 446 private void notifyPageChanged(int newPage) { 447 for (String tag : mFragmentTags) { 448 final FragmentManager fm = getFragmentManager(); 449 DeskClockFragment f = (DeskClockFragment) fm.findFragmentByTag(tag); 450 if (f != null) { 451 f.onPageChanged(newPage); 452 } 453 } 454 } 455 456 public void registerPageChangedListener(DeskClockFragment frag) { 457 String tag = frag.getTag(); 458 if (mFragmentTags.contains(tag)) { 459 LogUtils.wtf(TAG, "Trying to add an existing fragment " + tag); 460 } else { 461 mFragmentTags.add(frag.getTag()); 462 } 463 // Since registering a listener by the fragment is done sometimes after the page 464 // was already changed, make sure the fragment gets the current page 465 frag.onPageChanged(mTabLayout.getSelectedTabPosition()); 466 } 467 468 public void unregisterPageChangedListener(DeskClockFragment frag) { 469 mFragmentTags.remove(frag.getTag()); 470 } 471 } 472 473 /** 474 * Called by the LabelDialogFormat class after the dialog is finished. * 475 */ 476 @Override 477 public void onDialogLabelSet(TimerObj timer, String label, String tag) { 478 Fragment frag = getFragmentManager().findFragmentByTag(tag); 479 if (frag instanceof TimerFragment) { 480 ((TimerFragment) frag).setLabel(timer, label); 481 } 482 } 483 484 /** 485 * Called by the LabelDialogFormat class after the dialog is finished. * 486 */ 487 @Override 488 public void onDialogLabelSet(Alarm alarm, String label, String tag) { 489 Fragment frag = getFragmentManager().findFragmentByTag(tag); 490 if (frag instanceof AlarmClockFragment) { 491 ((AlarmClockFragment) frag).setLabel(alarm, label); 492 } 493 } 494 495 public int getSelectedTab() { 496 return mSelectedTab; 497 } 498 499 public ImageView getFab() { 500 return mFab; 501 } 502 503 public ImageButton getLeftButton() { 504 return mLeftButton; 505 } 506 507 public ImageButton getRightButton() { 508 return mRightButton; 509 } 510} 511