/* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.calendar import android.accounts.AccountManager import android.accounts.AccountManagerCallback import android.accounts.AccountManagerFuture import android.animation.Animator import android.animation.Animator.AnimatorListener import android.animation.ObjectAnimator import android.app.ActionBar import android.app.ActionBar.Tab import android.app.Activity import android.app.Fragment import android.app.FragmentManager import android.app.FragmentTransaction import android.content.AsyncQueryHandler import android.content.ContentResolver import android.content.Intent import android.content.SharedPreferences import android.content.SharedPreferences.OnSharedPreferenceChangeListener import android.content.res.Configuration import android.content.res.Resources import android.database.ContentObserver import android.database.Cursor import android.graphics.drawable.LayerDrawable import android.net.Uri import android.os.Bundle import android.os.Handler import android.provider.CalendarContract import android.provider.CalendarContract.Attendees import android.provider.CalendarContract.Calendars import android.provider.CalendarContract.Events import android.text.TextUtils import android.text.format.DateFormat import android.text.format.DateUtils import android.text.format.Time import android.util.Log import android.view.Menu import android.view.MenuItem import android.view.View import android.view.accessibility.AccessibilityEvent import android.widget.LinearLayout import android.widget.RelativeLayout import android.widget.RelativeLayout.LayoutParams import android.widget.TextView import com.android.calendar.CalendarController.EventHandler import com.android.calendar.CalendarController.EventInfo import com.android.calendar.CalendarController.EventType import com.android.calendar.CalendarController.ViewType import com.android.calendar.month.MonthByWeekFragment import java.util.Locale import java.util.TimeZone import android.provider.CalendarContract.Attendees.ATTENDEE_STATUS import android.provider.CalendarContract.EXTRA_EVENT_ALL_DAY import android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME import android.provider.CalendarContract.EXTRA_EVENT_END_TIME class AllInOneActivity : Activity(), EventHandler, OnSharedPreferenceChangeListener, ActionBar.TabListener, ActionBar.OnNavigationListener { private var mController: CalendarController? = null private var mOnSaveInstanceStateCalled = false private var mBackToPreviousView = false private var mContentResolver: ContentResolver? = null private var mPreviousView = 0 private var mCurrentView = 0 private var mPaused = true private var mUpdateOnResume = false private var mHideControls = false private var mShowSideViews = true private var mShowWeekNum = false private var mHomeTime: TextView? = null private var mDateRange: TextView? = null private var mWeekTextView: TextView? = null private var mMiniMonth: View? = null private var mCalendarsList: View? = null private var mMiniMonthContainer: View? = null private var mSecondaryPane: View? = null private var mTimeZone: String? = null private var mShowCalendarControls = false private var mShowEventInfoFullScreen = false private var mWeekNum = 0 private var mCalendarControlsAnimationTime = 0 private var mControlsAnimateWidth = 0 private var mControlsAnimateHeight = 0 private var mViewEventId: Long = -1 private var mIntentEventStartMillis: Long = -1 private var mIntentEventEndMillis: Long = -1 private var mIntentAttendeeResponse: Int = Attendees.ATTENDEE_STATUS_NONE private var mIntentAllDay = false // Action bar and Navigation bar (left side of Action bar) private var mActionBar: ActionBar? = null private val mDayTab: Tab? = null private val mWeekTab: Tab? = null private val mMonthTab: Tab? = null private var mControlsMenu: MenuItem? = null private var mOptionsMenu: Menu? = null private var mActionBarMenuSpinnerAdapter: CalendarViewAdapter? = null private var mHandler: QueryHandler? = null private var mCheckForAccounts = true private var mHideString: String? = null private var mShowString: String? = null var mDayOfMonthIcon: DayOfMonthDrawable? = null var mOrientation = 0 // Params for animating the controls on the right private var mControlsParams: LayoutParams? = null private var mVerticalControlsParams: LinearLayout.LayoutParams? = null private val mSlideAnimationDoneListener: AnimatorListener = object : AnimatorListener { @Override override fun onAnimationCancel(animation: Animator) { } @Override override fun onAnimationEnd(animation: Animator) { val visibility: Int = if (mShowSideViews) View.VISIBLE else View.GONE mMiniMonth?.setVisibility(visibility) mCalendarsList?.setVisibility(visibility) mMiniMonthContainer?.setVisibility(visibility) } @Override override fun onAnimationRepeat(animation: Animator) { } @Override override fun onAnimationStart(animation: Animator) { } } private inner class QueryHandler(cr: ContentResolver?) : AsyncQueryHandler(cr) { @Override protected override fun onQueryComplete(token: Int, cookie: Any?, cursor: Cursor?) { mCheckForAccounts = false try { // If the query didn't return a cursor for some reason return if (cursor == null || cursor.getCount() > 0 || isFinishing()) { return } } finally { if (cursor != null) { cursor.close() } } val options = Bundle() options.putCharSequence( "introMessage", getResources().getString(R.string.create_an_account_desc) ) options.putBoolean("allowSkip", true) val am: AccountManager = AccountManager.get(this@AllInOneActivity) am.addAccount("com.google", CalendarContract.AUTHORITY, null, options, this@AllInOneActivity, object : AccountManagerCallback { @Override override fun run(future: AccountManagerFuture?) { } }, null ) } } private val mHomeTimeUpdater: Runnable = object : Runnable { @Override override fun run() { mTimeZone = Utils.getTimeZone(this@AllInOneActivity, this) updateSecondaryTitleFields(-1) this@AllInOneActivity.invalidateOptionsMenu() Utils.setMidnightUpdater(mHandler, mTimeChangesUpdater, mTimeZone) } } // runs every midnight/time changes and refreshes the today icon private val mTimeChangesUpdater: Runnable = object : Runnable { @Override override fun run() { mTimeZone = Utils.getTimeZone(this@AllInOneActivity, mHomeTimeUpdater) this@AllInOneActivity.invalidateOptionsMenu() Utils.setMidnightUpdater(mHandler, this, mTimeZone) } } // Create an observer so that we can update the views whenever a // Calendar event changes. private val mObserver: ContentObserver = object : ContentObserver(Handler()) { @Override override fun deliverSelfNotifications(): Boolean { return true } @Override override fun onChange(selfChange: Boolean) { eventsChanged() } } @Override protected override fun onNewIntent(intent: Intent) { val action: String? = intent.getAction() if (DEBUG) Log.d(TAG, "New intent received " + intent.toString()) // Don't change the date if we're just returning to the app's home if (Intent.ACTION_VIEW.equals(action) && !intent.getBooleanExtra(Utils.INTENT_KEY_HOME, false) ) { var millis = parseViewAction(intent) if (millis == -1L) { millis = Utils.timeFromIntentInMillis(intent) as Long } if (millis != -1L && mViewEventId == -1L && mController != null) { val time = Time(mTimeZone) time.set(millis) time.normalize(true) mController?.sendEvent(this as Object?, EventType.GO_TO, time, time, -1, ViewType.CURRENT) } } } @Override protected override fun onCreate(icicle: Bundle?) { super.onCreate(icicle) if (icicle != null && icicle.containsKey(BUNDLE_KEY_CHECK_ACCOUNTS)) { mCheckForAccounts = icicle.getBoolean(BUNDLE_KEY_CHECK_ACCOUNTS) } // Launch add google account if this is first time and there are no // accounts yet if (mCheckForAccounts) { mHandler = QueryHandler(this.getContentResolver()) mHandler?.startQuery( 0, null, Calendars.CONTENT_URI, arrayOf( Calendars._ID ), null, null /* selection args */, null /* sort order */ ) } // This needs to be created before setContentView mController = CalendarController.getInstance(this) // Get time from intent or icicle var timeMillis: Long = -1 var viewType = -1 val intent: Intent = getIntent() if (icicle != null) { timeMillis = icicle.getLong(BUNDLE_KEY_RESTORE_TIME) viewType = icicle.getInt(BUNDLE_KEY_RESTORE_VIEW, -1) } else { val action: String? = intent.getAction() if (Intent.ACTION_VIEW.equals(action)) { // Open EventInfo later timeMillis = parseViewAction(intent) } if (timeMillis == -1L) { timeMillis = Utils.timeFromIntentInMillis(intent) as Long } } if (viewType == -1 || viewType > ViewType.MAX_VALUE) { viewType = Utils.getViewTypeFromIntentAndSharedPref(this) } mTimeZone = Utils.getTimeZone(this, mHomeTimeUpdater) val t = Time(mTimeZone) t.set(timeMillis) if (DEBUG) { if (icicle != null && intent != null) { Log.d( TAG, "both, icicle:" + icicle.toString().toString() + " intent:" + intent.toString() ) } else { Log.d(TAG, "not both, icicle:$icicle intent:$intent") } } val res: Resources = getResources() mHideString = res.getString(R.string.hide_controls) mShowString = res.getString(R.string.show_controls) mOrientation = res.getConfiguration().orientation if (mOrientation == Configuration.ORIENTATION_LANDSCAPE) { mControlsAnimateWidth = res.getDimension(R.dimen.calendar_controls_width).toInt() if (mControlsParams == null) { mControlsParams = LayoutParams(mControlsAnimateWidth, 0) } mControlsParams?.addRule(RelativeLayout.ALIGN_PARENT_RIGHT) } else { // Make sure width is in between allowed min and max width values mControlsAnimateWidth = Math.max( res.getDisplayMetrics().widthPixels * 45 / 100, res.getDimension(R.dimen.min_portrait_calendar_controls_width).toInt() ) mControlsAnimateWidth = Math.min( mControlsAnimateWidth, res.getDimension(R.dimen.max_portrait_calendar_controls_width).toInt() ) } mControlsAnimateHeight = res.getDimension(R.dimen.calendar_controls_height).toInt() mHideControls = true mIsMultipane = Utils.getConfigBool(this, R.bool.multiple_pane_config) mIsTabletConfig = Utils.getConfigBool(this, R.bool.tablet_config) mShowCalendarControls = Utils.getConfigBool(this, R.bool.show_calendar_controls) mShowEventInfoFullScreen = Utils.getConfigBool(this, R.bool.show_event_info_full_screen) mCalendarControlsAnimationTime = res.getInteger(R.integer.calendar_controls_animation_time) Utils.setAllowWeekForDetailView(mIsMultipane) // setContentView must be called before configureActionBar setContentView(R.layout.all_in_one) if (mIsTabletConfig) { mDateRange = findViewById(R.id.date_bar) as TextView? mWeekTextView = findViewById(R.id.week_num) as TextView? } else { mDateRange = getLayoutInflater().inflate(R.layout.date_range_title, null) as TextView } // configureActionBar auto-selects the first tab you add, so we need to // call it before we set up our own fragments to make sure it doesn't // overwrite us configureActionBar(viewType) mHomeTime = findViewById(R.id.home_time) as TextView? mMiniMonth = findViewById(R.id.mini_month) if (mIsTabletConfig && mOrientation == Configuration.ORIENTATION_PORTRAIT) { mMiniMonth?.setLayoutParams( LayoutParams( mControlsAnimateWidth, mControlsAnimateHeight ) ) } mCalendarsList = findViewById(R.id.calendar_list) mMiniMonthContainer = findViewById(R.id.mini_month_container) mSecondaryPane = findViewById(R.id.secondary_pane) // Must register as the first activity because this activity can modify // the list of event handlers in it's handle method. This affects who // the rest of the handlers the controller dispatches to are. mController?.registerFirstEventHandler(HANDLER_KEY, this) initFragments(timeMillis, viewType, icicle) // Listen for changes that would require this to be refreshed val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(this) prefs?.registerOnSharedPreferenceChangeListener(this) mContentResolver = getContentResolver() } private fun parseViewAction(intent: Intent?): Long { var timeMillis: Long = -1 val data: Uri? = intent?.getData() if (data != null && data.isHierarchical()) { val path = data.getPathSegments() if (path?.size == 2 && path[0].equals("events")) { try { mViewEventId = data.getLastPathSegment()?.toLong() as Long if (mViewEventId != -1L) { mIntentEventStartMillis = intent.getLongExtra(EXTRA_EVENT_BEGIN_TIME, 0) mIntentEventEndMillis = intent.getLongExtra(EXTRA_EVENT_END_TIME, 0) mIntentAttendeeResponse = intent.getIntExtra( ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_NONE ) mIntentAllDay = intent.getBooleanExtra(EXTRA_EVENT_ALL_DAY, false) as Boolean timeMillis = mIntentEventStartMillis } } catch (e: NumberFormatException) { // Ignore if mViewEventId can't be parsed } } } return timeMillis } private fun configureActionBar(viewType: Int) { createButtonsSpinner(viewType, mIsTabletConfig) if (mIsMultipane) { mActionBar?.setDisplayOptions( ActionBar.DISPLAY_SHOW_CUSTOM or ActionBar.DISPLAY_SHOW_HOME ) } else { mActionBar?.setDisplayOptions(0) } } private fun createButtonsSpinner(viewType: Int, tabletConfig: Boolean) { // If tablet configuration , show spinner with no dates mActionBarMenuSpinnerAdapter = CalendarViewAdapter(this, viewType, !tabletConfig) mActionBar = getActionBar() mActionBar?.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST) mActionBar?.setListNavigationCallbacks(mActionBarMenuSpinnerAdapter, this) when (viewType) { ViewType.AGENDA -> { } ViewType.DAY -> mActionBar?.setSelectedNavigationItem(BUTTON_DAY_INDEX) ViewType.WEEK -> mActionBar?.setSelectedNavigationItem(BUTTON_WEEK_INDEX) ViewType.MONTH -> mActionBar?.setSelectedNavigationItem(BUTTON_MONTH_INDEX) else -> mActionBar?.setSelectedNavigationItem(BUTTON_DAY_INDEX) } } // Clear buttons used in the agenda view private fun clearOptionsMenu() { if (mOptionsMenu == null) { return } val cancelItem: MenuItem? = mOptionsMenu?.findItem(R.id.action_cancel) if (cancelItem != null) { cancelItem.setVisible(false) } } @Override protected override fun onResume() { super.onResume() // Check if the upgrade code has ever been run. If not, force a sync just this one time. Utils.trySyncAndDisableUpgradeReceiver(this) // Must register as the first activity because this activity can modify // the list of event handlers in it's handle method. This affects who // the rest of the handlers the controller dispatches to are. mController?.registerFirstEventHandler(HANDLER_KEY, this) mOnSaveInstanceStateCalled = false mContentResolver?.registerContentObserver( CalendarContract.Events.CONTENT_URI, true, mObserver ) if (mUpdateOnResume) { initFragments(mController?.time as Long, mController?.viewType as Int, null) mUpdateOnResume = false } val t = Time(mTimeZone) t.set(mController?.time as Long) mController?.sendEvent( this as Object?, EventType.UPDATE_TITLE, t, t, -1, ViewType.CURRENT, mController?.dateFlags as Long, null, null ) // Make sure the drop-down menu will get its date updated at midnight if (mActionBarMenuSpinnerAdapter != null) { mActionBarMenuSpinnerAdapter?.refresh(this) } if (mControlsMenu != null) { mControlsMenu?.setTitle(if (mHideControls) mShowString else mHideString) } mPaused = false if (mViewEventId != -1L && mIntentEventStartMillis != -1L && mIntentEventEndMillis != -1L) { val currentMillis: Long = System.currentTimeMillis() var selectedTime: Long = -1 if (currentMillis > mIntentEventStartMillis && currentMillis < mIntentEventEndMillis) { selectedTime = currentMillis } mController?.sendEventRelatedEventWithExtra( this as Object?, EventType.VIEW_EVENT, mViewEventId, mIntentEventStartMillis, mIntentEventEndMillis, -1, -1, EventInfo.buildViewExtraLong(mIntentAttendeeResponse, mIntentAllDay), selectedTime ) mViewEventId = -1 mIntentEventStartMillis = -1 mIntentEventEndMillis = -1 mIntentAllDay = false } Utils.setMidnightUpdater(mHandler, mTimeChangesUpdater, mTimeZone) // Make sure the today icon is up to date invalidateOptionsMenu() } @Override protected override fun onPause() { super.onPause() mController?.deregisterEventHandler(HANDLER_KEY) mPaused = true mHomeTime?.removeCallbacks(mHomeTimeUpdater) if (mActionBarMenuSpinnerAdapter != null) { mActionBarMenuSpinnerAdapter?.onPause() } mContentResolver?.unregisterContentObserver(mObserver) if (isFinishing()) { // Stop listening for changes that would require this to be refreshed val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(this) prefs?.unregisterOnSharedPreferenceChangeListener(this) } // FRAG_TODO save highlighted days of the week; if (mController?.viewType != ViewType.EDIT) { Utils.setDefaultView(this, mController?.viewType as Int) } Utils.resetMidnightUpdater(mHandler, mTimeChangesUpdater) } @Override protected override fun onUserLeaveHint() { mController?.sendEvent(this as Object?, EventType.USER_HOME, null, null, -1, ViewType.CURRENT) super.onUserLeaveHint() } @Override override fun onSaveInstanceState(outState: Bundle) { mOnSaveInstanceStateCalled = true super.onSaveInstanceState(outState) } @Override protected override fun onDestroy() { super.onDestroy() val prefs: SharedPreferences? = GeneralPreferences.getSharedPreferences(this) prefs?.unregisterOnSharedPreferenceChangeListener(this) mController?.deregisterAllEventHandlers() CalendarController.removeInstance(this) } private fun initFragments(timeMillis: Long, viewType: Int, icicle: Bundle?) { if (DEBUG) { Log.d(TAG, "Initializing to $timeMillis for view $viewType") } val ft: FragmentTransaction = getFragmentManager().beginTransaction() if (mShowCalendarControls) { val miniMonthFrag: Fragment = MonthByWeekFragment(timeMillis, true) ft.replace(R.id.mini_month, miniMonthFrag) mController?.registerEventHandler(R.id.mini_month, miniMonthFrag as EventHandler) } if (!mShowCalendarControls || viewType == ViewType.EDIT) { mMiniMonth?.setVisibility(View.GONE) mCalendarsList?.setVisibility(View.GONE) } var info: EventInfo? = null if (viewType == ViewType.EDIT) { mPreviousView = GeneralPreferences.getSharedPreferences(this)?.getInt( GeneralPreferences.KEY_START_VIEW, GeneralPreferences.DEFAULT_START_VIEW ) as Int var eventId: Long = -1 val intent: Intent = getIntent() val data: Uri? = intent.getData() if (data != null) { try { eventId = data.getLastPathSegment()?.toLong() as Long } catch (e: NumberFormatException) { if (DEBUG) { Log.d(TAG, "Create new event") } } } else if (icicle != null && icicle.containsKey(BUNDLE_KEY_EVENT_ID)) { eventId = icicle.getLong(BUNDLE_KEY_EVENT_ID) } val begin: Long = intent.getLongExtra(EXTRA_EVENT_BEGIN_TIME, -1) val end: Long = intent.getLongExtra(EXTRA_EVENT_END_TIME, -1) info = EventInfo() if (end != -1L) { info.endTime = Time() info.endTime?.set(end) } if (begin != -1L) { info.startTime = Time() info.startTime?.set(begin) } info.id = eventId // We set the viewtype so if the user presses back when they are // done editing the controller knows we were in the Edit Event // screen. Likewise for eventId mController?.viewType = viewType mController?.eventId = eventId } else { mPreviousView = viewType } setMainPane(ft, R.id.main_pane, viewType, timeMillis, true) ft.commit() // this needs to be after setMainPane() val t = Time(mTimeZone) t.set(timeMillis) if (viewType != ViewType.EDIT) { mController?.sendEvent(this as Object?, EventType.GO_TO, t, null, -1, viewType) } } @Override override fun onBackPressed() { if (mCurrentView == ViewType.EDIT || mBackToPreviousView) { mController?.sendEvent(this as Object?, EventType.GO_TO, null, null, -1, mPreviousView) } else { super.onBackPressed() } } @Override override fun onCreateOptionsMenu(menu: Menu): Boolean { super.onCreateOptionsMenu(menu) mOptionsMenu = menu getMenuInflater().inflate(R.menu.all_in_one_title_bar, menu) // Hide the "show/hide controls" button if this is a phone // or the view type is "Month". mControlsMenu = menu.findItem(R.id.action_hide_controls) if (!mShowCalendarControls) { if (mControlsMenu != null) { mControlsMenu?.setVisible(false) mControlsMenu?.setEnabled(false) } } else if (mControlsMenu != null && mController != null && mController?.viewType == ViewType.MONTH) { mControlsMenu?.setVisible(false) mControlsMenu?.setEnabled(false) } else if (mControlsMenu != null) { mControlsMenu?.setTitle(if (mHideControls) mShowString else mHideString) } val menuItem: MenuItem = menu.findItem(R.id.action_today) if (Utils.isJellybeanOrLater()) { // replace the default top layer drawable of the today icon with a // custom drawable that shows the day of the month of today val icon: LayerDrawable = menuItem.getIcon() as LayerDrawable Utils.setTodayIcon(icon, this, mTimeZone) } else { menuItem.setIcon(R.drawable.ic_menu_today_no_date_holo_light) } return true } @Override override fun onOptionsItemSelected(item: MenuItem): Boolean { var t: Time? = null var viewType: Int = ViewType.CURRENT var extras: Long = CalendarController.EXTRA_GOTO_TIME val itemId: Int = item.getItemId() if (itemId == R.id.action_today) { viewType = ViewType.CURRENT t = Time(mTimeZone) t.setToNow() extras = extras or CalendarController.EXTRA_GOTO_TODAY } else if (itemId == R.id.action_hide_controls) { mHideControls = !mHideControls item.setTitle(if (mHideControls) mShowString else mHideString) if (!mHideControls) { mMiniMonth?.setVisibility(View.VISIBLE) mCalendarsList?.setVisibility(View.VISIBLE) mMiniMonthContainer?.setVisibility(View.VISIBLE) } val slideAnimation: ObjectAnimator = ObjectAnimator.ofInt( this, "controlsOffset", if (mHideControls) 0 else mControlsAnimateWidth, if (mHideControls) mControlsAnimateWidth else 0 ) slideAnimation.setDuration(mCalendarControlsAnimationTime.toLong()) ObjectAnimator.setFrameDelay(0) slideAnimation.start() return true } else { Log.d(TAG, "Unsupported itemId: $itemId") return true } mController?.sendEvent(this as Object?, EventType.GO_TO, t, null, t, -1, viewType, extras, null, null) return true } /** * Sets the offset of the controls on the right for animating them off/on * screen. ProGuard strips this if it's not in proguard.flags * * @param controlsOffset The current offset in pixels */ fun setControlsOffset(controlsOffset: Int) { if (mOrientation == Configuration.ORIENTATION_LANDSCAPE) { mMiniMonth?.setTranslationX(controlsOffset.toFloat()) mCalendarsList?.setTranslationX(controlsOffset.toFloat()) mControlsParams?.width = Math.max(0, mControlsAnimateWidth - controlsOffset) mMiniMonthContainer?.setLayoutParams(mControlsParams) } else { mMiniMonth?.setTranslationY(controlsOffset.toFloat()) mCalendarsList?.setTranslationY(controlsOffset.toFloat()) if (mVerticalControlsParams == null) { mVerticalControlsParams = LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, mControlsAnimateHeight ) as LinearLayout.LayoutParams? } mVerticalControlsParams?.height = Math.max(0, mControlsAnimateHeight - controlsOffset) mMiniMonthContainer?.setLayoutParams(mVerticalControlsParams) } } @Override override fun onSharedPreferenceChanged(prefs: SharedPreferences, key: String?) { if (key.equals(GeneralPreferences.KEY_WEEK_START_DAY)) { if (mPaused) { mUpdateOnResume = true } else { initFragments(mController?.time as Long, mController?.viewType as Int, null) } } } private fun setMainPane( ft: FragmentTransaction?, viewId: Int, viewType: Int, timeMillis: Long, force: Boolean ) { var ft: FragmentTransaction? = ft if (mOnSaveInstanceStateCalled) { return } if (!force && mCurrentView == viewType) { return } // Remove this when transition to and from month view looks fine. val doTransition = viewType != ViewType.MONTH && mCurrentView != ViewType.MONTH val fragmentManager: FragmentManager = getFragmentManager() if (viewType != mCurrentView) { // The rules for this previous view are different than the // controller's and are used for intercepting the back button. if (mCurrentView != ViewType.EDIT && mCurrentView > 0) { mPreviousView = mCurrentView } mCurrentView = viewType } // Create new fragment var frag: Fragment? = null val secFrag: Fragment? = null when (viewType) { ViewType.AGENDA -> { } ViewType.DAY -> { if (mActionBar != null && mActionBar?.getSelectedTab() != mDayTab) { mActionBar?.selectTab(mDayTab) } if (mActionBarMenuSpinnerAdapter != null) { mActionBar?.setSelectedNavigationItem(CalendarViewAdapter.DAY_BUTTON_INDEX) } frag = DayFragment(timeMillis, 1) } ViewType.MONTH -> { if (mActionBar != null && mActionBar?.getSelectedTab() != mMonthTab) { mActionBar?.selectTab(mMonthTab) } if (mActionBarMenuSpinnerAdapter != null) { mActionBar?.setSelectedNavigationItem(CalendarViewAdapter.MONTH_BUTTON_INDEX) } frag = MonthByWeekFragment(timeMillis, false) } ViewType.WEEK -> { if (mActionBar != null && mActionBar?.getSelectedTab() != mWeekTab) { mActionBar?.selectTab(mWeekTab) } if (mActionBarMenuSpinnerAdapter != null) { mActionBar?.setSelectedNavigationItem(CalendarViewAdapter.WEEK_BUTTON_INDEX) } frag = DayFragment(timeMillis, 7) } else -> { if (mActionBar != null && mActionBar?.getSelectedTab() != mWeekTab) { mActionBar?.selectTab(mWeekTab) } if (mActionBarMenuSpinnerAdapter != null) { mActionBar?.setSelectedNavigationItem(CalendarViewAdapter.WEEK_BUTTON_INDEX) } frag = DayFragment(timeMillis, 7) } } // Update the current view so that the menu can update its look according to the // current view. if (mActionBarMenuSpinnerAdapter != null) { mActionBarMenuSpinnerAdapter?.setMainView(viewType) if (!mIsTabletConfig) { mActionBarMenuSpinnerAdapter?.setTime(timeMillis) } } // Show date only on tablet configurations in views different than Agenda if (!mIsTabletConfig) { mDateRange?.setVisibility(View.GONE) } else { mDateRange?.setVisibility(View.GONE) } // Clear unnecessary buttons from the option menu when switching from the agenda view if (viewType != ViewType.AGENDA) { clearOptionsMenu() } var doCommit = false if (ft == null) { doCommit = true ft = fragmentManager.beginTransaction() } if (doTransition) { ft?.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) } ft?.replace(viewId, frag) if (DEBUG) { Log.d(TAG, "Adding handler with viewId $viewId and type $viewType") } // If the key is already registered this will replace it mController?.registerEventHandler(viewId, frag as EventHandler?) if (doCommit) { if (DEBUG) { Log.d(TAG, "setMainPane AllInOne=" + this + " finishing:" + this.isFinishing()) } ft?.commit() } } private fun setTitleInActionBar(event: EventInfo) { if (event.eventType != EventType.UPDATE_TITLE || mActionBar == null) { return } val start: Long? = event.startTime?.toMillis(false /* use isDst */) val end: Long? end = if (event.endTime != null) { event.endTime?.toMillis(false /* use isDst */) } else { start } val msg: String? = Utils.formatDateRange(this, start as Long, end as Long, event.extraLong.toInt() ) val oldDate: CharSequence? = mDateRange?.getText() mDateRange?.setText(msg) updateSecondaryTitleFields(if (event.selectedTime != null) event.selectedTime?.toMillis(true) as Long else start) if (!TextUtils.equals(oldDate, msg)) { mDateRange?.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED) if (mShowWeekNum && mWeekTextView != null) { mWeekTextView?.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED) } } } private fun updateSecondaryTitleFields(visibleMillisSinceEpoch: Long) { mShowWeekNum = Utils.getShowWeekNumber(this) mTimeZone = Utils.getTimeZone(this, mHomeTimeUpdater) if (visibleMillisSinceEpoch != -1L) { val weekNum: Int = Utils.getWeekNumberFromTime(visibleMillisSinceEpoch, this) mWeekNum = weekNum } if (mShowWeekNum && mCurrentView == ViewType.WEEK && mIsTabletConfig && mWeekTextView != null ) { val weekString: String = getResources().getQuantityString( R.plurals.weekN, mWeekNum, mWeekNum ) mWeekTextView?.setText(weekString) mWeekTextView?.setVisibility(View.VISIBLE) } else if (visibleMillisSinceEpoch != -1L && mWeekTextView != null && mCurrentView == ViewType.DAY && mIsTabletConfig) { val time = Time(mTimeZone) time.set(visibleMillisSinceEpoch) val julianDay: Int = Time.getJulianDay(visibleMillisSinceEpoch, time.gmtoff) time.setToNow() val todayJulianDay: Int = Time.getJulianDay(time.toMillis(false), time.gmtoff) val dayString: String = Utils.getDayOfWeekString( julianDay, todayJulianDay, visibleMillisSinceEpoch, this ) mWeekTextView?.setText(dayString) mWeekTextView?.setVisibility(View.VISIBLE) } else if (mWeekTextView != null && (!mIsTabletConfig || mCurrentView != ViewType.DAY)) { mWeekTextView?.setVisibility(View.GONE) } if (mHomeTime != null && (mCurrentView == ViewType.DAY || mCurrentView == ViewType.WEEK) && !TextUtils.equals(mTimeZone, Time.getCurrentTimezone()) ) { val time = Time(mTimeZone) time.setToNow() val millis: Long = time.toMillis(true) val isDST = time.isDst !== 0 var flags: Int = DateUtils.FORMAT_SHOW_TIME if (DateFormat.is24HourFormat(this)) { flags = flags or DateUtils.FORMAT_24HOUR } // Formats the time as val timeString: String = StringBuilder( Utils.formatDateRange(this, millis, millis, flags) ).append(" ").append( TimeZone.getTimeZone(mTimeZone).getDisplayName( isDST, TimeZone.SHORT, Locale.getDefault() ) ).toString() mHomeTime?.setText(timeString) mHomeTime?.setVisibility(View.VISIBLE) // Update when the minute changes mHomeTime?.removeCallbacks(mHomeTimeUpdater) mHomeTime?.postDelayed( mHomeTimeUpdater, DateUtils.MINUTE_IN_MILLIS - millis % DateUtils.MINUTE_IN_MILLIS ) } else if (mHomeTime != null) { mHomeTime?.setVisibility(View.GONE) } } @get:Override override val supportedEventTypes: Long get() = EventType.GO_TO or EventType.UPDATE_TITLE @Override override fun handleEvent(event: EventInfo?) { var displayTime: Long = -1 if (event?.eventType == EventType.GO_TO) { if (event.extraLong and CalendarController.EXTRA_GOTO_BACK_TO_PREVIOUS != 0L) { mBackToPreviousView = true } else if (event.viewType != mController?.previousViewType && event.viewType != ViewType.EDIT ) { // Clear the flag is change to a different view type mBackToPreviousView = false } setMainPane( null, R.id.main_pane, event.viewType, event.startTime?.toMillis(false) as Long, false ) if (mShowCalendarControls) { val animationSize = if (mOrientation == Configuration.ORIENTATION_LANDSCAPE) mControlsAnimateWidth else mControlsAnimateHeight val noControlsView = event.viewType == ViewType.MONTH if (mControlsMenu != null) { mControlsMenu?.setVisible(!noControlsView) mControlsMenu?.setEnabled(!noControlsView) } if (noControlsView || mHideControls) { // hide minimonth and calendar frag mShowSideViews = false if (!mHideControls) { val slideAnimation: ObjectAnimator = ObjectAnimator.ofInt( this, "controlsOffset", 0, animationSize ) slideAnimation.addListener(mSlideAnimationDoneListener) slideAnimation.setDuration(mCalendarControlsAnimationTime.toLong()) ObjectAnimator.setFrameDelay(0) slideAnimation.start() } else { mMiniMonth?.setVisibility(View.GONE) mCalendarsList?.setVisibility(View.GONE) mMiniMonthContainer?.setVisibility(View.GONE) } } else { // show minimonth and calendar frag mShowSideViews = true mMiniMonth?.setVisibility(View.VISIBLE) mCalendarsList?.setVisibility(View.VISIBLE) mMiniMonthContainer?.setVisibility(View.VISIBLE) if (!mHideControls && mController?.previousViewType == ViewType.MONTH ) { val slideAnimation: ObjectAnimator = ObjectAnimator.ofInt( this, "controlsOffset", animationSize, 0 ) slideAnimation.setDuration(mCalendarControlsAnimationTime.toLong()) ObjectAnimator.setFrameDelay(0) slideAnimation.start() } } } displayTime = if (event.selectedTime != null) event.selectedTime?.toMillis(true) as Long else event.startTime?.toMillis(true) as Long if (!mIsTabletConfig) { mActionBarMenuSpinnerAdapter?.setTime(displayTime) } } else if (event?.eventType == EventType.UPDATE_TITLE) { setTitleInActionBar(event as CalendarController.EventInfo) if (!mIsTabletConfig) { mActionBarMenuSpinnerAdapter?.setTime(mController?.time as Long) } } updateSecondaryTitleFields(displayTime) } @Override override fun eventsChanged() { mController?.sendEvent(this as Object?, EventType.EVENTS_CHANGED, null, null, -1, ViewType.CURRENT) } @Override override fun onTabSelected(tab: Tab?, ft: FragmentTransaction?) { Log.w(TAG, "TabSelected AllInOne=" + this + " finishing:" + this.isFinishing()) if (tab == mDayTab && mCurrentView != ViewType.DAY) { mController?.sendEvent(this as Object?, EventType.GO_TO, null, null, -1, ViewType.DAY) } else if (tab == mWeekTab && mCurrentView != ViewType.WEEK) { mController?.sendEvent(this as Object?, EventType.GO_TO, null, null, -1, ViewType.WEEK) } else if (tab == mMonthTab && mCurrentView != ViewType.MONTH) { mController?.sendEvent(this as Object?, EventType.GO_TO, null, null, -1, ViewType.MONTH) } else { Log.w( TAG, "TabSelected event from unknown tab: " + if (tab == null) "null" else tab.getText() ) Log.w( TAG, "CurrentView:" + mCurrentView + " Tab:" + tab.toString() + " Day:" + mDayTab + " Week:" + mWeekTab + " Month:" + mMonthTab ) } } @Override override fun onTabReselected(tab: Tab?, ft: FragmentTransaction?) { } @Override override fun onTabUnselected(tab: Tab?, ft: FragmentTransaction?) { } @Override override fun onNavigationItemSelected(itemPosition: Int, itemId: Long): Boolean { when (itemPosition) { CalendarViewAdapter.DAY_BUTTON_INDEX -> if (mCurrentView != ViewType.DAY) { mController?.sendEvent(this as Object?, EventType.GO_TO, null, null, -1, ViewType.DAY) } CalendarViewAdapter.WEEK_BUTTON_INDEX -> if (mCurrentView != ViewType.WEEK) { mController?.sendEvent(this as Object?, EventType.GO_TO, null, null, -1, ViewType.WEEK) } CalendarViewAdapter.MONTH_BUTTON_INDEX -> if (mCurrentView != ViewType.MONTH) { mController?.sendEvent(this as Object?, EventType.GO_TO, null, null, -1, ViewType.MONTH) } CalendarViewAdapter.AGENDA_BUTTON_INDEX -> { } else -> { Log.w(TAG, "ItemSelected event from unknown button: $itemPosition") Log.w( TAG, "CurrentView:" + mCurrentView + " Button:" + itemPosition + " Day:" + mDayTab + " Week:" + mWeekTab + " Month:" + mMonthTab ) } } return false } companion object { private const val TAG = "AllInOneActivity" private const val DEBUG = false private const val EVENT_INFO_FRAGMENT_TAG = "EventInfoFragment" private const val BUNDLE_KEY_RESTORE_TIME = "key_restore_time" private const val BUNDLE_KEY_EVENT_ID = "key_event_id" private const val BUNDLE_KEY_RESTORE_VIEW = "key_restore_view" private const val BUNDLE_KEY_CHECK_ACCOUNTS = "key_check_for_accounts" private const val HANDLER_KEY = 0 // Indices of buttons for the drop down menu (tabs replacement) // Must match the strings in the array buttons_list in arrays.xml and the // OnNavigationListener private const val BUTTON_DAY_INDEX = 0 private const val BUTTON_WEEK_INDEX = 1 private const val BUTTON_MONTH_INDEX = 2 private const val BUTTON_AGENDA_INDEX = 3 private var mIsMultipane = false private var mIsTabletConfig = false } }