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 } else { 297 // Make sure width is in between allowed min and max width values 298 mControlsAnimateWidth = Math.max( 299 res.getDisplayMetrics().widthPixels * 45 / 100, 300 res.getDimension(R.dimen.min_portrait_calendar_controls_width).toInt() 301 ) 302 mControlsAnimateWidth = Math.min( 303 mControlsAnimateWidth, 304 res.getDimension(R.dimen.max_portrait_calendar_controls_width).toInt() 305 ) 306 } 307 mControlsAnimateHeight = res.getDimension(R.dimen.calendar_controls_height).toInt() 308 mHideControls = true 309 mIsMultipane = Utils.getConfigBool(this, R.bool.multiple_pane_config) 310 mIsTabletConfig = Utils.getConfigBool(this, R.bool.tablet_config) 311 mShowCalendarControls = Utils.getConfigBool(this, R.bool.show_calendar_controls) 312 mShowEventInfoFullScreen = Utils.getConfigBool(this, R.bool.show_event_info_full_screen) 313 mCalendarControlsAnimationTime = res.getInteger(R.integer.calendar_controls_animation_time) 314 Utils.setAllowWeekForDetailView(mIsMultipane) 315 316 // setContentView must be called before configureActionBar 317 setContentView(R.layout.all_in_one) 318 if (mIsTabletConfig) { 319 mDateRange = findViewById(R.id.date_bar) as TextView? 320 mWeekTextView = findViewById(R.id.week_num) as TextView? 321 } else { 322 mDateRange = getLayoutInflater().inflate(R.layout.date_range_title, null) as TextView 323 } 324 325 // configureActionBar auto-selects the first tab you add, so we need to 326 // call it before we set up our own fragments to make sure it doesn't 327 // overwrite us 328 configureActionBar(viewType) 329 mHomeTime = findViewById(R.id.home_time) as TextView? 330 mMiniMonth = findViewById(R.id.mini_month) 331 if (mIsTabletConfig && mOrientation == Configuration.ORIENTATION_PORTRAIT) { 332 mMiniMonth?.setLayoutParams( 333 LayoutParams( 334 mControlsAnimateWidth, 335 mControlsAnimateHeight 336 ) 337 ) 338 } 339 mCalendarsList = findViewById(R.id.calendar_list) 340 mMiniMonthContainer = findViewById(R.id.mini_month_container) 341 mSecondaryPane = findViewById(R.id.secondary_pane) 342 343 // Must register as the first activity because this activity can modify 344 // the list of event handlers in it's handle method. This affects who 345 // the rest of the handlers the controller dispatches to are. 346 mController?.registerFirstEventHandler(HANDLER_KEY, this) 347 initFragments(timeMillis, viewType, icicle) 348 349 // Listen for changes that would require this to be refreshed 350 val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(this) 351 prefs?.registerOnSharedPreferenceChangeListener(this) 352 mContentResolver = getContentResolver() 353 } 354 parseViewActionnull355 private fun parseViewAction(intent: Intent?): Long { 356 var timeMillis: Long = -1 357 val data: Uri? = intent?.getData() 358 if (data != null && data.isHierarchical()) { 359 val path = data.getPathSegments() 360 if (path?.size == 2 && path[0].equals("events")) { 361 try { 362 mViewEventId = data.getLastPathSegment()?.toLong() as Long 363 if (mViewEventId != -1L) { 364 mIntentEventStartMillis = intent.getLongExtra(EXTRA_EVENT_BEGIN_TIME, 0) 365 mIntentEventEndMillis = intent.getLongExtra(EXTRA_EVENT_END_TIME, 0) 366 mIntentAttendeeResponse = intent.getIntExtra( 367 ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_NONE 368 ) 369 mIntentAllDay = intent.getBooleanExtra(EXTRA_EVENT_ALL_DAY, false) 370 as Boolean 371 timeMillis = mIntentEventStartMillis 372 } 373 } catch (e: NumberFormatException) { 374 // Ignore if mViewEventId can't be parsed 375 } 376 } 377 } 378 return timeMillis 379 } 380 configureActionBarnull381 private fun configureActionBar(viewType: Int) { 382 createButtonsSpinner(viewType, mIsTabletConfig) 383 if (mIsMultipane) { 384 mActionBar?.setDisplayOptions( 385 ActionBar.DISPLAY_SHOW_CUSTOM or ActionBar.DISPLAY_SHOW_HOME 386 ) 387 } else { 388 mActionBar?.setDisplayOptions(0) 389 } 390 } 391 createButtonsSpinnernull392 private fun createButtonsSpinner(viewType: Int, tabletConfig: Boolean) { 393 // If tablet configuration , show spinner with no dates 394 mActionBarMenuSpinnerAdapter = CalendarViewAdapter(this, viewType, !tabletConfig) 395 mActionBar = getActionBar() 396 mActionBar?.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST) 397 mActionBar?.setListNavigationCallbacks(mActionBarMenuSpinnerAdapter, this) 398 when (viewType) { 399 ViewType.AGENDA -> { 400 } 401 ViewType.DAY -> mActionBar?.setSelectedNavigationItem(BUTTON_DAY_INDEX) 402 ViewType.WEEK -> mActionBar?.setSelectedNavigationItem(BUTTON_WEEK_INDEX) 403 ViewType.MONTH -> mActionBar?.setSelectedNavigationItem(BUTTON_MONTH_INDEX) 404 else -> mActionBar?.setSelectedNavigationItem(BUTTON_DAY_INDEX) 405 } 406 } 407 408 // Clear buttons used in the agenda view clearOptionsMenunull409 private fun clearOptionsMenu() { 410 if (mOptionsMenu == null) { 411 return 412 } 413 val cancelItem: MenuItem? = mOptionsMenu?.findItem(R.id.action_cancel) 414 if (cancelItem != null) { 415 cancelItem.setVisible(false) 416 } 417 } 418 419 @Override onResumenull420 protected override fun onResume() { 421 super.onResume() 422 423 // Check if the upgrade code has ever been run. If not, force a sync just this one time. 424 Utils.trySyncAndDisableUpgradeReceiver(this) 425 426 // Must register as the first activity because this activity can modify 427 // the list of event handlers in it's handle method. This affects who 428 // the rest of the handlers the controller dispatches to are. 429 mController?.registerFirstEventHandler(HANDLER_KEY, this) 430 mOnSaveInstanceStateCalled = false 431 mContentResolver?.registerContentObserver( 432 CalendarContract.Events.CONTENT_URI, 433 true, mObserver 434 ) 435 if (mUpdateOnResume) { 436 initFragments(mController?.time as Long, mController?.viewType as Int, null) 437 mUpdateOnResume = false 438 } 439 val t = Time(mTimeZone) 440 t.set(mController?.time as Long) 441 mController?.sendEvent( 442 this as Object?, EventType.UPDATE_TITLE, t, t, -1, ViewType.CURRENT, 443 mController?.dateFlags as Long, null, null 444 ) 445 // Make sure the drop-down menu will get its date updated at midnight 446 if (mActionBarMenuSpinnerAdapter != null) { 447 mActionBarMenuSpinnerAdapter?.refresh(this) 448 } 449 if (mControlsMenu != null) { 450 mControlsMenu?.setTitle(if (mHideControls) mShowString else mHideString) 451 } 452 mPaused = false 453 if (mViewEventId != -1L && mIntentEventStartMillis != -1L && mIntentEventEndMillis != -1L) { 454 val currentMillis: Long = System.currentTimeMillis() 455 var selectedTime: Long = -1 456 if (currentMillis > mIntentEventStartMillis && currentMillis < mIntentEventEndMillis) { 457 selectedTime = currentMillis 458 } 459 mController?.sendEventRelatedEventWithExtra( 460 this as Object?, EventType.VIEW_EVENT, mViewEventId, 461 mIntentEventStartMillis, mIntentEventEndMillis, -1, -1, 462 EventInfo.buildViewExtraLong(mIntentAttendeeResponse, mIntentAllDay), 463 selectedTime 464 ) 465 mViewEventId = -1 466 mIntentEventStartMillis = -1 467 mIntentEventEndMillis = -1 468 mIntentAllDay = false 469 } 470 Utils.setMidnightUpdater(mHandler, mTimeChangesUpdater, mTimeZone) 471 // Make sure the today icon is up to date 472 invalidateOptionsMenu() 473 } 474 475 @Override onPausenull476 protected override fun onPause() { 477 super.onPause() 478 mController?.deregisterEventHandler(HANDLER_KEY) 479 mPaused = true 480 mHomeTime?.removeCallbacks(mHomeTimeUpdater) 481 if (mActionBarMenuSpinnerAdapter != null) { 482 mActionBarMenuSpinnerAdapter?.onPause() 483 } 484 mContentResolver?.unregisterContentObserver(mObserver) 485 if (isFinishing()) { 486 // Stop listening for changes that would require this to be refreshed 487 val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(this) 488 prefs?.unregisterOnSharedPreferenceChangeListener(this) 489 } 490 // FRAG_TODO save highlighted days of the week; 491 if (mController?.viewType != ViewType.EDIT) { 492 Utils.setDefaultView(this, mController?.viewType as Int) 493 } 494 Utils.resetMidnightUpdater(mHandler, mTimeChangesUpdater) 495 } 496 497 @Override onUserLeaveHintnull498 protected override fun onUserLeaveHint() { 499 mController?.sendEvent(this as Object?, EventType.USER_HOME, null, null, -1, 500 ViewType.CURRENT) 501 super.onUserLeaveHint() 502 } 503 504 @Override onSaveInstanceStatenull505 override fun onSaveInstanceState(outState: Bundle) { 506 mOnSaveInstanceStateCalled = true 507 super.onSaveInstanceState(outState) 508 } 509 510 @Override onDestroynull511 protected override fun onDestroy() { 512 super.onDestroy() 513 val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(this) 514 prefs?.unregisterOnSharedPreferenceChangeListener(this) 515 mController?.deregisterAllEventHandlers() 516 CalendarController.removeInstance(this) 517 } 518 initFragmentsnull519 private fun initFragments(timeMillis: Long, viewType: Int, icicle: Bundle?) { 520 if (DEBUG) { 521 Log.d(TAG, "Initializing to $timeMillis for view $viewType") 522 } 523 val ft: FragmentTransaction = getFragmentManager().beginTransaction() 524 if (mShowCalendarControls) { 525 val miniMonthFrag: Fragment = MonthByWeekFragment(timeMillis, true) 526 ft.replace(R.id.mini_month, miniMonthFrag) 527 mController?.registerEventHandler(R.id.mini_month, miniMonthFrag as EventHandler) 528 } 529 if (!mShowCalendarControls || viewType == ViewType.EDIT) { 530 mMiniMonth?.setVisibility(View.GONE) 531 mCalendarsList?.setVisibility(View.GONE) 532 } 533 var info: EventInfo? = null 534 if (viewType == ViewType.EDIT) { 535 mPreviousView = GeneralPreferences.getSharedPreferences(this)?.getInt( 536 GeneralPreferences.KEY_START_VIEW, GeneralPreferences.DEFAULT_START_VIEW 537 ) as Int 538 var eventId: Long = -1 539 val intent: Intent = getIntent() 540 val data: Uri? = intent.getData() 541 if (data != null) { 542 try { 543 eventId = data.getLastPathSegment()?.toLong() as Long 544 } catch (e: NumberFormatException) { 545 if (DEBUG) { 546 Log.d(TAG, "Create new event") 547 } 548 } 549 } else if (icicle != null && icicle.containsKey(BUNDLE_KEY_EVENT_ID)) { 550 eventId = icicle.getLong(BUNDLE_KEY_EVENT_ID) 551 } 552 val begin: Long = intent.getLongExtra(EXTRA_EVENT_BEGIN_TIME, -1) 553 val end: Long = intent.getLongExtra(EXTRA_EVENT_END_TIME, -1) 554 info = EventInfo() 555 if (end != -1L) { 556 info.endTime = Time() 557 info.endTime?.set(end) 558 } 559 if (begin != -1L) { 560 info.startTime = Time() 561 info.startTime?.set(begin) 562 } 563 info.id = eventId 564 // We set the viewtype so if the user presses back when they are 565 // done editing the controller knows we were in the Edit Event 566 // screen. Likewise for eventId 567 mController?.viewType = viewType 568 mController?.eventId = eventId 569 } else { 570 mPreviousView = viewType 571 } 572 setMainPane(ft, R.id.main_pane, viewType, timeMillis, true) 573 ft.commit() // this needs to be after setMainPane() 574 val t = Time(mTimeZone) 575 t.set(timeMillis) 576 if (viewType != ViewType.EDIT) { 577 mController?.sendEvent(this as Object?, EventType.GO_TO, t, null, -1, viewType) 578 } 579 } 580 581 @Override onBackPressednull582 override fun onBackPressed() { 583 if (mCurrentView == ViewType.EDIT || mBackToPreviousView) { 584 mController?.sendEvent(this as Object?, EventType.GO_TO, null, null, -1, mPreviousView) 585 } else { 586 super.onBackPressed() 587 } 588 } 589 590 @Override onCreateOptionsMenunull591 override fun onCreateOptionsMenu(menu: Menu): Boolean { 592 super.onCreateOptionsMenu(menu) 593 mOptionsMenu = menu 594 getMenuInflater().inflate(R.menu.all_in_one_title_bar, menu) 595 596 // Hide the "show/hide controls" button if this is a phone 597 // or the view type is "Month". 598 mControlsMenu = menu.findItem(R.id.action_hide_controls) 599 if (!mShowCalendarControls) { 600 if (mControlsMenu != null) { 601 mControlsMenu?.setVisible(false) 602 mControlsMenu?.setEnabled(false) 603 } 604 } else if (mControlsMenu != null && mController != null && 605 mController?.viewType == ViewType.MONTH) { 606 mControlsMenu?.setVisible(false) 607 mControlsMenu?.setEnabled(false) 608 } else if (mControlsMenu != null) { 609 mControlsMenu?.setTitle(if (mHideControls) mShowString else mHideString) 610 } 611 val menuItem: MenuItem = menu.findItem(R.id.action_today) 612 if (Utils.isJellybeanOrLater()) { 613 // replace the default top layer drawable of the today icon with a 614 // custom drawable that shows the day of the month of today 615 val icon: LayerDrawable = menuItem.getIcon() as LayerDrawable 616 Utils.setTodayIcon(icon, this, mTimeZone) 617 } else { 618 menuItem.setIcon(R.drawable.ic_menu_today_no_date_holo_light) 619 } 620 return true 621 } 622 623 @Override onOptionsItemSelectednull624 override fun onOptionsItemSelected(item: MenuItem): Boolean { 625 var t: Time? = null 626 var viewType: Int = ViewType.CURRENT 627 var extras: Long = CalendarController.EXTRA_GOTO_TIME 628 val itemId: Int = item.getItemId() 629 if (itemId == R.id.action_today) { 630 viewType = ViewType.CURRENT 631 t = Time(mTimeZone) 632 t.setToNow() 633 extras = extras or CalendarController.EXTRA_GOTO_TODAY 634 } else if (itemId == R.id.action_hide_controls) { 635 mHideControls = !mHideControls 636 item.setTitle(if (mHideControls) mShowString else mHideString) 637 if (!mHideControls) { 638 mMiniMonth?.setVisibility(View.VISIBLE) 639 mCalendarsList?.setVisibility(View.VISIBLE) 640 mMiniMonthContainer?.setVisibility(View.VISIBLE) 641 } 642 val slideAnimation: ObjectAnimator = ObjectAnimator.ofInt( 643 this, "controlsOffset", 644 if (mHideControls) 0 else mControlsAnimateWidth, 645 if (mHideControls) mControlsAnimateWidth else 0 646 ) 647 slideAnimation.setDuration(mCalendarControlsAnimationTime.toLong()) 648 ObjectAnimator.setFrameDelay(0) 649 slideAnimation.start() 650 return true 651 } else { 652 Log.d(TAG, "Unsupported itemId: $itemId") 653 return true 654 } 655 mController?.sendEvent(this as Object?, EventType.GO_TO, t, null, t, -1, 656 viewType, extras, null, null) 657 return true 658 } 659 660 /** 661 * Sets the offset of the controls on the right for animating them off/on 662 * screen. ProGuard strips this if it's not in proguard.flags 663 * 664 * @param controlsOffset The current offset in pixels 665 */ setControlsOffsetnull666 fun setControlsOffset(controlsOffset: Int) { 667 if (mOrientation == Configuration.ORIENTATION_LANDSCAPE) { 668 mMiniMonth?.setTranslationX(controlsOffset.toFloat()) 669 mCalendarsList?.setTranslationX(controlsOffset.toFloat()) 670 mControlsParams?.width = Math.max(0, mControlsAnimateWidth - controlsOffset) 671 mMiniMonthContainer?.setLayoutParams(mControlsParams) 672 } else { 673 mMiniMonth?.setTranslationY(controlsOffset.toFloat()) 674 mCalendarsList?.setTranslationY(controlsOffset.toFloat()) 675 if (mVerticalControlsParams == null) { 676 mVerticalControlsParams = LayoutParams( 677 LinearLayout.LayoutParams.MATCH_PARENT, mControlsAnimateHeight 678 ) as LinearLayout.LayoutParams? 679 } 680 mVerticalControlsParams?.height = Math.max(0, mControlsAnimateHeight - controlsOffset) 681 mMiniMonthContainer?.setLayoutParams(mVerticalControlsParams) 682 } 683 } 684 685 @Override onSharedPreferenceChangednull686 override fun onSharedPreferenceChanged(prefs: SharedPreferences, key: String?) { 687 if (key.equals(GeneralPreferences.KEY_WEEK_START_DAY)) { 688 if (mPaused) { 689 mUpdateOnResume = true 690 } else { 691 initFragments(mController?.time as Long, mController?.viewType as Int, null) 692 } 693 } 694 } 695 setMainPanenull696 private fun setMainPane( 697 ft: FragmentTransaction?, 698 viewId: Int, 699 viewType: Int, 700 timeMillis: Long, 701 force: Boolean 702 ) { 703 var ft: FragmentTransaction? = ft 704 if (mOnSaveInstanceStateCalled) { 705 return 706 } 707 if (!force && mCurrentView == viewType) { 708 return 709 } 710 711 // Remove this when transition to and from month view looks fine. 712 val doTransition = viewType != ViewType.MONTH && mCurrentView != ViewType.MONTH 713 val fragmentManager: FragmentManager = getFragmentManager() 714 if (viewType != mCurrentView) { 715 // The rules for this previous view are different than the 716 // controller's and are used for intercepting the back button. 717 if (mCurrentView != ViewType.EDIT && mCurrentView > 0) { 718 mPreviousView = mCurrentView 719 } 720 mCurrentView = viewType 721 } 722 // Create new fragment 723 var frag: Fragment? = null 724 val secFrag: Fragment? = null 725 when (viewType) { 726 ViewType.AGENDA -> { 727 } 728 ViewType.DAY -> { 729 if (mActionBar != null && mActionBar?.getSelectedTab() != mDayTab) { 730 mActionBar?.selectTab(mDayTab) 731 } 732 if (mActionBarMenuSpinnerAdapter != null) { 733 mActionBar?.setSelectedNavigationItem(CalendarViewAdapter.DAY_BUTTON_INDEX) 734 } 735 frag = DayFragment(timeMillis, 1) 736 } 737 ViewType.MONTH -> { 738 if (mActionBar != null && mActionBar?.getSelectedTab() != mMonthTab) { 739 mActionBar?.selectTab(mMonthTab) 740 } 741 if (mActionBarMenuSpinnerAdapter != null) { 742 mActionBar?.setSelectedNavigationItem(CalendarViewAdapter.MONTH_BUTTON_INDEX) 743 } 744 frag = MonthByWeekFragment(timeMillis, false) 745 } 746 ViewType.WEEK -> { 747 if (mActionBar != null && mActionBar?.getSelectedTab() != mWeekTab) { 748 mActionBar?.selectTab(mWeekTab) 749 } 750 if (mActionBarMenuSpinnerAdapter != null) { 751 mActionBar?.setSelectedNavigationItem(CalendarViewAdapter.WEEK_BUTTON_INDEX) 752 } 753 frag = DayFragment(timeMillis, 7) 754 } 755 else -> { 756 if (mActionBar != null && mActionBar?.getSelectedTab() != mWeekTab) { 757 mActionBar?.selectTab(mWeekTab) 758 } 759 if (mActionBarMenuSpinnerAdapter != null) { 760 mActionBar?.setSelectedNavigationItem(CalendarViewAdapter.WEEK_BUTTON_INDEX) 761 } 762 frag = DayFragment(timeMillis, 7) 763 } 764 } 765 766 // Update the current view so that the menu can update its look according to the 767 // current view. 768 if (mActionBarMenuSpinnerAdapter != null) { 769 mActionBarMenuSpinnerAdapter?.setMainView(viewType) 770 if (!mIsTabletConfig) { 771 mActionBarMenuSpinnerAdapter?.setTime(timeMillis) 772 } 773 } 774 775 // Show date only on tablet configurations in views different than Agenda 776 if (!mIsTabletConfig) { 777 mDateRange?.setVisibility(View.GONE) 778 } else { 779 mDateRange?.setVisibility(View.GONE) 780 } 781 782 // Clear unnecessary buttons from the option menu when switching from the agenda view 783 if (viewType != ViewType.AGENDA) { 784 clearOptionsMenu() 785 } 786 var doCommit = false 787 if (ft == null) { 788 doCommit = true 789 ft = fragmentManager.beginTransaction() 790 } 791 if (doTransition) { 792 ft?.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) 793 } 794 ft?.replace(viewId, frag) 795 if (DEBUG) { 796 Log.d(TAG, "Adding handler with viewId $viewId and type $viewType") 797 } 798 // If the key is already registered this will replace it 799 mController?.registerEventHandler(viewId, frag as EventHandler?) 800 if (doCommit) { 801 if (DEBUG) { 802 Log.d(TAG, "setMainPane AllInOne=" + this + " finishing:" + this.isFinishing()) 803 } 804 ft?.commit() 805 } 806 } 807 setTitleInActionBarnull808 private fun setTitleInActionBar(event: EventInfo) { 809 if (event.eventType != EventType.UPDATE_TITLE || mActionBar == null) { 810 return 811 } 812 val start: Long? = event.startTime?.toMillis(false /* use isDst */) 813 val end: Long? 814 end = if (event.endTime != null) { 815 event.endTime?.toMillis(false /* use isDst */) 816 } else { 817 start 818 } 819 val msg: String? = Utils.formatDateRange(this, 820 start as Long, 821 end as Long, 822 event.extraLong.toInt() 823 ) 824 val oldDate: CharSequence? = mDateRange?.getText() 825 mDateRange?.setText(msg) 826 updateSecondaryTitleFields(if (event.selectedTime != null) 827 event.selectedTime?.toMillis(true) as Long else start) 828 if (!TextUtils.equals(oldDate, msg)) { 829 mDateRange?.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED) 830 if (mShowWeekNum && mWeekTextView != null) { 831 mWeekTextView?.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED) 832 } 833 } 834 } 835 updateSecondaryTitleFieldsnull836 private fun updateSecondaryTitleFields(visibleMillisSinceEpoch: Long) { 837 mShowWeekNum = Utils.getShowWeekNumber(this) 838 mTimeZone = Utils.getTimeZone(this, mHomeTimeUpdater) 839 if (visibleMillisSinceEpoch != -1L) { 840 val weekNum: Int = Utils.getWeekNumberFromTime(visibleMillisSinceEpoch, this) 841 mWeekNum = weekNum 842 } 843 if (mShowWeekNum && mCurrentView == ViewType.WEEK && mIsTabletConfig && 844 mWeekTextView != null 845 ) { 846 val weekString: String = getResources().getQuantityString( 847 R.plurals.weekN, mWeekNum, 848 mWeekNum 849 ) 850 mWeekTextView?.setText(weekString) 851 mWeekTextView?.setVisibility(View.VISIBLE) 852 } else if (visibleMillisSinceEpoch != -1L && mWeekTextView != null && 853 mCurrentView == ViewType.DAY && mIsTabletConfig) { 854 val time = Time(mTimeZone) 855 time.set(visibleMillisSinceEpoch) 856 val julianDay: Int = Time.getJulianDay(visibleMillisSinceEpoch, time.gmtoff) 857 time.setToNow() 858 val todayJulianDay: Int = Time.getJulianDay(time.toMillis(false), time.gmtoff) 859 val dayString: String = Utils.getDayOfWeekString( 860 julianDay, 861 todayJulianDay, 862 visibleMillisSinceEpoch, 863 this 864 ) 865 mWeekTextView?.setText(dayString) 866 mWeekTextView?.setVisibility(View.VISIBLE) 867 } else if (mWeekTextView != null && (!mIsTabletConfig || mCurrentView != ViewType.DAY)) { 868 mWeekTextView?.setVisibility(View.GONE) 869 } 870 if (mHomeTime != null && (mCurrentView == ViewType.DAY || mCurrentView == ViewType.WEEK) && 871 !TextUtils.equals(mTimeZone, Time.getCurrentTimezone()) 872 ) { 873 val time = Time(mTimeZone) 874 time.setToNow() 875 val millis: Long = time.toMillis(true) 876 val isDST = time.isDst !== 0 877 var flags: Int = DateUtils.FORMAT_SHOW_TIME 878 if (DateFormat.is24HourFormat(this)) { 879 flags = flags or DateUtils.FORMAT_24HOUR 880 } 881 // Formats the time as 882 val timeString: String = StringBuilder( 883 Utils.formatDateRange(this, millis, millis, flags) 884 ).append(" ").append( 885 TimeZone.getTimeZone(mTimeZone).getDisplayName( 886 isDST, TimeZone.SHORT, Locale.getDefault() 887 ) 888 ).toString() 889 mHomeTime?.setText(timeString) 890 mHomeTime?.setVisibility(View.VISIBLE) 891 // Update when the minute changes 892 mHomeTime?.removeCallbacks(mHomeTimeUpdater) 893 mHomeTime?.postDelayed( 894 mHomeTimeUpdater, 895 DateUtils.MINUTE_IN_MILLIS - millis % DateUtils.MINUTE_IN_MILLIS 896 ) 897 } else if (mHomeTime != null) { 898 mHomeTime?.setVisibility(View.GONE) 899 } 900 } 901 902 @get:Override override val supportedEventTypes: Long 903 get() = EventType.GO_TO or EventType.UPDATE_TITLE 904 905 @Override handleEventnull906 override fun handleEvent(event: EventInfo?) { 907 var displayTime: Long = -1 908 if (event?.eventType == EventType.GO_TO) { 909 if (event.extraLong and CalendarController.EXTRA_GOTO_BACK_TO_PREVIOUS != 0L) { 910 mBackToPreviousView = true 911 } else if (event.viewType != mController?.previousViewType && 912 event.viewType != ViewType.EDIT 913 ) { 914 // Clear the flag is change to a different view type 915 mBackToPreviousView = false 916 } 917 setMainPane( 918 null, R.id.main_pane, event.viewType, event.startTime?.toMillis(false) 919 as Long, false 920 ) 921 if (mShowCalendarControls) { 922 val animationSize = 923 if (mOrientation == Configuration.ORIENTATION_LANDSCAPE) mControlsAnimateWidth 924 else mControlsAnimateHeight 925 val noControlsView = event.viewType == ViewType.MONTH 926 if (mControlsMenu != null) { 927 mControlsMenu?.setVisible(!noControlsView) 928 mControlsMenu?.setEnabled(!noControlsView) 929 } 930 if (noControlsView || mHideControls) { 931 // hide minimonth and calendar frag 932 mShowSideViews = false 933 if (!mHideControls) { 934 val slideAnimation: ObjectAnimator = ObjectAnimator.ofInt( 935 this, 936 "controlsOffset", 0, animationSize 937 ) 938 slideAnimation.addListener(mSlideAnimationDoneListener) 939 slideAnimation.setDuration(mCalendarControlsAnimationTime.toLong()) 940 ObjectAnimator.setFrameDelay(0) 941 slideAnimation.start() 942 } else { 943 mMiniMonth?.setVisibility(View.GONE) 944 mCalendarsList?.setVisibility(View.GONE) 945 mMiniMonthContainer?.setVisibility(View.GONE) 946 } 947 } else { 948 // show minimonth and calendar frag 949 mShowSideViews = true 950 mMiniMonth?.setVisibility(View.VISIBLE) 951 mCalendarsList?.setVisibility(View.VISIBLE) 952 mMiniMonthContainer?.setVisibility(View.VISIBLE) 953 if (!mHideControls && 954 mController?.previousViewType == ViewType.MONTH 955 ) { 956 val slideAnimation: ObjectAnimator = ObjectAnimator.ofInt( 957 this, 958 "controlsOffset", animationSize, 0 959 ) 960 slideAnimation.setDuration(mCalendarControlsAnimationTime.toLong()) 961 ObjectAnimator.setFrameDelay(0) 962 slideAnimation.start() 963 } 964 } 965 } 966 displayTime = 967 if (event.selectedTime != null) event.selectedTime?.toMillis(true) as Long 968 else event.startTime?.toMillis(true) as Long 969 if (!mIsTabletConfig) { 970 mActionBarMenuSpinnerAdapter?.setTime(displayTime) 971 } 972 } else if (event?.eventType == EventType.UPDATE_TITLE) { 973 setTitleInActionBar(event as CalendarController.EventInfo) 974 if (!mIsTabletConfig) { 975 mActionBarMenuSpinnerAdapter?.setTime(mController?.time as Long) 976 } 977 } 978 updateSecondaryTitleFields(displayTime) 979 } 980 981 @Override eventsChangednull982 override fun eventsChanged() { 983 mController?.sendEvent(this as Object?, EventType.EVENTS_CHANGED, null, null, -1, 984 ViewType.CURRENT) 985 } 986 987 @Override onTabSelectednull988 override fun onTabSelected(tab: Tab?, ft: FragmentTransaction?) { 989 Log.w(TAG, "TabSelected AllInOne=" + this + " finishing:" + this.isFinishing()) 990 if (tab == mDayTab && mCurrentView != ViewType.DAY) { 991 mController?.sendEvent(this as Object?, EventType.GO_TO, null, null, -1, ViewType.DAY) 992 } else if (tab == mWeekTab && mCurrentView != ViewType.WEEK) { 993 mController?.sendEvent(this as Object?, EventType.GO_TO, null, null, -1, ViewType.WEEK) 994 } else if (tab == mMonthTab && mCurrentView != ViewType.MONTH) { 995 mController?.sendEvent(this as Object?, EventType.GO_TO, null, null, -1, ViewType.MONTH) 996 } else { 997 Log.w( 998 TAG, "TabSelected event from unknown tab: " + 999 if (tab == null) "null" else tab.getText() 1000 ) 1001 Log.w( 1002 TAG, "CurrentView:" + mCurrentView + " Tab:" + tab.toString() + " Day:" + mDayTab + 1003 " Week:" + mWeekTab + " Month:" + mMonthTab 1004 ) 1005 } 1006 } 1007 1008 @Override onTabReselectednull1009 override fun onTabReselected(tab: Tab?, ft: FragmentTransaction?) { 1010 } 1011 1012 @Override onTabUnselectednull1013 override fun onTabUnselected(tab: Tab?, ft: FragmentTransaction?) { 1014 } 1015 1016 @Override onNavigationItemSelectednull1017 override fun onNavigationItemSelected(itemPosition: Int, itemId: Long): Boolean { 1018 when (itemPosition) { 1019 CalendarViewAdapter.DAY_BUTTON_INDEX -> if (mCurrentView != ViewType.DAY) { 1020 mController?.sendEvent(this as Object?, EventType.GO_TO, null, null, -1, 1021 ViewType.DAY) 1022 } 1023 CalendarViewAdapter.WEEK_BUTTON_INDEX -> if (mCurrentView != ViewType.WEEK) { 1024 mController?.sendEvent(this as Object?, EventType.GO_TO, null, null, -1, 1025 ViewType.WEEK) 1026 } 1027 CalendarViewAdapter.MONTH_BUTTON_INDEX -> if (mCurrentView != ViewType.MONTH) { 1028 mController?.sendEvent(this as Object?, EventType.GO_TO, null, null, -1, 1029 ViewType.MONTH) 1030 } 1031 CalendarViewAdapter.AGENDA_BUTTON_INDEX -> { 1032 } 1033 else -> { 1034 Log.w(TAG, "ItemSelected event from unknown button: $itemPosition") 1035 Log.w( 1036 TAG, "CurrentView:" + mCurrentView + " Button:" + itemPosition + 1037 " Day:" + mDayTab + " Week:" + mWeekTab + " Month:" + mMonthTab 1038 ) 1039 } 1040 } 1041 return false 1042 } 1043 1044 companion object { 1045 private const val TAG = "AllInOneActivity" 1046 private const val DEBUG = false 1047 private const val EVENT_INFO_FRAGMENT_TAG = "EventInfoFragment" 1048 private const val BUNDLE_KEY_RESTORE_TIME = "key_restore_time" 1049 private const val BUNDLE_KEY_EVENT_ID = "key_event_id" 1050 private const val BUNDLE_KEY_RESTORE_VIEW = "key_restore_view" 1051 private const val BUNDLE_KEY_CHECK_ACCOUNTS = "key_check_for_accounts" 1052 private const val HANDLER_KEY = 0 1053 1054 // Indices of buttons for the drop down menu (tabs replacement) 1055 // Must match the strings in the array buttons_list in arrays.xml and the 1056 // OnNavigationListener 1057 private const val BUTTON_DAY_INDEX = 0 1058 private const val BUTTON_WEEK_INDEX = 1 1059 private const val BUTTON_MONTH_INDEX = 2 1060 private const val BUTTON_AGENDA_INDEX = 3 1061 private var mIsMultipane = false 1062 private var mIsTabletConfig = false 1063 } 1064 } 1065