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