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