blob: 78d69e4097b9096919e118056398c9995e8ac44e [file] [log] [blame]
/*
* Copyright 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 androidx.wear.watchface.editor.sample
import android.annotation.SuppressLint
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.View
import android.view.accessibility.AccessibilityEvent
import androidx.annotation.RestrictTo
import androidx.annotation.RestrictTo.Scope.LIBRARY
import androidx.annotation.VisibleForTesting
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.wear.complications.data.ComplicationData
import androidx.wear.watchface.editor.EditorSession
import androidx.wear.watchface.style.UserStyle
import androidx.wear.watchface.style.UserStyleSchema
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.android.asCoroutineDispatcher
import kotlinx.coroutines.launch
/** @hide */
@RestrictTo(LIBRARY)
internal interface FragmentController {
/** Show the [ConfigFragment] which lets the user select what they want to configure. */
fun showConfigFragment()
/**
* Show the [ComplicationConfigFragment] which lets the user select the complication
* they want to configure.
*/
fun showComplicationConfigSelectionFragment()
/** Show the [StyleConfigFragment] which lets the user configure the watch face style. */
fun showStyleConfigFragment(
settingId: String,
styleSchema: UserStyleSchema,
userStyle: UserStyle
)
/** Lets the user configure the complication provider for a single complication slot. */
suspend fun showComplicationConfig(complicationId: Int): Boolean
}
// Reference time for editor screenshots for analog watch faces.
// 2020/10/10 at 09:30 Note the date doesn't matter, only the hour.
private const val ANALOG_WATCHFACE_REFERENCE_TIME_MS = 1602318600000L
// Reference time for editor screenshots for digital watch faces.
// 2020/10/10 at 10:10 Note the date doesn't matter, only the hour.
private const val DIGITAL_WATCHFACE_REFERENCE_TIME_MS = 1602321000000L
/**
* Config activity for the watch face, which supports complication and provider selection, as well
* as userStyle configuration.
*/
class WatchFaceConfigActivity : FragmentActivity() {
internal val complicationData = HashMap<Int, ComplicationData>()
internal lateinit var editorSession: EditorSession
internal lateinit var fragmentController: FragmentController
internal lateinit var handler: Handler
internal lateinit var coroutineScope: CoroutineScope
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
handler = Handler(Looper.getMainLooper())
coroutineScope = CoroutineScope(handler.asCoroutineDispatcher())
val deferredEditorSession = EditorSession.createOnWatchEditingSessionAsync(
this@WatchFaceConfigActivity,
intent!!
)
coroutineScope.launch {
init(
deferredEditorSession.await()!!,
object : FragmentController {
@SuppressLint("SyntheticAccessor")
override fun showConfigFragment() {
showFragment(ConfigFragment())
}
@SuppressLint("SyntheticAccessor")
override fun showComplicationConfigSelectionFragment() {
showFragment(ComplicationConfigFragment())
}
@SuppressLint("SyntheticAccessor")
override fun showStyleConfigFragment(
settingId: String,
styleSchema: UserStyleSchema,
userStyle: UserStyle
) {
showFragment(
StyleConfigFragment.newInstance(settingId, styleSchema, userStyle)
)
}
/**
* Displays a config screen which allows the user to select the data source for the
* complication.
*/
@SuppressWarnings("deprecation")
override suspend fun showComplicationConfig(
complicationId: Int
) = editorSession.launchComplicationProviderChooser(complicationId)
}
)
}
}
override fun onDestroy() {
super.onDestroy()
}
private fun focusCurrentFragment() {
val curFragment = supportFragmentManager.findFragmentById(android.R.id.content)
if (curFragment != null) {
curFragment.view?.importantForAccessibility =
View.IMPORTANT_FOR_ACCESSIBILITY_YES
}
// Clear focus on the old fragment that is behind the new one, and announce the new title.
// This prevents the Talkback linear navigation from selecting elements that are behind the
// new fragment.
window.decorView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED)
}
private fun showFragment(fragment: Fragment) {
val curFragment = supportFragmentManager.findFragmentById(android.R.id.content)
curFragment?.view?.importantForAccessibility =
View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
// The new fragment will have its importance set by OnBackStackChangedListener.
supportFragmentManager
.beginTransaction()
.add(android.R.id.content, fragment)
.addToBackStack(null)
.commit()
}
@VisibleForTesting
internal fun init(
editorSession: EditorSession,
fragmentController: FragmentController
) {
this.editorSession = editorSession
this.fragmentController = fragmentController
supportFragmentManager
.addOnBackStackChangedListener {
if (supportFragmentManager.backStackEntryCount == 0) {
finish()
} else {
focusCurrentFragment()
}
}
var topLevelOptionCount = editorSession.userStyleSchema.userStyleSettings.size
val hasBackgroundComplication = editorSession.backgroundComplicationId != null
if (hasBackgroundComplication) {
topLevelOptionCount++
}
val numComplications = editorSession.complicationState.size
val hasNonBackgroundComplication =
numComplications > (if (hasBackgroundComplication) 1 else 0)
if (hasNonBackgroundComplication) {
topLevelOptionCount++
}
when {
// More than one top level option, so show a fragment which lets the user choose what
// they want to configure.
topLevelOptionCount > 1 -> fragmentController.showConfigFragment()
// For a single complication go directly to the provider selector.
numComplications == 1 -> {
val onlyComplication = editorSession.complicationState.entries.first()
coroutineScope.launch {
fragmentController.showComplicationConfig(onlyComplication.key)
}
}
// For multiple complications select the complication to configure first.
numComplications > 1 -> fragmentController.showComplicationConfigSelectionFragment()
// For a single style, go select the option.
editorSession.userStyleSchema.userStyleSettings.size == 1 -> {
// There should only be a single userStyle setting if we get here.
val onlyStyleSetting = editorSession.userStyleSchema.userStyleSettings.first()
fragmentController.showStyleConfigFragment(
onlyStyleSetting.id,
editorSession.userStyleSchema,
editorSession.userStyle
)
}
}
}
override fun onStop() {
super.onStop()
editorSession.close()
}
}