1 /* 2 * Copyright (C) 2021 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 package com.android.calendar 17 18 import android.accounts.AccountManager 19 import android.accounts.AccountManagerCallback 20 import android.accounts.AccountManagerFuture 21 import android.animation.Animator 22 import android.animation.Animator.AnimatorListener 23 import android.animation.ObjectAnimator 24 import android.app.ActionBar 25 import android.app.ActionBar.Tab 26 import android.app.Activity 27 import android.app.Fragment 28 import android.app.FragmentManager 29 import android.app.FragmentTransaction 30 import android.content.AsyncQueryHandler 31 import android.content.ContentResolver 32 import android.content.Intent 33 import android.content.SharedPreferences 34 import android.content.SharedPreferences.OnSharedPreferenceChangeListener 35 import android.content.res.Configuration 36 import android.content.res.Resources 37 import android.database.ContentObserver 38 import android.database.Cursor 39 import android.graphics.drawable.LayerDrawable 40 import android.net.Uri 41 import android.os.Bundle 42 import android.os.Handler 43 import android.provider.CalendarContract 44 import android.provider.CalendarContract.Attendees 45 import android.provider.CalendarContract.Calendars 46 import android.provider.CalendarContract.Events 47 import android.text.TextUtils 48 import android.text.format.DateFormat 49 import android.text.format.DateUtils 50 import android.text.format.Time 51 import android.util.Log 52 import android.view.Menu 53 import android.view.MenuItem 54 import android.view.View 55 import android.view.accessibility.AccessibilityEvent 56 import android.widget.LinearLayout 57 import android.widget.RelativeLayout 58 import android.widget.RelativeLayout.LayoutParams 59 import android.widget.TextView 60 import com.android.calendar.CalendarController.EventHandler 61 import com.android.calendar.CalendarController.EventInfo 62 import com.android.calendar.CalendarController.EventType 63 import com.android.calendar.CalendarController.ViewType 64 import com.android.calendar.month.MonthByWeekFragment 65 import java.util.Locale 66 import java.util.TimeZone 67 import android.provider.CalendarContract.Attendees.ATTENDEE_STATUS 68 import android.provider.CalendarContract.EXTRA_EVENT_ALL_DAY 69 import android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME 70 import android.provider.CalendarContract.EXTRA_EVENT_END_TIME 71 72 class AllInOneActivity : Activity(), EventHandler, OnSharedPreferenceChangeListener, 73 ActionBar.TabListener, ActionBar.OnNavigationListener { 74 private var mController: CalendarController? = null 75 private var mOnSaveInstanceStateCalled = false 76 private var mBackToPreviousView = false 77 private var mContentResolver: ContentResolver? = null 78 private var mPreviousView = 0 79 private var mCurrentView = 0 80 private var mPaused = true 81 private var mUpdateOnResume = false 82 private var mHideControls = false 83 private var mShowSideViews = true 84 private var mShowWeekNum = false 85 private var mHomeTime: TextView? = null 86 private var mDateRange: TextView? = null 87 private var mWeekTextView: TextView? = null 88 private var mMiniMonth: View? = null 89 private var mCalendarsList: View? = null 90 private var mMiniMonthContainer: View? = null 91 private var mSecondaryPane: View? = null 92 private var mTimeZone: String? = null 93 private var mShowCalendarControls = false 94 private var mShowEventInfoFullScreen = false 95 private var mWeekNum = 0 96 private var mCalendarControlsAnimationTime = 0 97 private var mControlsAnimateWidth = 0 98 private var mControlsAnimateHeight = 0 99 private var mViewEventId: Long = -1 100 private var mIntentEventStartMillis: Long = -1 101 private var mIntentEventEndMillis: Long = -1 102 private var mIntentAttendeeResponse: Int = Attendees.ATTENDEE_STATUS_NONE 103 private var mIntentAllDay = false 104 105 // Action bar and Navigation bar (left side of Action bar) 106 private var mActionBar: ActionBar? = null 107 private val mDayTab: Tab? = null 108 private val mWeekTab: Tab? = null 109 private val mMonthTab: Tab? = null 110 private var mControlsMenu: MenuItem? = null 111 private var mOptionsMenu: Menu? = null 112 private var mActionBarMenuSpinnerAdapter: CalendarViewAdapter? = null 113 private var mHandler: QueryHandler? = null 114 private var mCheckForAccounts = true 115 private var mHideString: String? = null 116 private var mShowString: String? = null 117 var mDayOfMonthIcon: DayOfMonthDrawable? = null 118 var mOrientation = 0 119 120 // Params for animating the controls on the right 121 private var mControlsParams: LayoutParams? = null 122 private var mVerticalControlsParams: LinearLayout.LayoutParams? = null 123 private val mSlideAnimationDoneListener: AnimatorListener = object : AnimatorListener { 124 @Override onAnimationCancelnull125 override fun onAnimationCancel(animation: Animator) { 126 } 127 128 @Override onAnimationEndnull129 override fun onAnimationEnd(animation: Animator) { 130 val visibility: Int = if (mShowSideViews) View.VISIBLE else View.GONE 131 mMiniMonth?.setVisibility(visibility) 132 mCalendarsList?.setVisibility(visibility) 133 mMiniMonthContainer?.setVisibility(visibility) 134 } 135 136 @Override onAnimationRepeatnull137 override fun onAnimationRepeat(animation: Animator) { 138 } 139 140 @Override onAnimationStartnull141 override fun onAnimationStart(animation: Animator) { 142 } 143 } 144 145 private inner class QueryHandler(cr: ContentResolver?) : AsyncQueryHandler(cr) { 146 @Override onQueryCompletenull147 protected override fun onQueryComplete(token: Int, cookie: Any?, cursor: Cursor?) { 148 mCheckForAccounts = false 149 try { 150 // If the query didn't return a cursor for some reason return 151 if (cursor == null || cursor.getCount() > 0 || isFinishing()) { 152 return 153 } 154 } finally { 155 if (cursor != null) { 156 cursor.close() 157 } 158 } 159 val options = Bundle() 160 options.putCharSequence( 161 "introMessage", 162 getResources().getString(R.string.create_an_account_desc) 163 ) 164 options.putBoolean("allowSkip", true) 165 val am: AccountManager = AccountManager.get(this@AllInOneActivity) 166 am.addAccount("com.google", CalendarContract.AUTHORITY, null, options, 167 this@AllInOneActivity, 168 object : AccountManagerCallback<Bundle?> { 169 @Override 170 override fun run(future: AccountManagerFuture<Bundle?>?) { 171 } 172 }, null 173 ) 174 } 175 } 176 177 private val mHomeTimeUpdater: Runnable = object : Runnable { 178 @Override runnull179 override fun run() { 180 mTimeZone = Utils.getTimeZone(this@AllInOneActivity, this) 181 updateSecondaryTitleFields(-1) 182 this@AllInOneActivity.invalidateOptionsMenu() 183 Utils.setMidnightUpdater(mHandler, mTimeChangesUpdater, mTimeZone) 184 } 185 } 186 187 // runs every midnight/time changes and refreshes the today icon 188 private val mTimeChangesUpdater: Runnable = object : Runnable { 189 @Override runnull190 override fun run() { 191 mTimeZone = Utils.getTimeZone(this@AllInOneActivity, mHomeTimeUpdater) 192 this@AllInOneActivity.invalidateOptionsMenu() 193 Utils.setMidnightUpdater(mHandler, this, mTimeZone) 194 } 195 } 196 197 // Create an observer so that we can update the views whenever a 198 // Calendar event changes. 199 private val mObserver: ContentObserver = object : ContentObserver(Handler()) { 200 @Override deliverSelfNotificationsnull201 override fun deliverSelfNotifications(): Boolean { 202 return true 203 } 204 205 @Override onChangenull206 override fun onChange(selfChange: Boolean) { 207 eventsChanged() 208 } 209 } 210 211 @Override onNewIntentnull212 protected override fun onNewIntent(intent: Intent) { 213 val action: String? = intent.getAction() 214 if (DEBUG) Log.d(TAG, "New intent received " + intent.toString()) 215 // Don't change the date if we're just returning to the app's home 216 if (Intent.ACTION_VIEW.equals(action) && 217 !intent.getBooleanExtra(Utils.INTENT_KEY_HOME, false) 218 ) { 219 var millis = parseViewAction(intent) 220 if (millis == -1L) { 221 millis = Utils.timeFromIntentInMillis(intent) as Long 222 } 223 if (millis != -1L && mViewEventId == -1L && mController != null) { 224 val time = Time(mTimeZone) 225 time.set(millis) 226 time.normalize(true) 227 mController?.sendEvent(this as Object?, EventType.GO_TO, time, time, -1, 228 ViewType.CURRENT) 229 } 230 } 231 } 232 233 @Override onCreatenull234 protected override fun onCreate(icicle: Bundle?) { 235 super.onCreate(icicle) 236 if (icicle != null && icicle.containsKey(BUNDLE_KEY_CHECK_ACCOUNTS)) { 237 mCheckForAccounts = icicle.getBoolean(BUNDLE_KEY_CHECK_ACCOUNTS) 238 } 239 // Launch add google account if this is first time and there are no 240 // accounts yet 241 if (mCheckForAccounts) { 242 mHandler = QueryHandler(this.getContentResolver()) 243 mHandler?.startQuery( 244 0, null, Calendars.CONTENT_URI, arrayOf<String>( 245 Calendars._ID 246 ), null, null /* selection args */, null /* sort order */ 247 ) 248 } 249 250 // This needs to be created before setContentView 251 mController = CalendarController.getInstance(this) 252 253 // Get time from intent or icicle 254 var timeMillis: Long = -1 255 var viewType = -1 256 val intent: Intent = getIntent() 257 if (icicle != null) { 258 timeMillis = icicle.getLong(BUNDLE_KEY_RESTORE_TIME) 259 viewType = icicle.getInt(BUNDLE_KEY_RESTORE_VIEW, -1) 260 } else { 261 val action: String? = intent.getAction() 262 if (Intent.ACTION_VIEW.equals(action)) { 263 // Open EventInfo later 264 timeMillis = parseViewAction(intent) 265 } 266 if (timeMillis == -1L) { 267 timeMillis = Utils.timeFromIntentInMillis(intent) as Long 268 } 269 } 270 if (viewType == -1 || viewType > ViewType.MAX_VALUE) { 271 viewType = Utils.getViewTypeFromIntentAndSharedPref(this) 272 } 273 mTimeZone = Utils.getTimeZone(this, mHomeTimeUpdater) 274 val t = Time(mTimeZone) 275 t.set(timeMillis) 276 if (DEBUG) { 277 if (icicle != null && intent != null) { 278 Log.d( 279 TAG, 280 "both, icicle:" + icicle.toString().toString() + " intent:" + intent.toString() 281 ) 282 } else { 283 Log.d(TAG, "not both, icicle:$icicle intent:$intent") 284 } 285 } 286 val res: Resources = getResources() 287 mHideString = res.getString(R.string.hide_controls) 288 mShowString = res.getString(R.string.show_controls) 289 mOrientation = res.getConfiguration().orientation 290 if (mOrientation == Configuration.ORIENTATION_LANDSCAPE) { 291 mControlsAnimateWidth = res.getDimension(R.dimen.calendar_controls_width).toInt() 292 if (mControlsParams == null) { 293 mControlsParams = LayoutParams(mControlsAnimateWidth, 0) 294 } 295 mControlsParams?.addRule(RelativeLayout.ALIGN_PARENT_RIGHT) 296 as RelativeLayout.LayoutParams 297 } else { 298 // Make sure width is in between allowed min and max width values 299 mControlsAnimateWidth = Math.max( 300 res.getDisplayMetrics().widthPixels * 45 / 100, 301 res.getDimension(R.dimen.min_portrait_calendar_controls_width).toInt() 302 ) 303 mControlsAnimateWidth = Math.min( 304 mControlsAnimateWidth, 305 res.getDimension(R.dimen.max_portrait_calendar_controls_width).toInt() 306 ) 307 } 308 mControlsAnimateHeight = res.getDimension(R.dimen.calendar_controls_height).toInt() 309 mHideControls = true 310 mIsMultipane = Utils.getConfigBool(this, R.bool.multiple_pane_config) 311 mIsTabletConfig = Utils.getConfigBool(this, R.bool.tablet_config) 312 mShowCalendarControls = Utils.getConfigBool(this, R.bool.show_calendar_controls) 313 mShowEventInfoFullScreen = Utils.getConfigBool(this, R.bool.show_event_info_full_screen) 314 mCalendarControlsAnimationTime = res.getInteger(R.integer.calendar_controls_animation_time) 315 Utils.setAllowWeekForDetailView(mIsMultipane) 316 317 // setContentView must be called before configureActionBar 318 setContentView(R.layout.all_in_one) 319 if (mIsTabletConfig) { 320 mDateRange = findViewById(R.id.date_bar) as TextView? 321 mWeekTextView = findViewById(R.id.week_num) as TextView? 322 } else { 323 mDateRange = getLayoutInflater().inflate(R.layout.date_range_title, null) as TextView 324 } 325 326 // configureActionBar auto-selects the first tab you add, so we need to 327 // call it before we set up our own fragments to make sure it doesn't 328 // overwrite us 329 configureActionBar(viewType) 330 mHomeTime = findViewById(R.id.home_time) as TextView? 331 mMiniMonth = findViewById(R.id.mini_month) 332 if (mIsTabletConfig && mOrientation == Configuration.ORIENTATION_PORTRAIT) { 333 mMiniMonth?.setLayoutParams( 334 LayoutParams( 335 mControlsAnimateWidth, 336 mControlsAnimateHeight 337 ) 338 ) 339 } 340 mCalendarsList = findViewById(R.id.calendar_list) 341 mMiniMonthContainer = findViewById(R.id.mini_month_container) 342 mSecondaryPane = findViewById(R.id.secondary_pane) 343 344 // Must register as the first activity because this activity can modify 345 // the list of event handlers in it's handle method. This affects who 346 // the rest of the handlers the controller dispatches to are. 347 mController?.registerFirstEventHandler(HANDLER_KEY, this) 348 initFragments(timeMillis, viewType, icicle) 349 350 // Listen for changes that would require this to be refreshed 351 val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(this) 352 prefs?.registerOnSharedPreferenceChangeListener(this) 353 mContentResolver = getContentResolver() 354 } 355 parseViewActionnull356 private fun parseViewAction(intent: Intent?): Long { 357 var timeMillis: Long = -1 358 val data: Uri? = intent?.getData() 359 if (data != null && data.isHierarchical()) { 360 val path = data.getPathSegments() 361 if (path?.size == 2 && path[0].equals("events")) { 362 try { 363 mViewEventId = data.getLastPathSegment()?.toLong() as Long 364 if (mViewEventId != -1L) { 365 mIntentEventStartMillis = intent.getLongExtra(EXTRA_EVENT_BEGIN_TIME, 0) 366 mIntentEventEndMillis = intent.getLongExtra(EXTRA_EVENT_END_TIME, 0) 367 mIntentAttendeeResponse = intent.getIntExtra( 368 ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_NONE 369 ) 370 mIntentAllDay = intent.getBooleanExtra(EXTRA_EVENT_ALL_DAY, false) 371 as Boolean 372 timeMillis = mIntentEventStartMillis 373 } 374 } catch (e: NumberFormatException) { 375 // Ignore if mViewEventId can't be parsed 376 } 377 } 378 } 379 return timeMillis 380 } 381 configureActionBarnull382 private fun configureActionBar(viewType: Int) { 383 createButtonsSpinner(viewType, mIsTabletConfig) 384 if (mIsMultipane) { 385 mActionBar?.setDisplayOptions( 386 ActionBar.DISPLAY_SHOW_CUSTOM or ActionBar.DISPLAY_SHOW_HOME 387 ) 388 } else { 389 mActionBar?.setDisplayOptions(0) 390 } 391 } 392 createButtonsSpinnernull393 private fun createButtonsSpinner(viewType: Int, tabletConfig: Boolean) { 394 // If tablet configuration , show spinner with no dates 395 mActionBarMenuSpinnerAdapter = CalendarViewAdapter(this, viewType, !tabletConfig) 396 mActionBar = getActionBar() 397 mActionBar?.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST) 398 mActionBar?.setListNavigationCallbacks(mActionBarMenuSpinnerAdapter, this) 399 when (viewType) { 400 ViewType.AGENDA -> { 401 } 402 ViewType.DAY -> mActionBar?.setSelectedNavigationItem(BUTTON_DAY_INDEX) 403 ViewType.WEEK -> mActionBar?.setSelectedNavigationItem(BUTTON_WEEK_INDEX) 404 ViewType.MONTH -> mActionBar?.setSelectedNavigationItem(BUTTON_MONTH_INDEX) 405 else -> mActionBar?.setSelectedNavigationItem(BUTTON_DAY_INDEX) 406 } 407 } 408 409 // Clear buttons used in the agenda view clearOptionsMenunull410 private fun clearOptionsMenu() { 411 if (mOptionsMenu == null) { 412 return 413 } 414 val cancelItem: MenuItem? = mOptionsMenu?.findItem(R.id.action_cancel) 415 if (cancelItem != null) { 416 cancelItem.setVisible(false) 417 } 418 } 419 420 @Override onResumenull421 protected override fun onResume() { 422 super.onResume() 423 424 // Check if the upgrade code has ever been run. If not, force a sync just this one time. 425 Utils.trySyncAndDisableUpgradeReceiver(this) 426 427 // Must register as the first activity because this activity can modify 428 // the list of event handlers in it's handle method. This affects who 429 // the rest of the handlers the controller dispatches to are. 430 mController?.registerFirstEventHandler(HANDLER_KEY, this) 431 mOnSaveInstanceStateCalled = false 432 mContentResolver?.registerContentObserver( 433 CalendarContract.Events.CONTENT_URI, 434 true, mObserver 435 ) 436 if (mUpdateOnResume) { 437 initFragments(mController?.time as Long, mController?.viewType as Int, null) 438 mUpdateOnResume = false 439 } 440 val t = Time(mTimeZone) 441 t.set(mController?.time as Long) 442 mController?.sendEvent( 443 this as Object?, EventType.UPDATE_TITLE, t, t, -1, ViewType.CURRENT, 444 mController?.dateFlags as Long, null, null 445 ) 446 // Make sure the drop-down menu will get its date updated at midnight 447 if (mActionBarMenuSpinnerAdapter != null) { 448 mActionBarMenuSpinnerAdapter?.refresh(this) 449 } 450 if (mControlsMenu != null) { 451 mControlsMenu?.setTitle(if (mHideControls) mShowString else mHideString) 452 } 453 mPaused = false 454 if (mViewEventId != -1L && mIntentEventStartMillis != -1L && mIntentEventEndMillis != -1L) { 455 val currentMillis: Long = System.currentTimeMillis() 456 var selectedTime: Long = -1 457 if (currentMillis > mIntentEventStartMillis && currentMillis < mIntentEventEndMillis) { 458 selectedTime = currentMillis 459 } 460 mController?.sendEventRelatedEventWithExtra( 461 this as Object?, EventType.VIEW_EVENT, mViewEventId, 462 mIntentEventStartMillis, mIntentEventEndMillis, -1, -1, 463 EventInfo.buildViewExtraLong(mIntentAttendeeResponse, mIntentAllDay), 464 selectedTime 465 ) 466 mViewEventId = -1 467 mIntentEventStartMillis = -1 468 mIntentEventEndMillis = -1 469 mIntentAllDay = false 470 } 471 Utils.setMidnightUpdater(mHandler, mTimeChangesUpdater, mTimeZone) 472 // Make sure the today icon is up to date 473 invalidateOptionsMenu() 474 } 475 476 @Override onPausenull477 protected override fun onPause() { 478 super.onPause() 479 mController?.deregisterEventHandler(HANDLER_KEY) 480 mPaused = true 481 mHomeTime?.removeCallbacks(mHomeTimeUpdater) 482 if (mActionBarMenuSpinnerAdapter != null) { 483 mActionBarMenuSpinnerAdapter?.onPause() 484 } 485 mContentResolver?.unregisterContentObserver(mObserver) 486 if (isFinishing()) { 487 // Stop listening for changes that would require this to be refreshed 488 val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(this) 489 prefs?.unregisterOnSharedPreferenceChangeListener(this) 490 } 491 // FRAG_TODO save highlighted days of the week; 492 if (mController?.viewType != ViewType.EDIT) { 493 Utils.setDefaultView(this, mController?.viewType as Int) 494 } 495 Utils.resetMidnightUpdater(mHandler, mTimeChangesUpdater) 496 } 497 498 @Override onUserLeaveHintnull499 protected override fun onUserLeaveHint() { 500 mController?.sendEvent(this as Object?, EventType.USER_HOME, null, null, -1, 501 ViewType.CURRENT) 502 super.onUserLeaveHint() 503 } 504 505 @Override onSaveInstanceStatenull506 override fun onSaveInstanceState(outState: Bundle) { 507 mOnSaveInstanceStateCalled = true 508 super.onSaveInstanceState(outState) 509 } 510 511 @Override onDestroynull512 protected override fun onDestroy() { 513 super.onDestroy() 514 val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(this) 515 prefs?.unregisterOnSharedPreferenceChangeListener(this) 516 mController?.deregisterAllEventHandlers() 517 CalendarController.removeInstance(this) 518 } 519 initFragmentsnull520 private fun initFragments(timeMillis: Long, viewType: Int, icicle: Bundle?) { 521 if (DEBUG) { 522 Log.d(TAG, "Initializing to $timeMillis for view $viewType") 523 } 524 val ft: FragmentTransaction = getFragmentManager().beginTransaction() 525 if (mShowCalendarControls) { 526 val miniMonthFrag: Fragment = MonthByWeekFragment(timeMillis, true) 527 ft.replace(R.id.mini_month, miniMonthFrag) 528 mController?.registerEventHandler(R.id.mini_month, miniMonthFrag as EventHandler) 529 } 530 if (!mShowCalendarControls || viewType == ViewType.EDIT) { 531 mMiniMonth?.setVisibility(View.GONE) 532 mCalendarsList?.setVisibility(View.GONE) 533 } 534 var info: EventInfo? = null 535 if (viewType == ViewType.EDIT) { 536 mPreviousView = GeneralPreferences.getSharedPreferences(this)?.getInt( 537 GeneralPreferences.KEY_START_VIEW, GeneralPreferences.DEFAULT_START_VIEW 538 ) as Int 539 var eventId: Long = -1 540 val intent: Intent = getIntent() 541 val data: Uri? = intent.getData() 542 if (data != null) { 543 try { 544 eventId = data.getLastPathSegment()?.toLong() as Long 545 } catch (e: NumberFormatException) { 546 if (DEBUG) { 547 Log.d(TAG, "Create new event") 548 } 549 } 550 } else if (icicle != null && icicle.containsKey(BUNDLE_KEY_EVENT_ID)) { 551 eventId = icicle.getLong(BUNDLE_KEY_EVENT_ID) 552 } 553 val begin: Long = intent.getLongExtra(EXTRA_EVENT_BEGIN_TIME, -1) 554 val end: Long = intent.getLongExtra(EXTRA_EVENT_END_TIME, -1) 555 info = EventInfo() 556 if (end != -1L) { 557 info.endTime = Time() 558 info.endTime?.set(end) 559 } 560 if (begin != -1L) { 561 info.startTime = Time() 562 info.startTime?.set(begin) 563 } 564 info.id = eventId 565 // We set the viewtype so if the user presses back when they are 566 // done editing the controller knows we were in the Edit Event 567 // screen. Likewise for eventId 568 mController?.viewType = viewType 569 mController?.eventId = eventId 570 } else { 571 mPreviousView = viewType 572 } 573 setMainPane(ft, R.id.main_pane, viewType, timeMillis, true) 574 ft.commit() // this needs to be after setMainPane() 575 val t = Time(mTimeZone) 576 t.set(timeMillis) 577 if (viewType != ViewType.EDIT) { 578 mController?.sendEvent(this as Object?, EventType.GO_TO, t, null, -1, viewType) 579 } 580 } 581 582 @Override onBackPressednull583 override fun onBackPressed() { 584 if (mCurrentView == ViewType.EDIT || mBackToPreviousView) { 585 mController?.sendEvent(this as Object?, EventType.GO_TO, null, null, -1, mPreviousView) 586 } else { 587 super.onBackPressed() 588 } 589 } 590 591 @Override onCreateOptionsMenunull592 override fun onCreateOptionsMenu(menu: Menu): Boolean { 593 super.onCreateOptionsMenu(menu) 594 mOptionsMenu = menu 595 getMenuInflater().inflate(R.menu.all_in_one_title_bar, menu) 596 597 // Hide the "show/hide controls" button if this is a phone 598 // or the view type is "Month". 599 mControlsMenu = menu.findItem(R.id.action_hide_controls) 600 if (!mShowCalendarControls) { 601 if (mControlsMenu != null) { 602 mControlsMenu?.setVisible(false) 603 mControlsMenu?.setEnabled(false) 604 } 605 } else if (mControlsMenu != null && mController != null && 606 mController?.viewType == ViewType.MONTH) { 607 mControlsMenu?.setVisible(false) 608 mControlsMenu?.setEnabled(false) 609 } else if (mControlsMenu != null) { 610 mControlsMenu?.setTitle(if (mHideControls) mShowString else mHideString) 611 } 612 val menuItem: MenuItem = menu.findItem(R.id.action_today) 613 if (Utils.isJellybeanOrLater()) { 614 // replace the default top layer drawable of the today icon with a 615 // custom drawable that shows the day of the month of today 616 val icon: LayerDrawable = menuItem.getIcon() as LayerDrawable 617 Utils.setTodayIcon(icon, this, mTimeZone) 618 } else { 619 menuItem.setIcon(R.drawable.ic_menu_today_no_date_holo_light) 620 } 621 return true 622 } 623 624 @Override onOptionsItemSelectednull625 override fun onOptionsItemSelected(item: MenuItem): Boolean { 626 var t: Time? = null 627 var viewType: Int = ViewType.CURRENT 628 var extras: Long = CalendarController.EXTRA_GOTO_TIME 629 val itemId: Int = item.getItemId() 630 if (itemId == R.id.action_today) { 631 viewType = ViewType.CURRENT 632 t = Time(mTimeZone) 633 t.setToNow() 634 extras = extras or CalendarController.EXTRA_GOTO_TODAY 635 } else if (itemId == R.id.action_hide_controls) { 636 mHideControls = !mHideControls 637 item.setTitle(if (mHideControls) mShowString else mHideString) 638 if (!mHideControls) { 639 mMiniMonth?.setVisibility(View.VISIBLE) 640 mCalendarsList?.setVisibility(View.VISIBLE) 641 mMiniMonthContainer?.setVisibility(View.VISIBLE) 642 } 643 val slideAnimation: ObjectAnimator = ObjectAnimator.ofInt( 644 this, "controlsOffset", 645 if (mHideControls) 0 else mControlsAnimateWidth, 646 if (mHideControls) mControlsAnimateWidth else 0 647 ) 648 slideAnimation.setDuration(mCalendarControlsAnimationTime.toLong()) 649 ObjectAnimator.setFrameDelay(0) 650 slideAnimation.start() 651 return true 652 } else { 653 Log.d(TAG, "Unsupported itemId: $itemId") 654 return true 655 } 656 mController?.sendEvent(this as Object?, EventType.GO_TO, t, null, t, -1, 657 viewType, extras, null, null) 658 return true 659 } 660 661 /** 662 * Sets the offset of the controls on the right for animating them off/on 663 * screen. ProGuard strips this if it's not in proguard.flags 664 * 665 * @param controlsOffset The current offset in pixels 666 */ setControlsOffsetnull667 fun setControlsOffset(controlsOffset: Int) { 668 if (mOrientation == Configuration.ORIENTATION_LANDSCAPE) { 669 mMiniMonth?.setTranslationX(controlsOffset.toFloat()) 670 mCalendarsList?.setTranslationX(controlsOffset.toFloat()) 671 mControlsParams?.width = Math.max(0, mControlsAnimateWidth - controlsOffset) 672 mMiniMonthContainer?.setLayoutParams(mControlsParams) 673 } else { 674 mMiniMonth?.setTranslationY(controlsOffset.toFloat()) 675 mCalendarsList?.setTranslationY(controlsOffset.toFloat()) 676 if (mVerticalControlsParams == null) { 677 mVerticalControlsParams = LayoutParams( 678 LinearLayout.LayoutParams.MATCH_PARENT, mControlsAnimateHeight 679 ) as LinearLayout.LayoutParams? 680 } 681 mVerticalControlsParams?.height = Math.max(0, mControlsAnimateHeight - controlsOffset) 682 mMiniMonthContainer?.setLayoutParams(mVerticalControlsParams) 683 } 684 } 685 686 @Override onSharedPreferenceChangednull687 override fun onSharedPreferenceChanged(prefs: SharedPreferences?, key: String) { 688 if (key.equals(GeneralPreferences.KEY_WEEK_START_DAY)) { 689 if (mPaused) { 690 mUpdateOnResume = true 691 } else { 692 initFragments(mController?.time as Long, mController?.viewType as Int, null) 693 } 694 } 695 } 696 setMainPanenull697 private fun setMainPane( 698 ft: FragmentTransaction?, 699 viewId: Int, 700 viewType: Int, 701 timeMillis: Long, 702 force: Boolean 703 ) { 704 var ft: FragmentTransaction? = ft 705 if (mOnSaveInstanceStateCalled) { 706 return 707 } 708 if (!force && mCurrentView == viewType) { 709 return 710 } 711 712 // Remove this when transition to and from month view looks fine. 713 val doTransition = viewType != ViewType.MONTH && mCurrentView != ViewType.MONTH 714 val fragmentManager: FragmentManager = getFragmentManager() 715 if (viewType != mCurrentView) { 716 // The rules for this previous view are different than the 717 // controller's and are used for intercepting the back button. 718 if (mCurrentView != ViewType.EDIT && mCurrentView > 0) { 719 mPreviousView = mCurrentView 720 } 721 mCurrentView = viewType 722 } 723 // Create new fragment 724 var frag: Fragment? = null 725 val secFrag: Fragment? = null 726 when (viewType) { 727 ViewType.AGENDA -> { 728 } 729 ViewType.DAY -> { 730 if (mActionBar != null && mActionBar?.getSelectedTab() != mDayTab) { 731 mActionBar?.selectTab(mDayTab) 732 } 733 if (mActionBarMenuSpinnerAdapter != null) { 734 mActionBar?.setSelectedNavigationItem(CalendarViewAdapter.DAY_BUTTON_INDEX) 735 } 736 frag = DayFragment(timeMillis, 1) 737 } 738 ViewType.MONTH -> { 739 if (mActionBar != null && mActionBar?.getSelectedTab() != mMonthTab) { 740 mActionBar?.selectTab(mMonthTab) 741 } 742 if (mActionBarMenuSpinnerAdapter != null) { 743 mActionBar?.setSelectedNavigationItem(CalendarViewAdapter.MONTH_BUTTON_INDEX) 744 } 745 frag = MonthByWeekFragment(timeMillis, false) 746 } 747 ViewType.WEEK -> { 748 if (mActionBar != null && mActionBar?.getSelectedTab() != mWeekTab) { 749 mActionBar?.selectTab(mWeekTab) 750 } 751 if (mActionBarMenuSpinnerAdapter != null) { 752 mActionBar?.setSelectedNavigationItem(CalendarViewAdapter.WEEK_BUTTON_INDEX) 753 } 754 frag = DayFragment(timeMillis, 7) 755 } 756 else -> { 757 if (mActionBar != null && mActionBar?.getSelectedTab() != mWeekTab) { 758 mActionBar?.selectTab(mWeekTab) 759 } 760 if (mActionBarMenuSpinnerAdapter != null) { 761 mActionBar?.setSelectedNavigationItem(CalendarViewAdapter.WEEK_BUTTON_INDEX) 762 } 763 frag = DayFragment(timeMillis, 7) 764 } 765 } 766 767 // Update the current view so that the menu can update its look according to the 768 // current view. 769 if (mActionBarMenuSpinnerAdapter != null) { 770 mActionBarMenuSpinnerAdapter?.setMainView(viewType) 771 if (!mIsTabletConfig) { 772 mActionBarMenuSpinnerAdapter?.setTime(timeMillis) 773 } 774 } 775 776 // Show date only on tablet configurations in views different than Agenda 777 if (!mIsTabletConfig) { 778 mDateRange?.setVisibility(View.GONE) 779 } else { 780 mDateRange?.setVisibility(View.GONE) 781 } 782 783 // Clear unnecessary buttons from the option menu when switching from the agenda view 784 if (viewType != ViewType.AGENDA) { 785 clearOptionsMenu() 786 } 787 var doCommit = false 788 if (ft == null) { 789 doCommit = true 790 ft = fragmentManager.beginTransaction() 791 } 792 if (doTransition) { 793 ft?.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) 794 } 795 ft?.replace(viewId, frag) 796 if (DEBUG) { 797 Log.d(TAG, "Adding handler with viewId $viewId and type $viewType") 798 } 799 // If the key is already registered this will replace it 800 mController?.registerEventHandler(viewId, frag as EventHandler?) 801 if (doCommit) { 802 if (DEBUG) { 803 Log.d(TAG, "setMainPane AllInOne=" + this + " finishing:" + this.isFinishing()) 804 } 805 ft?.commit() 806 } 807 } 808 setTitleInActionBarnull809 private fun setTitleInActionBar(event: EventInfo) { 810 if (event.eventType != EventType.UPDATE_TITLE || mActionBar == null) { 811 return 812 } 813 val start: Long? = event.startTime?.toMillis(false /* use isDst */) 814 val end: Long? 815 end = if (event.endTime != null) { 816 event.endTime?.toMillis(false /* use isDst */) 817 } else { 818 start 819 } 820 val msg: String? = Utils.formatDateRange(this, 821 start as Long, 822 end as Long, 823 event.extraLong.toInt() 824 ) 825 val oldDate: CharSequence? = mDateRange?.getText() 826 mDateRange?.setText(msg) 827 updateSecondaryTitleFields(if (event.selectedTime != null) 828 event.selectedTime?.toMillis(true) as Long else start) 829 if (!TextUtils.equals(oldDate, msg)) { 830 mDateRange?.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED) 831 if (mShowWeekNum && mWeekTextView != null) { 832 mWeekTextView?.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED) 833 } 834 } 835 } 836 updateSecondaryTitleFieldsnull837 private fun updateSecondaryTitleFields(visibleMillisSinceEpoch: Long) { 838 mShowWeekNum = Utils.getShowWeekNumber(this) 839 mTimeZone = Utils.getTimeZone(this, mHomeTimeUpdater) 840 if (visibleMillisSinceEpoch != -1L) { 841 val weekNum: Int = Utils.getWeekNumberFromTime(visibleMillisSinceEpoch, this) 842 mWeekNum = weekNum 843 } 844 if (mShowWeekNum && mCurrentView == ViewType.WEEK && mIsTabletConfig && 845 mWeekTextView != null 846 ) { 847 val weekString: String = getResources().getQuantityString( 848 R.plurals.weekN, mWeekNum, 849 mWeekNum 850 ) 851 mWeekTextView?.setText(weekString) 852 mWeekTextView?.setVisibility(View.VISIBLE) 853 } else if (visibleMillisSinceEpoch != -1L && mWeekTextView != null && 854 mCurrentView == ViewType.DAY && mIsTabletConfig) { 855 val time = Time(mTimeZone) 856 time.set(visibleMillisSinceEpoch) 857 val julianDay: Int = Time.getJulianDay(visibleMillisSinceEpoch, time.gmtoff) 858 time.setToNow() 859 val todayJulianDay: Int = Time.getJulianDay(time.toMillis(false), time.gmtoff) 860 val dayString: String = Utils.getDayOfWeekString( 861 julianDay, 862 todayJulianDay, 863 visibleMillisSinceEpoch, 864 this 865 ) 866 mWeekTextView?.setText(dayString) 867 mWeekTextView?.setVisibility(View.VISIBLE) 868 } else if (mWeekTextView != null && (!mIsTabletConfig || mCurrentView != ViewType.DAY)) { 869 mWeekTextView?.setVisibility(View.GONE) 870 } 871 if (mHomeTime != null && (mCurrentView == ViewType.DAY || mCurrentView == ViewType.WEEK) && 872 !TextUtils.equals(mTimeZone, Time.getCurrentTimezone()) 873 ) { 874 val time = Time(mTimeZone) 875 time.setToNow() 876 val millis: Long = time.toMillis(true) 877 val isDST = time.isDst !== 0 878 var flags: Int = DateUtils.FORMAT_SHOW_TIME 879 if (DateFormat.is24HourFormat(this)) { 880 flags = flags or DateUtils.FORMAT_24HOUR 881 } 882 // Formats the time as 883 val timeString: String = StringBuilder( 884 Utils.formatDateRange(this, millis, millis, flags) 885 ).append(" ").append( 886 TimeZone.getTimeZone(mTimeZone).getDisplayName( 887 isDST, TimeZone.SHORT, Locale.getDefault() 888 ) 889 ).toString() 890 mHomeTime?.setText(timeString) 891 mHomeTime?.setVisibility(View.VISIBLE) 892 // Update when the minute changes 893 mHomeTime?.removeCallbacks(mHomeTimeUpdater) 894 mHomeTime?.postDelayed( 895 mHomeTimeUpdater, 896 DateUtils.MINUTE_IN_MILLIS - millis % DateUtils.MINUTE_IN_MILLIS 897 ) 898 } else if (mHomeTime != null) { 899 mHomeTime?.setVisibility(View.GONE) 900 } 901 } 902 903 @get:Override override val supportedEventTypes: Long 904 get() = EventType.GO_TO or EventType.UPDATE_TITLE 905 906 @Override handleEventnull907 override fun handleEvent(event: EventInfo?) { 908 var displayTime: Long = -1 909 if (event?.eventType == EventType.GO_TO) { 910 if (event.extraLong and CalendarController.EXTRA_GOTO_BACK_TO_PREVIOUS != 0L) { 911 mBackToPreviousView = true 912 } else if (event.viewType != mController?.previousViewType && 913 event.viewType != ViewType.EDIT 914 ) { 915 // Clear the flag is change to a different view type 916 mBackToPreviousView = false 917 } 918 setMainPane( 919 null, R.id.main_pane, event.viewType, event.startTime?.toMillis(false) 920 as Long, false 921 ) 922 if (mShowCalendarControls) { 923 val animationSize = 924 if (mOrientation == Configuration.ORIENTATION_LANDSCAPE) mControlsAnimateWidth 925 else mControlsAnimateHeight 926 val noControlsView = event.viewType == ViewType.MONTH 927 if (mControlsMenu != null) { 928 mControlsMenu?.setVisible(!noControlsView) 929 mControlsMenu?.setEnabled(!noControlsView) 930 } 931 if (noControlsView || mHideControls) { 932 // hide minimonth and calendar frag 933 mShowSideViews = false 934 if (!mHideControls) { 935 val slideAnimation: ObjectAnimator = ObjectAnimator.ofInt( 936 this, 937 "controlsOffset", 0, animationSize 938 ) 939 slideAnimation.addListener(mSlideAnimationDoneListener) 940 slideAnimation.setDuration(mCalendarControlsAnimationTime.toLong()) 941 ObjectAnimator.setFrameDelay(0) 942 slideAnimation.start() 943 } else { 944 mMiniMonth?.setVisibility(View.GONE) 945 mCalendarsList?.setVisibility(View.GONE) 946 mMiniMonthContainer?.setVisibility(View.GONE) 947 } 948 } else { 949 // show minimonth and calendar frag 950 mShowSideViews = true 951 mMiniMonth?.setVisibility(View.VISIBLE) 952 mCalendarsList?.setVisibility(View.VISIBLE) 953 mMiniMonthContainer?.setVisibility(View.VISIBLE) 954 if (!mHideControls && 955 mController?.previousViewType == ViewType.MONTH 956 ) { 957 val slideAnimation: ObjectAnimator = ObjectAnimator.ofInt( 958 this, 959 "controlsOffset", animationSize, 0 960 ) 961 slideAnimation.setDuration(mCalendarControlsAnimationTime.toLong()) 962 ObjectAnimator.setFrameDelay(0) 963 slideAnimation.start() 964 } 965 } 966 } 967 displayTime = 968 if (event.selectedTime != null) event.selectedTime?.toMillis(true) as Long 969 else event.startTime?.toMillis(true) as Long 970 if (!mIsTabletConfig) { 971 mActionBarMenuSpinnerAdapter?.setTime(displayTime) 972 } 973 } else if (event?.eventType == EventType.UPDATE_TITLE) { 974 setTitleInActionBar(event as CalendarController.EventInfo) 975 if (!mIsTabletConfig) { 976 mActionBarMenuSpinnerAdapter?.setTime(mController?.time as Long) 977 } 978 } 979 updateSecondaryTitleFields(displayTime) 980 } 981 982 @Override eventsChangednull983 override fun eventsChanged() { 984 mController?.sendEvent(this as Object?, EventType.EVENTS_CHANGED, null, null, -1, 985 ViewType.CURRENT) 986 } 987 988 @Override onTabSelectednull989 override fun onTabSelected(tab: Tab?, ft: FragmentTransaction?) { 990 Log.w(TAG, "TabSelected AllInOne=" + this + " finishing:" + this.isFinishing()) 991 if (tab == mDayTab && mCurrentView != ViewType.DAY) { 992 mController?.sendEvent(this as Object?, EventType.GO_TO, null, null, -1, ViewType.DAY) 993 } else if (tab == mWeekTab && mCurrentView != ViewType.WEEK) { 994 mController?.sendEvent(this as Object?, EventType.GO_TO, null, null, -1, ViewType.WEEK) 995 } else if (tab == mMonthTab && mCurrentView != ViewType.MONTH) { 996 mController?.sendEvent(this as Object?, EventType.GO_TO, null, null, -1, ViewType.MONTH) 997 } else { 998 Log.w( 999 TAG, "TabSelected event from unknown tab: " + 1000 if (tab == null) "null" else tab.getText() 1001 ) 1002 Log.w( 1003 TAG, "CurrentView:" + mCurrentView + " Tab:" + tab.toString() + " Day:" + mDayTab + 1004 " Week:" + mWeekTab + " Month:" + mMonthTab 1005 ) 1006 } 1007 } 1008 1009 @Override onTabReselectednull1010 override fun onTabReselected(tab: Tab?, ft: FragmentTransaction?) { 1011 } 1012 1013 @Override onTabUnselectednull1014 override fun onTabUnselected(tab: Tab?, ft: FragmentTransaction?) { 1015 } 1016 1017 @Override onNavigationItemSelectednull1018 override fun onNavigationItemSelected(itemPosition: Int, itemId: Long): Boolean { 1019 when (itemPosition) { 1020 CalendarViewAdapter.DAY_BUTTON_INDEX -> if (mCurrentView != ViewType.DAY) { 1021 mController?.sendEvent(this as Object?, EventType.GO_TO, null, null, -1, 1022 ViewType.DAY) 1023 } 1024 CalendarViewAdapter.WEEK_BUTTON_INDEX -> if (mCurrentView != ViewType.WEEK) { 1025 mController?.sendEvent(this as Object?, EventType.GO_TO, null, null, -1, 1026 ViewType.WEEK) 1027 } 1028 CalendarViewAdapter.MONTH_BUTTON_INDEX -> if (mCurrentView != ViewType.MONTH) { 1029 mController?.sendEvent(this as Object?, EventType.GO_TO, null, null, -1, 1030 ViewType.MONTH) 1031 } 1032 CalendarViewAdapter.AGENDA_BUTTON_INDEX -> { 1033 } 1034 else -> { 1035 Log.w(TAG, "ItemSelected event from unknown button: $itemPosition") 1036 Log.w( 1037 TAG, "CurrentView:" + mCurrentView + " Button:" + itemPosition + 1038 " Day:" + mDayTab + " Week:" + mWeekTab + " Month:" + mMonthTab 1039 ) 1040 } 1041 } 1042 return false 1043 } 1044 1045 companion object { 1046 private const val TAG = "AllInOneActivity" 1047 private const val DEBUG = false 1048 private const val EVENT_INFO_FRAGMENT_TAG = "EventInfoFragment" 1049 private const val BUNDLE_KEY_RESTORE_TIME = "key_restore_time" 1050 private const val BUNDLE_KEY_EVENT_ID = "key_event_id" 1051 private const val BUNDLE_KEY_RESTORE_VIEW = "key_restore_view" 1052 private const val BUNDLE_KEY_CHECK_ACCOUNTS = "key_check_for_accounts" 1053 private const val HANDLER_KEY = 0 1054 1055 // Indices of buttons for the drop down menu (tabs replacement) 1056 // Must match the strings in the array buttons_list in arrays.xml and the 1057 // OnNavigationListener 1058 private const val BUTTON_DAY_INDEX = 0 1059 private const val BUTTON_WEEK_INDEX = 1 1060 private const val BUTTON_MONTH_INDEX = 2 1061 private const val BUTTON_AGENDA_INDEX = 3 1062 private var mIsMultipane = false 1063 private var mIsTabletConfig = false 1064 } 1065 }