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