mirror of
https://github.com/alesimula/Murine-launcher.git
synced 2026-10-28 09:43:42 +00:00
add libs
This commit is contained in:
1 parent
1889ce3062
commit
40f4d97dc0
34 files changed
+2130
-7
No files matched your search
+13
-1
@@ -20,6 +20,7 @@ final String FRAMEWORK_PREBUILTS_DIR = "${ANDROID_TOP}/prebuilts/libs/"
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
apply plugin: 'com.google.protobuf'
|
apply plugin: 'com.google.protobuf'
|
||||||
apply plugin: 'org.jetbrains.kotlin.android'
|
apply plugin: 'org.jetbrains.kotlin.android'
|
||||||
|
apply plugin: 'kotlin-kapt'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion COMPILE_SDK
|
compileSdkVersion COMPILE_SDK
|
||||||
@@ -155,6 +156,15 @@ android {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 启用增量注解处理(提升编译速度)
|
||||||
|
kapt {
|
||||||
|
useBuildCache = true
|
||||||
|
correctErrorTypes = true
|
||||||
|
javacOptions {
|
||||||
|
option("-Xmaxerrs", 1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
// maven { url "../../../prebuilts/sdk/current/androidx/m2repository" }
|
// maven { url "../../../prebuilts/sdk/current/androidx/m2repository" }
|
||||||
@@ -176,6 +186,8 @@ dependencies {
|
|||||||
implementation project(':IconLoader')
|
implementation project(':IconLoader')
|
||||||
implementation project(':systemUIPluginCore')
|
implementation project(':systemUIPluginCore')
|
||||||
implementation project(':animationlib')
|
implementation project(':animationlib')
|
||||||
|
implementation project(':Shared')
|
||||||
|
implementation project(':msdllib')
|
||||||
// implementation project(':UiTestsLibLauncher')
|
// implementation project(':UiTestsLibLauncher')
|
||||||
// withQuickstepImplementation project(':SharedLibWrapper')
|
// withQuickstepImplementation project(':SharedLibWrapper')
|
||||||
implementation 'com.google.protobuf:protobuf-javalite:4.26.1'
|
implementation 'com.google.protobuf:protobuf-javalite:4.26.1'
|
||||||
@@ -199,7 +211,7 @@ dependencies {
|
|||||||
|
|
||||||
api 'com.airbnb.android:lottie:6.6.6'
|
api 'com.airbnb.android:lottie:6.6.6'
|
||||||
implementation 'com.google.dagger:dagger:2.56.2'
|
implementation 'com.google.dagger:dagger:2.56.2'
|
||||||
annotationProcessor 'com.google.dagger:dagger-compiler:2.56.2'
|
kapt 'com.google.dagger:dagger-compiler:2.56.2'
|
||||||
|
|
||||||
implementation 'com.google.android.material:material:1.12.0'
|
implementation 'com.google.android.material:material:1.12.0'
|
||||||
implementation 'androidx.slice:slice-builders:1.0.0'
|
implementation 'androidx.slice:slice-builders:1.0.0'
|
||||||
|
|||||||
+1
-2
@@ -19,5 +19,4 @@ PROTOBUF_DEPENDENCY=com.google.protobuf.nano:protobuf-javanano:3.1.0
|
|||||||
|
|
||||||
BUILD_TOOLS_VERSION=28.0.3
|
BUILD_TOOLS_VERSION=28.0.3
|
||||||
COMPILE_SDK=android-36
|
COMPILE_SDK=android-36
|
||||||
#KOTLIN=org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10
|
KOTLIN=org.jetbrains.kotlin:kotlin-gradle-plugin:2.1.21
|
||||||
KOTLIN=org.jetbrains.kotlin:kotlin-gradle-plugin:2.1.20
|
|
||||||
@@ -28,7 +28,7 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("androidx.core:core:1.15.0")
|
implementation("androidx.core:core:1.16.0")
|
||||||
implementation("androidx.annotation:annotation:1.9.1")
|
implementation("androidx.annotation:annotation:1.9.1")
|
||||||
// api(project(":NexusLauncher:Flags"))
|
// api(project(":NexusLauncher:Flags"))
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,161 @@ package com.android.launcher3
|
|||||||
|
|
||||||
object Flags {
|
object Flags {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enableShortcutDontSuggestApp(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun navigateToChildPreference(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun workSchedulerInWorkProfile(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enableGeneratedPreviews(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enableFocusOutline(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun privateSpaceAnimation(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun gridMigrationRefactor(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enableDismissPredictionUndo(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun privateSpaceAddFloatingMaskView(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun msdlFeedback(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun privateSpaceRestrictAccessibilityDrag(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enableTwolineToggle(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun oneGridRotationHandling(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enableAdditionalHomeAnimations(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enableLauncherOverviewInWindow(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enableGridOnlyOverview(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enableFallbackOverviewInWindow(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enableMouseInteractionChanges(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun useSystemRadiusForAppWidgets(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enableOverviewOnConnectedDisplays(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enableFirstScreenBroadcastArchivingExtras(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enableSmartspaceAsAWidget(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enableLauncherBrMetricsFixed(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enableCursorHoverStates(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun allAppsSheetForHandheld(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enableOverviewIconMenu(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enableContrastTiles(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enableStateManagerProtoLog(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enableLauncherVisualRefresh(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enableTieredWidgetsByDefaultInPicker(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enableTwoPaneLauncherSettings(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun restoreArchivedAppIconsFromDb(): Boolean {
|
fun restoreArchivedAppIconsFromDb(): Boolean {
|
||||||
return true
|
return true
|
||||||
@@ -56,4 +211,94 @@ object Flags {
|
|||||||
fun enableScalingRevealHomeAnimation(): Boolean {
|
fun enableScalingRevealHomeAnimation(): Boolean {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enableTaskbarNoRecreate(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enableTaskbarPinning(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enableHomeTransitionListener(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enableAllAppsButtonInHotseat(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enableResponsiveWorkspace(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun letterFastScroller(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enablePrivateSpace(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun privateSpaceAppInstallerButton(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun privateSpaceSysAppsSeparation(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enableExpandingPauseWorkButton(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun allAppsBlur(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun multilineSearchBar(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun floatingSearchBar(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun privateSpaceRestrictItemDrag(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun extendibleThemeManager(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun removeAppsRefreshOnRightClick(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enableAddAppWidgetViaConfigActivityV2(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enableStrictMode(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
// Copyright (C) 2024 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 {
|
||||||
|
default_team: "trendy_team_motion",
|
||||||
|
default_applicable_licenses: ["Android-Apache-2.0"],
|
||||||
|
}
|
||||||
|
|
||||||
|
android_library {
|
||||||
|
name: "msdl",
|
||||||
|
manifest: "AndroidManifest.xml",
|
||||||
|
sdk_version: "system_current",
|
||||||
|
min_sdk_version: "31",
|
||||||
|
static_libs: [
|
||||||
|
"kotlinx_coroutines_android",
|
||||||
|
"androidx.annotation_annotation",
|
||||||
|
],
|
||||||
|
srcs: [
|
||||||
|
"src/**/*.java",
|
||||||
|
"src/**/*.kt",
|
||||||
|
],
|
||||||
|
kotlincflags: ["-Xjvm-default=all"],
|
||||||
|
}
|
||||||
|
|
||||||
|
android_library {
|
||||||
|
name: "msdl-tests-base",
|
||||||
|
libs: [
|
||||||
|
"android.test.base.stubs.system",
|
||||||
|
"androidx.test.core",
|
||||||
|
],
|
||||||
|
static_libs: [
|
||||||
|
"msdl",
|
||||||
|
"androidx.test.ext.junit",
|
||||||
|
"androidx.test.rules",
|
||||||
|
"testables",
|
||||||
|
"truth",
|
||||||
|
"kotlinx_coroutines_test",
|
||||||
|
"kotlin-test",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
android_app {
|
||||||
|
name: "TestMSDLApp",
|
||||||
|
platform_apis: true,
|
||||||
|
static_libs: [
|
||||||
|
"msdl-tests-base",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
android_test {
|
||||||
|
name: "msdl_tests",
|
||||||
|
manifest: "tests/AndroidManifest.xml",
|
||||||
|
|
||||||
|
static_libs: [
|
||||||
|
"msdl-tests-base",
|
||||||
|
],
|
||||||
|
srcs: [
|
||||||
|
"tests/src/**/*.kt",
|
||||||
|
],
|
||||||
|
kotlincflags: ["-Xjvm-default=all"],
|
||||||
|
test_suites: ["general-tests"],
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright (C) 2024 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.google.android.msdl">
|
||||||
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"postsubmit": [
|
||||||
|
{
|
||||||
|
"name": "msdl_tests"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
plugins {
|
||||||
|
id("com.android.library")
|
||||||
|
id("kotlin-android")
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.google.android.msdl"
|
||||||
|
|
||||||
|
compileSdk = 36
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdk = 26
|
||||||
|
targetSdk = 36
|
||||||
|
|
||||||
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
vectorDrawables.useSupportLibrary = true
|
||||||
|
}
|
||||||
|
sourceSets {
|
||||||
|
named("main") {
|
||||||
|
java.setSrcDirs(listOf("src"))
|
||||||
|
manifest.srcFile("AndroidManifest.xml")
|
||||||
|
res.setSrcDirs(listOf("res"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
kotlin {
|
||||||
|
jvmToolchain(21)//1.8(8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation("androidx.core:core:1.16.0")
|
||||||
|
implementation("androidx.annotation:annotation:1.9.1")
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 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.google.android.msdl.data.model
|
||||||
|
|
||||||
|
import android.os.VibrationEffect
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A haptic composition as a list of [HapticCompositionPrimitive] and a [android.os.VibrationEffect]
|
||||||
|
* to use as a fallback.
|
||||||
|
*/
|
||||||
|
data class HapticComposition(
|
||||||
|
val primitives: List<HapticCompositionPrimitive>,
|
||||||
|
val fallbackEffect: VibrationEffect,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An abstraction of a haptic primitive in a composition that includes:
|
||||||
|
*
|
||||||
|
* @param[primitiveId] The id of the primitive.
|
||||||
|
* @param[scale] The scale of the primitive.
|
||||||
|
* @param[delayMillis] The delay of the primitive relative to the end of a previous primitive. Given
|
||||||
|
* in milliseconds.
|
||||||
|
*/
|
||||||
|
data class HapticCompositionPrimitive(
|
||||||
|
val primitiveId: Int,
|
||||||
|
var scale: Float = 1f,
|
||||||
|
var delayMillis: Int = 0,
|
||||||
|
)
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 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.google.android.msdl.data.model
|
||||||
|
|
||||||
|
/** Haptic tokens from the Multi-sensory Design Language (MSDL) */
|
||||||
|
enum class HapticToken {
|
||||||
|
NEGATIVE_CONFIRMATION_HIGH_EMPHASIS,
|
||||||
|
NEGATIVE_CONFIRMATION_MEDIUM_EMPHASIS,
|
||||||
|
POSITIVE_CONFIRMATION_HIGH_EMPHASIS,
|
||||||
|
POSITIVE_CONFIRMATION_MEDIUM_EMPHASIS,
|
||||||
|
POSITIVE_CONFIRMATION_LOW_EMPHASIS,
|
||||||
|
NEUTRAL_CONFIRMATION_HIGH_EMPHASIS,
|
||||||
|
NEUTRAL_CONFIRMATION_MEDIUM_EMPHASIS,
|
||||||
|
LONG_PRESS,
|
||||||
|
SWIPE_THRESHOLD_INDICATOR,
|
||||||
|
TAP_HIGH_EMPHASIS,
|
||||||
|
TAP_MEDIUM_EMPHASIS,
|
||||||
|
DRAG_THRESHOLD_INDICATOR,
|
||||||
|
DRAG_INDICATOR_CONTINUOUS,
|
||||||
|
DRAG_INDICATOR_DISCRETE,
|
||||||
|
TAP_LOW_EMPHASIS,
|
||||||
|
KEYPRESS_STANDARD,
|
||||||
|
KEYPRESS_SPACEBAR,
|
||||||
|
KEYPRESS_RETURN,
|
||||||
|
KEYPRESS_DELETE,
|
||||||
|
}
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 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.google.android.msdl.data.model
|
||||||
|
|
||||||
|
/** System-level tokens defined in the Multi-sensory Design Language (MSDL) */
|
||||||
|
enum class MSDLToken(
|
||||||
|
val hapticToken: HapticToken,
|
||||||
|
val soundToken: SoundToken,
|
||||||
|
val minimumFeedbackLevel: FeedbackLevel,
|
||||||
|
) {
|
||||||
|
/* Inform the user with emphasis that their current action FAILED to complete */
|
||||||
|
FAILURE_HIGH_EMPHASIS(
|
||||||
|
HapticToken.NEGATIVE_CONFIRMATION_HIGH_EMPHASIS,
|
||||||
|
SoundToken.FAILURE_HIGH_EMPHASIS,
|
||||||
|
FeedbackLevel.MINIMAL,
|
||||||
|
),
|
||||||
|
/* Inform the user that their current action FAILED to complete */
|
||||||
|
FAILURE(
|
||||||
|
HapticToken.NEGATIVE_CONFIRMATION_MEDIUM_EMPHASIS,
|
||||||
|
SoundToken.FAILURE,
|
||||||
|
FeedbackLevel.MINIMAL,
|
||||||
|
),
|
||||||
|
/* Inform the user their current action was completed SUCCESSFULLY */
|
||||||
|
SUCCESS(
|
||||||
|
HapticToken.POSITIVE_CONFIRMATION_HIGH_EMPHASIS,
|
||||||
|
SoundToken.SUCCESS,
|
||||||
|
FeedbackLevel.MINIMAL,
|
||||||
|
),
|
||||||
|
/* Inform the user that an ongoing activity has started */
|
||||||
|
START(HapticToken.NEUTRAL_CONFIRMATION_HIGH_EMPHASIS, SoundToken.START, FeedbackLevel.DEFAULT),
|
||||||
|
/* Inform the user that an ongoing activity has paused */
|
||||||
|
PAUSE(
|
||||||
|
HapticToken.NEUTRAL_CONFIRMATION_MEDIUM_EMPHASIS,
|
||||||
|
SoundToken.PAUSE,
|
||||||
|
FeedbackLevel.DEFAULT,
|
||||||
|
),
|
||||||
|
/* Inform the user that their previously started activity has stopped SUCCESSFULLY */
|
||||||
|
STOP(HapticToken.POSITIVE_CONFIRMATION_MEDIUM_EMPHASIS, SoundToken.STOP, FeedbackLevel.DEFAULT),
|
||||||
|
/* Inform the user that their previously started activity has cancelled SUCCESSFULLY */
|
||||||
|
CANCEL(
|
||||||
|
HapticToken.POSITIVE_CONFIRMATION_MEDIUM_EMPHASIS,
|
||||||
|
SoundToken.CANCEL,
|
||||||
|
FeedbackLevel.DEFAULT,
|
||||||
|
),
|
||||||
|
/* Inform the user that the state of an interactive component has been switched to on SUCCESSFULLY */
|
||||||
|
SWITCH_ON(
|
||||||
|
HapticToken.POSITIVE_CONFIRMATION_MEDIUM_EMPHASIS,
|
||||||
|
SoundToken.SWITCH_ON,
|
||||||
|
FeedbackLevel.DEFAULT,
|
||||||
|
),
|
||||||
|
/* Inform the user that the state of an interactive component has been switched to off SUCCESSFULLY */
|
||||||
|
SWITCH_OFF(
|
||||||
|
HapticToken.POSITIVE_CONFIRMATION_MEDIUM_EMPHASIS,
|
||||||
|
SoundToken.SWITCH_OFF,
|
||||||
|
FeedbackLevel.DEFAULT,
|
||||||
|
),
|
||||||
|
/* Inform the user the state of their device changed to unlocked SUCCESSFULLY */
|
||||||
|
UNLOCK(
|
||||||
|
HapticToken.POSITIVE_CONFIRMATION_LOW_EMPHASIS,
|
||||||
|
SoundToken.UNLOCK,
|
||||||
|
FeedbackLevel.DEFAULT,
|
||||||
|
),
|
||||||
|
/* Inform the user the state of their device changed to locked SUCCESSFULLY */
|
||||||
|
LOCK(HapticToken.POSITIVE_CONFIRMATION_LOW_EMPHASIS, SoundToken.LOCK, FeedbackLevel.DEFAULT),
|
||||||
|
/* Inform the user that their long-press gesture has resulted in the revealing of more contextual information */
|
||||||
|
LONG_PRESS(HapticToken.LONG_PRESS, SoundToken.LONG_PRESS, FeedbackLevel.MINIMAL),
|
||||||
|
/* Inform the user that their swipe gesture has reached a threshold that confirms navigation or the reveal of additional information. */
|
||||||
|
SWIPE_THRESHOLD_INDICATOR(
|
||||||
|
HapticToken.SWIPE_THRESHOLD_INDICATOR,
|
||||||
|
SoundToken.SWIPE_THRESHOLD_INDICATOR,
|
||||||
|
FeedbackLevel.MINIMAL,
|
||||||
|
),
|
||||||
|
/* Played when the user taps on a high-emphasis UI element */
|
||||||
|
TAP_HIGH_EMPHASIS(
|
||||||
|
HapticToken.TAP_HIGH_EMPHASIS,
|
||||||
|
SoundToken.TAP_HIGH_EMPHASIS,
|
||||||
|
FeedbackLevel.EXPRESSIVE,
|
||||||
|
),
|
||||||
|
/* Inform the user that their tap has resulted in a selection */
|
||||||
|
TAP_MEDIUM_EMPHASIS(
|
||||||
|
HapticToken.TAP_MEDIUM_EMPHASIS,
|
||||||
|
SoundToken.TAP_MEDIUM_EMPHASIS,
|
||||||
|
FeedbackLevel.DEFAULT,
|
||||||
|
),
|
||||||
|
/* Played when a users drag gesture reaches the maximum or minimum value */
|
||||||
|
DRAG_THRESHOLD_INDICATOR_LIMIT(
|
||||||
|
HapticToken.DRAG_THRESHOLD_INDICATOR,
|
||||||
|
SoundToken.DRAG_THRESHOLD_INDICATOR_LIMIT,
|
||||||
|
FeedbackLevel.DEFAULT,
|
||||||
|
),
|
||||||
|
/* Inform the user that their drag gesture has resulted in an incremental value change.
|
||||||
|
* For usage in haptic sliders that change continuously, this token can be played along with
|
||||||
|
* [InteractionProperties.DynamicVibrationScale] properties to control haptic scaling as a
|
||||||
|
* function of position and velocity.
|
||||||
|
*/
|
||||||
|
DRAG_INDICATOR_CONTINUOUS(
|
||||||
|
HapticToken.DRAG_INDICATOR_CONTINUOUS,
|
||||||
|
SoundToken.NO_SOUND,
|
||||||
|
FeedbackLevel.DEFAULT,
|
||||||
|
),
|
||||||
|
/* Inform the user that their drag gesture has resulted in a stepped value change.
|
||||||
|
* For usage in haptic sliders that change in discrete steps, this token can be played with
|
||||||
|
* [InteractionProperties.DynamicVibrationScale] properties to control haptic scaling as a
|
||||||
|
* function of position and velocity.
|
||||||
|
*/
|
||||||
|
DRAG_INDICATOR_DISCRETE(
|
||||||
|
HapticToken.DRAG_INDICATOR_DISCRETE,
|
||||||
|
SoundToken.DRAG_INDICATOR,
|
||||||
|
FeedbackLevel.DEFAULT,
|
||||||
|
),
|
||||||
|
/* Played when a user taps on any UI element that can be interacted with but is not otherwise defined */
|
||||||
|
TAP_LOW_EMPHASIS(
|
||||||
|
HapticToken.TAP_LOW_EMPHASIS,
|
||||||
|
SoundToken.TAP_LOW_EMPHASIS,
|
||||||
|
FeedbackLevel.EXPRESSIVE,
|
||||||
|
),
|
||||||
|
/* Played when the user touches a key on the keyboard that is otherwise undefined */
|
||||||
|
KEYPRESS_STANDARD(
|
||||||
|
HapticToken.KEYPRESS_STANDARD,
|
||||||
|
SoundToken.KEYPRESS_STANDARD,
|
||||||
|
FeedbackLevel.DEFAULT,
|
||||||
|
),
|
||||||
|
/* Played when the user touches the space key */
|
||||||
|
KEYPRESS_SPACEBAR(
|
||||||
|
HapticToken.KEYPRESS_SPACEBAR,
|
||||||
|
SoundToken.KEYPRESS_SPACEBAR,
|
||||||
|
FeedbackLevel.DEFAULT,
|
||||||
|
),
|
||||||
|
/* Played when the user touches the return key */
|
||||||
|
KEYPRESS_RETURN(HapticToken.KEYPRESS_RETURN, SoundToken.KEYPRESS_RETURN, FeedbackLevel.DEFAULT),
|
||||||
|
/* Played when the user touches the delete key */
|
||||||
|
KEYPRESS_DELETE(HapticToken.KEYPRESS_DELETE, SoundToken.KEYPRESS_DELETE, FeedbackLevel.DEFAULT),
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Level of feedback that contains a token */
|
||||||
|
enum class FeedbackLevel {
|
||||||
|
NO_FEEDBACK,
|
||||||
|
MINIMAL,
|
||||||
|
DEFAULT,
|
||||||
|
EXPRESSIVE,
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 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.google.android.msdl.data.model
|
||||||
|
|
||||||
|
/** Sound tokens from the Multi-sensory Design Language (MSDL) */
|
||||||
|
enum class SoundToken {
|
||||||
|
FAILURE_HIGH_EMPHASIS,
|
||||||
|
FAILURE,
|
||||||
|
SUCCESS,
|
||||||
|
START,
|
||||||
|
PAUSE,
|
||||||
|
STOP,
|
||||||
|
CANCEL,
|
||||||
|
SWITCH_ON,
|
||||||
|
SWITCH_OFF,
|
||||||
|
UNLOCK,
|
||||||
|
LOCK,
|
||||||
|
LONG_PRESS,
|
||||||
|
SWIPE_THRESHOLD_INDICATOR,
|
||||||
|
TAP_HIGH_EMPHASIS,
|
||||||
|
TAP_MEDIUM_EMPHASIS,
|
||||||
|
DRAG_THRESHOLD_INDICATOR_LIMIT,
|
||||||
|
DRAG_INDICATOR,
|
||||||
|
TAP_LOW_EMPHASIS,
|
||||||
|
KEYPRESS_STANDARD,
|
||||||
|
KEYPRESS_SPACEBAR,
|
||||||
|
KEYPRESS_RETURN,
|
||||||
|
KEYPRESS_DELETE,
|
||||||
|
NO_SOUND,
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 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.google.android.msdl.data.repository
|
||||||
|
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
|
import com.google.android.msdl.data.model.HapticToken
|
||||||
|
import com.google.android.msdl.data.model.SoundToken
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A repository of data for [HapticToken] and [SoundToken].
|
||||||
|
*
|
||||||
|
* The principle behind this repository is to hold the data for all tokens as a cache in memory.
|
||||||
|
* This is only suitable if the number of tokens and the data stored is manageable. The purpose of
|
||||||
|
* this design choice is to provide fast and easy access to the data when required to be played by
|
||||||
|
* UI interactions.
|
||||||
|
*/
|
||||||
|
sealed interface MSDLRepository {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the [MSDLHapticData] that corresponds to the given haptic reference token. This function
|
||||||
|
* needs to be fast since it will be called repeatedly to deliver feedback. If necessary, a
|
||||||
|
* caching strategy should be applied.
|
||||||
|
*
|
||||||
|
* @param[hapticToken] The [HapticToken] that points to the data.
|
||||||
|
* @return the data that corresponds to the token at the time this function is called.
|
||||||
|
*/
|
||||||
|
fun getHapticData(hapticToken: HapticToken): MSDLHapticData?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the [MSDLSoundData] that corresponds to the given sound reference token. This function
|
||||||
|
* needs to be fast since it will be called repeatedly to deliver feedback. If necessary, a
|
||||||
|
* caching strategy should be applied.
|
||||||
|
*
|
||||||
|
* @param[soundToken] The [SoundToken] that points to the data.
|
||||||
|
* @return the data that corresponds to the token at the time this function is called.
|
||||||
|
*/
|
||||||
|
fun getAudioData(soundToken: SoundToken): MSDLSoundData?
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
@VisibleForTesting fun createRepository(): MSDLRepository = MSDLRepositoryImpl()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Representation of data contained in a [MSDLRepository] */
|
||||||
|
fun interface MSDLHapticData {
|
||||||
|
|
||||||
|
/** Retrieve the haptic data */
|
||||||
|
fun get(): Any?
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Representation of data contained in a [MSDLRepository] */
|
||||||
|
fun interface MSDLSoundData {
|
||||||
|
|
||||||
|
/** Retrieve the sound data */
|
||||||
|
fun get(): Any?
|
||||||
|
}
|
||||||
@@ -0,0 +1,360 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 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.google.android.msdl.data.repository
|
||||||
|
|
||||||
|
import android.os.VibrationEffect
|
||||||
|
import com.google.android.msdl.data.model.HapticComposition
|
||||||
|
import com.google.android.msdl.data.model.HapticCompositionPrimitive
|
||||||
|
import com.google.android.msdl.data.model.HapticToken
|
||||||
|
import com.google.android.msdl.data.model.SoundToken
|
||||||
|
|
||||||
|
/** A [MSDLRepository] that holds haptic compositions as haptic data. */
|
||||||
|
internal class MSDLRepositoryImpl : MSDLRepository {
|
||||||
|
|
||||||
|
override fun getAudioData(soundToken: SoundToken): MSDLSoundData? {
|
||||||
|
// TODO(b/345248875) Implement a caching strategy in accordance to the audio file strategy
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getHapticData(hapticToken: HapticToken): MSDLHapticData? = HAPTIC_DATA[hapticToken]
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
// Timings and amplitudes that recreate a composition of three SPIN primitives as a waveform
|
||||||
|
private val SPIN_TIMINGS = longArrayOf(20, 20, 3, 43, 20, 20, 3)
|
||||||
|
private val SPIN_AMPLITUDES = intArrayOf(40, 80, 40, 0, 40, 80, 40)
|
||||||
|
private const val SPIN_DELAY = 56L
|
||||||
|
private const val SPIN_BREAK = 10
|
||||||
|
private val SPIN_WAVEFORM_TIMINGS =
|
||||||
|
SPIN_TIMINGS + SPIN_DELAY + SPIN_TIMINGS + SPIN_DELAY + SPIN_TIMINGS
|
||||||
|
private val SPIN_WAVEFORM_AMPLITUDES =
|
||||||
|
SPIN_AMPLITUDES + SPIN_BREAK + SPIN_AMPLITUDES + SPIN_BREAK + SPIN_AMPLITUDES
|
||||||
|
|
||||||
|
private val HAPTIC_DATA: Map<HapticToken, MSDLHapticData> =
|
||||||
|
mapOf(
|
||||||
|
HapticToken.NEGATIVE_CONFIRMATION_HIGH_EMPHASIS to
|
||||||
|
MSDLHapticData {
|
||||||
|
HapticComposition(
|
||||||
|
listOf(
|
||||||
|
HapticCompositionPrimitive(
|
||||||
|
VibrationEffect.Composition.PRIMITIVE_SPIN,
|
||||||
|
scale = 1f,
|
||||||
|
delayMillis = 0,
|
||||||
|
),
|
||||||
|
HapticCompositionPrimitive(
|
||||||
|
VibrationEffect.Composition.PRIMITIVE_SPIN,
|
||||||
|
scale = 1f,
|
||||||
|
delayMillis = SPIN_DELAY.toInt(),
|
||||||
|
),
|
||||||
|
HapticCompositionPrimitive(
|
||||||
|
VibrationEffect.Composition.PRIMITIVE_SPIN,
|
||||||
|
scale = 1f,
|
||||||
|
delayMillis = SPIN_DELAY.toInt(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
VibrationEffect.createWaveform(
|
||||||
|
SPIN_WAVEFORM_TIMINGS,
|
||||||
|
SPIN_WAVEFORM_AMPLITUDES,
|
||||||
|
-1,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
HapticToken.NEGATIVE_CONFIRMATION_MEDIUM_EMPHASIS to
|
||||||
|
MSDLHapticData {
|
||||||
|
HapticComposition(
|
||||||
|
listOf(
|
||||||
|
HapticCompositionPrimitive(
|
||||||
|
VibrationEffect.Composition.PRIMITIVE_CLICK,
|
||||||
|
scale = 1f,
|
||||||
|
delayMillis = 0,
|
||||||
|
),
|
||||||
|
HapticCompositionPrimitive(
|
||||||
|
VibrationEffect.Composition.PRIMITIVE_CLICK,
|
||||||
|
scale = 1f,
|
||||||
|
delayMillis = 114,
|
||||||
|
),
|
||||||
|
HapticCompositionPrimitive(
|
||||||
|
VibrationEffect.Composition.PRIMITIVE_CLICK,
|
||||||
|
scale = 1f,
|
||||||
|
delayMillis = 114,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
VibrationEffect.createWaveform(
|
||||||
|
longArrayOf(10, 10, 10, 114, 10, 10, 10, 114, 10, 10, 10),
|
||||||
|
intArrayOf(10, 255, 20, 0, 10, 255, 20, 0, 10, 255, 20),
|
||||||
|
-1,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
HapticToken.POSITIVE_CONFIRMATION_HIGH_EMPHASIS to
|
||||||
|
MSDLHapticData {
|
||||||
|
HapticComposition(
|
||||||
|
listOf(
|
||||||
|
HapticCompositionPrimitive(
|
||||||
|
VibrationEffect.Composition.PRIMITIVE_CLICK,
|
||||||
|
scale = 1f,
|
||||||
|
delayMillis = 0,
|
||||||
|
),
|
||||||
|
HapticCompositionPrimitive(
|
||||||
|
VibrationEffect.Composition.PRIMITIVE_CLICK,
|
||||||
|
scale = 1f,
|
||||||
|
delayMillis = 114,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
VibrationEffect.createWaveform(
|
||||||
|
longArrayOf(10, 10, 10, 114, 10, 10, 10),
|
||||||
|
intArrayOf(10, 255, 20, 0, 10, 255, 20),
|
||||||
|
-1,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
HapticToken.POSITIVE_CONFIRMATION_MEDIUM_EMPHASIS to
|
||||||
|
MSDLHapticData {
|
||||||
|
HapticComposition(
|
||||||
|
listOf(
|
||||||
|
HapticCompositionPrimitive(
|
||||||
|
VibrationEffect.Composition.PRIMITIVE_CLICK,
|
||||||
|
scale = 1f,
|
||||||
|
delayMillis = 0,
|
||||||
|
),
|
||||||
|
HapticCompositionPrimitive(
|
||||||
|
VibrationEffect.Composition.PRIMITIVE_CLICK,
|
||||||
|
scale = 1f,
|
||||||
|
delayMillis = 52,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
VibrationEffect.createWaveform(
|
||||||
|
longArrayOf(10, 10, 10, 52, 10, 10, 10),
|
||||||
|
intArrayOf(10, 255, 20, 0, 10, 255, 20),
|
||||||
|
-1,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
HapticToken.POSITIVE_CONFIRMATION_LOW_EMPHASIS to
|
||||||
|
MSDLHapticData {
|
||||||
|
HapticComposition(
|
||||||
|
listOf(
|
||||||
|
HapticCompositionPrimitive(
|
||||||
|
VibrationEffect.Composition.PRIMITIVE_TICK,
|
||||||
|
scale = 1f,
|
||||||
|
delayMillis = 0,
|
||||||
|
),
|
||||||
|
HapticCompositionPrimitive(
|
||||||
|
VibrationEffect.Composition.PRIMITIVE_CLICK,
|
||||||
|
scale = 1f,
|
||||||
|
delayMillis = 52,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
VibrationEffect.createWaveform(
|
||||||
|
longArrayOf(5, 52, 10, 10, 10),
|
||||||
|
intArrayOf(100, 0, 10, 255, 20),
|
||||||
|
-1,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
HapticToken.NEUTRAL_CONFIRMATION_HIGH_EMPHASIS to
|
||||||
|
MSDLHapticData {
|
||||||
|
HapticComposition(
|
||||||
|
listOf(
|
||||||
|
HapticCompositionPrimitive(
|
||||||
|
VibrationEffect.Composition.PRIMITIVE_THUD,
|
||||||
|
scale = 1f,
|
||||||
|
delayMillis = 0,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
VibrationEffect.createWaveform(
|
||||||
|
longArrayOf(50, 100, 100, 50),
|
||||||
|
intArrayOf(5, 50, 20, 10),
|
||||||
|
-1,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
HapticToken.NEUTRAL_CONFIRMATION_MEDIUM_EMPHASIS to
|
||||||
|
MSDLHapticData {
|
||||||
|
HapticComposition(
|
||||||
|
listOf(
|
||||||
|
HapticCompositionPrimitive(
|
||||||
|
VibrationEffect.Composition.PRIMITIVE_CLICK,
|
||||||
|
scale = 1f,
|
||||||
|
delayMillis = 0,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
HapticToken.LONG_PRESS to
|
||||||
|
MSDLHapticData {
|
||||||
|
HapticComposition(
|
||||||
|
listOf(
|
||||||
|
HapticCompositionPrimitive(
|
||||||
|
VibrationEffect.Composition.PRIMITIVE_CLICK,
|
||||||
|
scale = 1f,
|
||||||
|
delayMillis = 0,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
HapticToken.SWIPE_THRESHOLD_INDICATOR to
|
||||||
|
MSDLHapticData {
|
||||||
|
HapticComposition(
|
||||||
|
listOf(
|
||||||
|
HapticCompositionPrimitive(
|
||||||
|
VibrationEffect.Composition.PRIMITIVE_CLICK,
|
||||||
|
scale = 0.7f,
|
||||||
|
delayMillis = 0,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
HapticToken.TAP_HIGH_EMPHASIS to
|
||||||
|
MSDLHapticData {
|
||||||
|
HapticComposition(
|
||||||
|
listOf(
|
||||||
|
HapticCompositionPrimitive(
|
||||||
|
VibrationEffect.Composition.PRIMITIVE_CLICK,
|
||||||
|
scale = 0.7f,
|
||||||
|
delayMillis = 0,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
HapticToken.TAP_MEDIUM_EMPHASIS to
|
||||||
|
MSDLHapticData {
|
||||||
|
HapticComposition(
|
||||||
|
listOf(
|
||||||
|
HapticCompositionPrimitive(
|
||||||
|
VibrationEffect.Composition.PRIMITIVE_CLICK,
|
||||||
|
scale = 0.5f,
|
||||||
|
delayMillis = 0,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
HapticToken.DRAG_THRESHOLD_INDICATOR to
|
||||||
|
MSDLHapticData {
|
||||||
|
HapticComposition(
|
||||||
|
listOf(
|
||||||
|
HapticCompositionPrimitive(
|
||||||
|
VibrationEffect.Composition.PRIMITIVE_CLICK,
|
||||||
|
scale = 1f,
|
||||||
|
delayMillis = 0,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
HapticToken.DRAG_INDICATOR_CONTINUOUS to
|
||||||
|
MSDLHapticData {
|
||||||
|
HapticComposition(
|
||||||
|
List(size = 5) {
|
||||||
|
HapticCompositionPrimitive(
|
||||||
|
VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
|
||||||
|
scale = 0.3f,
|
||||||
|
delayMillis = 0,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
VibrationEffect.createWaveform(
|
||||||
|
longArrayOf(10, 20, 20, 10),
|
||||||
|
intArrayOf(10, 30, 50, 10),
|
||||||
|
-1,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
HapticToken.DRAG_INDICATOR_DISCRETE to
|
||||||
|
MSDLHapticData {
|
||||||
|
HapticComposition(
|
||||||
|
listOf(
|
||||||
|
HapticCompositionPrimitive(
|
||||||
|
VibrationEffect.Composition.PRIMITIVE_TICK,
|
||||||
|
scale = 0.5f,
|
||||||
|
delayMillis = 0,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
HapticToken.TAP_LOW_EMPHASIS to
|
||||||
|
MSDLHapticData {
|
||||||
|
HapticComposition(
|
||||||
|
listOf(
|
||||||
|
HapticCompositionPrimitive(
|
||||||
|
VibrationEffect.Composition.PRIMITIVE_CLICK,
|
||||||
|
scale = 0.3f,
|
||||||
|
delayMillis = 0,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
HapticToken.KEYPRESS_STANDARD to
|
||||||
|
MSDLHapticData {
|
||||||
|
HapticComposition(
|
||||||
|
listOf(
|
||||||
|
HapticCompositionPrimitive(
|
||||||
|
VibrationEffect.Composition.PRIMITIVE_CLICK,
|
||||||
|
scale = 0.5f,
|
||||||
|
delayMillis = 0,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
HapticToken.KEYPRESS_SPACEBAR to
|
||||||
|
MSDLHapticData {
|
||||||
|
HapticComposition(
|
||||||
|
listOf(
|
||||||
|
HapticCompositionPrimitive(
|
||||||
|
VibrationEffect.Composition.PRIMITIVE_CLICK,
|
||||||
|
scale = 0.7f,
|
||||||
|
delayMillis = 0,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
HapticToken.KEYPRESS_RETURN to
|
||||||
|
MSDLHapticData {
|
||||||
|
HapticComposition(
|
||||||
|
listOf(
|
||||||
|
HapticCompositionPrimitive(
|
||||||
|
VibrationEffect.Composition.PRIMITIVE_CLICK,
|
||||||
|
scale = 0.7f,
|
||||||
|
delayMillis = 0,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
HapticToken.KEYPRESS_DELETE to
|
||||||
|
MSDLHapticData {
|
||||||
|
HapticComposition(
|
||||||
|
listOf(
|
||||||
|
HapticCompositionPrimitive(
|
||||||
|
VibrationEffect.Composition.PRIMITIVE_CLICK,
|
||||||
|
scale = 1f,
|
||||||
|
delayMillis = 0,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 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.google.android.msdl.domain
|
||||||
|
|
||||||
|
import com.google.android.msdl.data.model.FeedbackLevel
|
||||||
|
import com.google.android.msdl.data.model.MSDLToken
|
||||||
|
import com.google.android.msdl.logging.MSDLEvent
|
||||||
|
|
||||||
|
/** An empty [MSDLPlayer] that was created without a [android.os.Vibrator] */
|
||||||
|
internal class EmptyMSDLPlayer : MSDLPlayer {
|
||||||
|
override fun getSystemFeedbackLevel(): FeedbackLevel = FeedbackLevel.NO_FEEDBACK
|
||||||
|
|
||||||
|
override fun playToken(token: MSDLToken, properties: InteractionProperties?) {}
|
||||||
|
|
||||||
|
override fun getHistory(): List<MSDLEvent> = listOf()
|
||||||
|
|
||||||
|
override fun toString(): String = "Empty MSDL player without a vibrator."
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 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.google.android.msdl.domain
|
||||||
|
|
||||||
|
import android.os.VibrationAttributes
|
||||||
|
import androidx.annotation.FloatRange
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Properties associated to an interaction that is currently occurring.
|
||||||
|
*
|
||||||
|
* The properties define parameters required to play the data associated with a
|
||||||
|
* [com.google.android.msdl.data.model.MSDLToken]. These can be dynamic, in the sense that they can
|
||||||
|
* be created as an interaction progresses throughout time.
|
||||||
|
*
|
||||||
|
* Each set of properties needs to define [VibrationAttributes] for a haptic effect to play with. If
|
||||||
|
* no properties are provided when playing a token, the effect will play with a default set of
|
||||||
|
* attributes with [VibrationAttributes.USAGE_TOUCH] usage.
|
||||||
|
*/
|
||||||
|
interface InteractionProperties {
|
||||||
|
|
||||||
|
/** [android.os.VibrationAttributes] for haptics in the interaction */
|
||||||
|
val vibrationAttributes: VibrationAttributes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Properties for a vibration that changes scale dynamically.
|
||||||
|
*
|
||||||
|
* The scale must be calculated at the time of calling the
|
||||||
|
* [com.google.android.msdl.domain.MSDLPlayer] API to play feedback. Use these properties for
|
||||||
|
* effects where vibration scales depend on temporal variables, such as position and velocity
|
||||||
|
* for slider haptics.
|
||||||
|
*
|
||||||
|
* @param[scale] The scale of the vibration at the time of calling. Must be between 0 and 1.
|
||||||
|
* @param[vibrationUsageId] Id used to create [android.os.VibrationAttributes]
|
||||||
|
*/
|
||||||
|
data class DynamicVibrationScale(
|
||||||
|
@FloatRange(from = 0.0, to = 1.0) val scale: Float,
|
||||||
|
override val vibrationAttributes: VibrationAttributes,
|
||||||
|
) : InteractionProperties
|
||||||
|
}
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 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.google.android.msdl.domain
|
||||||
|
|
||||||
|
import android.os.Vibrator
|
||||||
|
import android.util.Log
|
||||||
|
import com.google.android.msdl.data.model.FeedbackLevel
|
||||||
|
import com.google.android.msdl.data.model.HapticComposition
|
||||||
|
import com.google.android.msdl.data.model.MSDLToken
|
||||||
|
import com.google.android.msdl.data.repository.MSDLRepository
|
||||||
|
import com.google.android.msdl.data.repository.MSDLRepositoryImpl
|
||||||
|
import com.google.android.msdl.domain.MSDLPlayerImpl.Companion.REQUIRED_PRIMITIVES
|
||||||
|
import com.google.android.msdl.logging.MSDLEvent
|
||||||
|
import java.util.concurrent.Executor
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Player of MSDL feedback.
|
||||||
|
*
|
||||||
|
* This player is central API to deliver audio and haptic feedback bundled and referenced by
|
||||||
|
* instances of [MSDLToken].
|
||||||
|
*/
|
||||||
|
interface MSDLPlayer {
|
||||||
|
|
||||||
|
// Current feedback level set in the system
|
||||||
|
fun getSystemFeedbackLevel(): FeedbackLevel
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play a [MSDLToken].
|
||||||
|
*
|
||||||
|
* @param[token] The [MSDLToken] to play. This will be used to fetch its corresponding haptic
|
||||||
|
* and sound data.
|
||||||
|
* @param[properties] [InteractionProperties] associated with the token requested to play. These
|
||||||
|
* properties can modify how a token plays (e.g.,
|
||||||
|
* [InteractionProperties.DynamicVibrationScale] for slider haptics in the
|
||||||
|
* [MSDLToken.DRAG_INDICATOR] token) and can be supplied if custom
|
||||||
|
* [android.os.VibrationAttributes] are required for haptic playback. If no properties are
|
||||||
|
* supplied, haptic feedback will play using USAGE_TOUCH [android.os.VibrationAttributes].
|
||||||
|
*/
|
||||||
|
fun playToken(token: MSDLToken, properties: InteractionProperties? = null)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the history of recent [MSDLEvent]s. The list can be useful to include in loggers and
|
||||||
|
* system dumps for debugging purposes.
|
||||||
|
*/
|
||||||
|
fun getHistory(): List<MSDLEvent>
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
// TODO(b/355230334): remove once we have a system setting for the level
|
||||||
|
var SYSTEM_FEEDBACK_LEVEL = FeedbackLevel.DEFAULT
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new [MSDLPlayer].
|
||||||
|
*
|
||||||
|
* @param[vibrator] The [Vibrator] this player will use for haptic playback.
|
||||||
|
* @param[executor] An [Executor] to schedule haptic playback.
|
||||||
|
* @param[useHapticFeedbackForToken] A map that determines if a haptic fallback effect
|
||||||
|
* should be used to play haptics for a given [MSDLToken]. If null, the map will be
|
||||||
|
* created using the support information from the given vibrator.
|
||||||
|
*/
|
||||||
|
fun createPlayer(
|
||||||
|
vibrator: Vibrator?,
|
||||||
|
executor: Executor = Executors.newSingleThreadExecutor(),
|
||||||
|
useHapticFeedbackForToken: Map<MSDLToken, Boolean>? = null,
|
||||||
|
): MSDLPlayer {
|
||||||
|
// Return an empty player if no vibrator is available
|
||||||
|
if (vibrator == null) {
|
||||||
|
Log.w(
|
||||||
|
"MSDLPlayer",
|
||||||
|
"A null vibrator was used to create a MSDLPlayer. An empty player was created",
|
||||||
|
)
|
||||||
|
return EmptyMSDLPlayer()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create repository
|
||||||
|
val repository = MSDLRepositoryImpl()
|
||||||
|
|
||||||
|
// Determine the support for haptic primitives to know if fallbacks will be used.
|
||||||
|
// This can be provided by the client. If omitted, it will be determined from the
|
||||||
|
// supported primitives of the given vibrator.
|
||||||
|
val shouldUseFallbackForToken =
|
||||||
|
useHapticFeedbackForToken ?: createHapticFallbackDecisionMap(vibrator, repository)
|
||||||
|
|
||||||
|
return MSDLPlayerImpl(repository, vibrator, executor, shouldUseFallbackForToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createHapticFallbackDecisionMap(
|
||||||
|
vibrator: Vibrator,
|
||||||
|
repository: MSDLRepository,
|
||||||
|
): Map<MSDLToken, Boolean> {
|
||||||
|
val supportedPrimitives =
|
||||||
|
REQUIRED_PRIMITIVES.associateWith { vibrator.arePrimitivesSupported(it).first() }
|
||||||
|
return MSDLToken.entries.associateWith { token ->
|
||||||
|
// For each token, determine if the haptic data from the repository
|
||||||
|
// should use the fallback effect.
|
||||||
|
val hapticComposition =
|
||||||
|
repository.getHapticData(token.hapticToken)?.get() as? HapticComposition
|
||||||
|
hapticComposition?.shouldPlayFallback(supportedPrimitives) ?: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun HapticComposition.shouldPlayFallback(supportedPrimitives: Map<Int, Boolean>): Boolean {
|
||||||
|
primitives.forEach { primitive ->
|
||||||
|
val isSupported = supportedPrimitives[primitive.primitiveId]
|
||||||
|
if (isSupported == null || isSupported == false) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 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.google.android.msdl.domain
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.VibrationAttributes
|
||||||
|
import android.os.VibrationEffect
|
||||||
|
import android.os.Vibrator
|
||||||
|
import com.google.android.msdl.data.model.FeedbackLevel
|
||||||
|
import com.google.android.msdl.data.model.HapticComposition
|
||||||
|
import com.google.android.msdl.data.model.MSDLToken
|
||||||
|
import com.google.android.msdl.data.repository.MSDLRepository
|
||||||
|
import com.google.android.msdl.logging.MSDLEvent
|
||||||
|
import com.google.android.msdl.logging.MSDLHistoryLogger
|
||||||
|
import com.google.android.msdl.logging.MSDLHistoryLoggerImpl
|
||||||
|
import java.util.concurrent.Executor
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of the MSDLPlayer.
|
||||||
|
*
|
||||||
|
* At the core, the player is in charge of delivering haptic and audio feedback closely in time.
|
||||||
|
*
|
||||||
|
* @param[repository] Repository to retrieve audio and haptic data.
|
||||||
|
* @param[executor] An [Executor] used to schedule haptic playback.
|
||||||
|
* @param[vibrator] Instance of the default [Vibrator] on the device.
|
||||||
|
* @param[useHapticFallbackForToken] A map that determines if the haptic fallback effect should be
|
||||||
|
* used for a given token.
|
||||||
|
*/
|
||||||
|
internal class MSDLPlayerImpl(
|
||||||
|
private val repository: MSDLRepository,
|
||||||
|
private val vibrator: Vibrator,
|
||||||
|
private val executor: Executor,
|
||||||
|
private val useHapticFallbackForToken: Map<MSDLToken, Boolean?>,
|
||||||
|
) : MSDLPlayer {
|
||||||
|
|
||||||
|
/** A logger to keep a history of playback events */
|
||||||
|
private val historyLogger = MSDLHistoryLoggerImpl(MSDLHistoryLogger.HISTORY_SIZE)
|
||||||
|
|
||||||
|
// TODO(b/355230334): This should be retrieved from the system Settings
|
||||||
|
override fun getSystemFeedbackLevel(): FeedbackLevel = MSDLPlayer.SYSTEM_FEEDBACK_LEVEL
|
||||||
|
|
||||||
|
override fun playToken(token: MSDLToken, properties: InteractionProperties?) {
|
||||||
|
// Don't play the data for the token if the current feedback level is below the minimal
|
||||||
|
// level of the token
|
||||||
|
if (getSystemFeedbackLevel() < token.minimumFeedbackLevel) return
|
||||||
|
|
||||||
|
// Play the data for the token with the given properties
|
||||||
|
playData(token, properties)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun playData(token: MSDLToken, properties: InteractionProperties?) {
|
||||||
|
// Gather the data from the repositories
|
||||||
|
val hapticData = repository.getHapticData(token.hapticToken)
|
||||||
|
val soundData = repository.getAudioData(token.soundToken)
|
||||||
|
|
||||||
|
// Nothing to play
|
||||||
|
if (hapticData == null && soundData == null) return
|
||||||
|
|
||||||
|
if (soundData == null) {
|
||||||
|
// Play haptics only
|
||||||
|
// 1. Create the effect
|
||||||
|
val composition: HapticComposition? = hapticData?.get() as? HapticComposition
|
||||||
|
val effect =
|
||||||
|
if (useHapticFallbackForToken[token] == true) {
|
||||||
|
composition?.fallbackEffect
|
||||||
|
} else {
|
||||||
|
when (properties) {
|
||||||
|
is InteractionProperties.DynamicVibrationScale -> {
|
||||||
|
composition?.composeIntoVibrationEffect(
|
||||||
|
scaleOverride = properties.scale
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> composition?.composeIntoVibrationEffect() // compose as-is
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Deliver the haptics with or without attributes
|
||||||
|
if (effect == null || !vibrator.hasVibrator()) return
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
val attributes =
|
||||||
|
if (properties?.vibrationAttributes != null) {
|
||||||
|
properties.vibrationAttributes
|
||||||
|
} else {
|
||||||
|
VibrationAttributes.Builder()
|
||||||
|
.setUsage(VibrationAttributes.USAGE_TOUCH)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
executor.execute { vibrator.vibrate(effect, attributes) }
|
||||||
|
} else {
|
||||||
|
executor.execute { vibrator.vibrate(effect) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Log the event
|
||||||
|
historyLogger.addEvent(MSDLEvent(token, properties))
|
||||||
|
} else {
|
||||||
|
// TODO(b/345248875): Play audio and haptics
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getHistory(): List<MSDLEvent> = historyLogger.getHistory()
|
||||||
|
|
||||||
|
override fun toString(): String =
|
||||||
|
"""
|
||||||
|
Default MSDL player implementation.
|
||||||
|
Vibrator: $vibrator
|
||||||
|
Repository: $repository
|
||||||
|
"""
|
||||||
|
.trimIndent()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val REQUIRED_PRIMITIVES =
|
||||||
|
listOf(
|
||||||
|
VibrationEffect.Composition.PRIMITIVE_SPIN,
|
||||||
|
VibrationEffect.Composition.PRIMITIVE_THUD,
|
||||||
|
VibrationEffect.Composition.PRIMITIVE_TICK,
|
||||||
|
VibrationEffect.Composition.PRIMITIVE_CLICK,
|
||||||
|
VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun HapticComposition.composeIntoVibrationEffect(
|
||||||
|
scaleOverride: Float? = null,
|
||||||
|
delayOverride: Int? = null,
|
||||||
|
): VibrationEffect? {
|
||||||
|
val effectComposition = VibrationEffect.startComposition()
|
||||||
|
primitives.forEach { primitive ->
|
||||||
|
effectComposition.addPrimitive(
|
||||||
|
primitive.primitiveId,
|
||||||
|
scaleOverride ?: primitive.scale,
|
||||||
|
delayOverride ?: primitive.delayMillis,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return effectComposition.compose()
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 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.google.android.msdl.logging
|
||||||
|
|
||||||
|
import com.google.android.msdl.data.model.MSDLToken
|
||||||
|
import com.google.android.msdl.domain.InteractionProperties
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A summary that represents a MSDL event. The event summarizes the delivery of playback from a
|
||||||
|
* [MSDLToken] along with optional [InteractionProperties].
|
||||||
|
*
|
||||||
|
* @param[tokenName] The name of the [MSDLToken] played.
|
||||||
|
* @param[properties] The text representation of [InteractionProperties] used to play the token.
|
||||||
|
* @param[timeStamp] A formatted time stamp for when the event occurred. The format for this time
|
||||||
|
* stamp is [MSDLHistoryLogger.DATE_FORMAT]
|
||||||
|
*/
|
||||||
|
data class MSDLEvent(val tokenName: String, val properties: String?, val timeStamp: String) {
|
||||||
|
constructor(
|
||||||
|
token: MSDLToken,
|
||||||
|
properties: InteractionProperties?,
|
||||||
|
) : this(
|
||||||
|
token.name,
|
||||||
|
properties?.toString(),
|
||||||
|
MSDLHistoryLogger.DATE_FORMAT.format(System.currentTimeMillis()),
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun toString(): String = "$timeStamp | token: $tokenName | properties: $properties"
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 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.google.android.msdl.logging
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
/** A logging component to keep track of recent [MSDLEvent]s */
|
||||||
|
interface MSDLHistoryLogger {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an event to the history.
|
||||||
|
*
|
||||||
|
* @param[event] The event to log.
|
||||||
|
*/
|
||||||
|
fun addEvent(event: MSDLEvent)
|
||||||
|
|
||||||
|
/** Get the history of latest events. */
|
||||||
|
fun getHistory(): List<MSDLEvent>
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
const val HISTORY_SIZE = 20
|
||||||
|
val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 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.google.android.msdl.logging
|
||||||
|
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
|
import androidx.annotation.VisibleForTesting.Companion.PACKAGE_PRIVATE
|
||||||
|
import java.util.ArrayDeque
|
||||||
|
import java.util.Deque
|
||||||
|
|
||||||
|
@VisibleForTesting(otherwise = PACKAGE_PRIVATE)
|
||||||
|
class MSDLHistoryLoggerImpl(private val maxHistorySize: Int) : MSDLHistoryLogger {
|
||||||
|
|
||||||
|
// Use an [ArrayDequeue] with a fixed size as the history structure
|
||||||
|
private val history: Deque<MSDLEvent> = ArrayDeque(maxHistorySize)
|
||||||
|
|
||||||
|
override fun addEvent(event: MSDLEvent) {
|
||||||
|
// Keep the history as a FIFO structure
|
||||||
|
if (history.size == maxHistorySize) {
|
||||||
|
history.removeFirst()
|
||||||
|
}
|
||||||
|
history.addLast(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getHistory(): List<MSDLEvent> = history.toList()
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
~ Copyright (C) 2024 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.google.android.msdl.tests">
|
||||||
|
|
||||||
|
<instrumentation
|
||||||
|
android:name="android.testing.TestableInstrumentation"
|
||||||
|
android:label="Tests for the public MSDL Lib"
|
||||||
|
android:targetPackage="com.google.android.msdl.tests"/>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
|
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 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.google.android.msdl.data.repository
|
||||||
|
|
||||||
|
import com.google.android.msdl.data.model.HapticComposition
|
||||||
|
import com.google.android.msdl.data.model.HapticToken
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.junit.runners.JUnit4
|
||||||
|
|
||||||
|
@RunWith(JUnit4::class)
|
||||||
|
class MSDLRepositoryImplTest {
|
||||||
|
|
||||||
|
private val repository = MSDLRepository.createRepository()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun getHapticData_forAllHapticTokens_returnsCompositions() {
|
||||||
|
var validCompositions = 0
|
||||||
|
HapticToken.entries.forEach { token ->
|
||||||
|
if (repository.getHapticData(token)?.get() is HapticComposition) {
|
||||||
|
validCompositions++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertThat(validCompositions).isEqualTo(HapticToken.entries.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 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.google.android.msdl.domain
|
||||||
|
|
||||||
|
import android.os.VibrationAttributes
|
||||||
|
import android.os.VibrationEffect
|
||||||
|
import android.os.Vibrator
|
||||||
|
|
||||||
|
class FakeVibrator : Vibrator() {
|
||||||
|
var hasAmplitudeControl = true
|
||||||
|
var hasVibrator = true
|
||||||
|
var latestVibration: VibrationEffect? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
val supportedPrimitives =
|
||||||
|
mutableMapOf(
|
||||||
|
VibrationEffect.Composition.PRIMITIVE_CLICK to true,
|
||||||
|
VibrationEffect.Composition.PRIMITIVE_SPIN to true,
|
||||||
|
VibrationEffect.Composition.PRIMITIVE_THUD to true,
|
||||||
|
VibrationEffect.Composition.PRIMITIVE_TICK to true,
|
||||||
|
VibrationEffect.Composition.PRIMITIVE_LOW_TICK to true,
|
||||||
|
VibrationEffect.Composition.PRIMITIVE_QUICK_FALL to true,
|
||||||
|
VibrationEffect.Composition.PRIMITIVE_QUICK_RISE to true,
|
||||||
|
VibrationEffect.Composition.PRIMITIVE_SLOW_RISE to true,
|
||||||
|
)
|
||||||
|
|
||||||
|
var latestAttributes: VibrationAttributes? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun setSupportForAllPrimitives(supported: Boolean) =
|
||||||
|
supportedPrimitives.replaceAll { _, _ -> supported }
|
||||||
|
|
||||||
|
override fun cancel() {}
|
||||||
|
|
||||||
|
override fun cancel(usageFilter: Int) {}
|
||||||
|
|
||||||
|
override fun hasAmplitudeControl(): Boolean = this.hasAmplitudeControl
|
||||||
|
|
||||||
|
override fun hasVibrator(): Boolean = this.hasVibrator
|
||||||
|
|
||||||
|
override fun arePrimitivesSupported(vararg primitiveIds: Int): BooleanArray =
|
||||||
|
primitiveIds.map { id -> supportedPrimitives[id] ?: false }.toBooleanArray()
|
||||||
|
|
||||||
|
override fun vibrate(
|
||||||
|
uid: Int,
|
||||||
|
opPkg: String,
|
||||||
|
vibe: VibrationEffect,
|
||||||
|
reason: String,
|
||||||
|
attributes: VibrationAttributes,
|
||||||
|
) {
|
||||||
|
latestVibration = vibe
|
||||||
|
latestAttributes = attributes
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun vibrate(
|
||||||
|
vibe: VibrationEffect,
|
||||||
|
attributes: VibrationAttributes,
|
||||||
|
) {
|
||||||
|
latestVibration = vibe
|
||||||
|
latestAttributes = attributes
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 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.google.android.msdl.domain
|
||||||
|
|
||||||
|
import android.os.VibrationAttributes
|
||||||
|
import android.os.VibrationEffect
|
||||||
|
import com.google.android.msdl.data.model.FeedbackLevel
|
||||||
|
import com.google.android.msdl.data.model.HapticComposition
|
||||||
|
import com.google.android.msdl.data.model.MSDLToken
|
||||||
|
import com.google.android.msdl.data.repository.MSDLRepository
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import java.util.concurrent.Executor
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.junit.runners.Parameterized
|
||||||
|
|
||||||
|
@RunWith(Parameterized::class)
|
||||||
|
class MSDLPlayerImplTest {
|
||||||
|
|
||||||
|
@Parameterized.Parameter lateinit var token: MSDLToken
|
||||||
|
|
||||||
|
private val repository = MSDLRepository.createRepository()
|
||||||
|
private val vibrator = FakeVibrator()
|
||||||
|
private val executor = Executor { it.run() }
|
||||||
|
private val useHapticFallbackForToken = MSDLToken.entries.associateWith { false }.toMutableMap()
|
||||||
|
|
||||||
|
private var msdlPlayer = MSDLPlayer.createPlayer(vibrator, executor, useHapticFallbackForToken)
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
MSDLPlayer.SYSTEM_FEEDBACK_LEVEL = FeedbackLevel.EXPRESSIVE
|
||||||
|
vibrator.setSupportForAllPrimitives(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun playToken_withExpressiveSetting_deliversAllFeedback() {
|
||||||
|
// GIVEN that the feedback level is expressive (all tokens play)
|
||||||
|
MSDLPlayer.SYSTEM_FEEDBACK_LEVEL = FeedbackLevel.EXPRESSIVE
|
||||||
|
val composition = repository.getHapticData(token.hapticToken)?.get() as? HapticComposition
|
||||||
|
val effect = composition?.composeIntoVibrationEffect()
|
||||||
|
|
||||||
|
// WHEN the token plays
|
||||||
|
msdlPlayer.playToken(token)
|
||||||
|
|
||||||
|
// THEN the vibration is delivered
|
||||||
|
assertThat(vibrator.latestVibration).isEqualTo(effect)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun playToken_withoutFeedbackSetting_doesNotDeliverAnyFeedback() {
|
||||||
|
// GIVEN that the feedback level specifies no feedback
|
||||||
|
MSDLPlayer.SYSTEM_FEEDBACK_LEVEL = FeedbackLevel.NO_FEEDBACK
|
||||||
|
|
||||||
|
// WHEN the token plays
|
||||||
|
msdlPlayer.playToken(token)
|
||||||
|
|
||||||
|
// THEN no vibration is delivered
|
||||||
|
assertThat(vibrator.latestVibration).isNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun playHapticComposition_withNullProperties_playsExpectedVibrationEffect() {
|
||||||
|
// GIVEN the vibration effect of a composition
|
||||||
|
val composition = repository.getHapticData(token.hapticToken)?.get() as? HapticComposition
|
||||||
|
val effect = composition?.composeIntoVibrationEffect()
|
||||||
|
|
||||||
|
// WHEN the composition is played for a token without interaction properties
|
||||||
|
msdlPlayer.playToken(token)
|
||||||
|
|
||||||
|
// THEN the vibration delivers the same vibration effect with USAGE_TOUCH vibration
|
||||||
|
// attributes and the correct token reason.
|
||||||
|
val touchAttributes =
|
||||||
|
VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_TOUCH).build()
|
||||||
|
assertVibrationEffectDelivered(effect, touchAttributes)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun playHapticComposition_withoutSupportedPrimitives_playsFallbackEffects() {
|
||||||
|
// GIVEN that no primitives are supported
|
||||||
|
useHapticFallbackForToken.replaceAll { _, _ -> true }
|
||||||
|
|
||||||
|
// GIVEN the fallback effect of a composition
|
||||||
|
val composition = repository.getHapticData(token.hapticToken)?.get() as? HapticComposition
|
||||||
|
val effect = composition?.fallbackEffect
|
||||||
|
|
||||||
|
// WHEN the composition is played for a token without interaction properties
|
||||||
|
msdlPlayer.playToken(token)
|
||||||
|
|
||||||
|
// THEN the vibration delivers the same fallback effect with USAGE_TOUCH vibration
|
||||||
|
val touchAttributes =
|
||||||
|
VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_TOUCH).build()
|
||||||
|
assertVibrationEffectDelivered(effect, touchAttributes)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun playHapticComposition_withDynamicVibrationScaleProperties_playsExpectedVibrationEffect() {
|
||||||
|
// GIVEN DynamicVibrationScaleProperties and a vibration effect built with this scale
|
||||||
|
val scaleOverride = 0.4f
|
||||||
|
val composition = repository.getHapticData(token.hapticToken)?.get() as? HapticComposition
|
||||||
|
val effect = composition?.composeIntoVibrationEffect(scaleOverride)
|
||||||
|
val attributes =
|
||||||
|
VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_ALARM).build()
|
||||||
|
val properties = InteractionProperties.DynamicVibrationScale(scaleOverride, attributes)
|
||||||
|
|
||||||
|
// WHEN the composition is played for the token with the properties
|
||||||
|
msdlPlayer.playToken(token, properties)
|
||||||
|
|
||||||
|
// THEN the vibration effect delivered is the same vibration effect with the properties
|
||||||
|
assertVibrationEffectDelivered(effect, attributes)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun playHapticComposition_withoutHardwareVibrator_doesNotPlayVibrationEffect() {
|
||||||
|
// GIVEN the vibration effect of a composition when there is no hardware vibrator
|
||||||
|
vibrator.hasVibrator = false
|
||||||
|
|
||||||
|
// WHEN the composition is played for a token
|
||||||
|
msdlPlayer.playToken(token)
|
||||||
|
|
||||||
|
// THEN the vibration does not deliver any effect
|
||||||
|
assertThat(vibrator.latestVibration).isNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assertVibrationEffectDelivered(
|
||||||
|
effect: VibrationEffect?,
|
||||||
|
attributes: VibrationAttributes,
|
||||||
|
) {
|
||||||
|
assertThat(vibrator.latestVibration).isEqualTo(effect)
|
||||||
|
if (effect != null) {
|
||||||
|
assertThat(vibrator.latestAttributes).isEqualTo(attributes)
|
||||||
|
} else {
|
||||||
|
assertThat(vibrator.latestAttributes).isNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic @Parameterized.Parameters fun tokens() = MSDLToken.entries
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 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.google.android.msdl.logging
|
||||||
|
|
||||||
|
import android.os.VibrationAttributes
|
||||||
|
import com.google.android.msdl.data.model.MSDLToken
|
||||||
|
import com.google.android.msdl.domain.InteractionProperties
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import kotlin.random.Random
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.junit.runners.JUnit4
|
||||||
|
|
||||||
|
@RunWith(JUnit4::class)
|
||||||
|
class MSDLHistoryLoggerImplTest {
|
||||||
|
|
||||||
|
private val properties =
|
||||||
|
object : InteractionProperties {
|
||||||
|
override val vibrationAttributes: VibrationAttributes =
|
||||||
|
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK)
|
||||||
|
}
|
||||||
|
private val random = Random(0)
|
||||||
|
|
||||||
|
private val logger = MSDLHistoryLoggerImpl(MSDLHistoryLogger.HISTORY_SIZE)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun addEvent_eventIsAddedToHistory() {
|
||||||
|
val token = getRandomToken()
|
||||||
|
val event = MSDLEvent(token, properties)
|
||||||
|
logger.addEvent(event)
|
||||||
|
|
||||||
|
assertThat(logger.getHistory().containsEvent(event)).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun addEvent_beyondLimit_keepsHistorySize() {
|
||||||
|
val tokens = getRandomTokens(MSDLHistoryLogger.HISTORY_SIZE + 10)
|
||||||
|
tokens.forEach { logger.addEvent(MSDLEvent(it, properties)) }
|
||||||
|
|
||||||
|
assertThat(logger.getHistory().size).isEqualTo(MSDLHistoryLogger.HISTORY_SIZE)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun addEvent_beyondLimit_keepsLatestEvents() {
|
||||||
|
val localHistory = arrayListOf<MSDLEvent>()
|
||||||
|
val tokens = getRandomTokens(MSDLHistoryLogger.HISTORY_SIZE + 10)
|
||||||
|
var event: MSDLEvent
|
||||||
|
tokens.forEach {
|
||||||
|
event = MSDLEvent(it, properties)
|
||||||
|
logger.addEvent(event)
|
||||||
|
localHistory.add(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
val latestLocalHistory = localHistory.takeLast(MSDLHistoryLogger.HISTORY_SIZE)
|
||||||
|
val loggerHistory = logger.getHistory()
|
||||||
|
assertThat(latestLocalHistory.isEqualTo(loggerHistory)).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getRandomToken(): MSDLToken =
|
||||||
|
MSDLToken.entries[random.nextInt(0, MSDLToken.entries.size)]
|
||||||
|
|
||||||
|
private fun getRandomTokens(n: Int): List<MSDLToken> = List(n) { getRandomToken() }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the list is equal to another by making sure it has the same elements in the same
|
||||||
|
* order. Events are compared by their token name and interaction properties.
|
||||||
|
*
|
||||||
|
* @param[other] The other list to compare to.
|
||||||
|
*/
|
||||||
|
private fun List<MSDLEvent>.isEqualTo(other: List<MSDLEvent>): Boolean {
|
||||||
|
assert(other.size == this.size) { "Both lists must be of the same size" }
|
||||||
|
this.forEachIndexed { i, event ->
|
||||||
|
if (event.tokenName != other[i].tokenName || event.properties != other[i].properties) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the list contains an event. Events are compared by their token name and interaction
|
||||||
|
* properties.
|
||||||
|
*
|
||||||
|
* @param[other] The event to find.
|
||||||
|
*/
|
||||||
|
private fun List<MSDLEvent>.containsEvent(other: MSDLEvent): Boolean {
|
||||||
|
this.forEach { event ->
|
||||||
|
if (event.tokenName == other.tokenName && event.properties == other.properties) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -474,15 +474,15 @@
|
|||||||
<dimen name="bubblebar_overflow_inset">16dp</dimen>
|
<dimen name="bubblebar_overflow_inset">16dp</dimen>
|
||||||
<dimen name="bubblebar_icon_spacing">6dp</dimen>
|
<dimen name="bubblebar_icon_spacing">6dp</dimen>
|
||||||
<dimen name="bubblebar_icon_spacing_large">8dp</dimen>
|
<dimen name="bubblebar_icon_spacing_large">8dp</dimen>
|
||||||
<dimen name="bubblebar_icon_spacing_persistent_taskbar">@dimen/bubblebar_icon_spacing</dimen>
|
<dimen name="bubblebar_icon_spacing_persistent_taskbar">6dp</dimen>
|
||||||
<dimen name="bubblebar_expanded_icon_spacing">12dp</dimen>
|
<dimen name="bubblebar_expanded_icon_spacing">12dp</dimen>
|
||||||
<dimen name="bubblebar_icon_elevation">1dp</dimen>
|
<dimen name="bubblebar_icon_elevation">1dp</dimen>
|
||||||
<dimen name="bubblebar_transient_taskbar_min_distance">12dp</dimen>
|
<dimen name="bubblebar_transient_taskbar_min_distance">12dp</dimen>
|
||||||
|
|
||||||
<!-- Bubble bar dismiss view -->
|
<!-- Bubble bar dismiss view -->
|
||||||
<dimen name="bubblebar_dismiss_target_size">@dimen/floating_dismiss_background_size</dimen>
|
<dimen name="bubblebar_dismiss_target_size">96dp</dimen>
|
||||||
<dimen name="bubblebar_dismiss_target_small_size">60dp</dimen>
|
<dimen name="bubblebar_dismiss_target_small_size">60dp</dimen>
|
||||||
<dimen name="bubblebar_dismiss_target_icon_size">@dimen/floating_dismiss_icon_size</dimen>
|
<dimen name="bubblebar_dismiss_target_icon_size">32dp</dimen>
|
||||||
<dimen name="bubblebar_dismiss_target_bottom_margin">50dp</dimen>
|
<dimen name="bubblebar_dismiss_target_bottom_margin">50dp</dimen>
|
||||||
<dimen name="bubblebar_dismiss_floating_gradient_height">548dp</dimen>
|
<dimen name="bubblebar_dismiss_floating_gradient_height">548dp</dimen>
|
||||||
<dimen name="bubblebar_dismiss_zone_width">192dp</dimen>
|
<dimen name="bubblebar_dismiss_zone_width">192dp</dimen>
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
include ':IconLoader'
|
include ':IconLoader'
|
||||||
project(':IconLoader').projectDir = new File(rootDir, 'iconloaderlib')
|
project(':IconLoader').projectDir = new File(rootDir, 'iconloaderlib')
|
||||||
|
include ':Shared'
|
||||||
|
project(':Shared').projectDir = new File(rootDir, 'shared')
|
||||||
|
include ':msdllib'
|
||||||
|
project(':msdllib').projectDir = new File(rootDir, 'msdllib')
|
||||||
|
|
||||||
//include ':SharedLibWrapper'
|
//include ':SharedLibWrapper'
|
||||||
//project(':SharedLibWrapper').projectDir = new File(rootDir, 'SharedLibWrapper')
|
//project(':SharedLibWrapper').projectDir = new File(rootDir, 'SharedLibWrapper')
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
plugins {
|
||||||
|
id("com.android.library")
|
||||||
|
id("kotlin-android")
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.android.launcher3.testing.shared"
|
||||||
|
|
||||||
|
compileSdk = 36
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdk = 26
|
||||||
|
targetSdk = 36
|
||||||
|
|
||||||
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
vectorDrawables.useSupportLibrary = true
|
||||||
|
}
|
||||||
|
sourceSets {
|
||||||
|
named("main") {
|
||||||
|
java.setSrcDirs(listOf("src"))
|
||||||
|
manifest.srcFile("AndroidManifest.xml")
|
||||||
|
res.setSrcDirs(listOf("res"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
kotlin {
|
||||||
|
jvmToolchain(21)//1.8(8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation("androidx.core:core:1.16.0")
|
||||||
|
implementation("androidx.annotation:annotation:1.9.1")
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package android.appwidget.flags
|
||||||
|
|
||||||
|
object Flags {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun generatedPreviews(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package android.multiuser
|
||||||
|
|
||||||
|
object Flags {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enableMovingContentIntoPrivateSpace(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.android.systemui.shared
|
||||||
|
|
||||||
|
object Flags {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun newCustomizationPickerUi(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.android.window.flags
|
||||||
|
|
||||||
|
object Flags {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun predictiveBackThreeButtonNav(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enableTaskbarConnectedDisplays(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enableDesktopWindowingMode(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enableDesktopWindowingWallpaperActivity(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package com.android.wm.shell
|
||||||
|
|
||||||
|
object Flags {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enableRetrievableBubbles(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enableTaskbarNavbarUnification(): Boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enableGridOnlyOverview(): Boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enableTaskbarOnPhones(): Boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enableGsf(): Boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enableBubbleBar(): Boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enableBubbleBarOnPhones(): Boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun enableTinyTaskbar(): Boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in new issue
Block a user