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 17 package com.android.deskclock; 18 19 import android.animation.ArgbEvaluator; 20 import android.animation.ObjectAnimator; 21 import android.app.ActionBar; 22 import android.app.ActionBar.Tab; 23 import android.app.Activity; 24 import android.app.Fragment; 25 import android.app.FragmentManager; 26 import android.app.FragmentTransaction; 27 import android.content.ActivityNotFoundException; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.SharedPreferences; 31 import android.content.res.Configuration; 32 import android.graphics.Outline; 33 import android.media.AudioManager; 34 import android.os.Bundle; 35 import android.os.Handler; 36 import android.preference.PreferenceManager; 37 import android.support.v13.app.FragmentPagerAdapter; 38 import android.support.v4.view.ViewPager; 39 import android.text.TextUtils; 40 import android.text.format.DateUtils; 41 import android.util.Log; 42 import android.view.Menu; 43 import android.view.MenuItem; 44 import android.view.MotionEvent; 45 import android.view.View; 46 import android.view.View.OnClickListener; 47 import android.view.View.OnTouchListener; 48 import android.view.ViewOutlineProvider; 49 import android.widget.ImageButton; 50 import android.widget.TextView; 51 52 import com.android.deskclock.alarms.AlarmStateManager; 53 import com.android.deskclock.provider.Alarm; 54 import com.android.deskclock.stopwatch.StopwatchFragment; 55 import com.android.deskclock.stopwatch.StopwatchService; 56 import com.android.deskclock.stopwatch.Stopwatches; 57 import com.android.deskclock.timer.TimerFragment; 58 import com.android.deskclock.timer.TimerObj; 59 import com.android.deskclock.timer.Timers; 60 61 import java.util.ArrayList; 62 import java.util.HashSet; 63 import java.util.Locale; 64 import java.util.TimeZone; 65 66 /** 67 * DeskClock clock view for desk docks. 68 */ 69 public class DeskClock extends Activity implements LabelDialogFragment.TimerLabelDialogHandler, 70 LabelDialogFragment.AlarmLabelDialogHandler { 71 private static final boolean DEBUG = false; 72 private static final String LOG_TAG = "DeskClock"; 73 // Alarm action for midnight (so we can update the date display). 74 private static final String KEY_SELECTED_TAB = "selected_tab"; 75 private static final String KEY_LAST_HOUR_COLOR = "last_hour_color"; 76 // Check whether to change background every minute 77 private static final long BACKGROUND_COLOR_CHECK_DELAY_MILLIS = DateUtils.MINUTE_IN_MILLIS; 78 private static final int BACKGROUND_COLOR_INITIAL_ANIMATION_DURATION_MILLIS = 3000; 79 private static final int UNKNOWN_COLOR_ID = 0; 80 81 private boolean mIsFirstLaunch = true; 82 private ActionBar mActionBar; 83 private Tab mAlarmTab; 84 private Tab mClockTab; 85 private Tab mTimerTab; 86 private Tab mStopwatchTab; 87 private Menu mMenu; 88 private ViewPager mViewPager; 89 private TabsAdapter mTabsAdapter; 90 private Handler mHander; 91 private ImageButton mFab; 92 private ImageButton mLeftButton; 93 private ImageButton mRightButton; 94 private int mSelectedTab; 95 private int mLastHourColor = UNKNOWN_COLOR_ID; 96 private final Runnable mBackgroundColorChanger = new Runnable() { 97 @Override 98 public void run() { 99 setBackgroundColor(); 100 mHander.postDelayed(this, BACKGROUND_COLOR_CHECK_DELAY_MILLIS); 101 } 102 }; 103 104 public static final int ALARM_TAB_INDEX = 0; 105 public static final int CLOCK_TAB_INDEX = 1; 106 public static final int TIMER_TAB_INDEX = 2; 107 public static final int STOPWATCH_TAB_INDEX = 3; 108 // Tabs indices are switched for right-to-left since there is no 109 // native support for RTL in the ViewPager. 110 public static final int RTL_ALARM_TAB_INDEX = 3; 111 public static final int RTL_CLOCK_TAB_INDEX = 2; 112 public static final int RTL_TIMER_TAB_INDEX = 1; 113 public static final int RTL_STOPWATCH_TAB_INDEX = 0; 114 public static final String SELECT_TAB_INTENT_EXTRA = "deskclock.select.tab"; 115 116 // TODO(rachelzhang): adding a broadcast receiver to adjust color when the timezone/time 117 // changes in the background. 118 119 @Override onStart()120 protected void onStart() { 121 super.onStart(); 122 if (mHander == null) { 123 mHander = new Handler(); 124 } 125 mHander.postDelayed(mBackgroundColorChanger, BACKGROUND_COLOR_CHECK_DELAY_MILLIS); 126 } 127 128 @Override onStop()129 protected void onStop() { 130 super.onStop(); 131 mHander.removeCallbacks(mBackgroundColorChanger); 132 } 133 134 @Override onNewIntent(Intent newIntent)135 public void onNewIntent(Intent newIntent) { 136 super.onNewIntent(newIntent); 137 if (DEBUG) Log.d(LOG_TAG, "onNewIntent with intent: " + newIntent); 138 139 // update our intent so that we can consult it to determine whether or 140 // not the most recent launch was via a dock event 141 setIntent(newIntent); 142 143 // Timer receiver may ask to go to the timers fragment if a timer expired. 144 int tab = newIntent.getIntExtra(SELECT_TAB_INTENT_EXTRA, -1); 145 if (tab != -1) { 146 if (mActionBar != null) { 147 mActionBar.setSelectedNavigationItem(tab); 148 } 149 } 150 } 151 152 private static final ViewOutlineProvider OVAL_OUTLINE_PROVIDER = new ViewOutlineProvider() { 153 @Override 154 public void getOutline(View view, Outline outline) { 155 outline.setOval(0, 0, view.getWidth(), view.getHeight()); 156 } 157 }; 158 initViews()159 private void initViews() { 160 setContentView(R.layout.desk_clock); 161 mFab = (ImageButton) findViewById(R.id.fab); 162 mFab.setOutlineProvider(OVAL_OUTLINE_PROVIDER); 163 mLeftButton = (ImageButton) findViewById(R.id.left_button); 164 mRightButton = (ImageButton) findViewById(R.id.right_button); 165 if (mTabsAdapter == null) { 166 mViewPager = (ViewPager) findViewById(R.id.desk_clock_pager); 167 // Keep all four tabs to minimize jank. 168 mViewPager.setOffscreenPageLimit(3); 169 mTabsAdapter = new TabsAdapter(this, mViewPager); 170 createTabs(mSelectedTab); 171 } 172 173 mFab.setOnClickListener(new OnClickListener() { 174 @Override 175 public void onClick(View view) { 176 getSelectedFragment().onFabClick(view); 177 } 178 }); 179 mLeftButton.setOnClickListener(new OnClickListener() { 180 @Override 181 public void onClick(View view) { 182 getSelectedFragment().onLeftButtonClick(view); 183 } 184 }); 185 mRightButton.setOnClickListener(new OnClickListener() { 186 @Override 187 public void onClick(View view) { 188 getSelectedFragment().onRightButtonClick(view); 189 } 190 }); 191 192 mActionBar.setSelectedNavigationItem(mSelectedTab); 193 } 194 getSelectedFragment()195 private DeskClockFragment getSelectedFragment() { 196 return (DeskClockFragment) mTabsAdapter.getItem(getRtlPosition(mSelectedTab)); 197 } 198 createTabs(int selectedIndex)199 private void createTabs(int selectedIndex) { 200 mActionBar = getActionBar(); 201 202 if (mActionBar != null) { 203 mActionBar.setDisplayOptions(0); 204 mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); 205 206 mAlarmTab = mActionBar.newTab(); 207 mAlarmTab.setIcon(R.drawable.ic_alarm_animation); 208 mAlarmTab.setContentDescription(R.string.menu_alarm); 209 mTabsAdapter.addTab(mAlarmTab, AlarmClockFragment.class, ALARM_TAB_INDEX); 210 211 mClockTab = mActionBar.newTab(); 212 mClockTab.setIcon(R.drawable.ic_clock_animation); 213 mClockTab.setContentDescription(R.string.menu_clock); 214 mTabsAdapter.addTab(mClockTab, ClockFragment.class, CLOCK_TAB_INDEX); 215 216 mTimerTab = mActionBar.newTab(); 217 mTimerTab.setIcon(R.drawable.ic_timer_animation); 218 mTimerTab.setContentDescription(R.string.menu_timer); 219 mTabsAdapter.addTab(mTimerTab, TimerFragment.class, TIMER_TAB_INDEX); 220 221 mStopwatchTab = mActionBar.newTab(); 222 mStopwatchTab.setIcon(R.drawable.ic_stopwatch_animation); 223 mStopwatchTab.setContentDescription(R.string.menu_stopwatch); 224 mTabsAdapter.addTab(mStopwatchTab, StopwatchFragment.class, STOPWATCH_TAB_INDEX); 225 226 mActionBar.setSelectedNavigationItem(selectedIndex); 227 mTabsAdapter.notifySelectedPage(selectedIndex); 228 } 229 } 230 231 @Override onCreate(Bundle icicle)232 protected void onCreate(Bundle icicle) { 233 super.onCreate(icicle); 234 setVolumeControlStream(AudioManager.STREAM_ALARM); 235 236 mIsFirstLaunch = (icicle == null); 237 getWindow().setBackgroundDrawable(null); 238 239 mIsFirstLaunch = true; 240 mSelectedTab = CLOCK_TAB_INDEX; 241 if (icicle != null) { 242 mSelectedTab = icicle.getInt(KEY_SELECTED_TAB, CLOCK_TAB_INDEX); 243 mLastHourColor = icicle.getInt(KEY_LAST_HOUR_COLOR, UNKNOWN_COLOR_ID); 244 if (mLastHourColor != UNKNOWN_COLOR_ID) { 245 getWindow().getDecorView().setBackgroundColor(mLastHourColor); 246 } 247 } 248 249 // Timer receiver may ask the app to go to the timer fragment if a timer expired 250 Intent i = getIntent(); 251 if (i != null) { 252 int tab = i.getIntExtra(SELECT_TAB_INTENT_EXTRA, -1); 253 if (tab != -1) { 254 mSelectedTab = tab; 255 } 256 } 257 initViews(); 258 setHomeTimeZone(); 259 260 // We need to update the system next alarm time on app startup because the 261 // user might have clear our data. 262 AlarmStateManager.updateNextAlarm(this); 263 ExtensionsFactory.init(getAssets()); 264 } 265 266 @Override onResume()267 protected void onResume() { 268 super.onResume(); 269 270 setBackgroundColor(); 271 272 // We only want to show notifications for stopwatch/timer when the app is closed so 273 // that we don't have to worry about keeping the notifications in perfect sync with 274 // the app. 275 Intent stopwatchIntent = new Intent(getApplicationContext(), StopwatchService.class); 276 stopwatchIntent.setAction(Stopwatches.KILL_NOTIF); 277 startService(stopwatchIntent); 278 279 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 280 SharedPreferences.Editor editor = prefs.edit(); 281 editor.putBoolean(Timers.NOTIF_APP_OPEN, true); 282 editor.apply(); 283 Intent timerIntent = new Intent(); 284 timerIntent.setAction(Timers.NOTIF_IN_USE_CANCEL); 285 sendBroadcast(timerIntent); 286 } 287 288 @Override onPause()289 public void onPause() { 290 Intent intent = new Intent(getApplicationContext(), StopwatchService.class); 291 intent.setAction(Stopwatches.SHOW_NOTIF); 292 startService(intent); 293 294 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 295 SharedPreferences.Editor editor = prefs.edit(); 296 editor.putBoolean(Timers.NOTIF_APP_OPEN, false); 297 editor.apply(); 298 Utils.showInUseNotifications(this); 299 300 super.onPause(); 301 } 302 303 @Override onSaveInstanceState(Bundle outState)304 protected void onSaveInstanceState(Bundle outState) { 305 super.onSaveInstanceState(outState); 306 outState.putInt(KEY_SELECTED_TAB, mActionBar.getSelectedNavigationIndex()); 307 outState.putInt(KEY_LAST_HOUR_COLOR, mLastHourColor); 308 } 309 310 @Override onCreateOptionsMenu(Menu menu)311 public boolean onCreateOptionsMenu(Menu menu) { 312 // We only want to show it as a menu in landscape, and only for clock/alarm fragment. 313 mMenu = menu; 314 if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { 315 if (mActionBar.getSelectedNavigationIndex() == ALARM_TAB_INDEX || 316 mActionBar.getSelectedNavigationIndex() == CLOCK_TAB_INDEX) { 317 // Clear the menu so that it doesn't get duplicate items in case onCreateOptionsMenu 318 // was called multiple times. 319 menu.clear(); 320 getMenuInflater().inflate(R.menu.desk_clock_menu, menu); 321 } 322 // Always return true for landscape, regardless of whether we've inflated the menu, so 323 // that when we switch tabs this method will get called and we can inflate the menu. 324 return true; 325 } 326 return false; 327 } 328 329 @Override onPrepareOptionsMenu(Menu menu)330 public boolean onPrepareOptionsMenu(Menu menu) { 331 updateMenu(menu); 332 return true; 333 } 334 updateMenu(Menu menu)335 private void updateMenu(Menu menu) { 336 // Hide "help" if we don't have a URI for it. 337 MenuItem help = menu.findItem(R.id.menu_item_help); 338 if (help != null) { 339 Utils.prepareHelpMenuItem(this, help); 340 } 341 342 // Hide "lights out" for timer. 343 MenuItem nightMode = menu.findItem(R.id.menu_item_night_mode); 344 if (mActionBar.getSelectedNavigationIndex() == ALARM_TAB_INDEX) { 345 nightMode.setVisible(false); 346 } else if (mActionBar.getSelectedNavigationIndex() == CLOCK_TAB_INDEX) { 347 nightMode.setVisible(true); 348 } 349 } 350 351 @Override onOptionsItemSelected(MenuItem item)352 public boolean onOptionsItemSelected(MenuItem item) { 353 if (processMenuClick(item)) { 354 return true; 355 } 356 357 return super.onOptionsItemSelected(item); 358 } 359 processMenuClick(MenuItem item)360 private boolean processMenuClick(MenuItem item) { 361 switch (item.getItemId()) { 362 case R.id.menu_item_settings: 363 startActivity(new Intent(DeskClock.this, SettingsActivity.class)); 364 return true; 365 case R.id.menu_item_help: 366 Intent i = item.getIntent(); 367 if (i != null) { 368 try { 369 startActivity(i); 370 } catch (ActivityNotFoundException e) { 371 // No activity found to match the intent - ignore 372 } 373 } 374 return true; 375 case R.id.menu_item_night_mode: 376 startActivity(new Intent(DeskClock.this, ScreensaverActivity.class)); 377 default: 378 break; 379 } 380 return true; 381 } 382 383 /** 384 * Insert the local time zone as the Home Time Zone if one is not set 385 */ setHomeTimeZone()386 private void setHomeTimeZone() { 387 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 388 String homeTimeZone = prefs.getString(SettingsActivity.KEY_HOME_TZ, ""); 389 if (!homeTimeZone.isEmpty()) { 390 return; 391 } 392 homeTimeZone = TimeZone.getDefault().getID(); 393 SharedPreferences.Editor editor = prefs.edit(); 394 editor.putString(SettingsActivity.KEY_HOME_TZ, homeTimeZone); 395 editor.apply(); 396 Log.v(LOG_TAG, "Setting home time zone to " + homeTimeZone); 397 } 398 registerPageChangedListener(DeskClockFragment frag)399 public void registerPageChangedListener(DeskClockFragment frag) { 400 if (mTabsAdapter != null) { 401 mTabsAdapter.registerPageChangedListener(frag); 402 } 403 } 404 unregisterPageChangedListener(DeskClockFragment frag)405 public void unregisterPageChangedListener(DeskClockFragment frag) { 406 if (mTabsAdapter != null) { 407 mTabsAdapter.unregisterPageChangedListener(frag); 408 } 409 } 410 setBackgroundColor()411 private void setBackgroundColor() { 412 final int duration; 413 if (mLastHourColor == UNKNOWN_COLOR_ID) { 414 mLastHourColor = getResources().getColor(R.color.default_background); 415 duration = BACKGROUND_COLOR_INITIAL_ANIMATION_DURATION_MILLIS; 416 } else { 417 duration = getResources().getInteger(android.R.integer.config_longAnimTime); 418 } 419 final int currHourColor = Utils.getCurrentHourColor(); 420 if (mLastHourColor != currHourColor) { 421 final ObjectAnimator animator = ObjectAnimator.ofInt(getWindow().getDecorView(), 422 "backgroundColor", mLastHourColor, currHourColor); 423 animator.setDuration(duration); 424 animator.setEvaluator(new ArgbEvaluator()); 425 animator.start(); 426 mLastHourColor = currHourColor; 427 } 428 } 429 430 /** 431 * Adapter for wrapping together the ActionBar's tab with the ViewPager 432 */ 433 private class TabsAdapter extends FragmentPagerAdapter 434 implements ActionBar.TabListener, ViewPager.OnPageChangeListener { 435 436 private static final String KEY_TAB_POSITION = "tab_position"; 437 438 final class TabInfo { 439 private final Class<?> clss; 440 private final Bundle args; 441 TabInfo(Class<?> _class, int position)442 TabInfo(Class<?> _class, int position) { 443 clss = _class; 444 args = new Bundle(); 445 args.putInt(KEY_TAB_POSITION, position); 446 } 447 getPosition()448 public int getPosition() { 449 return args.getInt(KEY_TAB_POSITION, 0); 450 } 451 } 452 453 private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>(); 454 ActionBar mMainActionBar; 455 Context mContext; 456 ViewPager mPager; 457 // Used for doing callbacks to fragments. 458 HashSet<String> mFragmentTags = new HashSet<String>(); 459 TabsAdapter(Activity activity, ViewPager pager)460 public TabsAdapter(Activity activity, ViewPager pager) { 461 super(activity.getFragmentManager()); 462 mContext = activity; 463 mMainActionBar = activity.getActionBar(); 464 mPager = pager; 465 mPager.setAdapter(this); 466 mPager.setOnPageChangeListener(this); 467 } 468 469 @Override getItem(int position)470 public Fragment getItem(int position) { 471 // Because this public method is called outside many times, 472 // check if it exits first before creating a new one. 473 final String name = makeFragmentName(R.id.desk_clock_pager, position); 474 Fragment fragment = getFragmentManager().findFragmentByTag(name); 475 if (fragment == null) { 476 TabInfo info = mTabs.get(getRtlPosition(position)); 477 fragment = Fragment.instantiate(mContext, info.clss.getName(), info.args); 478 if (fragment instanceof TimerFragment) { 479 ((TimerFragment) fragment).setFabAppearance(); 480 ((TimerFragment) fragment).setLeftRightButtonAppearance(); 481 } 482 } 483 return fragment; 484 } 485 486 /** 487 * Copied from: 488 * android/frameworks/support/v13/java/android/support/v13/app/FragmentPagerAdapter.java#94 489 * Create unique name for the fragment so fragment manager knows it exist. 490 */ makeFragmentName(int viewId, int index)491 private String makeFragmentName(int viewId, int index) { 492 return "android:switcher:" + viewId + ":" + index; 493 } 494 495 @Override getCount()496 public int getCount() { 497 return mTabs.size(); 498 } 499 addTab(ActionBar.Tab tab, Class<?> clss, int position)500 public void addTab(ActionBar.Tab tab, Class<?> clss, int position) { 501 TabInfo info = new TabInfo(clss, position); 502 tab.setTag(info); 503 tab.setTabListener(this); 504 mTabs.add(info); 505 mMainActionBar.addTab(tab); 506 notifyDataSetChanged(); 507 } 508 509 @Override onPageScrolled(int position, float positionOffset, int positionOffsetPixels)510 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 511 // Do nothing 512 } 513 514 @Override onPageSelected(int position)515 public void onPageSelected(int position) { 516 // Set the page before doing the menu so that onCreateOptionsMenu knows what page it is. 517 mMainActionBar.setSelectedNavigationItem(getRtlPosition(position)); 518 notifyPageChanged(position); 519 520 // Only show the overflow menu for alarm and world clock. 521 if (mMenu != null) { 522 // Make sure the menu's been initialized. 523 if (position == ALARM_TAB_INDEX || position == CLOCK_TAB_INDEX) { 524 mMenu.setGroupVisible(R.id.menu_items, true); 525 onCreateOptionsMenu(mMenu); 526 } else { 527 mMenu.setGroupVisible(R.id.menu_items, false); 528 } 529 } 530 } 531 532 @Override onPageScrollStateChanged(int state)533 public void onPageScrollStateChanged(int state) { 534 // Do nothing 535 } 536 537 @Override onTabReselected(Tab tab, FragmentTransaction arg1)538 public void onTabReselected(Tab tab, FragmentTransaction arg1) { 539 // Do nothing 540 } 541 542 @Override onTabSelected(Tab tab, FragmentTransaction ft)543 public void onTabSelected(Tab tab, FragmentTransaction ft) { 544 final TabInfo info = (TabInfo) tab.getTag(); 545 final int position = info.getPosition(); 546 final int rtlSafePosition = getRtlPosition(position); 547 mSelectedTab = position; 548 549 if (mIsFirstLaunch && isClockTab(rtlSafePosition)) { 550 mLeftButton.setVisibility(View.INVISIBLE); 551 mRightButton.setVisibility(View.INVISIBLE); 552 mFab.setVisibility(View.VISIBLE); 553 mFab.setImageResource(R.drawable.ic_globe); 554 mFab.setContentDescription(getString(R.string.button_cities)); 555 mIsFirstLaunch = false; 556 } else { 557 DeskClockFragment f = (DeskClockFragment) getItem(rtlSafePosition); 558 f.setFabAppearance(); 559 f.setLeftRightButtonAppearance(); 560 } 561 mPager.setCurrentItem(rtlSafePosition); 562 } 563 564 @Override onTabUnselected(Tab arg0, FragmentTransaction arg1)565 public void onTabUnselected(Tab arg0, FragmentTransaction arg1) { 566 // Do nothing 567 } 568 isClockTab(int rtlSafePosition)569 private boolean isClockTab(int rtlSafePosition) { 570 final int clockTabIndex = isRtl() ? RTL_CLOCK_TAB_INDEX : CLOCK_TAB_INDEX; 571 return rtlSafePosition == clockTabIndex; 572 } 573 notifySelectedPage(int page)574 public void notifySelectedPage(int page) { 575 notifyPageChanged(page); 576 } 577 notifyPageChanged(int newPage)578 private void notifyPageChanged(int newPage) { 579 for (String tag : mFragmentTags) { 580 final FragmentManager fm = getFragmentManager(); 581 DeskClockFragment f = (DeskClockFragment) fm.findFragmentByTag(tag); 582 if (f != null) { 583 f.onPageChanged(newPage); 584 } 585 } 586 } 587 registerPageChangedListener(DeskClockFragment frag)588 public void registerPageChangedListener(DeskClockFragment frag) { 589 String tag = frag.getTag(); 590 if (mFragmentTags.contains(tag)) { 591 Log.wtf(LOG_TAG, "Trying to add an existing fragment " + tag); 592 } else { 593 mFragmentTags.add(frag.getTag()); 594 } 595 // Since registering a listener by the fragment is done sometimes after the page 596 // was already changed, make sure the fragment gets the current page 597 frag.onPageChanged(mMainActionBar.getSelectedNavigationIndex()); 598 } 599 unregisterPageChangedListener(DeskClockFragment frag)600 public void unregisterPageChangedListener(DeskClockFragment frag) { 601 mFragmentTags.remove(frag.getTag()); 602 } 603 604 } 605 606 public static abstract class OnTapListener implements OnTouchListener { 607 private float mLastTouchX; 608 private float mLastTouchY; 609 private long mLastTouchTime; 610 private final TextView mMakePressedTextView; 611 private final int mPressedColor, mGrayColor; 612 private final float MAX_MOVEMENT_ALLOWED = 20; 613 private final long MAX_TIME_ALLOWED = 500; 614 OnTapListener(Activity activity, TextView makePressedView)615 public OnTapListener(Activity activity, TextView makePressedView) { 616 mMakePressedTextView = makePressedView; 617 mPressedColor = activity.getResources().getColor(Utils.getPressedColorId()); 618 mGrayColor = activity.getResources().getColor(Utils.getGrayColorId()); 619 } 620 621 @Override onTouch(View v, MotionEvent e)622 public boolean onTouch(View v, MotionEvent e) { 623 switch (e.getAction()) { 624 case (MotionEvent.ACTION_DOWN): 625 mLastTouchTime = Utils.getTimeNow(); 626 mLastTouchX = e.getX(); 627 mLastTouchY = e.getY(); 628 if (mMakePressedTextView != null) { 629 mMakePressedTextView.setTextColor(mPressedColor); 630 } 631 break; 632 case (MotionEvent.ACTION_UP): 633 float xDiff = Math.abs(e.getX() - mLastTouchX); 634 float yDiff = Math.abs(e.getY() - mLastTouchY); 635 long timeDiff = (Utils.getTimeNow() - mLastTouchTime); 636 if (xDiff < MAX_MOVEMENT_ALLOWED && yDiff < MAX_MOVEMENT_ALLOWED 637 && timeDiff < MAX_TIME_ALLOWED) { 638 if (mMakePressedTextView != null) { 639 v = mMakePressedTextView; 640 } 641 processClick(v); 642 resetValues(); 643 return true; 644 } 645 resetValues(); 646 break; 647 case (MotionEvent.ACTION_MOVE): 648 xDiff = Math.abs(e.getX() - mLastTouchX); 649 yDiff = Math.abs(e.getY() - mLastTouchY); 650 if (xDiff >= MAX_MOVEMENT_ALLOWED || yDiff >= MAX_MOVEMENT_ALLOWED) { 651 resetValues(); 652 } 653 break; 654 default: 655 resetValues(); 656 } 657 return false; 658 } 659 resetValues()660 private void resetValues() { 661 mLastTouchX = -1 * MAX_MOVEMENT_ALLOWED + 1; 662 mLastTouchY = -1 * MAX_MOVEMENT_ALLOWED + 1; 663 mLastTouchTime = -1 * MAX_TIME_ALLOWED + 1; 664 if (mMakePressedTextView != null) { 665 mMakePressedTextView.setTextColor(mGrayColor); 666 } 667 } 668 processClick(View v)669 protected abstract void processClick(View v); 670 } 671 672 /** 673 * Called by the LabelDialogFormat class after the dialog is finished. * 674 */ 675 @Override onDialogLabelSet(TimerObj timer, String label, String tag)676 public void onDialogLabelSet(TimerObj timer, String label, String tag) { 677 Fragment frag = getFragmentManager().findFragmentByTag(tag); 678 if (frag instanceof TimerFragment) { 679 ((TimerFragment) frag).setLabel(timer, label); 680 } 681 } 682 683 /** 684 * Called by the LabelDialogFormat class after the dialog is finished. * 685 */ 686 @Override onDialogLabelSet(Alarm alarm, String label, String tag)687 public void onDialogLabelSet(Alarm alarm, String label, String tag) { 688 Fragment frag = getFragmentManager().findFragmentByTag(tag); 689 if (frag instanceof AlarmClockFragment) { 690 ((AlarmClockFragment) frag).setLabel(alarm, label); 691 } 692 } 693 getSelectedTab()694 public int getSelectedTab() { 695 return mSelectedTab; 696 } 697 isRtl()698 private boolean isRtl() { 699 return TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) == 700 View.LAYOUT_DIRECTION_RTL; 701 } 702 getRtlPosition(int position)703 private int getRtlPosition(int position) { 704 if (isRtl()) { 705 switch (position) { 706 case TIMER_TAB_INDEX: 707 return RTL_TIMER_TAB_INDEX; 708 case CLOCK_TAB_INDEX: 709 return RTL_CLOCK_TAB_INDEX; 710 case STOPWATCH_TAB_INDEX: 711 return RTL_STOPWATCH_TAB_INDEX; 712 case ALARM_TAB_INDEX: 713 return RTL_ALARM_TAB_INDEX; 714 default: 715 break; 716 } 717 } 718 return position; 719 } 720 getFab()721 public ImageButton getFab() { 722 return mFab; 723 } 724 getLeftButton()725 public ImageButton getLeftButton() { 726 return mLeftButton; 727 } 728 getRightButton()729 public ImageButton getRightButton() { 730 return mRightButton; 731 } 732 } 733