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