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