DeskClock.java revision dbe6548e7be765d52c47bb8d878e260da9ffb58b
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(MenuItemControllerFactory.getInstance() 187 .buildMenuItemControllers(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.updateTimesUpNotification(this); 234 sendBroadcast(new Intent(Timers.NOTIF_IN_USE_CANCEL)); 235 mActivityResumed = true; 236 } 237 238 @Override 239 public void onPause() { 240 mActivityResumed = false; 241 Intent intent = new Intent(getApplicationContext(), StopwatchService.class); 242 intent.setAction(Stopwatches.SHOW_NOTIF); 243 startService(intent); 244 245 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 246 SharedPreferences.Editor editor = prefs.edit(); 247 editor.putBoolean(Timers.NOTIF_APP_OPEN, false); 248 editor.apply(); 249 Utils.showInUseNotifications(this); 250 Utils.updateTimesUpNotification(this); 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