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.app.ActionBar; 20 import android.app.ActionBar.Tab; 21 import android.app.Activity; 22 import android.app.Fragment; 23 import android.app.FragmentManager; 24 import android.app.FragmentTransaction; 25 import android.content.ActivityNotFoundException; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.SharedPreferences; 29 import android.os.Bundle; 30 import android.preference.PreferenceManager; 31 import android.support.v13.app.FragmentPagerAdapter; 32 import android.support.v4.view.ViewPager; 33 import android.text.TextUtils; 34 import android.util.Log; 35 import android.view.Menu; 36 import android.view.MenuItem; 37 import android.view.MotionEvent; 38 import android.view.View; 39 import android.view.View.OnTouchListener; 40 import android.widget.PopupMenu; 41 import android.widget.TextView; 42 43 import com.android.deskclock.stopwatch.StopwatchFragment; 44 import com.android.deskclock.stopwatch.StopwatchService; 45 import com.android.deskclock.stopwatch.Stopwatches; 46 import com.android.deskclock.timer.TimerFragment; 47 import com.android.deskclock.timer.TimerObj; 48 import com.android.deskclock.timer.Timers; 49 import com.android.deskclock.worldclock.CitiesActivity; 50 51 import java.util.ArrayList; 52 import java.util.HashSet; 53 import java.util.Locale; 54 import java.util.TimeZone; 55 56 /** 57 * DeskClock clock view for desk docks. 58 */ 59 public class DeskClock extends Activity implements LabelDialogFragment.TimerLabelDialogHandler { 60 private static final boolean DEBUG = false; 61 62 private static final String LOG_TAG = "DeskClock"; 63 64 // Alarm action for midnight (so we can update the date display). 65 private static final String KEY_SELECTED_TAB = "selected_tab"; 66 private static final String KEY_CLOCK_STATE = "clock_state"; 67 68 public static final String SELECT_TAB_INTENT_EXTRA = "deskclock.select.tab"; 69 70 private ActionBar mActionBar; 71 private Tab mTimerTab; 72 private Tab mClockTab; 73 private Tab mStopwatchTab; 74 75 private ViewPager mViewPager; 76 private TabsAdapter mTabsAdapter; 77 78 public static final int TIMER_TAB_INDEX = 0; 79 public static final int CLOCK_TAB_INDEX = 1; 80 public static final int STOPWATCH_TAB_INDEX = 2; 81 // Tabs indices are switched for right-to-left since there is no 82 // native support for RTL in the ViewPager. 83 public static final int RTL_TIMER_TAB_INDEX = 2; 84 public static final int RTL_CLOCK_TAB_INDEX = 1; 85 public static final int RTL_STOPWATCH_TAB_INDEX = 0; 86 87 private int mSelectedTab; 88 89 @Override onNewIntent(Intent newIntent)90 public void onNewIntent(Intent newIntent) { 91 super.onNewIntent(newIntent); 92 if (DEBUG) Log.d(LOG_TAG, "onNewIntent with intent: " + 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) { 101 if (mActionBar != null) { 102 mActionBar.setSelectedNavigationItem(tab); 103 } 104 } 105 } 106 initViews()107 private void initViews() { 108 109 if (mTabsAdapter == null) { 110 mViewPager = new ViewPager(this); 111 mViewPager.setId(R.id.desk_clock_pager); 112 mTabsAdapter = new TabsAdapter(this, mViewPager); 113 createTabs(mSelectedTab); 114 } 115 setContentView(mViewPager); 116 mActionBar.setSelectedNavigationItem(mSelectedTab); 117 } 118 createTabs(int selectedIndex)119 private void createTabs(int selectedIndex) { 120 mActionBar = getActionBar(); 121 122 mActionBar.setDisplayOptions(0); 123 if (mActionBar != null) { 124 mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); 125 mTimerTab = mActionBar.newTab(); 126 mTimerTab.setIcon(R.drawable.timer_tab); 127 mTimerTab.setContentDescription(R.string.menu_timer); 128 mTabsAdapter.addTab(mTimerTab, TimerFragment.class,TIMER_TAB_INDEX); 129 130 mClockTab = mActionBar.newTab(); 131 mClockTab.setIcon(R.drawable.clock_tab); 132 mClockTab.setContentDescription(R.string.menu_clock); 133 mTabsAdapter.addTab(mClockTab, ClockFragment.class,CLOCK_TAB_INDEX); 134 mStopwatchTab = mActionBar.newTab(); 135 mStopwatchTab.setIcon(R.drawable.stopwatch_tab); 136 mStopwatchTab.setContentDescription(R.string.menu_stopwatch); 137 mTabsAdapter.addTab(mStopwatchTab, StopwatchFragment.class,STOPWATCH_TAB_INDEX); 138 mActionBar.setSelectedNavigationItem(selectedIndex); 139 mTabsAdapter.notifySelectedPage(selectedIndex); 140 } 141 } 142 143 @Override onCreate(Bundle icicle)144 protected void onCreate(Bundle icicle) { 145 super.onCreate(icicle); 146 147 mSelectedTab = CLOCK_TAB_INDEX; 148 if (icicle != null) { 149 mSelectedTab = icicle.getInt(KEY_SELECTED_TAB, CLOCK_TAB_INDEX); 150 } 151 152 // Timer receiver may ask the app to go to the timer fragment if a timer expired 153 Intent i = getIntent(); 154 if (i != null) { 155 int tab = i.getIntExtra(SELECT_TAB_INTENT_EXTRA, -1); 156 if (tab != -1) { 157 mSelectedTab = tab; 158 } 159 } 160 initViews(); 161 setHomeTimeZone(); 162 } 163 164 @Override onResume()165 protected void onResume() { 166 super.onResume(); 167 168 Intent stopwatchIntent = new Intent(getApplicationContext(), StopwatchService.class); 169 stopwatchIntent.setAction(Stopwatches.KILL_NOTIF); 170 startService(stopwatchIntent); 171 172 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 173 SharedPreferences.Editor editor = prefs.edit(); 174 editor.putBoolean(Timers.NOTIF_APP_OPEN, true); 175 editor.apply(); 176 Intent timerIntent = new Intent(); 177 timerIntent.setAction(Timers.NOTIF_IN_USE_CANCEL); 178 sendBroadcast(timerIntent); 179 } 180 181 @Override onPause()182 public void onPause() { 183 184 Intent intent = new Intent(getApplicationContext(), StopwatchService.class); 185 intent.setAction(Stopwatches.SHOW_NOTIF); 186 startService(intent); 187 188 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 189 SharedPreferences.Editor editor = prefs.edit(); 190 editor.putBoolean(Timers.NOTIF_APP_OPEN, false); 191 editor.apply(); 192 Utils.showInUseNotifications(this); 193 194 super.onPause(); 195 } 196 197 @Override onSaveInstanceState(Bundle outState)198 protected void onSaveInstanceState(Bundle outState) { 199 super.onSaveInstanceState(outState); 200 outState.putInt(KEY_SELECTED_TAB, mActionBar.getSelectedNavigationIndex()); 201 } 202 clockButtonsOnClick(View v)203 public void clockButtonsOnClick(View v) { 204 if (v == null) 205 return; 206 switch (v.getId()) { 207 case R.id.alarms_button: 208 startActivity(new Intent(this, AlarmClock.class)); 209 break; 210 case R.id.cities_button: 211 startActivity(new Intent(this, CitiesActivity.class)); 212 break; 213 case R.id.menu_button: 214 showMenu(v); 215 break; 216 default: 217 break; 218 } 219 } 220 showMenu(View v)221 private void showMenu(View v) { 222 PopupMenu popupMenu = new PopupMenu(this, v); 223 popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener () { 224 @Override 225 public boolean onMenuItemClick(MenuItem item) { 226 switch (item.getItemId()) { 227 case R.id.menu_item_settings: 228 startActivity(new Intent(DeskClock.this, SettingsActivity.class)); 229 return true; 230 case R.id.menu_item_help: 231 Intent i = item.getIntent(); 232 if (i != null) { 233 try { 234 startActivity(i); 235 } catch (ActivityNotFoundException e) { 236 // No activity found to match the intent - ignore 237 } 238 } 239 return true; 240 case R.id.menu_item_night_mode: 241 startActivity(new Intent(DeskClock.this, ScreensaverActivity.class)); 242 default: 243 break; 244 } 245 return true; 246 } 247 }); 248 popupMenu.inflate(R.menu.desk_clock_menu); 249 250 Menu menu = popupMenu.getMenu(); 251 MenuItem help = menu.findItem(R.id.menu_item_help); 252 if (help != null) { 253 Utils.prepareHelpMenuItem(this, help); 254 } 255 popupMenu.show(); 256 } 257 258 /*** 259 * Insert the local time zone as the Home Time Zone if one is not set 260 */ setHomeTimeZone()261 private void setHomeTimeZone() { 262 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 263 String homeTimeZone = prefs.getString(SettingsActivity.KEY_HOME_TZ, ""); 264 if (!homeTimeZone.isEmpty()) { 265 return; 266 } 267 homeTimeZone = TimeZone.getDefault().getID(); 268 SharedPreferences.Editor editor = prefs.edit(); 269 editor.putString(SettingsActivity.KEY_HOME_TZ, homeTimeZone); 270 editor.apply(); 271 Log.v(LOG_TAG, "Setting home time zone to " + homeTimeZone); 272 } 273 registerPageChangedListener(DeskClockFragment frag)274 public void registerPageChangedListener(DeskClockFragment frag) { 275 if (mTabsAdapter != null) { 276 mTabsAdapter.registerPageChangedListener(frag); 277 } 278 } 279 unregisterPageChangedListener(DeskClockFragment frag)280 public void unregisterPageChangedListener(DeskClockFragment frag) { 281 if (mTabsAdapter != null) { 282 mTabsAdapter.unregisterPageChangedListener(frag); 283 } 284 } 285 286 287 /*** 288 * Adapter for wrapping together the ActionBar's tab with the ViewPager 289 */ 290 291 private class TabsAdapter extends FragmentPagerAdapter 292 implements ActionBar.TabListener, ViewPager.OnPageChangeListener { 293 294 private static final String KEY_TAB_POSITION = "tab_position"; 295 296 final class TabInfo { 297 private final Class<?> clss; 298 private final Bundle args; 299 TabInfo(Class<?> _class, int position)300 TabInfo(Class<?> _class, int position) { 301 clss = _class; 302 args = new Bundle(); 303 args.putInt(KEY_TAB_POSITION, position); 304 } 305 getPosition()306 public int getPosition() { 307 return args.getInt(KEY_TAB_POSITION, 0); 308 } 309 } 310 311 private final ArrayList<TabInfo> mTabs = new ArrayList <TabInfo>(); 312 ActionBar mMainActionBar; 313 Context mContext; 314 ViewPager mPager; 315 // Used for doing callbacks to fragments. 316 HashSet<String> mFragmentTags = new HashSet<String>(); 317 TabsAdapter(Activity activity, ViewPager pager)318 public TabsAdapter(Activity activity, ViewPager pager) { 319 super(activity.getFragmentManager()); 320 mContext = activity; 321 mMainActionBar = activity.getActionBar(); 322 mPager = pager; 323 mPager.setAdapter(this); 324 mPager.setOnPageChangeListener(this); 325 } 326 327 @Override getItem(int position)328 public Fragment getItem(int position) { 329 TabInfo info = mTabs.get(getRtlPosition(position)); 330 DeskClockFragment f = (DeskClockFragment) Fragment.instantiate( 331 mContext, info.clss.getName(), info.args); 332 return f; 333 } 334 335 @Override getCount()336 public int getCount() { 337 return mTabs.size(); 338 } 339 addTab(ActionBar.Tab tab, Class<?> clss, int position)340 public void addTab(ActionBar.Tab tab, Class<?> clss, int position) { 341 TabInfo info = new TabInfo(clss, position); 342 tab.setTag(info); 343 tab.setTabListener(this); 344 mTabs.add(info); 345 mMainActionBar.addTab(tab); 346 notifyDataSetChanged(); 347 } 348 349 @Override onPageScrolled(int position, float positionOffset, int positionOffsetPixels)350 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 351 // Do nothing 352 } 353 354 @Override onPageSelected(int position)355 public void onPageSelected(int position) { 356 mMainActionBar.setSelectedNavigationItem(getRtlPosition(position)); 357 notifyPageChanged(position); 358 } 359 360 @Override onPageScrollStateChanged(int state)361 public void onPageScrollStateChanged(int state) { 362 // Do nothing 363 } 364 365 @Override onTabReselected(Tab arg0, FragmentTransaction arg1)366 public void onTabReselected(Tab arg0, FragmentTransaction arg1) { 367 // Do nothing 368 } 369 370 @Override onTabSelected(Tab tab, FragmentTransaction ft)371 public void onTabSelected(Tab tab, FragmentTransaction ft) { 372 TabInfo info = (TabInfo)tab.getTag(); 373 mPager.setCurrentItem(getRtlPosition(info.getPosition())); 374 } 375 376 @Override onTabUnselected(Tab arg0, FragmentTransaction arg1)377 public void onTabUnselected(Tab arg0, FragmentTransaction arg1) { 378 // Do nothing 379 380 } 381 notifySelectedPage(int page)382 public void notifySelectedPage(int page) { 383 notifyPageChanged(page); 384 } 385 notifyPageChanged(int newPage)386 private void notifyPageChanged(int newPage) { 387 for (String tag : mFragmentTags) { 388 final FragmentManager fm = getFragmentManager(); 389 DeskClockFragment f = (DeskClockFragment) fm.findFragmentByTag(tag); 390 if (f != null) { 391 f.onPageChanged(newPage); 392 } 393 } 394 } 395 registerPageChangedListener(DeskClockFragment frag)396 public void registerPageChangedListener(DeskClockFragment frag) { 397 String tag = frag.getTag(); 398 if (mFragmentTags.contains(tag)) { 399 Log.wtf(LOG_TAG, "Trying to add an existing fragment " + tag); 400 } else { 401 mFragmentTags.add(frag.getTag()); 402 } 403 // Since registering a listener by the fragment is done sometimes after the page 404 // was already changed, make sure the fragment gets the current page 405 frag.onPageChanged(mMainActionBar.getSelectedNavigationIndex()); 406 } 407 unregisterPageChangedListener(DeskClockFragment frag)408 public void unregisterPageChangedListener(DeskClockFragment frag) { 409 mFragmentTags.remove(frag.getTag()); 410 } 411 isRtl()412 private boolean isRtl() { 413 return TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) == 414 View.LAYOUT_DIRECTION_RTL; 415 } 416 getRtlPosition(int position)417 private int getRtlPosition(int position) { 418 if (isRtl()) { 419 switch (position) { 420 case TIMER_TAB_INDEX: 421 return RTL_TIMER_TAB_INDEX; 422 case CLOCK_TAB_INDEX: 423 return RTL_CLOCK_TAB_INDEX; 424 case STOPWATCH_TAB_INDEX: 425 return RTL_STOPWATCH_TAB_INDEX; 426 default: 427 break; 428 } 429 } 430 return position; 431 } 432 } 433 434 public static abstract class OnTapListener implements OnTouchListener { 435 private float mLastTouchX; 436 private float mLastTouchY; 437 private long mLastTouchTime; 438 private final TextView mMakePressedTextView; 439 private final int mPressedColor, mGrayColor; 440 private final float MAX_MOVEMENT_ALLOWED = 20; 441 private final long MAX_TIME_ALLOWED = 500; 442 OnTapListener(Activity activity, TextView makePressedView)443 public OnTapListener(Activity activity, TextView makePressedView) { 444 mMakePressedTextView = makePressedView; 445 mPressedColor = activity.getResources().getColor(Utils.getPressedColorId()); 446 mGrayColor = activity.getResources().getColor(Utils.getGrayColorId()); 447 } 448 449 @Override onTouch(View v, MotionEvent e)450 public boolean onTouch(View v, MotionEvent e) { 451 switch (e.getAction()) { 452 case (MotionEvent.ACTION_DOWN): 453 mLastTouchTime = Utils.getTimeNow(); 454 mLastTouchX = e.getX(); 455 mLastTouchY = e.getY(); 456 if (mMakePressedTextView != null) { 457 mMakePressedTextView.setTextColor(mPressedColor); 458 } 459 break; 460 case (MotionEvent.ACTION_UP): 461 float xDiff = Math.abs(e.getX()-mLastTouchX); 462 float yDiff = Math.abs(e.getY()-mLastTouchY); 463 long timeDiff = (Utils.getTimeNow() - mLastTouchTime); 464 if (xDiff < MAX_MOVEMENT_ALLOWED && yDiff < MAX_MOVEMENT_ALLOWED 465 && timeDiff < MAX_TIME_ALLOWED) { 466 if (mMakePressedTextView != null) { 467 v = mMakePressedTextView; 468 } 469 processClick(v); 470 resetValues(); 471 return true; 472 } 473 resetValues(); 474 break; 475 case (MotionEvent.ACTION_MOVE): 476 xDiff = Math.abs(e.getX()-mLastTouchX); 477 yDiff = Math.abs(e.getY()-mLastTouchY); 478 if (xDiff >= MAX_MOVEMENT_ALLOWED || yDiff >= MAX_MOVEMENT_ALLOWED) { 479 resetValues(); 480 } 481 break; 482 default: 483 resetValues(); 484 } 485 return false; 486 } 487 resetValues()488 private void resetValues() { 489 mLastTouchX = -1*MAX_MOVEMENT_ALLOWED + 1; 490 mLastTouchY = -1*MAX_MOVEMENT_ALLOWED + 1; 491 mLastTouchTime = -1*MAX_TIME_ALLOWED + 1; 492 if (mMakePressedTextView != null) { 493 mMakePressedTextView.setTextColor(mGrayColor); 494 } 495 } 496 processClick(View v)497 protected abstract void processClick(View v); 498 } 499 500 /** Called by the LabelDialogFormat class after the dialog is finished. **/ 501 @Override onDialogLabelSet(TimerObj timer, String label, String tag)502 public void onDialogLabelSet(TimerObj timer, String label, String tag) { 503 Fragment frag = getFragmentManager().findFragmentByTag(tag); 504 if (frag instanceof TimerFragment) { 505 ((TimerFragment) frag).setLabel(timer, label); 506 } 507 } 508 } 509