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