├── .arcconfig ├── .arclint ├── .editorconfig ├── .gitignore ├── .local.dependencies.template ├── .travis.yml ├── AUTHORS ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── build.gradle ├── checkstyle.xml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── install-local-dependency.sh ├── library ├── build.gradle ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── google │ │ └── android │ │ └── material │ │ └── motion │ │ └── runtime │ │ ├── ChoreographerCompat.java │ │ ├── LogcatTracer.java │ │ ├── MotionRuntime.java │ │ ├── NamedPerformer.java │ │ ├── NamedPlan.java │ │ ├── Performer.java │ │ ├── PerformerFeatures.java │ │ ├── Plan.java │ │ ├── PlanFeatures.java │ │ ├── TargetScope.java │ │ ├── Tracing.java │ │ ├── package-info.java │ │ └── testing │ │ └── StepChoreographer.java │ └── test │ └── java │ └── com │ └── google │ └── android │ └── material │ └── motion │ └── runtime │ ├── ChoreographerCompatTests.java │ ├── ComposablePlanTests.java │ ├── LegacyChoreographerCompatTests.java │ ├── LogcatTracerTests.java │ ├── MotionRuntimeTests.java │ ├── PerformerFeaturesTests.java │ ├── PlanFeaturesTests.java │ ├── PlanTests.java │ ├── RealChoreographerCompatTests.java │ ├── TargetScopeTests.java │ ├── plans │ ├── CounterAlteringPlan.java │ ├── NoOpPlan.java │ └── TextViewAlteringNamedPlan.java │ ├── targets │ └── IncrementerTarget.java │ └── testing │ └── StepChoreographerTests.java ├── local-dependency-substitution.gradle ├── sample ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── google │ │ └── android │ │ └── material │ │ └── motion │ │ └── runtime │ │ └── sample │ │ └── MainActivity.java │ └── res │ ├── layout │ └── main_activity.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ └── values │ ├── strings.xml │ └── styles.xml └── settings.gradle /.arcconfig: -------------------------------------------------------------------------------- 1 | { 2 | "load": [ 3 | "material-arc-tools/third_party/arc-hook-conphig", 4 | "material-arc-tools/third_party/arc-hook-github-issues", 5 | "material-arc-tools/third_party/arc-proselint" 6 | ], 7 | "arcanist_configuration": "HookConphig", 8 | "repository.callsign": "MDMRUNTIMEANDROID", 9 | "phabricator.uri": "http://codereview.cc/", 10 | "arc.land.onto.default": "develop", 11 | "arc.feature.start.default": "origin/develop" 12 | } 13 | -------------------------------------------------------------------------------- /.arclint: -------------------------------------------------------------------------------- 1 | { 2 | "linters": { 3 | "chmod": { 4 | "type": "chmod" 5 | }, 6 | "text": { 7 | "type": "text", 8 | "include": "(\\.(java|xml)$)", 9 | "exclude": [], 10 | "severity": { 11 | "3": "disabled", 12 | "5": "disabled" 13 | } 14 | }, 15 | "prose": { 16 | "type": "prose", 17 | "include": "(\\.(md)$)", 18 | "exclude": [ 19 | "(^CHANGELOG.md)" 20 | ], 21 | "severity": { 22 | "consistency.spacing": "disabled", 23 | "typography.symbols.curly_quotes": "disabled", 24 | "typography.symbols.ellipsis": "disabled", 25 | "leonard.exclamation.30ppm": "disabled", 26 | "misc.annotations": "warning" 27 | } 28 | }, 29 | "spelling": { 30 | "type": "spelling", 31 | "include": "(\\.(md)$)" 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | trim_trailing_whitespace = true 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | # Built application files 4 | *.apk 5 | *.ap_ 6 | 7 | # Files for the ART/Dalvik VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | out/ 17 | 18 | # Gradle files 19 | .gradle/ 20 | build/ 21 | 22 | # Local configuration file (sdk path, etc) 23 | local.properties 24 | local.dependencies 25 | 26 | # Proguard folder generated by Eclipse 27 | proguard/ 28 | 29 | # Log Files 30 | *.log 31 | 32 | # Android Studio Navigation editor temp files 33 | .navigation/ 34 | 35 | # Android Studio captures folder 36 | captures/ 37 | 38 | # Intellij 39 | *.iml 40 | .idea/ 41 | 42 | # Keystore files 43 | *.jks 44 | -------------------------------------------------------------------------------- /.local.dependencies.template: -------------------------------------------------------------------------------- 1 | # List local dependencies here to be applied to all projects in this library. 2 | # 3 | # ██████╗ ██╗ ███████╗ █████╗ ███████╗███████╗ ██████╗ ███████╗ █████╗ ██████╗ 4 | # ██╔══██╗██║ ██╔════╝██╔══██╗██╔════╝██╔════╝ ██╔══██╗██╔════╝██╔══██╗██╔══██╗ 5 | # ██████╔╝██║ █████╗ ███████║███████╗█████╗ ██████╔╝█████╗ ███████║██║ ██║ 6 | # ██╔═══╝ ██║ ██╔══╝ ██╔══██║╚════██║██╔══╝ ██╔══██╗██╔══╝ ██╔══██║██║ ██║ 7 | # ██║ ███████╗███████╗██║ ██║███████║███████╗ ██║ ██║███████╗██║ ██║██████╔╝ 8 | # ╚═╝ ╚══════╝╚══════╝╚═╝ ╚═╝╚══════╝╚══════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═════╝ 9 | # 10 | # Format: 11 | # 12 | # [group]:[name] 13 | # 14 | # Example: 15 | # 16 | # com.github.material-motion:runtime-android 17 | # 18 | # These are dependencies defined in your build.gradle for which you would like to reflect any local 19 | # changes. This is useful if you would like to develop multiple libraries in tandem. 20 | # 21 | # You must `Sync Project with Gradle Files` every time you add or remove a local dependency. 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: android 3 | jdk: oraclejdk8 4 | env: 5 | matrix: 6 | - ANDROID_TARGET=android-25 7 | 8 | android: 9 | components: 10 | - platform-tools 11 | - tools 12 | - android-25 13 | - build-tools-25.0.0 14 | 15 | licenses: 16 | - 'android-sdk-license-.+' 17 | 18 | before_install: 19 | - echo yes | android update sdk --filter extra-android-m2repository --no-ui --force > /dev/null 20 | 21 | script: 22 | - ./gradlew check 23 | 24 | after_success: 25 | - bash <(curl -s https://codecov.io/bash) 26 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the list of Material Motion Android Runtime authors for copyright purposes. 2 | # 3 | # This does not necessarily list everyone who has contributed code, since in 4 | # some cases, their employer may be the copyright holder. To see the full list 5 | # of contributors, see the revision history with git log. 6 | 7 | Google Inc. 8 | and other contributors 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 6.0.1 2 | 3 | ## Source changes 4 | 5 | * [Make #initialize() visible for other modules testing code.](https://github.com/material-motion/runtime-android/commit/7cd56c4a382f41de435d60f4f2e02cf1a18dd732) (Mark Wei) 6 | 7 | ## API changes 8 | 9 | Auto-generated by running: 10 | 11 | apidiff origin/stable release-candidate android library 12 | 13 | ## Performer 14 | 15 | *new* final method: `void initialize(T)` 16 | 17 | 18 | 19 | ## Non-source changes 20 | 21 | * [Automatic changelog preparation for release.](https://github.com/material-motion/runtime-android/commit/c65312b5391bc31fb23cfe89bc1087baddef861c) (Mark Wei) 22 | 23 | 24 | # 6.0.0 25 | 26 | ## Breaking changes 27 | 28 | * Performer.getTarget() now returns Object rather than implicit cast. 29 | * Deprecated Runtime removed. 30 | 31 | ## New deprecations 32 | 33 | ## New features 34 | 35 | * Plans and Performers are now type safe. Generics are used to define the type of target a plan and performer can act on. Motion library authors should audit their plans and performers to enable generics. 36 | 37 | ## Source changes 38 | 39 | * [Workaround possible mockito bug. We're doing some crazy spy stuff so I'm not surprised.](https://github.com/material-motion/runtime-android/commit/c7a044743d6e7d4f9e6d07ceacada4e1af8c2485) (Mark Wei) 40 | * [Remove deprecated Runtime.](https://github.com/material-motion/runtime-android/commit/530d03ab67cffabe745927d9f981447d371d1213) (Mark Wei) 41 | * [Audit all plans and performers generics for type-safety.](https://github.com/material-motion/runtime-android/commit/b77e284b3e2d099eef8e27c54470c90117722644) (Mark Wei) 42 | * [Type type-safe plans and performers.](https://github.com/material-motion/runtime-android/commit/129ac48a078f035cde9d35dc14630d3dcc3aabb4) (Mark Wei) 43 | * [Remove BasePlan and BasePerforming.](https://github.com/material-motion/runtime-android/commit/131744781459bf914f198b5f7b57b9b6715c4987) (Mark Wei) 44 | * [Test deprecated Runtime.](https://github.com/material-motion/runtime-android/commit/37b024b271d65146ff3d84786e4c087347729afb) (Mark Wei) 45 | * [Make NamedPlan and NamedPerformer abstract subclasses of Plan and Performer.](https://github.com/material-motion/runtime-android/commit/84b7dff4fb9e284b22e02b263edb36d769afcb08) (Mark Wei) 46 | 47 | ## API changes 48 | 49 | Auto-generated by running: 50 | 51 | apidiff origin/stable release-candidate android library 52 | 53 | ## LogcatTracer 54 | 55 | *new* method: `void onAddNamedPlan(NamedPlan, String, Object)` 56 | 57 | *removed* method: `void onAddNamedPlan(NamedPlan, String, Object)` 58 | 59 | 60 | ## MotionRuntime 61 | 62 | *new* method: ` void addNamedPlan(NamedPlan, String, T)` 63 | 64 | *new* method: ` void addPlan(Plan, T)` 65 | 66 | *new* method: ` void removeNamedPlan(String, T)` 67 | 68 | *removed* method: `void addNamedPlan(NamedPlan, String, Object)` 69 | 70 | *removed* method: `void addPlan(Plan, Object)` 71 | 72 | *removed* method: `void removeNamedPlan(String, Object)` 73 | 74 | 75 | ## NamedPerformer 76 | 77 | *new* abstract class: `NamedPerformer` 78 | 79 | *new* constructor: `NamedPerformer()` 80 | 81 | *new* abstract method: `void addPlan(NamedPlan, String)` 82 | 83 | *new* abstract method: `void removePlan(String)` 84 | 85 | 86 | ## NamedPlan 87 | 88 | *new* abstract class: `NamedPlan` 89 | 90 | *new* constructor: `NamedPlan()` 91 | 92 | *new* method: `Object clone()` 93 | 94 | *new* abstract method: `Class> getPerformerClass()` 95 | 96 | 97 | ## Performer 98 | 99 | *removed* abstract class: `Performer` 100 | 101 | *removed* constructor: `Performer()` 102 | 103 | *removed* final method: ` T getTarget()` 104 | 105 | *removed* final method: `void initialize(Object)` 106 | 107 | 108 | ## PerformerInstantiationException 109 | 110 | *new* constructor: `PerformerInstantiationException(Class, Exception)` 111 | 112 | *removed* constructor: `PerformerInstantiationException(Class, Exception)` 113 | 114 | 115 | ## Performer 116 | 117 | *new* abstract class: `Performer` 118 | 119 | *new* constructor: `Performer()` 120 | 121 | *new* final method: ` Type getTarget()` 122 | 123 | 124 | ## BasePerforming 125 | 126 | *removed* interface: `BasePerforming` 127 | 128 | *removed* method: ` T getTarget()` 129 | 130 | *removed* method: `void addPlan(BasePlan)` 131 | 132 | *removed* method: `void initialize(Object)` 133 | 134 | 135 | ## ComposablePerforming 136 | 137 | *removed* interface: `ComposablePerforming` 138 | 139 | *removed* method: `void setPlanEmitter(PlanEmitter)` 140 | 141 | 142 | ## PlanEmitter 143 | 144 | *removed* interface: `PlanEmitter` 145 | 146 | *removed* method: `void emit(Plan)` 147 | 148 | 149 | ## PlanEmitter 150 | 151 | *new* interface: `PlanEmitter` 152 | 153 | *new* method: `void emit(Plan)` 154 | 155 | 156 | ## ComposablePerforming 157 | 158 | *new* interface: `ComposablePerforming` 159 | 160 | *new* method: `void setPlanEmitter(PlanEmitter)` 161 | 162 | 163 | ## ContinuousPerforming 164 | 165 | *modified* interface: `ContinuousPerforming` 166 | 167 | | From: | public interface ContinuousPerforming extends BasePerforming | 168 | | To: | public interface ContinuousPerforming | 169 | 170 | 171 | ## ManualPerforming 172 | 173 | *modified* interface: `ManualPerforming` 174 | 175 | | From: | public interface ManualPerforming extends BasePerforming | 176 | | To: | public interface ManualPerforming | 177 | 178 | 179 | ## NamedPlanPerforming 180 | 181 | *removed* interface: `NamedPlanPerforming` 182 | 183 | *removed* method: `void addPlan(NamedPlan, String)` 184 | 185 | *removed* method: `void removePlan(String)` 186 | 187 | 188 | ## Plan 189 | 190 | *removed* abstract class: `Plan` 191 | 192 | *removed* constructor: `Plan()` 193 | 194 | *removed* method: `BasePlan clone()` 195 | 196 | 197 | ## Plan 198 | 199 | *new* abstract class: `Plan` 200 | 201 | *new* constructor: `Plan()` 202 | 203 | *new* method: `Object clone()` 204 | 205 | 206 | ## BasePlan 207 | 208 | *removed* interface: `BasePlan` 209 | 210 | *removed* method: `BasePlan clone()` 211 | 212 | *removed* method: `Class getPerformerClass()` 213 | 214 | 215 | ## NamedPlan 216 | 217 | *removed* interface: `NamedPlan` 218 | 219 | *removed* method: `Class getPerformerClass()` 220 | 221 | 222 | ## SerializablePlan 223 | 224 | *modified* interface: `SerializablePlan` 225 | 226 | | From: | public interface SerializablePlan extends BasePlan | 227 | | To: | public interface SerializablePlan | 228 | 229 | 230 | ## Runtime 231 | 232 | *removed* class: `Runtime` 233 | 234 | *removed* constructor: `Runtime()` 235 | 236 | 237 | ## Tracing 238 | 239 | *new* method: ` void onAddNamedPlan(NamedPlan, String, T)` 240 | 241 | *new* method: ` void onAddPlan(Plan, T)` 242 | 243 | *new* method: ` void onCreatePerformer(Performer, T)` 244 | 245 | *new* method: ` void onRemoveNamedPlan(String, T)` 246 | 247 | *removed* method: `void onAddNamedPlan(NamedPlan, String, Object)` 248 | 249 | *removed* method: `void onAddPlan(Plan, Object)` 250 | 251 | *removed* method: `void onCreatePerformer(Performer, Object)` 252 | 253 | *removed* method: `void onRemoveNamedPlan(String, Object)` 254 | 255 | 256 | 257 | ## Non-source changes 258 | 259 | * [Automatic changelog preparation for release.](https://github.com/material-motion/runtime-android/commit/58991003cc97b77b88129bdcf27b9483a5f955c8) (Mark Wei) 260 | 261 | 262 | # 5.1.0 263 | 264 | ## New deprecations 265 | 266 | Runtime is renamed to MotionRuntime. 267 | 268 | ## New features 269 | 270 | New tests. 100% coverage! 271 | 272 | ## Source changes 273 | 274 | * [Rename Runtime to MotionRuntime.](https://github.com/material-motion/runtime-android/commit/cb68e90d28a1aa774a342c3ecf6b9e784910d4a2) (Mark Wei) 275 | * [Tests for ChoreographerCompat.](https://github.com/material-motion/runtime-android/commit/9118cd94cc42ce5489109a7b136b7bc198754c08) (Mark Wei) 276 | * [Move StepChoreographer into testing package.](https://github.com/material-motion/runtime-android/commit/27e38378a07d28fd447ee328301ceb315746925c) (Mark Wei) 277 | * [Tests for PlanFeatures and PerformerFeatures.](https://github.com/material-motion/runtime-android/commit/61cfc28c2283d35d7c841a78d159b90ec7a73435) (Mark Wei) 278 | * [Tests for Plan's CloneNotSupportedException.](https://github.com/material-motion/runtime-android/commit/ab70cea168f43819f3c5f6b4e519fe9461e03ccd) (Mark Wei) 279 | 280 | ## API changes 281 | 282 | Auto-generated by running: 283 | 284 | apidiff origin/stable release-candidate android library 285 | 286 | ## ChoreographerCompat 287 | 288 | *new* abstract class: `ChoreographerCompat` 289 | 290 | *new* constructor: `ChoreographerCompat()` 291 | 292 | *new* static method: `ChoreographerCompat getInstance()` 293 | 294 | *new* abstract method: `void postFrameCallback(FrameCallback)` 295 | 296 | *new* abstract method: `void postFrameCallbackDelayed(FrameCallback, long)` 297 | 298 | *new* abstract method: `void removeFrameCallback(FrameCallback)` 299 | 300 | 301 | ## MotionRuntime 302 | 303 | *new* class: `MotionRuntime` 304 | 305 | *new* constructor: `MotionRuntime()` 306 | 307 | *new* static final field: `int ACTIVE` 308 | 309 | *new* static final field: `int IDLE` 310 | 311 | *new* method: `void addNamedPlan(NamedPlan, String, Object)` 312 | 313 | *new* method: `void addPlan(Plan, Object)` 314 | 315 | *new* method: `void addStateListener(StateListener)` 316 | 317 | *new* method: `void addTracer(Tracing)` 318 | 319 | *new* method: `int getState()` 320 | 321 | *new* method: `void removeNamedPlan(String, Object)` 322 | 323 | *new* method: `void removeStateListener(StateListener)` 324 | 325 | *new* method: `void removeTracer(Tracing)` 326 | 327 | 328 | ## State 329 | 330 | *new* annotation: `@State` 331 | 332 | 333 | ## StateListener 334 | 335 | *new* interface: `StateListener` 336 | 337 | *new* method: `void onStateChange(MotionRuntime, int)` 338 | 339 | 340 | ## Runtime 341 | 342 | *removed* static final field: `int ACTIVE` 343 | 344 | *removed* static final field: `int IDLE` 345 | 346 | *removed* method: `void addNamedPlan(NamedPlan, String, Object)` 347 | 348 | *removed* method: `void addPlan(Plan, Object)` 349 | 350 | *removed* method: `void addStateListener(StateListener)` 351 | 352 | *removed* method: `void addTracer(Tracing)` 353 | 354 | *removed* method: `int getState()` 355 | 356 | *removed* method: `void removeNamedPlan(String, Object)` 357 | 358 | *removed* method: `void removeStateListener(StateListener)` 359 | 360 | *removed* method: `void removeTracer(Tracing)` 361 | 362 | *modified* final class: `Runtime` 363 | 364 | | From: | public final class Runtime | 365 | | To: | public class Runtime extends MotionRuntime | 366 | 367 | 368 | ## State 369 | 370 | *removed* annotation: `@State` 371 | 372 | 373 | ## StateListener 374 | 375 | *removed* interface: `StateListener` 376 | 377 | *removed* method: `void onStateChange(Runtime, int)` 378 | 379 | 380 | ## StepChoreographer 381 | 382 | *new* class: `StepChoreographer` 383 | 384 | *new* constructor: `StepChoreographer()` 385 | 386 | *new* static final field: `long FRAME_MS` 387 | 388 | *new* method: `void advance(long)` 389 | 390 | *new* method: `void postFrameCallback(FrameCallback)` 391 | 392 | *new* method: `void postFrameCallbackDelayed(FrameCallback, long)` 393 | 394 | *new* method: `void removeFrameCallback(FrameCallback)` 395 | 396 | 397 | 398 | ## Non-source changes 399 | 400 | * [Automatic changelog preparation for release.](https://github.com/material-motion/runtime-android/commit/bfa267ed32de5fa7d6e3b37d56e9f07acb42490e) (Mark Wei) 401 | * [Run mdm new repo .](https://github.com/material-motion/runtime-android/commit/a7f02e16d8eb03a0bb70e79ef7fc9f985ea1cdd5) (Mark Wei) 402 | * [Update README.md](https://github.com/material-motion/runtime-android/commit/233dd95f9eea0ab8372a52dddb950e116f1b2b32) (Mark Wei) 403 | * [Add callsign.](https://github.com/material-motion/runtime-android/commit/b3bc31c31f537247cc1139f32eb198bfb31dd1ff) (Mark Wei) 404 | 405 | 406 | # 5.0.0 407 | 408 | ## New features 409 | 410 | * Tracing API and LogcatTracer implementation. Thanks to seanoshea. 411 | * Removed deprecated Scheduler. 412 | * Additional test coverage. 413 | 414 | ## Source changes 415 | 416 | * [Tests for runtime.](https://github.com/material-motion/runtime-android/commit/3fd4758807d85fd86bf159ba1b5b458d5c0f7339) (Mark Wei) 417 | * [Adding a logcat tracer. Fixing some issues from initial tracer implementation too.](https://github.com/material-motion/runtime-android/commit/6096f3a51c3379c6bcd1a7d1bc4d52533a1c054c) (seanoshea) 418 | * [Initial tracing implementation.](https://github.com/material-motion/runtime-android/commit/4e89ead26181386ab5178b4ee983d525bc1b820e) (seanoshea) 419 | * [TargetScope tests.](https://github.com/material-motion/runtime-android/commit/aae792b9d82c6cf652bed7ed7466468c94a53abb) (Mark Wei) 420 | * [Do not check nullability for non-null final field runtime.](https://github.com/material-motion/runtime-android/commit/8a5b61c977fd6203a556996bab8c96c7b9b58f94) (Mark Wei) 421 | * [Remove deprecated scheduler.](https://github.com/material-motion/runtime-android/commit/815451285231795191570b12aca8840f3a7a68b9) (Mark Wei) 422 | * [Only create PlanEmitter once per Performer.](https://github.com/material-motion/runtime-android/commit/4fedbbf2aae4b2c0511d9b79a1543d65033d2360) (Mark Wei) 423 | 424 | ## API changes 425 | 426 | Auto-generated by running: 427 | 428 | apidiff origin/stable release-candidate android library 429 | 430 | ## LogcatTracer 431 | 432 | *new* class: `LogcatTracer` 433 | 434 | *new* constructor: `LogcatTracer()` 435 | 436 | *new* method: `void onAddNamedPlan(NamedPlan, String, Object)` 437 | 438 | *new* method: `void onAddPlan(Plan, Object)` 439 | 440 | *new* method: `void onCreatePerformer(Performer, Object)` 441 | 442 | *new* method: `void onRemoveNamedPlan(String, Object)` 443 | 444 | 445 | ## Runtime 446 | 447 | *new* method: `void addTracer(Tracing)` 448 | 449 | *new* method: `void removeTracer(Tracing)` 450 | 451 | 452 | ## Scheduler 453 | 454 | *removed* final class: `Scheduler` 455 | 456 | *removed* constructor: `Scheduler()` 457 | 458 | *removed* static final field: `int ACTIVE` 459 | 460 | *removed* static final field: `int IDLE` 461 | 462 | *removed* method: `void addNamedPlan(NamedPlan, String, Object)` 463 | 464 | *removed* method: `void addPlan(Plan, Object)` 465 | 466 | *removed* method: `void addStateListener(StateListener)` 467 | 468 | *removed* method: `int getState()` 469 | 470 | *removed* method: `void removeNamedPlan(String, Object)` 471 | 472 | *removed* method: `void removeStateListener(StateListener)` 473 | 474 | 475 | ## State 476 | 477 | *removed* annotation: `@State` 478 | 479 | 480 | ## StateListener 481 | 482 | *removed* interface: `StateListener` 483 | 484 | *removed* method: `void onStateChange(Scheduler, int)` 485 | 486 | 487 | ## Tracing 488 | 489 | *new* interface: `Tracing` 490 | 491 | *new* method: `void onAddNamedPlan(NamedPlan, String, Object)` 492 | 493 | *new* method: `void onAddPlan(Plan, Object)` 494 | 495 | *new* method: `void onCreatePerformer(Performer, Object)` 496 | 497 | *new* method: `void onRemoveNamedPlan(String, Object)` 498 | 499 | 500 | 501 | ## Non-source changes 502 | 503 | * [Automatic changelog preparation for release.](https://github.com/material-motion/runtime-android/commit/e143aec0516ea449c7fb60ff874f90097b2dc50d) (Mark Wei) 504 | * [Update README.md](https://github.com/material-motion/runtime-android/commit/015ccdfa3f81f0376d34aea92cbd8e91e928bf40) (Mark Wei) 505 | * [Add code snippet to 'getting started' part of README](https://github.com/material-motion/runtime-android/commit/ca728d7f63d066fc45e18dfae1ca3cb8a73a38bd) (Mark Wei) 506 | * [Use jitpack.io javadoc](https://github.com/material-motion/runtime-android/commit/971dedd54044244f1e52051cfe57040238a82004) (Mark Wei) 507 | 508 | 509 | # 4.0.0 510 | 511 | ## New features 512 | 513 | * Breaking change release. 514 | * NamedPlans support. Thanks to seanoshea. 515 | 516 | ## Source changes 517 | 518 | * [Rename Scheduler to Runtime.](https://github.com/material-motion/material-motion-runtime-android/commit/83c52d3cf57131ea5579418ac8a772e65602eb0f) (Mark Wei) 519 | * [Rename interfaces to -Performing](https://github.com/material-motion/material-motion-runtime-android/commit/e352c015aa402b0631eae8701d1d3c67cb4b60b6) (Mark Wei) 520 | * [Remove deprecated Transaction and PlanPerformance.](https://github.com/material-motion/material-motion-runtime-android/commit/b1869651a18c60253cd889cda9c5112d6d049ffb) (Mark Wei) 521 | * [Fix TargetScope.commitAddNamedPlan().](https://github.com/material-motion/material-motion-runtime-android/commit/63143a06bf9f0b65acb56a0190f200cea456d020) (Mark Wei) 522 | * [Implement PlanEmitter api.](https://github.com/material-motion/material-motion-runtime-android/commit/061a4ae37022a4793b3c69e15b5e8b3eb19b7f6c) (Mark Wei) 523 | * [Restructure Plan/Performer types.](https://github.com/material-motion/material-motion-runtime-android/commit/0016d939b066416ab95f77c268f85bbfd53dbccd) (Mark Wei) 524 | * [Auto-format.](https://github.com/material-motion/material-motion-runtime-android/commit/821da4ed566d4bf4744d0da9bdf4f901f2f6988c) (Mark Wei) 525 | * [Adding and Removing Named Plans. Deprecating Transaction and adding plans directly to the Scheduler. (#37)](https://github.com/material-motion/material-motion-runtime-android/commit/4544360b75a5e0157a5ee9876447d45165ff0270) (Sean O'Shea) 526 | 527 | ## API changes 528 | 529 | Auto-generated by running: 530 | 531 | apidiff 7d8ad1151b654ab4c6dae87e23173dfe23a39dd7 a04da0662a8f08213ab87e309462db3ccc0fa8db android library 532 | 533 | ## Performer 534 | 535 | *new* final method: ` T getTarget()` 536 | 537 | *modified* abstract class: `Performer` 538 | 539 | | From: | public abstract class Performer | 540 | | To: | public abstract class Performer implements BasePerforming | 541 | 542 | 543 | ## ComposablePerformance 544 | 545 | *removed* interface: `ComposablePerformance` 546 | 547 | *removed* method: `void setTransactionEmitter(TransactionEmitter)` 548 | 549 | 550 | ## TransactionEmitter 551 | 552 | *removed* interface: `TransactionEmitter` 553 | 554 | *removed* method: `void emit(Transaction)` 555 | 556 | 557 | ## ContinuousPerformance 558 | 559 | *removed* interface: `ContinuousPerformance` 560 | 561 | *removed* method: `void setIsActiveTokenGenerator(IsActiveTokenGenerator)` 562 | 563 | 564 | ## IsActiveToken 565 | 566 | *removed* interface: `IsActiveToken` 567 | 568 | *removed* method: `void terminate()` 569 | 570 | 571 | ## IsActiveTokenGenerator 572 | 573 | *removed* interface: `IsActiveTokenGenerator` 574 | 575 | *removed* method: `IsActiveToken generate()` 576 | 577 | 578 | ## ManualPerformance 579 | 580 | *removed* interface: `ManualPerformance` 581 | 582 | *removed* method: `int update(float)` 583 | 584 | 585 | ## PerformerInstantiationException 586 | 587 | *new* constructor: `PerformerInstantiationException(Class, Exception)` 588 | 589 | *removed* constructor: `PerformerInstantiationException(Class, Exception)` 590 | 591 | 592 | ## PlanPerformance 593 | 594 | *removed* interface: `PlanPerformance` 595 | 596 | *removed* method: `void addPlan(Plan)` 597 | 598 | 599 | ## PerformerFeatures 600 | 601 | *new* final class: `PerformerFeatures` 602 | 603 | 604 | ## BasePerforming 605 | 606 | *new* interface: `BasePerforming` 607 | 608 | *new* method: ` T getTarget()` 609 | 610 | *new* method: `void addPlan(BasePlan)` 611 | 612 | *new* method: `void initialize(Object)` 613 | 614 | 615 | ## ComposablePerforming 616 | 617 | *new* interface: `ComposablePerforming` 618 | 619 | *new* method: `void setPlanEmitter(PlanEmitter)` 620 | 621 | 622 | ## PlanEmitter 623 | 624 | *new* interface: `PlanEmitter` 625 | 626 | *new* method: `void emit(Plan)` 627 | 628 | 629 | ## ContinuousPerforming 630 | 631 | *new* interface: `ContinuousPerforming` 632 | 633 | *new* method: `void setIsActiveTokenGenerator(IsActiveTokenGenerator)` 634 | 635 | 636 | ## IsActiveToken 637 | 638 | *new* interface: `IsActiveToken` 639 | 640 | *new* method: `void terminate()` 641 | 642 | 643 | ## IsActiveTokenGenerator 644 | 645 | *new* interface: `IsActiveTokenGenerator` 646 | 647 | *new* method: `IsActiveToken generate()` 648 | 649 | 650 | ## ManualPerforming 651 | 652 | *new* interface: `ManualPerforming` 653 | 654 | *new* method: `int update(float)` 655 | 656 | 657 | ## NamedPlanPerforming 658 | 659 | *new* interface: `NamedPlanPerforming` 660 | 661 | *new* method: `void addPlan(NamedPlan, String)` 662 | 663 | *new* method: `void removePlan(String)` 664 | 665 | 666 | ## Plan 667 | 668 | *removed* abstract method: `Class getPerformerClass()` 669 | 670 | *modified* abstract class: `Plan` 671 | 672 | | From: | public abstract class Plan implements Cloneable | 673 | | To: | public abstract class Plan implements BasePlan | 674 | 675 | *modified* method: `Object clone()` 676 | 677 | | From: | public Object clone() throws CloneNotSupportedException | 678 | | To: | public BasePlan clone() | 679 | 680 | 681 | ## SerializablePlan 682 | 683 | *removed* interface: `SerializablePlan` 684 | 685 | *removed* method: `void fromJson(JsonReader)` 686 | 687 | *removed* method: `void toJson(JsonWriter)` 688 | 689 | 690 | ## PlanFeatures 691 | 692 | *new* final class: `PlanFeatures` 693 | 694 | 695 | ## BasePlan 696 | 697 | *new* interface: `BasePlan` 698 | 699 | *new* method: `BasePlan clone()` 700 | 701 | *new* method: `Class getPerformerClass()` 702 | 703 | 704 | ## NamedPlan 705 | 706 | *new* interface: `NamedPlan` 707 | 708 | *new* method: `Class getPerformerClass()` 709 | 710 | 711 | ## SerializablePlan 712 | 713 | *new* interface: `SerializablePlan` 714 | 715 | *new* method: `void fromJson(JsonReader)` 716 | 717 | *new* method: `void toJson(JsonWriter)` 718 | 719 | 720 | ## Runtime 721 | 722 | *new* final class: `Runtime` 723 | 724 | *new* constructor: `Runtime()` 725 | 726 | *new* static final field: `int ACTIVE` 727 | 728 | *new* static final field: `int IDLE` 729 | 730 | *new* method: `void addNamedPlan(NamedPlan, String, Object)` 731 | 732 | *new* method: `void addPlan(Plan, Object)` 733 | 734 | *new* method: `void addStateListener(StateListener)` 735 | 736 | *new* method: `int getState()` 737 | 738 | *new* method: `void removeNamedPlan(String, Object)` 739 | 740 | *new* method: `void removeStateListener(StateListener)` 741 | 742 | 743 | ## State 744 | 745 | *new* annotation: `@State` 746 | 747 | 748 | ## StateListener 749 | 750 | *new* interface: `StateListener` 751 | 752 | *new* method: `void onStateChange(Runtime, int)` 753 | 754 | 755 | ## Scheduler 756 | 757 | *new* method: `void addNamedPlan(NamedPlan, String, Object)` 758 | 759 | *new* method: `void removeNamedPlan(String, Object)` 760 | 761 | *removed* method: `void commitTransaction(Transaction)` 762 | 763 | 764 | ## Transaction 765 | 766 | *removed* final class: `Transaction` 767 | 768 | *removed* constructor: `Transaction()` 769 | 770 | *removed* method: `void addNamedPlan(Plan, String, Object)` 771 | 772 | *removed* method: `void addPlan(Plan, Object)` 773 | 774 | *removed* method: `void removeNamedPlan(String, Object)` 775 | 776 | 777 | 778 | ## Non-source changes 779 | 780 | * [changelog](https://github.com/material-motion/material-motion-runtime-android/commit/a04da0662a8f08213ab87e309462db3ccc0fa8db) (Mark Wei) 781 | * [Automatic changelog preparation for release.](https://github.com/material-motion/material-motion-runtime-android/commit/114d715e07855d004484e0f126a91bd9c1b35378) (Mark Wei) 782 | * [Upgrade build chain.](https://github.com/material-motion/material-motion-runtime-android/commit/070af4b9f7babd06c8446852d6de09ac09eaebfe) (Mark Wei) 783 | * [Add runtime guides to README.](https://github.com/material-motion/material-motion-runtime-android/commit/932521cf70d2342e67adc9aa499b49fb14df5454) (Mark Wei) 784 | * [Run mdm new repo .](https://github.com/material-motion/material-motion-runtime-android/commit/fa7251b4067660da3ce47057e0b97e6a2690acd0) (Mark Wei) 785 | 786 | 787 | # 3.0.0 788 | 789 | ## New features 790 | 791 | * Deprecated `PlanPerformance`. Add Scheduler#addPlan. 792 | * Removed deprecated `DelegatedPerformance`. 793 | 794 | ## Source changes 795 | 796 | * [Move integration tests to unit tests.](https://github.com/material-motion/material-motion-runtime-android/commit/945416a9038c4ddc95f0d84049a06b3e5cbb04c0) (Mark Wei) 797 | * [Run mdm new repo .](https://github.com/material-motion/material-motion-runtime-android/commit/9c61c41c1d00a0dc4b81fbdce07655c338bd481d) (Mark Wei) 798 | * [Delete pre-2.0.0 deprecated APIs.](https://github.com/material-motion/material-motion-runtime-android/commit/6cae081ac9f73435235320ba1c0ebc2270fd0fa4) (Mark Wei) 799 | * [Deprecate PlanPerformance, add Performer#addPlan().](https://github.com/material-motion/material-motion-runtime-android/commit/bb4c5ee2f430456ad917b0f2b8c20516a0024b93) (Mark Wei) 800 | * [Adding plans directly to the scheduler (#36)](https://github.com/material-motion/material-motion-runtime-android/commit/78b22e3f51a0c9f9bb443cfd462727f16dd8804d) (Sean O'Shea) 801 | 802 | ## API changes 803 | 804 | Auto-generated by running: 805 | 806 | apidiff 93e81b724b31c9457434d5713f8169e593075c94 bd012d88a929ecee0402c666749526dc30eba82f android library 807 | 808 | ## DelegatedPerformance 809 | 810 | *removed* interface: `DelegatedPerformance` 811 | 812 | *removed* method: `void setDelegatedPerformanceCallback(DelegatedPerformanceTokenCallback)` 813 | 814 | 815 | ## DelegatedPerformanceToken 816 | 817 | *removed* final class: `DelegatedPerformanceToken` 818 | 819 | *removed* constructor: `DelegatedPerformanceToken()` 820 | 821 | 822 | ## DelegatedPerformanceTokenCallback 823 | 824 | *removed* interface: `DelegatedPerformanceTokenCallback` 825 | 826 | *removed* method: `void onDelegatedPerformanceEnd(DelegatedPerformance, DelegatedPerformanceToken)` 827 | 828 | *removed* method: `DelegatedPerformanceToken onDelegatedPerformanceStart(DelegatedPerformance)` 829 | 830 | 831 | ## Scheduler 832 | 833 | *new* method: `void addPlan(Plan, Object)` 834 | 835 | 836 | 837 | ## Non-source changes 838 | 839 | * [Automatic changelog preparation for release.](https://github.com/material-motion/material-motion-runtime-android/commit/bd012d88a929ecee0402c666749526dc30eba82f) (Mark Wei) 840 | 841 | # 2.0.0 842 | 843 | ## New features 844 | 845 | * Composable performing. Performers can now emit plans. 846 | * Continuous performing. These APIs were previously named Delegated performing. 847 | * Deprecated `DelegatedPerformance`. 848 | 849 | ## Breaking changes 850 | 851 | * Removed previously deprecated non-tokenized symbols in `DelegatedPerformance`. 852 | 853 | ## Source changes 854 | 855 | * [Remove deprecated transact() api from ComposablePerformance.](https://github.com/material-motion/material-motion-runtime-android/commit/21a557acb9adfca11214ae62fb468bb1bf6f2b35) (Mark Wei) 856 | * [Implement ContinuousPerformance APIs for android.](https://github.com/material-motion/material-motion-runtime-android/commit/f69add7b0fc5fe7ad0356204c169708e57ff1dd8) (Mark Wei) 857 | * [Reword](https://github.com/material-motion/material-motion-runtime-android/commit/db00b111a7e7940a235e93a4f787c8348589abf2) (Mark Wei) 858 | * [Implement the TransactionEmitter API for performers](https://github.com/material-motion/material-motion-runtime-android/commit/3de27b7246440983f717eb2aef89b1399a068758) (Mark Wei) 859 | * [Per issue #21 - Trying this out for a composable test. (#28)](https://github.com/material-motion/material-motion-runtime-android/commit/83494dd4a7f1b037e691d53a18176343463909e8) (Sean O'Shea) 860 | * [DelegatedPerformance tests (#27)](https://github.com/material-motion/material-motion-runtime-android/commit/944146d5a41e615786384b5a11d929398bd99b61) (Sean O'Shea) 861 | * [Scheduler Tests (#25)](https://github.com/material-motion/material-motion-runtime-android/commit/b98b2c34822ca6dee42cd90cffe8400345e1fccd) (Sean O'Shea) 862 | * [public Work api](https://github.com/material-motion/material-motion-runtime-android/commit/28593c1180b3da039ce9d7faf0d262c4e0facf70) (Mark Wei) 863 | * [Add ComposablePerformance to runtime.](https://github.com/material-motion/material-motion-runtime-android/commit/57aca95294d22e63db08778f5d10f5f443af660d) (Mark Wei) 864 | * [Add onInitialize() hook.](https://github.com/material-motion/material-motion-runtime-android/commit/b620aea84896a164930c3b7656355929130f4fbc) (Mark Wei) 865 | 866 | ## API changes 867 | 868 | Auto-generated by running: 869 | 870 | apidiff 48920f6b8046f3a9afba6c120f6f7bdc60d22e60 35788de8c9f2773f3540530fda06ad1011e06205 android library 871 | 872 | ## ComposablePerformance 873 | 874 | *new* interface: `ComposablePerformance` 875 | 876 | *new* method: `void setTransactionEmitter(TransactionEmitter)` 877 | 878 | 879 | ## TransactionEmitter 880 | 881 | *new* interface: `TransactionEmitter` 882 | 883 | *new* method: `void emit(Transaction)` 884 | 885 | 886 | ## ContinuousPerformance 887 | 888 | *new* interface: `ContinuousPerformance` 889 | 890 | *new* method: `void setIsActiveTokenGenerator(IsActiveTokenGenerator)` 891 | 892 | 893 | ## IsActiveToken 894 | 895 | *new* interface: `IsActiveToken` 896 | 897 | *new* method: `void terminate()` 898 | 899 | 900 | ## IsActiveTokenGenerator 901 | 902 | *new* interface: `IsActiveTokenGenerator` 903 | 904 | *new* method: `IsActiveToken generate()` 905 | 906 | 907 | ## DelegatedPerformance 908 | 909 | *removed* method: `void setDelegatedPerformanceCallback(DelegatedPerformanceCallback)` 910 | 911 | 912 | ## DelegatedPerformanceCallback 913 | 914 | *removed* interface: `DelegatedPerformanceCallback` 915 | 916 | *removed* method: `void onDelegatedPerformanceEnd(DelegatedPerformance, String)` 917 | 918 | *removed* method: `void onDelegatedPerformanceStart(DelegatedPerformance, String)` 919 | 920 | 921 | 922 | ## Non-source changes 923 | 924 | * [Automatic changelog preparation for release.](https://github.com/material-motion/material-motion-runtime-android/commit/35788de8c9f2773f3540530fda06ad1011e06205) (Mark Wei) 925 | * [Revert "Poking travis CI."](https://github.com/material-motion/material-motion-runtime-android/commit/baa0f9528c14ca85a568805c6cad2b9b0fa855b9) (Jeff Verkoeyen) 926 | * [Poking travis CI.](https://github.com/material-motion/material-motion-runtime-android/commit/89be7f2c7bc3aeb2825573118ba58d9bf6ff1dbd) (Jeff Verkoeyen) 927 | * [Unit Tests and Code Coverage (#19)](https://github.com/material-motion/material-motion-runtime-android/commit/7eb566f1872f751f8bbcb5907dc1753a14c0b4f2) (Sean O'Shea) 928 | * [Revert "mdm new repo ."](https://github.com/material-motion/material-motion-runtime-android/commit/20458394642a9c512f7ce79939a08f12819fabda) (Mark Wei) 929 | * [mdm new repo .](https://github.com/material-motion/material-motion-runtime-android/commit/f76b9944abbcc4c04210c8d61027d6ae5c903ed9) (Mark Wei) 930 | * [Automated large-scaled change: removed all submodules.](https://github.com/material-motion/material-motion-runtime-android/commit/6fc7f8f602aef6974e6cf3806cf1622b5ec4e0f2) (Jeff Verkoeyen) 931 | 932 | 933 | # 1.1.0 934 | 935 | ## Source changes 936 | 937 | * [New tokenized delegated performance API. Deprecated old api.](https://github.com/material-motion/material-motion-runtime-android/commit/f6f4b564b3de390697c5ba37caa2bf6cc54c4ecd) (Mark Wei) 938 | * [Apply mdm new repo . and arc lint --everything](https://github.com/material-motion/material-motion-runtime-android/commit/dbbb16ce41ad861e79a34effbcee2713a1dc9dca) (Mark Wei) 939 | * [Run mdm new repo.](https://github.com/material-motion/material-motion-runtime-android/commit/d725e609ea9f40f5404cd8356d79073c9af925ee) (Mark Wei) 940 | 941 | ## API changes 942 | 943 | Auto-generated by running: 944 | 945 | apidiff 01e43d2c6c08b2a414ca765f4d30af8f2f04b4db d70beca00fbfeaaf38f8c48fdc21ea5429cc5eb0 android library 946 | 947 | ## DelegatedPerformance 948 | 949 | *new* method: `setDelegatedPerformanceCallback(DelegatedPerformanceTokenCallback)` 950 | 951 | 952 | ## DelegatedPerformanceToken 953 | 954 | *new* class: `DelegatedPerformanceToken` 955 | 956 | *new* constructor: `DelegatedPerformanceToken()` 957 | 958 | 959 | ## DelegatedPerformanceTokenCallback 960 | 961 | *new* field: `DelegatedPerformanceTokenCallback` 962 | 963 | *new* method: `onDelegatedPerformanceEnd(DelegatedPerformance, DelegatedPerformanceToken)` 964 | 965 | *new* method: `onDelegatedPerformanceStart(DelegatedPerformance)` 966 | 967 | 968 | 969 | ## Non-source changes 970 | 971 | * [Automatic changelog preparation for release.](https://github.com/material-motion/material-motion-runtime-android/commit/d70beca00fbfeaaf38f8c48fdc21ea5429cc5eb0) (Mark Wei) 972 | * [Apply mdm new repo](https://github.com/material-motion/material-motion-runtime-android/commit/e5e74d92d54bb0402aefb5f317a49b13fafca18a) (Mark Wei) 973 | * [Apply mdm new repo .](https://github.com/material-motion/material-motion-runtime-android/commit/fbcc6e387c5d8b18c5d042d36fd960d7f05ed690) (Mark Wei) 974 | * [Apply mdm new repo](https://github.com/material-motion/material-motion-runtime-android/commit/7e5be232bdee7fe1231abea17f2082814a965016) (Mark Wei) 975 | * [Add dependency versioning guidance to README](https://github.com/material-motion/material-motion-runtime-android/commit/841e9146f75f9448df5ab05f4f5e43bb4f9cb37d) (Mark Wei) 976 | * [Set up local maven repository](https://github.com/material-motion/material-motion-runtime-android/commit/e0e83c523dc21896563fc34dbd9e2a8606fece16) (Mark Wei) 977 | * [Remove manual dependency section. Use jitpack.io](https://github.com/material-motion/material-motion-runtime-android/commit/1fe168259fcaa83bd2a237fdfd025ce8e655d79a) (Mark Wei) 978 | * [Run "mdm new repo" on existing repo.](https://github.com/material-motion/material-motion-runtime-android/commit/f9743269100a801ce5ba8c67cf084f9bad130aea) (Mark Wei) 979 | 980 | 981 | # 1.0.0 982 | 983 | - Initial release. 984 | - Includes runtime API and samples. 985 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Want to contribute? Great! First, read this page (including the small print at 2 | the end). 3 | 4 | ### Before you contribute 5 | 6 | Before we can use your code, you must sign the 7 | [Google Individual Contributor License Agreement] 8 | (https://cla.developers.google.com/about/google-individual) 9 | (CLA), which you can do online. The CLA is necessary mainly because you own the 10 | copyright to your changes, even after your contribution becomes part of our 11 | codebase, so we need your permission to use and distribute your code. We also 12 | need to be sure of various other things—for instance that you'll tell us if you 13 | know that your code infringes on other people's patents. You don't have to sign 14 | the CLA until after you've submitted your code for review and a member has 15 | approved it, but you must do it before we can put your code into our codebase. 16 | Before you start working on a larger contribution, you should get in touch with 17 | us first through the issue tracker with your idea so that we can help out and 18 | possibly guide you. Coordinating up front makes it much easier to avoid 19 | frustration later on. 20 | 21 | ### Code reviews 22 | 23 | All submissions, including submissions by project members, require review. 24 | We use GitHub pull requests for this purpose. 25 | 26 | ### The small print 27 | 28 | Contributions made by corporations are covered by a different agreement than 29 | the one above, the 30 | [Software Grant and Corporate Contributor License Agreement] 31 | (https://cla.developers.google.com/about/google-corporate). 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Material Motion Android Runtime 2 | 3 | [![Build Status](https://travis-ci.org/material-motion/runtime-android.svg?branch=develop)](https://travis-ci.org/material-motion/runtime-android) 4 | [![codecov](https://codecov.io/gh/material-motion/runtime-android/branch/develop/graph/badge.svg)](https://codecov.io/gh/material-motion/runtime-android) 5 | [![Release](https://img.shields.io/github/release/material-motion/runtime-android.svg)](https://github.com/material-motion/runtime-android/releases/latest) 6 | [![Docs](https://img.shields.io/badge/jitpack-docs-green.svg)](https://jitpack.io/com/github/material-motion/runtime-android/stable-SNAPSHOT/javadoc/) 7 | 8 | The Material Motion Runtime is a tool for describing motion declaratively. 9 | 10 | ## Declarative motion: motion as data 11 | 12 | This library does not do much on its own. What it does do, however, is enable the expression of 13 | motion as discrete units of data that can be introspected, composed, and sent over a wire. 14 | 15 | This library encourages you to describe motion as data, or what we call *plans*. Plans are committed 16 | to the *runtime*. The runtime coordinates the creation of *performers*, objects responsible for 17 | translating plans into concrete execution. 18 | 19 | To use the runtime, simply instantiate a `MotionRuntime` object and add a plan. 20 | 21 | ``` 22 | Plan plan; 23 | View target; 24 | 25 | MotionRuntime runtime = new MotionRuntime(); 26 | runtime.addPlan(plan, target); 27 | ``` 28 | 29 | Learn more about the APIs defined in the library by reading our 30 | [technical documentation](https://jitpack.io/com/github/material-motion/runtime-android/6.0.1/javadoc/) and our 31 | [Starmap](https://material-motion.github.io/material-motion/starmap/). 32 | 33 | ## Installation 34 | 35 | ### Installation with Jitpack 36 | 37 | Add the Jitpack repository to your project's `build.gradle`: 38 | 39 | ```gradle 40 | allprojects { 41 | repositories { 42 | maven { url "https://jitpack.io" } 43 | } 44 | } 45 | ``` 46 | 47 | Depend on the [latest version](https://github.com/material-motion/runtime-android/releases) of the library. 48 | Take care to occasionally [check for updates](https://github.com/ben-manes/gradle-versions-plugin). 49 | 50 | ```gradle 51 | dependencies { 52 | compile 'com.github.material-motion:runtime-android:6.0.1' 53 | } 54 | ``` 55 | 56 | For more information regarding versioning, see: 57 | 58 | - [Material Motion Versioning Policies](https://material-motion.github.io/material-motion/team/essentials/core_team_contributors/release_process#versioning) 59 | 60 | ### Using the files from a folder local to the machine 61 | 62 | You can have a copy of this library with local changes and test it in tandem 63 | with its client project. To add a local dependency on this library, add this 64 | library's identifier to your project's `local.dependencies`: 65 | 66 | ``` 67 | com.github.material-motion:runtime-android 68 | ``` 69 | 70 | > Because `local.dependencies` is never to be checked into Version Control 71 | Systems, you must also ensure that any local dependencies are also defined in 72 | `build.gradle` as explained in the previous section. 73 | 74 | **Important** 75 | 76 | For each local dependency listed, you *must* run `gradle install` from its 77 | project root every time you make a change to it. That command will publish your 78 | latest changes to the local maven repository. If your local dependencies have 79 | local dependencies of their own, you must `gradle install` them as well. 80 | 81 | You must `gradle clean` your project every time you add or remove a local 82 | dependency. 83 | 84 | ### Usage 85 | 86 | How to use the library in your project. 87 | 88 | #### Editing the library in Android Studio 89 | 90 | Open Android Studio, 91 | choose `File > New > Import`, 92 | choose the root `build.gradle` file. 93 | 94 | ## Example apps/unit tests 95 | 96 | To build the sample application, run the following commands: 97 | 98 | git clone https://github.com/material-motion/runtime-android.git 99 | cd runtime-android 100 | gradle installDebug 101 | 102 | To run all unit tests, run the following commands: 103 | 104 | git clone https://github.com/material-motion/runtime-android.git 105 | cd runtime-android 106 | gradle test 107 | 108 | # Guides 109 | 110 | 1. [Architecture](#architecture) 111 | 1. [How to define a new plan and performer type](#how-to-create-a-new-plan-and-performer-type) 112 | 1. [How to commit a plan to the runtime](#how-to-commit-a-plan-to-the-runtime) 113 | 1. [How to commit a named plan to the runtime](#how-to-commit-a-named-plan-to-the-runtime) 114 | 1. [How to configure performers with plans](#how-to-configure-performers-with-plans) 115 | 1. [How to configure performers with named plans](#how-to-configure-performers-with-named-plans) 116 | 1. [How to use composition to fulfill plans](#how-to-use-composition-to-fulfill-plans) 117 | 1. [How to indicate continuous performance](#how-to-indicate-continuous-performance) 118 | 1. [How to trace internal runtime events](#how-to-trace-internal-runtime-events) 119 | 1. [How to log runtime events to the console](#how-to-log-runtime-events-to-the-console) 120 | 121 | ## Architecture 122 | 123 | The Material Motion Runtime consists of two groups of APIs: a runtime object and a 124 | constellation of protocols loosely consisting of plan and performing types. 125 | 126 | ### MotionRuntime 127 | 128 | The [MotionRuntime](https://jitpack.io/com/github/material-motion/runtime-android/6.0.1/javadoc/index.html?com/google/android/material/motion/runtime/MotionRuntime.html) 129 | object is a coordinating entity whose primary responsibility is to fulfill plans by creating 130 | performers. You can create many runtimes throughout the lifetime of your application. A good rule 131 | of thumb is to have one runtime per interaction or transition. 132 | 133 | ### Plan + Performing types 134 | 135 | The [Plan](https://jitpack.io/com/github/material-motion/runtime-android/6.0.1/javadoc/index.html?com/google/android/material/motion/runtime/Plan.html) 136 | and [Performer](https://jitpack.io/com/github/material-motion/runtime-android/6.0.1/javadoc/index.html?com/google/android/material/motion/runtime/Performer.html) 137 | classes each define the minimal characteristics required for an object to be considered either a 138 | plan or a performer, respectively, by the Material Motion Runtime. 139 | 140 | Plans and performers have a symbiotic relationship. A plan is executed by the performer it defines. 141 | Performer behavior is configured by the provided plan instances. 142 | 143 | Learn more about the Material Motion Runtime by reading the 144 | [Starmap](https://material-motion.gitbooks.io/material-motion-starmap/content/specifications/runtime/). 145 | 146 | ## How to create a new plan and performer type 147 | 148 | The following steps provide copy-pastable snippets of code. 149 | 150 | ### Step 1: Define the plan type 151 | 152 | Questions to ask yourself when creating a new plan type: 153 | 154 | - What do I want my plan/performer to accomplish? 155 | - Will my performer need many plans to achieve the desired outcome? 156 | - How can I name my plan such that it clearly communicates either a **behavior** or a 157 | **change in state**? 158 | 159 | As general rules: 160 | 161 | 1. Plans with an *-able* suffix alter the **behavior** of the target, often indefinitely. Examples: 162 | Draggable, Pinchable, Tossable. 163 | 2. Plans that are *verbs* describe some **change in state**, often over a period of time. Examples: 164 | FadeIn, Tween, SpringTo. 165 | 166 | ```java 167 | public class MyPlan { 168 | } 169 | ``` 170 | 171 | ### Step 2: Define the performer type 172 | 173 | Performers are responsible for fulfilling plans. Fulfillment is possible in a variety of ways: 174 | 175 | - [NamedPerformer](https://jitpack.io/com/github/material-motion/runtime-android/6.0.1/javadoc/index.html?com/google/android/material/motion/runtime/NamedPerformer.html): [How to configure performers with named plans](#how-to-configure-performers-with-named-plans) 176 | - [ContinuousPerforming](https://jitpack.io/com/github/material-motion/runtime-android/6.0.1/javadoc/index.html?com/google/android/material/motion/runtime/PerformerFeatures.ContinuousPerforming.html): [How to indicate continuous performance](#how-to-indicate-continuous-performance) 177 | - [ComposablePerforming](https://jitpack.io/com/github/material-motion/runtime-android/6.0.1/javadoc/index.html?com/google/android/material/motion/runtime/PerformerFeatures.ComposablePerforming.html): [How to use composition to fulfill plans](#how-to-use-composition-to-fulfill-plans) 178 | 179 | See the associated links for more details on each performing type. 180 | 181 | > Note: only one instance of a type of performer **per target** is ever created. This allows you to 182 | > register multiple plans to the same target in order to configure a performer. See 183 | > [How to configure performers with plans](#how-to-configure-performers-with-plans) for more details. 184 | 185 | ```java 186 | public class MyPerformer { 187 | } 188 | ``` 189 | 190 | ### Step 3: Make the plan type a formal Plan 191 | 192 | Conforming to Plan requires: 193 | 194 | 1. that you choose the type of target your plan applies to, 195 | 1. that you define the type of performer your plan requires, and 196 | 1. that your plan be Cloneable. 197 | 198 | ```java 199 | public class MyPlan extends Plan { 200 | @Override 201 | public Class> getPerformerClass() { 202 | return MyPerformer.class; 203 | } 204 | 205 | @Override 206 | public Plan clone() { 207 | // Only override this method if you need to deep clone reference-typed fields. 208 | return super.clone(); 209 | } 210 | } 211 | ``` 212 | 213 | ### Step 4: Make the performer type a formal Performer 214 | 215 | Conforming to Performer requires: 216 | 217 | 1. that the type of target your performer can act on agrees with the plan, and 218 | 1. that you fulfill all plans passed to `addPlan()`. 219 | 220 | ```java 221 | public class MyPerformer extends Performer { 222 | @Override 223 | public void addPlan(Plan plan) { 224 | View target = getTarget(); 225 | } 226 | } 227 | ``` 228 | 229 | ## How to commit a plan to the runtime 230 | 231 | ### Step 1: Create and store a reference to a runtime instance 232 | 233 | ```java 234 | public class MyActivity extends Activity { 235 | private final MotionRuntime runtime = new MotionRuntime(); 236 | } 237 | ``` 238 | 239 | ### Step 2: Associate plans with targets 240 | 241 | ```java 242 | Plan plan; 243 | View target; 244 | 245 | runtime.addPlan(plan, target); 246 | ``` 247 | 248 | ## How to commit a named plan to the runtime 249 | 250 | ### Step 1: Create and store a reference to a runtime instance 251 | 252 | ```java 253 | public class MyActivity extends Activity { 254 | private final MotionRuntime runtime = new MotionRuntime(); 255 | } 256 | ``` 257 | 258 | ### Step 2: Associate named plans with targets 259 | 260 | ```java 261 | NamedPlan plan; 262 | String name; 263 | View target; 264 | 265 | runtime.addNamedPlan(plan, name, target); 266 | ``` 267 | 268 | ## How to configure performers with plans 269 | 270 | The `addPlan()` method will be invoked with plans that require use of this performer. 271 | 272 | ```java 273 | public class MyPerformer extends Performer { 274 | @Override 275 | public void addPlan(Plan plan) { 276 | MyPlan myPlan = (MyPlan) plan; 277 | 278 | // Do something with myPlan. 279 | } 280 | } 281 | ``` 282 | 283 | ***Handling multiple plan types*** 284 | 285 | ```java 286 | public class MyPerformer extends Performer { 287 | @Override 288 | public void addPlan(Plan plan) { 289 | if (plan instanceof Plan1) { 290 | addPlan1((Plan1) plan); 291 | } else if (plan instanceof Plan2) { 292 | addPlan2((Plan2) plan); 293 | } else { 294 | throw new IllegalArgumentException("Plan type not supported for " + plan); 295 | } 296 | } 297 | } 298 | ``` 299 | 300 | ## How to configure performers with named plans 301 | 302 | ```java 303 | public class MyPerformer extends NamedPerformer { 304 | @Override 305 | public void addPlan(NamedPlan plan, String name) { 306 | MyPlan myPlan = (MyPlan) plan; 307 | 308 | // Do something with myPlan. 309 | } 310 | 311 | @Override 312 | public void removePlan(String name) { 313 | // Remove any configuration associated with the given name. 314 | } 315 | } 316 | ``` 317 | 318 | ## How to use composition to fulfill plans 319 | 320 | A composition performer is able to emit new plans using a plan emitter. This feature enables the 321 | reuse of plans and the creation of higher-order abstractions. 322 | 323 | ### Step 1: Conform to ComposablePerforming and store the plan emitter 324 | 325 | ```java 326 | public class MyPerformer extends Performer implements ComposablePerforming { 327 | // Store the emitter in your class' definition. 328 | private PlanEmitter emitter; 329 | 330 | @Override 331 | public void setPlanEmitter(PlanEmitter planEmitter) { 332 | this.emitter = planEmitter; 333 | } 334 | } 335 | ``` 336 | 337 | ### Step 2: Emit plans 338 | 339 | Performers are only able to emit plans for their associated target. 340 | 341 | ```java 342 | PlanEmitter emitter; 343 | Plan plan; 344 | 345 | emitter.emit(plan); 346 | ``` 347 | 348 | ## How to indicate continuous performance 349 | 350 | Performers will often perform their actions over a period of time or while an interaction is 351 | active. These types of performers are called continuous performers. 352 | 353 | A continuous performer is able to affect the active state of the runtime by generating is-active 354 | tokens. The runtime is considered active so long as an is-active token exists and has not been 355 | terminated. Continuous performers are expected to terminate a token when its corresponding work has 356 | completed. 357 | 358 | For example, a performer that registers a platform animation might generate a token when the 359 | animation starts. When the animation completes the token would be terminated. 360 | 361 | ### Step 1: Conform to ContinuousPerforming and store the token generator 362 | 363 | ```java 364 | public class MyPerformer extends Performer implements ContinuousPerforming { 365 | // Store the emitter in your class' definition. 366 | private IsActiveTokenGenerator tokenGenerator; 367 | 368 | @Override 369 | public void setIsActiveTokenGenerator(IsActiveTokenGenerator isActiveTokenGenerator) { 370 | tokenGenerator = isActiveTokenGenerator; 371 | } 372 | } 373 | ``` 374 | 375 | ### Step 2: Generate a token when some continuous work has started 376 | 377 | You will likely need to store the token in order to be able to reference it at a later point. 378 | 379 | ```java 380 | Animator animator; 381 | animator.addListener(new AnimatorListenerAdapter() { 382 | 383 | private IsActiveToken token; 384 | 385 | @Override 386 | public void onAnimationStart(Animator animation) { 387 | token = tokenGenerator.generate(); 388 | } 389 | }); 390 | animator.start(); 391 | ``` 392 | 393 | ### Step 3: Terminate the token when work has completed 394 | 395 | ```java 396 | @Override 397 | public void onAnimationEnd(Animator animation) { 398 | token.terminate(); 399 | } 400 | ``` 401 | 402 | ## How to trace internal runtime events 403 | 404 | Tracing allows you to observe internal events occurring within a runtime. This information may be 405 | used for the following purposes: 406 | 407 | - Debug logging. 408 | - Inspection tooling. 409 | 410 | Use for other purposes is unsupported. 411 | 412 | ### Step 1: Create a tracer class 413 | 414 | ```java 415 | public class CustomTracer implements Tracing { 416 | } 417 | ``` 418 | 419 | ### Step 2: Implement methods 420 | 421 | The documentation for the Tracing interface enumerates the available methods. 422 | 423 | ```java 424 | public class CustomTracer implements Tracing { 425 | @Override 426 | public void onAddPlan(Plan plan, T target) { 427 | 428 | } 429 | } 430 | ``` 431 | 432 | ## How to log runtime events to the console 433 | 434 | ```java 435 | runtime.addTracer(new LogcatTracer()); 436 | ``` 437 | 438 | ## Contributing 439 | 440 | We welcome contributions! 441 | 442 | Check out our [upcoming milestones](https://github.com/material-motion/runtime-android/milestones). 443 | 444 | Learn more about [our team](https://material-motion.github.io/material-motion/team/), 445 | [our community](https://material-motion.github.io/material-motion/team/community/), and 446 | our [contributor essentials](https://material-motion.github.io/material-motion/team/essentials/). 447 | 448 | ## License 449 | 450 | Licensed under the Apache 2.0 license. See LICENSE for details. 451 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | apply from: 'local-dependency-substitution.gradle' 2 | apply plugin: "checkstyle" 3 | 4 | buildscript { 5 | repositories { 6 | jcenter() 7 | } 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:2.2.3' 10 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1' 11 | classpath 'com.vanniktech:gradle-android-junit-jacoco-plugin:0.5.0' 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | maven { url "https://jitpack.io" } 19 | } 20 | } 21 | 22 | subprojects { 23 | apply plugin: 'checkstyle' 24 | apply plugin: 'pmd' 25 | 26 | checkstyle { 27 | configFile = rootProject.file('checkstyle.xml') 28 | toolVersion = '7.1' 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 69 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 94 | 95 | 96 | 98 | 99 | 100 | 101 | 103 | 104 | 105 | 106 | 108 | 109 | 110 | 111 | 113 | 114 | 115 | 116 | 117 | 118 | 120 | 121 | 122 | 123 | 125 | 126 | 127 | 128 | 130 | 131 | 132 | 133 | 135 | 136 | 137 | 138 | 140 | 142 | 144 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/material-motion-archive/runtime-android/61efbbc62d4a24805f1cbfe834814bdf0592ef05/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Dec 15 20:21:14 PST 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.2.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save ( ) { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /install-local-dependency.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | # 3 | # Copyright 2016-present The Material Motion Authors. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | # Usage: `install-local-dependency.sh ` 18 | # 19 | # Publishes the dependency : to the local maven repository 20 | # using `gradle install`. This builds the local changes in that project 21 | # and propagates them to dependent projects. 22 | # 23 | # Used by local-dependency-substitution.gradle 24 | 25 | group="$1" 26 | name="$2" 27 | 28 | dir="$(mdm dir $name)" || { 29 | cat << EOF 30 | Failed to get the local repo path for dependency $group:$name. 31 | Make sure you read through our Contributor essentials: https://material-motion.github.io/material-motion/team/essentials/ 32 | 33 | Especially make sure that: 34 | 35 | * You have installed our team's mdm tool https://material-motion.github.io/material-motion/team/essentials/frequent_contributors/tools 36 | \$(mdm dir) should output the correct directory 37 | * You have cloned the repo for $group:$name 38 | \$(mdm dir $name) should output the correct directory 39 | EOF 40 | exit 1 41 | } 42 | 43 | cd "$dir" 44 | ./gradlew install || { 45 | echo "Failed to publish dependency $group:$name to the local maven repository." 46 | exit 1 47 | } 48 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.github.dcendents.android-maven' 3 | apply plugin: 'com.vanniktech.android.junit.jacoco' 4 | 5 | group = 'com.github.material-motion' 6 | 7 | install { 8 | repositories.mavenInstaller { 9 | pom.version = 'local' 10 | pom.artifactId = 'runtime-android' 11 | } 12 | } 13 | 14 | android { 15 | compileSdkVersion 25 16 | buildToolsVersion '25.0.0' 17 | 18 | defaultConfig { 19 | minSdkVersion 15 20 | targetSdkVersion 25 21 | versionCode 1 22 | versionName "1.0" 23 | consumerProguardFiles 'proguard-rules.pro' 24 | } 25 | 26 | lintOptions { 27 | abortOnError false 28 | } 29 | 30 | buildTypes { 31 | debug { 32 | testCoverageEnabled true 33 | } 34 | } 35 | } 36 | 37 | dependencies { 38 | // If you are developing any dependencies locally, also list them in local.dependencies. 39 | compile 'com.android.support:support-compat:25.1.0' 40 | 41 | testCompile 'com.google.truth:truth:0.28' 42 | testCompile 'junit:junit:4.12' 43 | testCompile 'org.mockito:mockito-core:1.10.19' 44 | testCompile 'org.robolectric:robolectric:3.1.2' 45 | } 46 | 47 | // build a jar with source files 48 | task sourcesJar(type: Jar) { 49 | from android.sourceSets.main.java.srcDirs 50 | classifier = 'sources' 51 | } 52 | 53 | task javadoc(type: Javadoc) { 54 | failOnError false 55 | source = android.sourceSets.main.java.sourceFiles 56 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 57 | classpath += configurations.compile 58 | } 59 | 60 | // build a jar with javadoc 61 | task javadocJar(type: Jar, dependsOn: javadoc) { 62 | classifier = 'javadoc' 63 | from javadoc.destinationDir 64 | } 65 | 66 | artifacts { 67 | archives sourcesJar 68 | archives javadocJar 69 | } 70 | -------------------------------------------------------------------------------- /library/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | -keep @android.support.annotation.Keep class * 13 | 14 | -keepclassmembers class * { 15 | @android.support.annotation.Keep *; 16 | } 17 | -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /library/src/main/java/com/google/android/material/motion/runtime/ChoreographerCompat.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Material Motion Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.material.motion.runtime; 18 | 19 | import android.annotation.TargetApi; 20 | import android.os.Build.VERSION; 21 | import android.os.Build.VERSION_CODES; 22 | import android.os.Handler; 23 | import android.os.Looper; 24 | import android.support.annotation.NonNull; 25 | import android.support.annotation.VisibleForTesting; 26 | import android.view.Choreographer; 27 | 28 | /** 29 | * A compatibility shim for {@link Choreographer} calls, since this class was not 30 | * available until API 16. For older versions of Android, a Handler will be used instead. 31 | */ 32 | public abstract class ChoreographerCompat { 33 | 34 | @VisibleForTesting 35 | static int sdkInt = VERSION.SDK_INT; 36 | 37 | @VisibleForTesting 38 | static ThreadLocal threadInstance = createThreadInstance(); 39 | 40 | @VisibleForTesting 41 | @NonNull 42 | static ThreadLocal createThreadInstance() { 43 | return new ThreadLocal() { 44 | @Override 45 | protected ChoreographerCompat initialValue() { 46 | if (sdkInt >= VERSION_CODES.JELLY_BEAN) { 47 | return new RealChoreographer(); 48 | } else { 49 | return new LegacyHandlerWrapper(Looper.myLooper()); 50 | } 51 | } 52 | }; 53 | } 54 | 55 | /** 56 | * Return the instance of {@link ChoreographerCompat} for the current thread. The thread must have 57 | * a looper associated with it. 58 | */ 59 | public static ChoreographerCompat getInstance() { 60 | return threadInstance.get(); 61 | } 62 | 63 | /** 64 | * Post a frame callback to run on the next frame. 65 | * 66 | *

The callback runs once then is automatically removed.

67 | */ 68 | public abstract void postFrameCallback(FrameCallback callback); 69 | 70 | /** 71 | * Post a frame callback to run on the next frame after the specified delay. 72 | * 73 | *

The callback runs once then is automatically removed.

74 | */ 75 | public abstract void postFrameCallbackDelayed(FrameCallback callback, long delayMillis); 76 | 77 | /** 78 | * Remove a previously posted frame callback. 79 | */ 80 | public abstract void removeFrameCallback(FrameCallback callback); 81 | 82 | /** 83 | * A callback that will occur on a future drawing frame. This is a compatible version of 84 | * {@link Choreographer.FrameCallback}. 85 | */ 86 | public abstract static class FrameCallback { 87 | private Runnable runnable; 88 | private Choreographer.FrameCallback realCallback; 89 | 90 | public abstract void doFrame(long frameTimeNanos); 91 | 92 | @TargetApi(VERSION_CODES.JELLY_BEAN) 93 | Choreographer.FrameCallback getRealCallback() { 94 | if (realCallback == null) { 95 | realCallback = 96 | new Choreographer.FrameCallback() { 97 | @Override 98 | public void doFrame(long frameTimeNanos) { 99 | FrameCallback.this.doFrame(frameTimeNanos); 100 | } 101 | }; 102 | } 103 | 104 | return realCallback; 105 | } 106 | 107 | Runnable getRunnable() { 108 | if (runnable == null) { 109 | runnable = 110 | new Runnable() { 111 | @Override 112 | public void run() { 113 | doFrame(System.nanoTime()); 114 | } 115 | }; 116 | } 117 | 118 | return runnable; 119 | } 120 | } 121 | 122 | /** 123 | * A {@link ChoreographerCompat} that just wraps a real {@link Choreographer}, for use on API 124 | * versions that support it. 125 | */ 126 | @TargetApi(VERSION_CODES.JELLY_BEAN) 127 | private static class RealChoreographer extends ChoreographerCompat { 128 | private Choreographer choreographer; 129 | 130 | public RealChoreographer() { 131 | choreographer = Choreographer.getInstance(); 132 | } 133 | 134 | @Override 135 | public void postFrameCallback(FrameCallback callback) { 136 | choreographer.postFrameCallback(callback.getRealCallback()); 137 | } 138 | 139 | @Override 140 | public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) { 141 | choreographer.postFrameCallbackDelayed(callback.getRealCallback(), delayMillis); 142 | } 143 | 144 | @Override 145 | public void removeFrameCallback(FrameCallback callback) { 146 | choreographer.removeFrameCallback(callback.getRealCallback()); 147 | } 148 | } 149 | 150 | /** 151 | * A {@link ChoreographerCompat} that wraps a {@link Handler} and emulates (at a basic level, 152 | * anyway) the behavior of a {@link Choreographer}. 153 | */ 154 | @VisibleForTesting 155 | static class LegacyHandlerWrapper extends ChoreographerCompat { 156 | private static final long FRAME_TIME_MS = 17; 157 | private Handler handler; 158 | 159 | public LegacyHandlerWrapper(Looper looper) { 160 | if (looper == null) { 161 | throw new IllegalStateException("The current thread must have a looper!"); 162 | } 163 | handler = new Handler(looper); 164 | } 165 | 166 | @Override 167 | public void postFrameCallback(FrameCallback callback) { 168 | handler.postDelayed(callback.getRunnable(), 0); 169 | } 170 | 171 | @Override 172 | public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) { 173 | handler.postDelayed(callback.getRunnable(), delayMillis + FRAME_TIME_MS); 174 | } 175 | 176 | @Override 177 | public void removeFrameCallback(FrameCallback callback) { 178 | handler.removeCallbacks(callback.getRunnable()); 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /library/src/main/java/com/google/android/material/motion/runtime/LogcatTracer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Material Motion Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.material.motion.runtime; 18 | 19 | import android.util.Log; 20 | 21 | /** 22 | * An implementation of {@link Tracing} which logs directly to logcat. 23 | */ 24 | public class LogcatTracer implements Tracing { 25 | 26 | private static final String TAG = "LogcatTracer"; 27 | 28 | @Override 29 | public void onAddPlan(Plan plan, Object target) { 30 | Log.v(TAG, String.format("didAddPlan: %s to: %s", plan, target)); 31 | } 32 | 33 | @Override 34 | public void onAddNamedPlan(NamedPlan plan, String name, Object target) { 35 | Log.v(TAG, String.format("didAddNamedPlan: %s named: %s to: %s", plan, name, target)); 36 | } 37 | 38 | @Override 39 | public void onRemoveNamedPlan(String name, Object target) { 40 | Log.v(TAG, String.format("didRemoveNamedPlan: %s from: %s", name, target)); 41 | } 42 | 43 | @Override 44 | public void onCreatePerformer(Performer performer, Object target) { 45 | Log.v(TAG, String.format("didCreatePerformer: %s for: %s", performer, target)); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /library/src/main/java/com/google/android/material/motion/runtime/MotionRuntime.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Material Motion Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.material.motion.runtime; 18 | 19 | import android.support.annotation.IntDef; 20 | import android.support.annotation.VisibleForTesting; 21 | import android.support.v4.util.SimpleArrayMap; 22 | import android.util.Log; 23 | 24 | import com.google.android.material.motion.runtime.ChoreographerCompat.FrameCallback; 25 | import com.google.android.material.motion.runtime.PerformerFeatures.ContinuousPerforming; 26 | import com.google.android.material.motion.runtime.PerformerFeatures.ManualPerforming; 27 | 28 | import java.lang.annotation.Retention; 29 | import java.lang.annotation.RetentionPolicy; 30 | import java.util.ArrayList; 31 | import java.util.HashSet; 32 | import java.util.List; 33 | import java.util.Set; 34 | import java.util.concurrent.CopyOnWriteArraySet; 35 | 36 | /** 37 | * The Material Motion runtime accepts {@link Plan Plans} and creates {@link Performer Performers}. 38 | * The runtime generates relevant events for Performers and {@link StateListener listeners} and 39 | * monitors {@link State}. 40 | *

41 | * Commit Plans to the runtime by calling {@link #addPlan(Plan, Object)}. A runtime ensures that 42 | * only one {@link Performer} instance is created for each type of Performer required by a target. 43 | * This allows multiple {@link Plan Plans} to affect a single Performer instance. The Performers can 44 | * then maintain state across multiple Plans. 45 | *

46 | * Query the State of the runtime by calling {@link #getState()}. The runtime is active if any 47 | * of its Performers are active. To listen for state changes, attach listeners via {@link 48 | * #addStateListener(StateListener)}. 49 | *

50 | * The runtime correctly handles all the interfaces defined in {@link PlanFeatures} and {@link 51 | * PerformerFeatures}. 52 | * 53 | * @see The 54 | * runtime specification 55 | */ 56 | public class MotionRuntime { 57 | 58 | /** 59 | * A listener that receives callbacks when the {@link MotionRuntime}'s {@link State} changes. 60 | */ 61 | public interface StateListener { 62 | 63 | /** 64 | * Notifies the {@link State} change of the {@link MotionRuntime}. 65 | */ 66 | void onStateChange(MotionRuntime runtime, @State int newState); 67 | } 68 | 69 | /** 70 | * An idle {@link State}, signifying no active {@link Performer Performers}. 71 | */ 72 | public static final int IDLE = 0; 73 | /** 74 | * An active {@link State}, signifying one or more active {@link Performer Performers}. 75 | */ 76 | public static final int ACTIVE = 1; 77 | 78 | /** 79 | * The state of a {@link MotionRuntime}. 80 | */ 81 | @IntDef({IDLE, ACTIVE}) 82 | @Retention(RetentionPolicy.SOURCE) 83 | public @interface State { 84 | 85 | } 86 | 87 | private static final String TAG = "MotionRuntime"; 88 | /** 89 | * Flag for detailed state bitmask specifying that the activity originates from a {@link 90 | * ManualPerforming}. 91 | */ 92 | static final int MANUAL_DETAILED_STATE_FLAG = 1 << 0; 93 | /** 94 | * Flag for detailed state bitmask specifying that the activity originates from a {@link 95 | * ContinuousPerforming}. 96 | */ 97 | static final int CONTINUOUS_DETAILED_STATE_FLAG = 1 << 1; 98 | 99 | @VisibleForTesting 100 | ChoreographerCompat choreographer = ChoreographerCompat.getInstance(); 101 | 102 | private final CopyOnWriteArraySet listeners = new CopyOnWriteArraySet<>(); 103 | private final ManualPerformingFrameCallback manualPerformingFrameCallback = 104 | new ManualPerformingFrameCallback(); 105 | 106 | private final SimpleArrayMap targets = new SimpleArrayMap<>(); 107 | private final Set activeManualPerformerTargets = new HashSet<>(); 108 | private final Set activeContinuousPerformerTargets = new HashSet<>(); 109 | 110 | private final List tracers = new ArrayList<>(); 111 | 112 | /** 113 | * @return The current {@link State} of the runtime. 114 | */ 115 | @State 116 | public int getState() { 117 | return getDetailedState() == 0 ? IDLE : ACTIVE; 118 | } 119 | 120 | /** 121 | * Returns the detailed state of the runtime, which includes information on the type of {@link 122 | * Performer} that affects this state. 123 | * 124 | * @return A bitmask representing the detailed state of the runtime. 125 | */ 126 | private int getDetailedState() { 127 | int state = 0; 128 | if (!activeManualPerformerTargets.isEmpty()) { 129 | state |= MANUAL_DETAILED_STATE_FLAG; 130 | } 131 | if (!activeContinuousPerformerTargets.isEmpty()) { 132 | state |= CONTINUOUS_DETAILED_STATE_FLAG; 133 | } 134 | return state; 135 | } 136 | 137 | /** 138 | * Adds a {@link StateListener} to be notified of the runtime's {@link State} changes. 139 | */ 140 | public void addStateListener(StateListener listener) { 141 | if (!listeners.contains(listener)) { 142 | listeners.add(listener); 143 | } 144 | } 145 | 146 | /** 147 | * Removes a {@link StateListener} from the runtime's {@link State} changes. 148 | */ 149 | public void removeStateListener(StateListener listener) { 150 | listeners.remove(listener); 151 | } 152 | 153 | /** 154 | * Adds a plan to the runtime. 155 | * 156 | * @param plan the {@link Plan} to add to the runtime. 157 | * @param target the target on which the plan will operate. 158 | * @param The type of target this plan can be applied to. 159 | */ 160 | public void addPlan(Plan plan, T target) { 161 | getTargetScope(target).commitPlan(plan.clone(), target); 162 | } 163 | 164 | /** 165 | * Adds a {@link NamedPlan} to the runtime. When this method is invoked, a {@link NamedPlan} 166 | * with the same name and target is removed from the runtime before the plan is eventually 167 | * added. 168 | * 169 | * @param plan the {@link NamedPlan} to add to the runtime. 170 | * @param name the name by which this plan can be identified. 171 | * @param target the target on which the plan will operate. 172 | * @param The type of target this plan can be applied to. 173 | */ 174 | public void addNamedPlan(NamedPlan plan, String name, T target) { 175 | if (name == null || name.isEmpty()) { 176 | throw new IllegalArgumentException("A NamedPlan must have a non-empty name."); 177 | } 178 | getTargetScope(target).commitAddNamedPlan(plan.clone(), name, target); 179 | } 180 | 181 | /** 182 | * Removes a {@link NamedPlan} from the runtime. 183 | * 184 | * @param name the name by which the named plan can be identified. 185 | * @param target the target on which the named plan was added. 186 | * @param The type of target this plan can be applied to. 187 | */ 188 | public void removeNamedPlan(String name, T target) { 189 | if (name == null || name.isEmpty()) { 190 | throw new IllegalArgumentException("A NamedPlan must have a non-empty name."); 191 | } 192 | getTargetScope(target).commitRemoveNamedPlan(name, target); 193 | } 194 | 195 | /** 196 | * Adds a {@link Tracing} instance to the runtime. 197 | * 198 | * @param tracer the tracer to add. 199 | */ 200 | public void addTracer(Tracing tracer) { 201 | if (!tracers.contains(tracer)) { 202 | tracers.add(tracer); 203 | } 204 | } 205 | 206 | /** 207 | * Removes a {@link Tracing} instance from the runtime. 208 | * 209 | * @param tracer the tracer to remove. 210 | */ 211 | public void removeTracer(Tracing tracer) { 212 | tracers.remove(tracer); 213 | } 214 | 215 | /** 216 | * Retrieves a collection of currently active tracers which have been added to the runtime. 217 | * 218 | * @return a {@link List} of {@link Tracing}s which are associated with the runtime. 219 | */ 220 | List getTracers() { 221 | return tracers; 222 | } 223 | 224 | private TargetScope getTargetScope(T target) { 225 | //noinspection unchecked 226 | TargetScope targetScope = targets.get(target); 227 | 228 | if (targetScope == null) { 229 | targetScope = new TargetScope<>(this); 230 | targets.put(target, targetScope); 231 | } 232 | 233 | return targetScope; 234 | } 235 | 236 | /** 237 | * Notifies the runtime that a {@link TargetScope}'s detailed state may or may not have 238 | * changed. 239 | */ 240 | void setTargetState(TargetScope target, int targetDetailedState) { 241 | int oldDetailedState = getDetailedState(); 242 | 243 | if (isSet(targetDetailedState, MANUAL_DETAILED_STATE_FLAG)) { 244 | activeManualPerformerTargets.add(target); 245 | } else { 246 | activeManualPerformerTargets.remove(target); 247 | } 248 | 249 | if (isSet(targetDetailedState, CONTINUOUS_DETAILED_STATE_FLAG)) { 250 | activeContinuousPerformerTargets.add(target); 251 | } else { 252 | activeContinuousPerformerTargets.remove(target); 253 | } 254 | 255 | int newDetailedState = getDetailedState(); 256 | if (oldDetailedState != newDetailedState) { 257 | onDetailedStateChange(oldDetailedState, newDetailedState); 258 | } 259 | } 260 | 261 | private void onDetailedStateChange(int oldDetailedState, int newDetailedState) { 262 | if (changed(oldDetailedState, newDetailedState, MANUAL_DETAILED_STATE_FLAG)) { 263 | if (isSet(newDetailedState, MANUAL_DETAILED_STATE_FLAG)) { 264 | Log.d(TAG, "Manual performing TargetScopes now active."); 265 | manualPerformingFrameCallback.start(); 266 | } else { 267 | Log.d(TAG, "Manual performing TargetScopes now idle."); 268 | manualPerformingFrameCallback.stop(); 269 | } 270 | } 271 | if (changed(oldDetailedState, newDetailedState, CONTINUOUS_DETAILED_STATE_FLAG)) { 272 | if (isSet(newDetailedState, CONTINUOUS_DETAILED_STATE_FLAG)) { 273 | Log.d(TAG, "Continuous performing TargetScopes now active."); 274 | } else { 275 | Log.d(TAG, "Continuous performing TargetScopes now idle."); 276 | } 277 | } 278 | 279 | if ((oldDetailedState == 0) != (newDetailedState == 0)) { 280 | @State int state = newDetailedState == 0 ? IDLE : ACTIVE; 281 | Log.d(TAG, "MotionRuntime state now: " + state); 282 | for (StateListener listener : listeners) { 283 | listener.onStateChange(this, state); 284 | } 285 | } 286 | } 287 | 288 | /** 289 | * Returns whether a flag bit on one bitmask differs from that on another bitmask. 290 | * 291 | * @param oldDetailedState The old bitmask. 292 | * @param newDetailedState The new bitmask. 293 | * @param flag The flag bit to check for a change. 294 | */ 295 | private static boolean changed(int oldDetailedState, int newDetailedState, int flag) { 296 | return (oldDetailedState & flag) != (newDetailedState & flag); 297 | } 298 | 299 | /** 300 | * Returns whether a flag bit is set on a bitmask. 301 | * 302 | * @param detailedState The bitmask. 303 | * @param flag The flag bit to check if is set. 304 | */ 305 | private static boolean isSet(int detailedState, int flag) { 306 | return (detailedState & flag) != 0; 307 | } 308 | 309 | /** 310 | * A {@link FrameCallback} that calls {@link ManualPerforming#update(float)} on each frame for 311 | * every active {@link ManualPerforming manual performer}. 312 | */ 313 | private class ManualPerformingFrameCallback extends FrameCallback { 314 | 315 | private double lastTimeMs = 0.0; 316 | 317 | public void start() { 318 | lastTimeMs = 0.0; 319 | choreographer.postFrameCallback(this); 320 | } 321 | 322 | public void stop() { 323 | choreographer.removeFrameCallback(this); 324 | } 325 | 326 | @Override 327 | public void doFrame(long frameTimeNanos) { 328 | double frameTimeMs = frameTimeNanos / 1000; 329 | choreographer.postFrameCallback(this); 330 | 331 | for (TargetScope activeTarget : activeManualPerformerTargets) { 332 | float deltaTimeMs = lastTimeMs == 0.0 ? 0f : (float) (frameTimeMs - lastTimeMs); 333 | activeTarget.update(deltaTimeMs); 334 | } 335 | 336 | lastTimeMs = frameTimeMs; 337 | } 338 | } 339 | } 340 | -------------------------------------------------------------------------------- /library/src/main/java/com/google/android/material/motion/runtime/NamedPerformer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-present The Material Motion Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.android.material.motion.runtime; 17 | 18 | /** 19 | * A performer that supports the named plan API. 20 | */ 21 | public abstract class NamedPerformer extends Performer { 22 | 23 | /** 24 | * Provides a {@link NamedPlan} to this Performer. The Performer is expected to execute any plan 25 | * added in this manner. 26 | * 27 | * @param plan the plan which was added to this performer. 28 | * @param name the name by which this plan can be identified. 29 | */ 30 | public abstract void addPlan(NamedPlan plan, String name); 31 | 32 | /** 33 | * Provides a {@link NamedPlan} to this Performer. The Performer is expected remove any plan 34 | * presented in this manner. 35 | * 36 | * @param name the name by which this plan was identified. 37 | */ 38 | public abstract void removePlan(String name); 39 | } 40 | -------------------------------------------------------------------------------- /library/src/main/java/com/google/android/material/motion/runtime/NamedPlan.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-present The Material Motion Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.android.material.motion.runtime; 17 | 18 | /** 19 | * A plan that supports the named plan API. 20 | *

21 | * A named plan is a {@link Plan} whose performer supports adding and remove the plan by name. 22 | * Register a named plan by calling {@link MotionRuntime#addNamedPlan(NamedPlan, String, Object)}, 23 | * and remove it by calling {@link MotionRuntime#removeNamedPlan(String, Object)}. 24 | *

25 | * A named plan or family of named plans enables fine configuration of a performer's behavior. 26 | * 27 | * @see The 28 | * Named Plan specificiation for more details. 29 | */ 30 | public abstract class NamedPlan extends Plan { 31 | 32 | @Override 33 | public abstract Class> getPerformerClass(); 34 | 35 | @Override 36 | public NamedPlan clone() { 37 | return (NamedPlan) super.clone(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /library/src/main/java/com/google/android/material/motion/runtime/Performer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Material Motion Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.material.motion.runtime; 18 | 19 | import android.support.annotation.VisibleForTesting; 20 | 21 | /** 22 | * A Performer is an object responsible for executing a {@link Plan}. 23 | *

24 | * Plans define the {@link Class} of Performer that can fulfill it. Your Performer will be 25 | * instantiated via reflection, so take care that a {@link PerformerInstantiationException} will not 26 | * be thrown. 27 | *

28 | * The {@link PerformerFeatures} interfaces define optional APIs. 29 | * 30 | * @param The type of target this performer can act on. 31 | * @see The 32 | * Performer specification 33 | */ 34 | 35 | public abstract class Performer { 36 | 37 | /** 38 | * Thrown when there is an instantiation failure. Make sure that your {@link Performer}'s class 39 | * name exists, is public, and has an empty constructor that is public. 40 | */ 41 | public static class PerformerInstantiationException extends RuntimeException { 42 | 43 | public PerformerInstantiationException( 44 | Class klass, Exception cause) { 45 | super( 46 | "Unable to instantiate Performer " 47 | + klass.getName() 48 | + ": make sure class name exists, is public, and has an empty constructor that is " 49 | + "public", 50 | cause); 51 | } 52 | } 53 | 54 | private T target; 55 | 56 | /** 57 | * Performers are initialized with a target. 58 | */ 59 | @VisibleForTesting 60 | public final void initialize(T target) { 61 | //noinspection unchecked 62 | this.target = (T) target; 63 | onInitialize(this.target); 64 | } 65 | 66 | /** 67 | * Invoked immediately after this Performer has been initialized with a target. 68 | */ 69 | protected void onInitialize(T target) { 70 | } 71 | 72 | /** 73 | * Provides a {@link Plan} to this Performer. The Performer is expected to execute this plan. 74 | */ 75 | protected abstract void addPlan(Plan plan); 76 | 77 | /** 78 | * ​Returns the target that this Performer is associated with. ​ 79 | * 80 | * @param Convenience to avoid casting, for when the caller knows the type of the 81 | * target. 82 | * @return The target. ​ 83 | */ 84 | public final Type getTarget() { 85 | //noinspection unchecked 86 | return (Type) target; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /library/src/main/java/com/google/android/material/motion/runtime/PerformerFeatures.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-present The Material Motion Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.android.material.motion.runtime; 17 | 18 | import android.support.annotation.VisibleForTesting; 19 | 20 | import com.google.android.material.motion.runtime.MotionRuntime.State; 21 | 22 | /** 23 | * Defines the APIs that a {@link Performer} can implement. 24 | */ 25 | public final class PerformerFeatures { 26 | 27 | @VisibleForTesting 28 | PerformerFeatures() { 29 | throw new UnsupportedOperationException(); 30 | } 31 | 32 | /** 33 | * A Performer implements this interface in order to request and release is-active tokens. The 34 | * runtime uses these tokens to inform its active state. If any performer owns an is-active 35 | * token then the runtime is active. Otherwise, the runtime is idle. 36 | *

37 | * The only requirement is that the Performer must request a token from the {@link 38 | * IsActiveTokenGenerator token generator} when the continuous performance {@link 39 | * IsActiveTokenGenerator#generate() starts} and release the token when the continuous 40 | * performance {@link IsActiveToken#terminate() ends}. 41 | */ 42 | public interface ContinuousPerforming { 43 | 44 | /** 45 | * Called by the {@link MotionRuntime} to supply the {@link Performer} with a {@link 46 | * IsActiveTokenGenerator}. 47 | */ 48 | void setIsActiveTokenGenerator(IsActiveTokenGenerator isActiveTokenGenerator); 49 | 50 | /** 51 | * A generator for {@link IsActiveToken}s. 52 | */ 53 | interface IsActiveTokenGenerator { 54 | 55 | /** 56 | * Generate and return a new is-active token. The receiver of this token is expected to 57 | * eventually {@link IsActiveToken#terminate()} the token. 58 | *

59 | * Usually called by a {@link ContinuousPerforming} when it starts. 60 | */ 61 | IsActiveToken generate(); 62 | } 63 | 64 | /** 65 | * A token representing a single unit of continuous performance. 66 | */ 67 | interface IsActiveToken { 68 | 69 | /** 70 | * Notifies that the continuous performance has ended. Subsequent invocations of this 71 | * method will result in an exception. 72 | */ 73 | void terminate(); 74 | } 75 | } 76 | 77 | /** 78 | * A Performer implements this interface in order to do manual calculations in {@link 79 | * #update(float)}. 80 | *

81 | * The Performer is expected to calculate and set its target's next state on each update. 82 | */ 83 | public interface ManualPerforming { 84 | 85 | /** 86 | * Called by the {@link MotionRuntime} to notify the {@link Performer} of a new frame. 87 | * 88 | * @param deltaTimeMs The elapsed time in milliseconds since the last update. 89 | * @return The {@link State} of this Performer after this update. {@link MotionRuntime#IDLE} 90 | * means this Performer does not wish to get any more frame updates. 91 | */ 92 | @State 93 | int update(float deltaTimeMs); 94 | } 95 | 96 | /** 97 | * A Performer implements this interface in order to commit new {@link Plan Plans}. 98 | *

99 | * The Performer should call {@link PlanEmitter#emit(Plan)} to add new plans. 100 | */ 101 | public interface ComposablePerforming { 102 | 103 | /** 104 | * Called by the {@link MotionRuntime} to supply the {@link Performer} with a {@link 105 | * PlanEmitter}. 106 | */ 107 | void setPlanEmitter(PlanEmitter planEmitter); 108 | 109 | /** 110 | * A plan emitter allows an object to emit new plans to a backing runtime for the target to 111 | * which the performer is associated. 112 | * 113 | * @param This emitter will only accept plans that can be applied to this type of 114 | * target. 115 | */ 116 | interface PlanEmitter { 117 | 118 | /** 119 | * Emit a new plan. The plan will immediately be added to the backing runtime. 120 | */ 121 | void emit(Plan plan); 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /library/src/main/java/com/google/android/material/motion/runtime/Plan.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Material Motion Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.material.motion.runtime; 18 | 19 | import android.support.annotation.VisibleForTesting; 20 | 21 | /** 22 | * A Plan is an object representing what you want something to do. A Plan uses a {@link Performer} 23 | * to fulfill itself. 24 | *

25 | * Plans are {@link Cloneable}, and by default {@link #clone()} makes a shallow copy. If your Plan 26 | * contains mutable Object references, override {@link #clone()} to make a deep copy. 27 | *

28 | * The {@link PlanFeatures} interfaces define optional APIs. 29 | * 30 | * @param The type of target this plan can be applied to. 31 | * @see The 32 | * Plan specificiation 33 | * @see Object#clone() 34 | */ 35 | public abstract class Plan implements Cloneable { 36 | 37 | /** 38 | * @return The {@link Class} of the {@link Performer} that can fulfill this plan. 39 | */ 40 | protected abstract Class> getPerformerClass(); 41 | 42 | /** 43 | * By default this implementation makes a shallow copy. If your Plan contains mutable Object 44 | * references, override this method to make a deep copy. 45 | */ 46 | @SuppressWarnings("CloneDoesntCallSuperClone") 47 | @Override 48 | public Plan clone() { 49 | try { 50 | //noinspection unchecked 51 | return (Plan) superClone(); 52 | } catch (CloneNotSupportedException e) { 53 | throw new AssertionError(); 54 | } 55 | } 56 | 57 | @VisibleForTesting 58 | Object superClone() throws CloneNotSupportedException { 59 | return super.clone(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /library/src/main/java/com/google/android/material/motion/runtime/PlanFeatures.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-present The Material Motion Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.android.material.motion.runtime; 17 | 18 | import android.support.annotation.VisibleForTesting; 19 | import android.util.JsonReader; 20 | import android.util.JsonWriter; 21 | 22 | /** 23 | * Defines the APIs that a {@link Plan} can implement. 24 | */ 25 | public final class PlanFeatures { 26 | 27 | @VisibleForTesting 28 | PlanFeatures() { 29 | throw new UnsupportedOperationException(); 30 | } 31 | 32 | /** 33 | * Plans should implement this interface if it wants to support the serialize API. 34 | *

35 | * Serializable Plans can be sent over a wire or recorded to disk. 36 | */ 37 | public interface SerializablePlan { 38 | 39 | /** 40 | * Serializes the Plan into JSON. 41 | * 42 | * @param writer Writer to record serialized JSON to. 43 | */ 44 | void toJson(JsonWriter writer); 45 | 46 | /** 47 | * Deserializes the Plan from JSON and populates this Plan's fields. 48 | * 49 | * @param reader Reader to consume serialized JSON from. 50 | */ 51 | void fromJson(JsonReader reader); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /library/src/main/java/com/google/android/material/motion/runtime/TargetScope.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Material Motion Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.material.motion.runtime; 18 | 19 | import android.support.annotation.VisibleForTesting; 20 | import android.support.v4.util.SimpleArrayMap; 21 | 22 | import com.google.android.material.motion.runtime.MotionRuntime.State; 23 | import com.google.android.material.motion.runtime.Performer.PerformerInstantiationException; 24 | import com.google.android.material.motion.runtime.PerformerFeatures.ComposablePerforming; 25 | import com.google.android.material.motion.runtime.PerformerFeatures.ComposablePerforming.PlanEmitter; 26 | import com.google.android.material.motion.runtime.PerformerFeatures.ContinuousPerforming; 27 | import com.google.android.material.motion.runtime.PerformerFeatures.ContinuousPerforming.IsActiveToken; 28 | import com.google.android.material.motion.runtime.PerformerFeatures.ContinuousPerforming.IsActiveTokenGenerator; 29 | import com.google.android.material.motion.runtime.PerformerFeatures.ManualPerforming; 30 | 31 | import java.util.HashSet; 32 | import java.util.Iterator; 33 | import java.util.Set; 34 | 35 | import static com.google.android.material.motion.runtime.MotionRuntime.CONTINUOUS_DETAILED_STATE_FLAG; 36 | import static com.google.android.material.motion.runtime.MotionRuntime.MANUAL_DETAILED_STATE_FLAG; 37 | 38 | /** 39 | * A helper class for {@link MotionRuntime} that scopes {@link Performer} instances by target. 40 | *

41 | * Ensures only a single instance of Performer is created for each type of Performer required by a 42 | * target. 43 | */ 44 | class TargetScope { 45 | 46 | private final SimpleArrayMap>, Performer> cache = new SimpleArrayMap<>(); 47 | private final SimpleArrayMap> namedCache = new SimpleArrayMap<>(); 48 | 49 | private final Set activeManualPerformers = new HashSet<>(); 50 | 51 | private final SimpleArrayMap> 52 | activeContinuousPerformers = new SimpleArrayMap<>(); 53 | 54 | private final MotionRuntime runtime; 55 | 56 | TargetScope(MotionRuntime runtime) { 57 | this.runtime = runtime; 58 | } 59 | 60 | void commitPlan(Plan plan, T target) { 61 | Performer performer = commitPlanInternal(plan, target); 62 | performer.addPlan(plan); 63 | 64 | // notify tracers 65 | for (Tracing tracer : runtime.getTracers()) { 66 | tracer.onAddPlan(plan, target); 67 | } 68 | } 69 | 70 | void commitAddNamedPlan(NamedPlan plan, String name, T target) { 71 | // remove first 72 | commitRemoveNamedPlan(name, target); 73 | 74 | // then add 75 | NamedPerformer performer = commitPlanInternal(plan, target); 76 | performer.addPlan(plan, name); 77 | namedCache.put(name, performer); 78 | 79 | // notify tracers 80 | for (Tracing tracer : runtime.getTracers()) { 81 | tracer.onAddNamedPlan(plan, name, target); 82 | } 83 | } 84 | 85 | private

> P commitPlanInternal(Plan plan, T target) { 86 | Performer performer = getPerformer(plan, target); 87 | 88 | if (performer instanceof ManualPerforming) { 89 | activeManualPerformers.add((ManualPerforming) performer); 90 | notifyTargetStateChanged(); 91 | } 92 | 93 | //noinspection unchecked 94 | return (P) performer; 95 | } 96 | 97 | void commitRemoveNamedPlan(String name, T target) { 98 | NamedPerformer performer = namedCache.get(name); 99 | if (performer != null) { 100 | performer.removePlan(name); 101 | 102 | // notify tracers 103 | for (Tracing tracer : runtime.getTracers()) { 104 | tracer.onRemoveNamedPlan(name, target); 105 | } 106 | } 107 | namedCache.remove(name); 108 | } 109 | 110 | void update(float deltaTimeMs) { 111 | Iterator iterator = activeManualPerformers.iterator(); 112 | 113 | boolean changed = false; 114 | while (iterator.hasNext()) { 115 | ManualPerforming performer = iterator.next(); 116 | @State int state = performer.update(deltaTimeMs); 117 | if (state == MotionRuntime.IDLE) { 118 | iterator.remove(); 119 | changed = true; 120 | } 121 | } 122 | 123 | if (changed) { 124 | notifyTargetStateChanged(); 125 | } 126 | } 127 | 128 | private void notifyTargetStateChanged() { 129 | runtime.setTargetState(this, getDetailedState()); 130 | } 131 | 132 | private int getDetailedState() { 133 | int state = 0; 134 | if (!activeManualPerformers.isEmpty()) { 135 | state |= MANUAL_DETAILED_STATE_FLAG; 136 | } 137 | if (!activeContinuousPerformers.isEmpty()) { 138 | state |= CONTINUOUS_DETAILED_STATE_FLAG; 139 | } 140 | return state; 141 | } 142 | 143 | private Performer getPerformer(Plan plan, T target) { 144 | Class> performerClass = plan.getPerformerClass(); 145 | Performer performer = cache.get(performerClass); 146 | 147 | if (performer == null) { 148 | performer = createPerformer(plan, target); 149 | cache.put(performerClass, performer); 150 | } 151 | 152 | return performer; 153 | } 154 | 155 | private Performer createPerformer(Plan plan, T target) { 156 | Class> performerClass = plan.getPerformerClass(); 157 | 158 | //noinspection TryWithIdenticalCatches 159 | try { 160 | Performer performer = performerClass.newInstance(); 161 | performer.initialize(target); 162 | 163 | if (performer instanceof ContinuousPerforming) { 164 | ContinuousPerforming continuousPerformer = (ContinuousPerforming) performer; 165 | continuousPerformer 166 | .setIsActiveTokenGenerator(createIsActiveTokenGenerator(continuousPerformer)); 167 | } 168 | 169 | if (performer instanceof ComposablePerforming) { 170 | //noinspection unchecked 171 | ComposablePerforming composablePerformer = (ComposablePerforming) performer; 172 | composablePerformer.setPlanEmitter(createPlanEmitter(performer)); 173 | } 174 | 175 | for (Tracing tracing : runtime.getTracers()) { 176 | tracing.onCreatePerformer(performer, target); 177 | } 178 | 179 | return performer; 180 | } catch (InstantiationException e) { 181 | throw new PerformerInstantiationException(performerClass, e); 182 | } catch (IllegalAccessException e) { 183 | throw new PerformerInstantiationException(performerClass, e); 184 | } 185 | } 186 | 187 | /** 188 | * Creates a {@link IsActiveTokenGenerator} to be assigned to the given {@link 189 | * ContinuousPerforming}. 190 | */ 191 | @VisibleForTesting 192 | IsActiveTokenGenerator createIsActiveTokenGenerator( 193 | final ContinuousPerforming performer) { 194 | return new IsActiveTokenGenerator() { 195 | @Override 196 | public IsActiveToken generate() { 197 | final Set tokens; 198 | 199 | if (activeContinuousPerformers.containsKey(performer)) { 200 | tokens = activeContinuousPerformers.get(performer); 201 | } else { 202 | tokens = new HashSet<>(); 203 | activeContinuousPerformers.put(performer, tokens); 204 | } 205 | 206 | IsActiveToken token = new IsActiveToken() { 207 | @Override 208 | public void terminate() { 209 | boolean modified = tokens.remove(this); 210 | if (!modified) { 211 | throw new IllegalStateException("IsActiveToken already terminated."); 212 | } 213 | 214 | if (tokens.isEmpty()) { 215 | activeContinuousPerformers.remove(performer); 216 | } 217 | notifyTargetStateChanged(); 218 | } 219 | }; 220 | tokens.add(token); 221 | 222 | notifyTargetStateChanged(); 223 | return token; 224 | } 225 | }; 226 | } 227 | 228 | /** 229 | * Creates a {@link PlanEmitter} to be assigned to the given performer. 230 | */ 231 | private PlanEmitter createPlanEmitter(final Performer performer) { 232 | return new PlanEmitter() { 233 | @Override 234 | public void emit(Plan plan) { 235 | runtime.addPlan(plan, performer.getTarget()); 236 | } 237 | }; 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /library/src/main/java/com/google/android/material/motion/runtime/Tracing.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 - present The Material Motion Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.material.motion.runtime; 18 | 19 | /** 20 | * A tracer object may implement a variety of hooks for the purposes of observing changes to the 21 | * internal workings of a runtime. 22 | * 23 | * @see The 24 | * Tracing specificiation 25 | */ 26 | public interface Tracing { 27 | 28 | /** 29 | * Invoked after a plan has been added to the runtime. 30 | * 31 | * @param plan the plan which was added. 32 | * @param target the object on which the plan was targeted. 33 | */ 34 | void onAddPlan(Plan plan, T target); 35 | 36 | /** 37 | * Invoked after a named plan has been added to the runtime. 38 | * 39 | * @param plan the plan which was added. 40 | * @param name the name by which the plan is identifiable. 41 | * @param target the object on which the plan was targeted. 42 | */ 43 | void onAddNamedPlan(NamedPlan plan, String name, T target); 44 | 45 | /** 46 | * Invoked when a named plan is removed from the runtime. 47 | * 48 | * @param name the name by which the plan was identifiable. 49 | * @param target the object on which the plan was previously targeted. 50 | */ 51 | void onRemoveNamedPlan(String name, T target); 52 | 53 | /** 54 | * Invoked after a performer has been created by the runtime. 55 | * 56 | * @param performer the {@link Performer} which was just created. 57 | * @param target the object on which the performer is targeted. 58 | */ 59 | void onCreatePerformer(Performer performer, T target); 60 | } 61 | -------------------------------------------------------------------------------- /library/src/main/java/com/google/android/material/motion/runtime/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Material Motion Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | *

Material Motion Android Runtime package.

19 | * 20 | *

21 | * API documentation for the Material Motion Android Runtime library. 22 | * 23 | *

24 | * This package defines two important abstract classes: 25 | *

    26 | *
  • {@link com.google.android.material.motion.runtime.Plan}
  • 27 | *
  • {@link com.google.android.material.motion.runtime.Performer}
  • 28 | *
29 | * and one concrete class: 30 | *
    31 | *
  • {@link com.google.android.material.motion.runtime.MotionRuntime}
  • 32 | *
33 | * 34 | * Learn more about these APIs by reading our Starmap. 35 | * 36 | *

Next steps

37 | * 38 | *
    39 | *
  • Create your own family of Plans/Performers.
  • 40 | *
41 | */ 42 | package com.google.android.material.motion.runtime; 43 | -------------------------------------------------------------------------------- /library/src/main/java/com/google/android/material/motion/runtime/testing/StepChoreographer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-present The Material Motion Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.android.material.motion.runtime.testing; 17 | 18 | import android.support.v4.util.SimpleArrayMap; 19 | 20 | import com.google.android.material.motion.runtime.ChoreographerCompat; 21 | 22 | /** 23 | * A {@link ChoreographerCompat} implementation for tests. Allows a test to {@link #advance(long)} 24 | * the choreographer in a controlled manner. 25 | */ 26 | public class StepChoreographer extends ChoreographerCompat { 27 | 28 | /** 29 | * Represents one frame. 16ms. 30 | */ 31 | public final static long FRAME_MS = 16L; 32 | 33 | private final SimpleArrayMap callbacks = new SimpleArrayMap<>(); 34 | private long frameTimeMs = 0L; 35 | 36 | /** 37 | * Advance the choreographer for the given milliseconds. Any callbacks scheduled within this 38 | * period will be invoked and removed. 39 | */ 40 | public void advance(long millis) { 41 | frameTimeMs += millis; 42 | 43 | for (int i = 0, count = callbacks.size(); i < count; i++) { 44 | FrameCallback callback = callbacks.keyAt(i); 45 | long delay = callbacks.valueAt(i); 46 | 47 | put(callback, delay - millis); 48 | } 49 | } 50 | 51 | private void put(FrameCallback callback, long delay) { 52 | if (delay <= 0) { 53 | callbacks.remove(callback); 54 | callback.doFrame(frameTimeMs * 1000); 55 | } else { 56 | callbacks.put(callback, delay); 57 | } 58 | } 59 | 60 | @Override 61 | public void postFrameCallback(FrameCallback callback) { 62 | callbacks.put(callback, FRAME_MS); 63 | } 64 | 65 | @Override 66 | public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) { 67 | callbacks.put(callback, delayMillis); 68 | } 69 | 70 | @Override 71 | public void removeFrameCallback(FrameCallback callback) { 72 | callbacks.remove(callback); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /library/src/test/java/com/google/android/material/motion/runtime/ChoreographerCompatTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-present The Material Motion Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.android.material.motion.runtime; 17 | 18 | import com.google.android.material.motion.runtime.ChoreographerCompat.FrameCallback; 19 | 20 | import org.junit.Test; 21 | import org.robolectric.Robolectric; 22 | 23 | import static com.google.common.truth.Truth.assertThat; 24 | 25 | public abstract class ChoreographerCompatTests { 26 | 27 | /** 28 | * One frame in ms. 29 | */ 30 | private static final long FRAME = 17; 31 | 32 | @Test 33 | public void postFrameCallback() { 34 | ChoreographerCompat choreographer = ChoreographerCompat.getInstance(); 35 | 36 | TestFrameCallback callback = new TestFrameCallback(); 37 | choreographer.postFrameCallback(callback); 38 | 39 | assertThat(callback.didFrame).isTrue(); 40 | } 41 | 42 | @Test 43 | public void postFrameCallbackDelayed() { 44 | ChoreographerCompat choreographer = ChoreographerCompat.getInstance(); 45 | 46 | TestFrameCallback callback = new TestFrameCallback(); 47 | choreographer.postFrameCallbackDelayed(callback, FRAME + 1); 48 | 49 | Robolectric.getForegroundThreadScheduler().advanceBy(FRAME); 50 | assertThat(callback.didFrame).isFalse(); 51 | Robolectric.getForegroundThreadScheduler().advanceToLastPostedRunnable(); 52 | assertThat(callback.didFrame).isTrue(); 53 | } 54 | 55 | @Test 56 | public void removeFrameCallback() { 57 | ChoreographerCompat choreographer = ChoreographerCompat.getInstance(); 58 | 59 | TestFrameCallback callback = new TestFrameCallback(); 60 | choreographer.postFrameCallbackDelayed(callback, FRAME + 1); 61 | 62 | Robolectric.getForegroundThreadScheduler().advanceBy(FRAME); 63 | assertThat(callback.didFrame).isFalse(); 64 | 65 | choreographer.removeFrameCallback(callback); 66 | 67 | Robolectric.getForegroundThreadScheduler().advanceBy(FRAME); 68 | assertThat(callback.didFrame).isFalse(); 69 | } 70 | 71 | private static class TestFrameCallback extends FrameCallback { 72 | public boolean didFrame = false; 73 | 74 | @Override 75 | public void doFrame(long frameTimeNanos) { 76 | didFrame = true; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /library/src/test/java/com/google/android/material/motion/runtime/ComposablePlanTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-present The Material Motion Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.android.material.motion.runtime; 17 | 18 | import android.app.Activity; 19 | import android.content.Context; 20 | import android.widget.TextView; 21 | 22 | import com.google.android.material.motion.runtime.PerformerFeatures.ComposablePerforming; 23 | 24 | import org.junit.Before; 25 | import org.junit.Test; 26 | import org.junit.runner.RunWith; 27 | import org.robolectric.Robolectric; 28 | import org.robolectric.RobolectricTestRunner; 29 | import org.robolectric.annotation.Config; 30 | 31 | import static com.google.common.truth.Truth.assertThat; 32 | 33 | @RunWith(RobolectricTestRunner.class) 34 | @Config(constants = BuildConfig.class, sdk = 21) 35 | public class ComposablePlanTests { 36 | 37 | private MotionRuntime runtime; 38 | private TextView textView; 39 | 40 | @Before 41 | public void setUp() { 42 | Context context = Robolectric.setupActivity(Activity.class); 43 | runtime = new MotionRuntime(); 44 | textView = new TextView(context); 45 | } 46 | 47 | @Test 48 | public void testComposablePlan() { 49 | // add the root plan and have it delegate to the leaf plan 50 | RootPlan rootPlan = new RootPlan("rootPlan"); 51 | runtime.addNamedPlan(rootPlan, "rootPlan", textView); 52 | 53 | assertThat(textView.getText()).isEqualTo("leafPlan"); 54 | } 55 | 56 | private class RootPlan extends NamedPlan { 57 | 58 | private String text; 59 | 60 | private RootPlan(String text) { 61 | this.text = text; 62 | } 63 | 64 | @Override 65 | public Class> getPerformerClass() { 66 | return ComposablePerformer.class; 67 | } 68 | } 69 | 70 | private static class LeafPlan extends Plan { 71 | 72 | private String text; 73 | 74 | private LeafPlan(String text) { 75 | this.text = text; 76 | } 77 | 78 | @Override 79 | public Class> getPerformerClass() { 80 | return LeafPerformer.class; 81 | } 82 | } 83 | 84 | public static class LeafPerformer extends Performer { 85 | 86 | @Override 87 | public void addPlan(Plan plan) { 88 | LeafPlan leafPlan = (LeafPlan) plan; 89 | TextView target = getTarget(); 90 | target.setText(leafPlan.text); 91 | } 92 | } 93 | 94 | public static class ComposablePerformer extends NamedPerformer 95 | implements ComposablePerforming { 96 | 97 | private PlanEmitter planEmitter; 98 | 99 | public void setPlanEmitter(PlanEmitter planEmitter) { 100 | this.planEmitter = planEmitter; 101 | } 102 | 103 | @Override 104 | public void addPlan(Plan plan) { 105 | // immediately delegate the actual work of changing the text view to the leaf plan 106 | planEmitter.emit(new LeafPlan("leafPlan")); 107 | } 108 | 109 | @Override 110 | public void addPlan(NamedPlan plan, String name) { 111 | addPlan(plan); 112 | } 113 | 114 | @Override 115 | public void removePlan(String name) { 116 | 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /library/src/test/java/com/google/android/material/motion/runtime/LegacyChoreographerCompatTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-present The Material Motion Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.android.material.motion.runtime; 17 | 18 | import org.junit.BeforeClass; 19 | import org.junit.Test; 20 | import org.junit.runner.RunWith; 21 | import org.robolectric.RobolectricTestRunner; 22 | import org.robolectric.annotation.Config; 23 | 24 | @RunWith(RobolectricTestRunner.class) 25 | @Config(constants = BuildConfig.class, sdk = 21) 26 | public class LegacyChoreographerCompatTests extends ChoreographerCompatTests { 27 | 28 | @BeforeClass 29 | public static void oneTimeSetUp() { 30 | ChoreographerCompat.sdkInt = 15; 31 | ChoreographerCompat.threadInstance = ChoreographerCompat.createThreadInstance(); 32 | } 33 | 34 | @Test(expected = IllegalStateException.class) 35 | public void nullLooperThrows() { 36 | new ChoreographerCompat.LegacyHandlerWrapper(null); 37 | } 38 | 39 | // Super-class runs all tests. 40 | } 41 | -------------------------------------------------------------------------------- /library/src/test/java/com/google/android/material/motion/runtime/LogcatTracerTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 - present The Material Motion Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.material.motion.runtime; 18 | 19 | import android.app.Activity; 20 | import android.widget.TextView; 21 | 22 | import com.google.android.material.motion.runtime.plans.NoOpPlan; 23 | import com.google.android.material.motion.runtime.targets.IncrementerTarget; 24 | 25 | import org.junit.Before; 26 | import org.junit.Test; 27 | import org.junit.runner.RunWith; 28 | import org.robolectric.Robolectric; 29 | import org.robolectric.RobolectricTestRunner; 30 | import org.robolectric.annotation.Config; 31 | 32 | import java.util.ArrayList; 33 | import java.util.HashMap; 34 | import java.util.List; 35 | 36 | import static com.google.common.truth.Truth.assertThat; 37 | 38 | @RunWith(RobolectricTestRunner.class) 39 | @Config(constants = BuildConfig.class, sdk = 21) 40 | public class LogcatTracerTests { 41 | 42 | private MotionRuntime runtime; 43 | 44 | @Before 45 | public void setUp() { 46 | runtime = new MotionRuntime(); 47 | runtime.addTracer(new LogcatTracer()); 48 | } 49 | 50 | @Test 51 | public void testAddingAndRemovingPlansToAndFromDifferentTargets() { 52 | Plan regularPlan = new NoOpPlan(); 53 | NamedPlan namedPlan = new NoOpPlan(); 54 | 55 | List targets = new ArrayList<>(); 56 | targets.add(new TextView(Robolectric.setupActivity(Activity.class))); 57 | targets.add(new Object()); 58 | targets.add(""); 59 | targets.add(" "); 60 | targets.add("\uD83D\uDC36"); 61 | targets.add(new ArrayList<>()); 62 | targets.add(new HashMap<>()); 63 | targets.add(-1); 64 | targets.add(0); 65 | targets.add(1); 66 | targets.add(Long.MAX_VALUE); 67 | targets.add(Long.MIN_VALUE); 68 | targets.add(null); 69 | targets.add(new IncrementerTarget()); 70 | 71 | boolean tracersThrew = false; 72 | try { 73 | for (Object target : targets) { 74 | runtime.addPlan(regularPlan, target); 75 | runtime.addNamedPlan(namedPlan, "text view altering named plan", target); 76 | runtime.removeNamedPlan("text view altering named plan", target); 77 | } 78 | } catch (Throwable t) { 79 | tracersThrew = true; 80 | } 81 | 82 | assertThat(tracersThrew).isFalse(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /library/src/test/java/com/google/android/material/motion/runtime/MotionRuntimeTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-present The Material Motion Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.android.material.motion.runtime; 17 | 18 | import android.app.Activity; 19 | import android.content.Context; 20 | import android.view.View; 21 | import android.widget.TextView; 22 | 23 | import com.google.android.material.motion.runtime.MotionRuntime.State; 24 | import com.google.android.material.motion.runtime.MotionRuntime.StateListener; 25 | import com.google.android.material.motion.runtime.PerformerFeatures.ContinuousPerforming; 26 | import com.google.android.material.motion.runtime.PerformerFeatures.ManualPerforming; 27 | import com.google.android.material.motion.runtime.plans.CounterAlteringPlan; 28 | import com.google.android.material.motion.runtime.plans.TextViewAlteringNamedPlan; 29 | import com.google.android.material.motion.runtime.targets.IncrementerTarget; 30 | import com.google.android.material.motion.runtime.testing.StepChoreographer; 31 | 32 | import org.junit.Before; 33 | import org.junit.Test; 34 | import org.junit.runner.RunWith; 35 | import org.robolectric.Robolectric; 36 | import org.robolectric.RobolectricTestRunner; 37 | import org.robolectric.annotation.Config; 38 | 39 | import java.util.ArrayList; 40 | import java.util.List; 41 | 42 | import static com.google.common.truth.Truth.assertThat; 43 | import static org.mockito.Mockito.mock; 44 | import static org.mockito.Mockito.times; 45 | import static org.mockito.Mockito.verify; 46 | 47 | @RunWith(RobolectricTestRunner.class) 48 | @Config(constants = BuildConfig.class, sdk = 21) 49 | public class MotionRuntimeTests { 50 | 51 | private static final float EPSILON = 0.0001f; 52 | 53 | private MotionRuntime runtime; 54 | private StepChoreographer choreographer; 55 | private TextView textView; 56 | 57 | @Before 58 | public void setUp() { 59 | Context context = Robolectric.setupActivity(Activity.class); 60 | runtime = new MotionRuntime(); 61 | choreographer = new StepChoreographer(); 62 | runtime.choreographer = choreographer; 63 | textView = new TextView(context); 64 | } 65 | 66 | @Test 67 | public void testInitialRuntimeState() { 68 | assertThat(runtime.getState()).isEqualTo(MotionRuntime.IDLE); 69 | } 70 | 71 | @Test 72 | public void testStandardPerformerRuntimeState() { 73 | runtime.addNamedPlan(new TextViewAlteringNamedPlan("standard"), "plan", textView); 74 | 75 | assertThat(runtime.getState()).isEqualTo(MotionRuntime.IDLE); 76 | } 77 | 78 | @Test 79 | public void testManualPerformerRuntimeState() { 80 | runtime.addNamedPlan(new ManualPlan("manual"), "plan", textView); 81 | 82 | assertThat(runtime.getState()).isEqualTo(MotionRuntime.ACTIVE); 83 | } 84 | 85 | @Test 86 | public void testTwoActivePerformersStillActive() { 87 | runtime.addNamedPlan(new ManualPlan("manual"), "plan", textView); 88 | 89 | assertThat(runtime.getState()).isEqualTo(MotionRuntime.ACTIVE); 90 | 91 | runtime.addPlan(new NeverEndingContinuousPlan("continuous"), textView); 92 | 93 | // Still active. 94 | assertThat(runtime.getState()).isEqualTo(MotionRuntime.ACTIVE); 95 | } 96 | 97 | @Test 98 | public void testManualPerformerUpdatesWithCorrectDelta() { 99 | runtime.addPlan(new ManualPlan("manual"), textView); 100 | 101 | // First frame updates with delta == 0. 102 | choreographer.advance(StepChoreographer.FRAME_MS); 103 | assertThat((float) textView.getTag()).isWithin(0f).of(0f); 104 | 105 | // Next frame updates with correct delta. 106 | choreographer.advance(StepChoreographer.FRAME_MS); 107 | assertThat((float) textView.getTag()).isWithin(EPSILON).of(StepChoreographer.FRAME_MS); 108 | } 109 | 110 | @Test 111 | public void testAddingMultipleRuntimeListeners() { 112 | TestRuntimeListener firstListener = new TestRuntimeListener(); 113 | TestRuntimeListener secondListener = new TestRuntimeListener(); 114 | runtime.addStateListener(firstListener); 115 | runtime.addStateListener(secondListener); 116 | 117 | runtime.addNamedPlan(new ManualPlan("manual one"), "plan", textView); 118 | runtime.addPlan(new TextViewAlteringNamedPlan("standard one"), textView); 119 | 120 | assertThat(firstListener.getState()).isEqualTo(MotionRuntime.ACTIVE); 121 | assertThat(secondListener.getState()).isEqualTo(MotionRuntime.ACTIVE); 122 | } 123 | 124 | @Test 125 | public void testAddOrderedMultipleRuntimeListeners() { 126 | TestRuntimeListener firstListener = new TestRuntimeListener(); 127 | TestRuntimeListener secondListener = new TestRuntimeListener(); 128 | runtime.addStateListener(firstListener); 129 | runtime.addStateListener(secondListener); 130 | 131 | runtime.addPlan(new TextViewAlteringNamedPlan("standard one"), textView); 132 | runtime.addNamedPlan(new ManualPlan("manual one"), "plan", textView); 133 | 134 | assertThat(firstListener.getState()).isEqualTo(MotionRuntime.ACTIVE); 135 | assertThat(secondListener.getState()).isEqualTo(MotionRuntime.ACTIVE); 136 | } 137 | 138 | @Test 139 | public void testRemovingRuntimeListeners() { 140 | TestRuntimeListener firstListener = new TestRuntimeListener(); 141 | TestRuntimeListener secondListener = new TestRuntimeListener(); 142 | runtime.addStateListener(firstListener); 143 | 144 | runtime.addStateListener(secondListener); 145 | runtime.removeStateListener(secondListener); 146 | 147 | runtime.addNamedPlan(new ManualPlan("manual"), "plan", textView); 148 | 149 | assertThat(firstListener.getState()).isEqualTo(MotionRuntime.ACTIVE); 150 | assertThat(secondListener.getState()).isEqualTo(MotionRuntime.IDLE); 151 | } 152 | 153 | @Test 154 | public void testAddingSameRuntimeListenerTwice() { 155 | StateListener listener = mock(StateListener.class); 156 | 157 | runtime.addStateListener(listener); 158 | runtime.addStateListener(listener); 159 | 160 | runtime.addPlan(new ManualPlan("manual"), textView); 161 | 162 | // Listener invoked only once. 163 | verify(listener, times(1)).onStateChange(runtime, MotionRuntime.ACTIVE); 164 | } 165 | 166 | @Test 167 | public void testNeverEndingDelegatePerformingRuntimeState() { 168 | runtime.addNamedPlan(new NeverEndingContinuousPlan("continuous"), "plan", textView); 169 | 170 | assertThat(runtime.getState()).isEqualTo(MotionRuntime.ACTIVE); 171 | } 172 | 173 | @Test 174 | public void testEndingContinuousPerformingRuntimeState() { 175 | runtime.addNamedPlan(new EndingContinuousPlan("continuous"), "plan", textView); 176 | 177 | assertThat(runtime.getState()).isEqualTo(MotionRuntime.IDLE); 178 | } 179 | 180 | @Test 181 | public void testAddingPlanDirectlyToRuntime() { 182 | runtime.addPlan(new NeverEndingContinuousPlan("continuous"), textView); 183 | 184 | assertThat(runtime.getState()).isEqualTo(MotionRuntime.ACTIVE); 185 | } 186 | 187 | @Test 188 | public void testAddingTextViewAlteringNamedPlanDirectlyToRuntime() { 189 | runtime.addPlan(new TextViewAlteringNamedPlan("standard"), textView); 190 | 191 | assertThat(textView.getText()).isEqualTo(" standard"); 192 | } 193 | 194 | @Test 195 | public void testAddingNamedPlan() { 196 | runtime.addNamedPlan(new NamedTargetAlteringPlan(), "common_name", textView); 197 | 198 | assertThat(textView.getText()).isEqualTo(" addPlanInvoked"); 199 | } 200 | 201 | @Test 202 | public void testAddAndRemoveTheSameNamedPlan() { 203 | runtime.addNamedPlan(new NamedTargetAlteringPlan(), "name_one", textView); 204 | runtime.removeNamedPlan("name_one", textView); 205 | 206 | assertThat(textView.getText()).isEqualTo(" addPlanInvoked removePlanInvoked"); 207 | } 208 | 209 | @Test 210 | public void testRemoveNamedPlanThatWasNeverAdded() { 211 | runtime.addNamedPlan(new NamedTargetAlteringPlan(), "common_name", textView); 212 | runtime.removeNamedPlan("this_was_never_added", textView); 213 | 214 | assertThat(textView.getText()).isEqualTo(" addPlanInvoked"); 215 | } 216 | 217 | @Test 218 | public void testNamedPlansMakeMultipleAddCalls() { 219 | runtime.addNamedPlan(new NamedTargetAlteringPlan(), "one", textView); 220 | runtime.addNamedPlan(new NamedTargetAlteringPlan(), "two", textView); 221 | 222 | assertThat(textView.getText()).isEqualTo(" addPlanInvoked addPlanInvoked"); 223 | } 224 | 225 | @Test 226 | public void testAddAndRemoveCallbacksAreInvoked() { 227 | NamedTargetAlteringPlan plan1 = new NamedTargetAlteringPlan(); 228 | NamedTargetAlteringPlan plan2 = new NamedTargetAlteringPlan(); 229 | MotionRuntime runtime = new MotionRuntime(); 230 | runtime.addNamedPlan(plan1, "common_name", textView); 231 | runtime.addNamedPlan(plan2, "common_name", textView); 232 | 233 | assertThat(textView.getText()).isEqualTo(" addPlanInvoked removePlanInvoked addPlanInvoked"); 234 | } 235 | 236 | @Test 237 | public void testNamedPlansOverwriteOneAnother() { 238 | IncrementerTarget incrementerTarget = new IncrementerTarget(); 239 | CounterAlteringPlan planA = new CounterAlteringPlan(); 240 | CounterAlteringPlan planB = new CounterAlteringPlan(); 241 | 242 | runtime.addNamedPlan(planA, "one", incrementerTarget); 243 | runtime.addNamedPlan(planB, "one", incrementerTarget); 244 | 245 | assertThat(incrementerTarget.addCounter).isEqualTo(2); 246 | assertThat(incrementerTarget.removeCounter).isEqualTo(1); 247 | } 248 | 249 | @Test 250 | public void testAddingTheSameNamedPlanToTheSameTarget() { 251 | IncrementerTarget incrementerTarget = new IncrementerTarget(); 252 | runtime.addNamedPlan(new CounterAlteringPlan(), "one", incrementerTarget); 253 | runtime.addNamedPlan(new CounterAlteringPlan(), "one", incrementerTarget); 254 | 255 | assertThat(incrementerTarget.addCounter).isEqualTo(2); 256 | assertThat(incrementerTarget.removeCounter).isEqualTo(1); 257 | } 258 | 259 | @Test 260 | public void testAddingSimilarNamesToTheSameTarget() { 261 | IncrementerTarget incrementerTarget = new IncrementerTarget(); 262 | runtime.addNamedPlan(new CounterAlteringPlan(), "one", incrementerTarget); 263 | runtime.addNamedPlan(new CounterAlteringPlan(), "One", incrementerTarget); 264 | runtime.addNamedPlan(new CounterAlteringPlan(), "1", incrementerTarget); 265 | runtime.addNamedPlan(new CounterAlteringPlan(), "ONE", incrementerTarget); 266 | 267 | assertThat(incrementerTarget.addCounter).isEqualTo(4); 268 | assertThat(incrementerTarget.removeCounter).isEqualTo(0); 269 | } 270 | 271 | @Test 272 | public void testAddingNamedPlansToDifferentTargets() { 273 | IncrementerTarget firstIncrementerTarget = new IncrementerTarget(); 274 | IncrementerTarget secondIncrementerTarget = new IncrementerTarget(); 275 | CounterAlteringPlan plan = new CounterAlteringPlan(); 276 | 277 | runtime.addNamedPlan(plan, "one", firstIncrementerTarget); 278 | runtime.addNamedPlan(plan, "one", secondIncrementerTarget); 279 | 280 | assertThat(firstIncrementerTarget.addCounter).isEqualTo(1); 281 | assertThat(firstIncrementerTarget.removeCounter).isEqualTo(0); 282 | assertThat(secondIncrementerTarget.addCounter).isEqualTo(1); 283 | assertThat(secondIncrementerTarget.removeCounter).isEqualTo(0); 284 | } 285 | 286 | @Test 287 | public void testNamedPlanOnlyInvokesNamedPlanCallbacks() { 288 | runtime.addNamedPlan(new NamedTargetAlteringPlan(), "one", textView); 289 | 290 | assertThat(textView.getText().toString().contains("regularAddPlanInvoked")); 291 | } 292 | 293 | @Test 294 | public void testPlanOnlyInvokedPlanCallbacks() { 295 | runtime.addPlan(new RegularPlanTargetAlteringPlan(), textView); 296 | 297 | assertThat(!textView.getText().toString().contains("addPlanInvoked")); 298 | assertThat(textView.getText().toString().contains("regularAddPlanInvoked")); 299 | } 300 | 301 | @Test 302 | public void testPlanStorageExample() { 303 | StorageNamedPlan plan = new StorageNamedPlan(); 304 | List list = new ArrayList<>(); 305 | runtime.addNamedPlan(plan, "one", list); 306 | 307 | assertThat(list.size()).isEqualTo(1); 308 | assertThat(list.get(0)).isEqualTo("one"); 309 | } 310 | 311 | @Test 312 | public void testPlanStorageRemoveNamedPlanExample() { 313 | List list = new ArrayList(); 314 | runtime.removeNamedPlan("never_added", list); 315 | 316 | assertThat(list.size() == 0); 317 | } 318 | 319 | @Test 320 | public void testExceptionThrownWhenAddingANamedPlanWithoutAName() { 321 | boolean errorThrown = false; 322 | try { 323 | runtime.addNamedPlan(new NamedTargetAlteringPlan(), null, textView); 324 | } catch (IllegalArgumentException e) { 325 | errorThrown = true; 326 | } 327 | assertThat(errorThrown).isTrue(); 328 | } 329 | 330 | @Test 331 | public void testExceptionThrownWhenAddingANamedPlanWithAnEmptyName() { 332 | boolean errorThrown = false; 333 | try { 334 | runtime.addNamedPlan(new NamedTargetAlteringPlan(), "", textView); 335 | } catch (IllegalArgumentException e) { 336 | errorThrown = true; 337 | } 338 | assertThat(errorThrown).isTrue(); 339 | } 340 | 341 | @Test 342 | public void testExceptionThrownWhenRemovingANamedPlanWithoutAName() { 343 | boolean errorThrown = false; 344 | try { 345 | runtime.removeNamedPlan(null, textView); 346 | } catch (IllegalArgumentException e) { 347 | errorThrown = true; 348 | } 349 | assertThat(errorThrown).isTrue(); 350 | } 351 | 352 | @Test 353 | public void testExceptionThrownWhenRemovingANamedPlanWithAnEmptyName() { 354 | boolean errorThrown = false; 355 | try { 356 | runtime.removeNamedPlan("", textView); 357 | } catch (IllegalArgumentException e) { 358 | errorThrown = true; 359 | } 360 | assertThat(errorThrown).isTrue(); 361 | } 362 | 363 | @Test 364 | public void testTracersCanBeAddedToARuntime() { 365 | StorageTracing firstTracer = new StorageTracing(); 366 | StorageTracing secondTracer = new StorageTracing(); 367 | runtime.addTracer(firstTracer); 368 | runtime.addTracer(secondTracer); 369 | 370 | assertThat(runtime.getTracers().size()).isEqualTo(2); 371 | } 372 | 373 | @Test 374 | public void testTracersAreOnlyAddedOnceToARuntime() { 375 | StorageTracing firstTracer = new StorageTracing(); 376 | runtime.addTracer(firstTracer); 377 | runtime.addTracer(firstTracer); 378 | 379 | assertThat(runtime.getTracers().size()).isEqualTo(1); 380 | } 381 | 382 | @Test 383 | public void testTracersCanBeRemovedFromARuntime() { 384 | StorageTracing firstTracer = new StorageTracing(); 385 | StorageTracing secondTracer = new StorageTracing(); 386 | runtime.addTracer(firstTracer); 387 | runtime.addTracer(secondTracer); 388 | runtime.removeTracer(firstTracer); 389 | 390 | assertThat(runtime.getTracers().size()).isEqualTo(1); 391 | assertThat(runtime.getTracers().contains(secondTracer)).isTrue(); 392 | } 393 | 394 | @Test 395 | public void testRegularPlansAreCommunicatedViaTracers() { 396 | StorageTracing storageTracer = new StorageTracing(); 397 | Plan plan = new RegularPlanTargetAlteringPlan(); 398 | 399 | runtime.addTracer(storageTracer); 400 | runtime.addPlan(plan, textView); 401 | 402 | assertThat(storageTracer.addedRegularPlans.get(0) instanceof RegularPlanTargetAlteringPlan).isTrue(); 403 | assertThat(textView.getText()).isEqualTo(" regularAddPlanInvoked"); 404 | } 405 | 406 | @Test 407 | public void testNamedPlansAreCommunicatedViaTracers() { 408 | StorageTracing storageTracer = new StorageTracing(); 409 | runtime.addTracer(storageTracer); 410 | 411 | runtime.addNamedPlan(new TextViewAlteringNamedPlan("standard"), "plan", textView); 412 | runtime.removeNamedPlan("plan", textView); 413 | 414 | assertThat(storageTracer.addedNamePlans.size()).isEqualTo(1); 415 | assertThat(storageTracer.removedNamePlans.size()).isEqualTo(1); 416 | assertThat("plan").isEqualTo(storageTracer.addedNamePlans.get(0)); 417 | assertThat("plan").isEqualTo(storageTracer.removedNamePlans.get(0)); 418 | } 419 | 420 | @Test 421 | public void testPlansReusePerformers() { 422 | StorageTracing storageTracer = new StorageTracing(); 423 | runtime.addTracer(storageTracer); 424 | 425 | runtime.addPlan(new ManualPlan("manual one"), textView); 426 | runtime.addNamedPlan(new TextViewAlteringNamedPlan("text view altering one"), "plan two", textView); 427 | runtime.addNamedPlan(new TextViewAlteringNamedPlan("text view altering two"), "plan three", textView); 428 | runtime.removeNamedPlan("plan two", textView); 429 | 430 | assertThat(storageTracer.performers.size()).isEqualTo(2); 431 | } 432 | 433 | @Test 434 | public void testPerformerCallbacksAreInvokedBeforeTracers() { 435 | TrackingTracing trackingTracer = new TrackingTracing(); 436 | TrackingPlan trackingPlan = new TrackingPlan(); 437 | 438 | runtime.addTracer(trackingTracer); 439 | runtime.addNamedPlan(trackingPlan, "tracking_plan_name", trackingTracer); 440 | runtime.removeNamedPlan("tracking_plan_name", trackingTracer); 441 | 442 | List expectedEvents = new ArrayList<>(); 443 | expectedEvents.add("performerAddPlan"); 444 | expectedEvents.add("onAddNamedPlan"); 445 | expectedEvents.add("performerRemovePlan"); 446 | expectedEvents.add("onRemoveNamedPlan"); 447 | 448 | assertThat(trackingTracer.getEvents()).isEqualTo(expectedEvents); 449 | } 450 | 451 | private static class TrackingTracing implements Tracing { 452 | 453 | List events = new ArrayList<>(); 454 | 455 | @Override 456 | public void onAddPlan(Plan plan, T target) { 457 | 458 | } 459 | 460 | @Override 461 | public void onAddNamedPlan(NamedPlan plan, String name, T target) { 462 | events.add("onAddNamedPlan"); 463 | } 464 | 465 | @Override 466 | public void onRemoveNamedPlan(String name, T target) { 467 | events.add("onRemoveNamedPlan"); 468 | } 469 | 470 | @Override 471 | public void onCreatePerformer(Performer performer, T target) { 472 | 473 | } 474 | 475 | List getEvents() { 476 | return events; 477 | } 478 | } 479 | 480 | private static class TrackingPlan extends NamedPlan { 481 | 482 | @Override 483 | public Class> getPerformerClass() { 484 | return TrackingPlanPerformer.class; 485 | } 486 | } 487 | 488 | public static class StorageTracing implements Tracing { 489 | 490 | List performers = new ArrayList<>(); 491 | List addedRegularPlans = new ArrayList<>(); 492 | List addedNamePlans = new ArrayList<>(); 493 | List removedNamePlans = new ArrayList<>(); 494 | 495 | @Override 496 | public void onAddPlan(Plan plan, T target) { 497 | addedRegularPlans.add(plan); 498 | } 499 | 500 | @Override 501 | public void onAddNamedPlan(NamedPlan plan, String name, T target) { 502 | addedNamePlans.add(name); 503 | } 504 | 505 | @Override 506 | public void onRemoveNamedPlan(String name, T target) { 507 | removedNamePlans.add(name); 508 | } 509 | 510 | @Override 511 | public void onCreatePerformer(Performer performer, T target) { 512 | performers.add(performer); 513 | } 514 | } 515 | 516 | private static class StorageNamedPlan extends NamedPlan> { 517 | 518 | @Override 519 | public Class>> getPerformerClass() { 520 | return StoragePlanPerformer.class; 521 | } 522 | } 523 | 524 | private static class RegularPlanTargetAlteringPlan extends Plan { 525 | 526 | @Override 527 | public Class> getPerformerClass() { 528 | return GenericPlanPerformer.class; 529 | } 530 | } 531 | 532 | private static class NamedTargetAlteringPlan extends NamedPlan { 533 | 534 | @Override 535 | public Class> getPerformerClass() { 536 | return GenericPlanPerformer.class; 537 | } 538 | } 539 | 540 | private static class ManualPlan extends NamedPlan { 541 | 542 | private final String text; 543 | 544 | private ManualPlan(String text) { 545 | this.text = text; 546 | } 547 | 548 | @Override 549 | public Class> getPerformerClass() { 550 | return ManualPerformer.class; 551 | } 552 | } 553 | 554 | private static class NeverEndingContinuousPlan extends NamedPlan { 555 | 556 | private final String text; 557 | 558 | private NeverEndingContinuousPlan(String text) { 559 | this.text = text; 560 | } 561 | 562 | @Override 563 | public Class> getPerformerClass() { 564 | return NeverEndingContinuousPerformer.class; 565 | } 566 | } 567 | 568 | private static class EndingContinuousPlan extends NamedPlan { 569 | 570 | private final String text; 571 | 572 | private EndingContinuousPlan(String text) { 573 | this.text = text; 574 | } 575 | 576 | @Override 577 | public Class> getPerformerClass() { 578 | return EndingContinuousPerformer.class; 579 | } 580 | } 581 | 582 | public static class TrackingPlanPerformer extends NamedPerformer { 583 | 584 | @Override 585 | public void addPlan(Plan plan) { 586 | throw new UnsupportedOperationException(); 587 | } 588 | 589 | @Override 590 | public void addPlan(NamedPlan plan, String name) { 591 | TrackingTracing tracer = getTarget(); 592 | tracer.events.add("performerAddPlan"); 593 | } 594 | 595 | @Override 596 | public void removePlan(String name) { 597 | TrackingTracing tracer = getTarget(); 598 | tracer.events.add("performerRemovePlan"); 599 | } 600 | } 601 | 602 | public static class StoragePlanPerformer extends NamedPerformer> { 603 | 604 | @Override 605 | public void addPlan(Plan> plan) { 606 | throw new UnsupportedOperationException(); 607 | } 608 | 609 | @Override 610 | public void addPlan(NamedPlan> plan, String name) { 611 | List target = getTarget(); 612 | target.add(name); 613 | } 614 | 615 | @Override 616 | public void removePlan(String name) { 617 | List target = getTarget(); 618 | target.add(name); 619 | } 620 | } 621 | 622 | public static class GenericPlanPerformer extends NamedPerformer { 623 | 624 | @Override 625 | public void addPlan(Plan plan) { 626 | TextView target = getTarget(); 627 | target.setText(target.getText() + " regularAddPlanInvoked"); 628 | } 629 | 630 | @Override 631 | public void addPlan(NamedPlan plan, String name) { 632 | TextView target = getTarget(); 633 | target.setText(target.getText() + " addPlanInvoked"); 634 | } 635 | 636 | @Override 637 | public void removePlan(String name) { 638 | TextView target = getTarget(); 639 | target.setText(target.getText() + " removePlanInvoked"); 640 | } 641 | } 642 | 643 | public static class ManualPerformer extends NamedPerformer implements ManualPerforming { 644 | 645 | @Override 646 | public int update(float deltaTimeMs) { 647 | // Incredibly ugly hack. Tests need to inspect that a certain deltaTimeMs was passed into 648 | // this function. Save it to the target. Perhaps tracing support will make this easier to 649 | // test. 650 | View target = getTarget(); 651 | target.setTag(deltaTimeMs); 652 | return MotionRuntime.ACTIVE; 653 | } 654 | 655 | @Override 656 | public void addPlan(Plan plan) { 657 | } 658 | 659 | @Override 660 | public void addPlan(NamedPlan plan, String name) { 661 | addPlan(plan); 662 | } 663 | 664 | @Override 665 | public void removePlan(String name) { 666 | } 667 | } 668 | 669 | public static class NeverEndingContinuousPerformer extends NamedPerformer 670 | implements ContinuousPerforming { 671 | 672 | private IsActiveTokenGenerator isActiveTokenGenerator; 673 | 674 | @Override 675 | public void addPlan(Plan plan) { 676 | // start the plan, but never finish it 677 | IsActiveToken token = isActiveTokenGenerator.generate(); 678 | } 679 | 680 | @Override 681 | public void setIsActiveTokenGenerator(IsActiveTokenGenerator isActiveTokenGenerator) { 682 | this.isActiveTokenGenerator = isActiveTokenGenerator; 683 | } 684 | 685 | @Override 686 | public void addPlan(NamedPlan plan, String name) { 687 | addPlan(plan); 688 | } 689 | 690 | @Override 691 | public void removePlan(String name) { 692 | } 693 | } 694 | 695 | public static class EndingContinuousPerformer extends NamedPerformer 696 | implements ContinuousPerforming { 697 | 698 | private IsActiveTokenGenerator isActiveTokenGenerator; 699 | 700 | @Override 701 | public void addPlan(Plan plan) { 702 | throw new UnsupportedOperationException(); 703 | } 704 | 705 | @Override 706 | public void setIsActiveTokenGenerator(IsActiveTokenGenerator isActiveTokenGenerator) { 707 | this.isActiveTokenGenerator = isActiveTokenGenerator; 708 | } 709 | 710 | @Override 711 | public void addPlan(NamedPlan plan, String name) { 712 | // start and end it immediately 713 | IsActiveToken token = isActiveTokenGenerator.generate(); 714 | token.terminate(); 715 | } 716 | 717 | @Override 718 | public void removePlan(String name) { 719 | } 720 | } 721 | 722 | public static class TestRuntimeListener implements MotionRuntime.StateListener { 723 | 724 | private int state; 725 | 726 | @Override 727 | public void onStateChange(MotionRuntime runtime, @State int newState) { 728 | this.state = newState; 729 | } 730 | 731 | public int getState() { 732 | return state; 733 | } 734 | } 735 | } 736 | -------------------------------------------------------------------------------- /library/src/test/java/com/google/android/material/motion/runtime/PerformerFeaturesTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-present The Material Motion Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.android.material.motion.runtime; 17 | 18 | import org.junit.Test; 19 | import org.junit.runner.RunWith; 20 | import org.robolectric.RobolectricTestRunner; 21 | import org.robolectric.annotation.Config; 22 | 23 | @RunWith(RobolectricTestRunner.class) 24 | @Config(constants = BuildConfig.class, sdk = 21) 25 | public class PerformerFeaturesTests { 26 | 27 | @Test(expected = UnsupportedOperationException.class) 28 | public void constructorIsDiabled() { 29 | new PerformerFeatures(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /library/src/test/java/com/google/android/material/motion/runtime/PlanFeaturesTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-present The Material Motion Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.android.material.motion.runtime; 17 | 18 | import org.junit.Test; 19 | import org.junit.runner.RunWith; 20 | import org.robolectric.RobolectricTestRunner; 21 | import org.robolectric.annotation.Config; 22 | 23 | @RunWith(RobolectricTestRunner.class) 24 | @Config(constants = BuildConfig.class, sdk = 21) 25 | public class PlanFeaturesTests { 26 | 27 | @Test(expected = UnsupportedOperationException.class) 28 | public void constructorIsDiabled() { 29 | new PlanFeatures(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /library/src/test/java/com/google/android/material/motion/runtime/PlanTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-present The Material Motion Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.android.material.motion.runtime; 17 | 18 | import org.junit.Test; 19 | import org.junit.runner.RunWith; 20 | import org.robolectric.RobolectricTestRunner; 21 | import org.robolectric.annotation.Config; 22 | 23 | import static org.mockito.Mockito.doThrow; 24 | import static org.mockito.Mockito.spy; 25 | 26 | @RunWith(RobolectricTestRunner.class) 27 | @Config(constants = BuildConfig.class, sdk = 21) 28 | public class PlanTests { 29 | 30 | @Test(expected = AssertionError.class) 31 | public void cloneExceptionThrowsAssertionError() throws CloneNotSupportedException { 32 | Plan plan = spy(new Plan() { 33 | @Override 34 | protected Class> getPerformerClass() { 35 | return null; 36 | } 37 | }); 38 | 39 | doThrow(new CloneNotSupportedException()).when(plan).superClone(); 40 | 41 | plan.clone(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /library/src/test/java/com/google/android/material/motion/runtime/RealChoreographerCompatTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-present The Material Motion Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.android.material.motion.runtime; 17 | 18 | import org.junit.BeforeClass; 19 | import org.junit.runner.RunWith; 20 | import org.robolectric.RobolectricTestRunner; 21 | import org.robolectric.annotation.Config; 22 | 23 | @RunWith(RobolectricTestRunner.class) 24 | @Config(constants = BuildConfig.class, sdk = 21) 25 | public class RealChoreographerCompatTests extends ChoreographerCompatTests { 26 | 27 | @BeforeClass 28 | public static void oneTimeSetUp() { 29 | ChoreographerCompat.threadInstance = ChoreographerCompat.createThreadInstance(); 30 | } 31 | 32 | // Super-class runs all tests. 33 | } 34 | -------------------------------------------------------------------------------- /library/src/test/java/com/google/android/material/motion/runtime/TargetScopeTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-present The Material Motion Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.android.material.motion.runtime; 17 | 18 | import android.app.Activity; 19 | import android.content.Context; 20 | import android.view.View; 21 | 22 | import com.google.android.material.motion.runtime.MotionRuntime.State; 23 | import com.google.android.material.motion.runtime.Performer.PerformerInstantiationException; 24 | import com.google.android.material.motion.runtime.PerformerFeatures.ContinuousPerforming; 25 | import com.google.android.material.motion.runtime.PerformerFeatures.ContinuousPerforming.IsActiveToken; 26 | import com.google.android.material.motion.runtime.PerformerFeatures.ContinuousPerforming.IsActiveTokenGenerator; 27 | import com.google.android.material.motion.runtime.PerformerFeatures.ManualPerforming; 28 | import com.google.android.material.motion.runtime.testing.StepChoreographer; 29 | 30 | import org.junit.Before; 31 | import org.junit.Rule; 32 | import org.junit.Test; 33 | import org.junit.rules.ExpectedException; 34 | import org.junit.runner.RunWith; 35 | import org.robolectric.Robolectric; 36 | import org.robolectric.RobolectricTestRunner; 37 | import org.robolectric.annotation.Config; 38 | 39 | import static com.google.common.truth.Truth.assertThat; 40 | import static org.mockito.Mockito.mock; 41 | 42 | @RunWith(RobolectricTestRunner.class) 43 | @Config(constants = BuildConfig.class, sdk = 21) 44 | public class TargetScopeTests { 45 | 46 | /** 47 | * One frame in ms. 48 | */ 49 | private static final float FRAME = 16; 50 | private MotionRuntime runtime; 51 | private View target; 52 | 53 | @Rule 54 | public ExpectedException thrown = ExpectedException.none(); 55 | 56 | @Before 57 | public void setUp() { 58 | runtime = new MotionRuntime(); 59 | runtime.choreographer = new StepChoreographer(); 60 | Context context = Robolectric.setupActivity(Activity.class); 61 | target = new View(context); 62 | } 63 | 64 | @Test 65 | public void manualPerformerIdleChangesRuntimeState() { 66 | TargetScope targetScope = new TargetScope(runtime); 67 | 68 | // Runtime starts as idle. 69 | assertThat(runtime.getState()).isEqualTo(MotionRuntime.IDLE); 70 | 71 | targetScope.commitPlan(new ManualPlan(MotionRuntime.IDLE), target); 72 | 73 | // Runtime becomes active when a manual performer exists. 74 | assertThat(runtime.getState()).isEqualTo(MotionRuntime.ACTIVE); 75 | 76 | targetScope.update(FRAME); 77 | 78 | // Runtime becomes idle when manual performer is idle. 79 | assertThat(runtime.getState()).isEqualTo(MotionRuntime.IDLE); 80 | } 81 | 82 | @Test(expected = PerformerInstantiationException.class) 83 | public void privatePerformerThrowsException() { 84 | TargetScope targetScope = new TargetScope(runtime); 85 | 86 | targetScope.commitPlan(new PrivatePlan(), target); 87 | } 88 | 89 | @Test(expected = PerformerInstantiationException.class) 90 | public void oneArgConstructorPerformerThrowsException() { 91 | TargetScope targetScope = new TargetScope(runtime); 92 | 93 | targetScope.commitPlan(new OneArgConstructorPlan(), target); 94 | } 95 | 96 | @Test 97 | public void canRequestMultipleTokensForSamePerformer() { 98 | TargetScope targetScope = new TargetScope(runtime); 99 | ContinuousPerforming performer = mock(ContinuousPerforming.class); 100 | 101 | IsActiveTokenGenerator generator = targetScope.createIsActiveTokenGenerator(performer); 102 | 103 | IsActiveToken token1 = generator.generate(); 104 | IsActiveToken token2 = generator.generate();// Should not crash. 105 | 106 | token1.terminate(); 107 | token2.terminate(); 108 | } 109 | 110 | @Test 111 | public void canNotTerminateTokenMultipleTimes() { 112 | TargetScope targetScope = new TargetScope(runtime); 113 | ContinuousPerforming performer = mock(ContinuousPerforming.class); 114 | 115 | IsActiveTokenGenerator generator = targetScope.createIsActiveTokenGenerator(performer); 116 | IsActiveToken token = generator.generate(); 117 | 118 | token.terminate(); 119 | 120 | thrown.expect(IllegalStateException.class); 121 | token.terminate(); 122 | } 123 | 124 | private static class ManualPlan extends Plan { 125 | 126 | @State 127 | private int state; 128 | 129 | private ManualPlan(@State int state) { 130 | this.state = state; 131 | } 132 | 133 | @Override 134 | public Class> getPerformerClass() { 135 | return ManualPerformer.class; 136 | } 137 | } 138 | 139 | public static class ManualPerformer extends Performer implements ManualPerforming { 140 | 141 | @State 142 | int state; 143 | 144 | @Override 145 | public int update(float deltaTimeMs) { 146 | return state; 147 | } 148 | 149 | @Override 150 | public void addPlan(Plan plan) { 151 | state = ((ManualPlan) plan).state; 152 | } 153 | } 154 | 155 | private static class PrivatePlan extends Plan { 156 | 157 | @Override 158 | public Class> getPerformerClass() { 159 | return PrivatePerformer.class; 160 | } 161 | } 162 | 163 | private static class PrivatePerformer extends Performer { 164 | 165 | @Override 166 | public void addPlan(Plan plan) { 167 | } 168 | } 169 | 170 | private static class OneArgConstructorPlan extends Plan { 171 | 172 | @Override 173 | public Class> getPerformerClass() { 174 | return OneArgConstructorPerformer.class; 175 | } 176 | } 177 | 178 | public static class OneArgConstructorPerformer extends Performer { 179 | 180 | public OneArgConstructorPerformer(Object param) { 181 | } 182 | 183 | @Override 184 | public void addPlan(Plan plan) { 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /library/src/test/java/com/google/android/material/motion/runtime/plans/CounterAlteringPlan.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 - present The Material Motion Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.material.motion.runtime.plans; 18 | 19 | import com.google.android.material.motion.runtime.NamedPerformer; 20 | import com.google.android.material.motion.runtime.NamedPlan; 21 | import com.google.android.material.motion.runtime.Performer; 22 | import com.google.android.material.motion.runtime.Plan; 23 | import com.google.android.material.motion.runtime.targets.IncrementerTarget; 24 | 25 | public class CounterAlteringPlan extends NamedPlan { 26 | 27 | @Override 28 | public Class> getPerformerClass() { 29 | return CounterAlteringPerformer.class; 30 | } 31 | 32 | public static class CounterAlteringPerformer extends NamedPerformer { 33 | 34 | @Override 35 | public void addPlan(Plan plan) { 36 | IncrementerTarget target = getTarget(); 37 | target.addCounter += 1; 38 | } 39 | 40 | @Override 41 | public void addPlan(NamedPlan plan, String name) { 42 | IncrementerTarget target = getTarget(); 43 | target.addCounter += 1; 44 | } 45 | 46 | @Override 47 | public void removePlan(String name) { 48 | IncrementerTarget target = getTarget(); 49 | target.removeCounter += 1; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /library/src/test/java/com/google/android/material/motion/runtime/plans/NoOpPlan.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-present The Material Motion Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.android.material.motion.runtime.plans; 17 | 18 | import com.google.android.material.motion.runtime.NamedPerformer; 19 | import com.google.android.material.motion.runtime.NamedPlan; 20 | import com.google.android.material.motion.runtime.Plan; 21 | 22 | /** 23 | * A plan that takes any target and does nothing. 24 | */ 25 | public class NoOpPlan extends NamedPlan { 26 | @Override 27 | public Class> getPerformerClass() { 28 | return NoOpPerformer.class; 29 | } 30 | 31 | public static class NoOpPerformer extends NamedPerformer { 32 | @Override 33 | public void addPlan(Plan plan) { 34 | // No-op. 35 | } 36 | 37 | @Override 38 | public void addPlan(NamedPlan plan, String name) { 39 | // No-op. 40 | } 41 | 42 | @Override 43 | public void removePlan(String name) { 44 | // No-op. 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /library/src/test/java/com/google/android/material/motion/runtime/plans/TextViewAlteringNamedPlan.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 - present The Material Motion Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.material.motion.runtime.plans; 18 | 19 | import android.widget.TextView; 20 | 21 | import com.google.android.material.motion.runtime.NamedPerformer; 22 | import com.google.android.material.motion.runtime.NamedPlan; 23 | import com.google.android.material.motion.runtime.Plan; 24 | 25 | public class TextViewAlteringNamedPlan extends NamedPlan { 26 | 27 | private final String text; 28 | 29 | public TextViewAlteringNamedPlan(String text) { 30 | this.text = text; 31 | } 32 | 33 | @Override 34 | public Class> getPerformerClass() { 35 | return TextViewAlteringPerformer.class; 36 | } 37 | 38 | public static class TextViewAlteringPerformer extends NamedPerformer { 39 | 40 | @Override 41 | public void addPlan(Plan plan) { 42 | TextView target = getTarget(); 43 | TextViewAlteringNamedPlan textViewAlteringNamedPlan = (TextViewAlteringNamedPlan) plan; 44 | target.setText(target.getText() + " " + textViewAlteringNamedPlan.text); 45 | } 46 | 47 | @Override 48 | public void addPlan(NamedPlan plan, String name) { 49 | addPlan(plan); 50 | } 51 | 52 | @Override 53 | public void removePlan(String name) { 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /library/src/test/java/com/google/android/material/motion/runtime/targets/IncrementerTarget.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 - present The Material Motion Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.material.motion.runtime.targets; 18 | 19 | public class IncrementerTarget { 20 | public int addCounter = 0; 21 | public int removeCounter = 0; 22 | } 23 | -------------------------------------------------------------------------------- /library/src/test/java/com/google/android/material/motion/runtime/testing/StepChoreographerTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-present The Material Motion Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.android.material.motion.runtime.testing; 17 | 18 | import com.google.android.material.motion.runtime.BuildConfig; 19 | import com.google.android.material.motion.runtime.ChoreographerCompat.FrameCallback; 20 | 21 | import org.junit.Test; 22 | import org.junit.runner.RunWith; 23 | import org.robolectric.RobolectricTestRunner; 24 | import org.robolectric.annotation.Config; 25 | 26 | import static com.google.common.truth.Truth.assertThat; 27 | 28 | @RunWith(RobolectricTestRunner.class) 29 | @Config(constants = BuildConfig.class, sdk = 21) 30 | public class StepChoreographerTests { 31 | 32 | /** 33 | * One frame in ms. 34 | */ 35 | private static final long FRAME = 17; 36 | 37 | @Test 38 | public void postFrameCallback() { 39 | StepChoreographer choreographer = new StepChoreographer(); 40 | TestFrameCallback callback = new TestFrameCallback(); 41 | 42 | choreographer.postFrameCallback(callback); 43 | choreographer.advance(FRAME); 44 | 45 | assertThat(callback.didFrame).isTrue(); 46 | } 47 | 48 | @Test 49 | public void postFrameCallbackDelayed() { 50 | StepChoreographer choreographer = new StepChoreographer(); 51 | TestFrameCallback callback = new TestFrameCallback(); 52 | 53 | choreographer.postFrameCallbackDelayed(callback, FRAME + 1); 54 | 55 | choreographer.advance(FRAME); 56 | assertThat(callback.didFrame).isFalse(); 57 | 58 | choreographer.advance(FRAME); 59 | assertThat(callback.didFrame).isTrue(); 60 | } 61 | 62 | private static class TestFrameCallback extends FrameCallback { 63 | public boolean didFrame = false; 64 | 65 | @Override 66 | public void doFrame(long frameTimeNanos) { 67 | didFrame = true; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /local-dependency-substitution.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-present The Material Motion Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* 18 | * This Gradle script does 4 things that enable local dependencies. 19 | * 20 | * 1. Ensure config file exists. 21 | * 2. Substitute each dependencies declared in the file for local versions. 22 | * 3. Publish each local dependencies on every build. 23 | * 4. Alert the user. 24 | */ 25 | 26 | def config = new File('local.dependencies') 27 | def substitutions = [] as Set 28 | 29 | /* 1. Ensure local.dependencies exists */ 30 | 31 | config.exists() || config << new File('.local.dependencies.template').text 32 | 33 | /* 2. Substitute each dependencies declared in local.dependencies with the ":local" version. */ 34 | 35 | subprojects { 36 | repositories { 37 | mavenLocal() 38 | } 39 | 40 | def trim = { line -> 41 | def comment = line.indexOf('#') 42 | if (comment != -1) { 43 | line = line.substring(0, comment) 44 | } 45 | line.trim() 46 | } 47 | 48 | configurations.all { 49 | List localDependencies = ( 50 | config as String[] 51 | ).collect { 52 | def line = trim it 53 | if (line) { 54 | this.project.dependencies.create("$line:local") 55 | } 56 | }.findAll() 57 | 58 | resolutionStrategy.dependencySubstitution.all { DependencySubstitution dependency -> 59 | if (dependency.requested instanceof ModuleComponentSelector) { 60 | ModuleComponentSelector requested = dependency.requested; 61 | 62 | Dependency local = localDependencies.find { 63 | it.group == requested.group && it.name == requested.module 64 | } 65 | 66 | if (local) { 67 | substitutions << local 68 | dependency.useTarget([ 69 | group : local.group, 70 | name : local.name, 71 | version: local.version, 72 | ]) 73 | logger.info("Substituded local dependency for: $requested.") 74 | } 75 | } 76 | } 77 | } 78 | } 79 | 80 | /* 3. Publish each local dependencies's changes to the local maven repository on every build. */ 81 | 82 | subprojects { 83 | afterEvaluate { 84 | preBuild.dependsOn installLocalDependencies 85 | } 86 | 87 | task installLocalDependencies << { 88 | configurations 89 | .collectMany { it.allDependencies } 90 | .unique() 91 | .each { Dependency dependency -> 92 | if (substitutions.find { it.group == dependency.group && it.name == dependency.name }) { 93 | logger.lifecycle("Installing $dependency.group:$dependency.name") 94 | logger.info( 95 | "Executing \"./install-local-dependency.sh $dependency.group $dependency.name\"") 96 | 97 | def log = new File("${getTemporaryDir()}/${dependency.group}/${dependency.name}.log") 98 | log.parentFile.mkdirs() 99 | def out = new FileOutputStream(log) 100 | logger.info("Streaming output to $log") 101 | 102 | def result = exec { 103 | commandLine './install-local-dependency.sh' 104 | args dependency.group, dependency.name 105 | standardOutput out 106 | errorOutput out 107 | ignoreExitValue true 108 | } 109 | 110 | if (result.exitValue != 0) { 111 | throw new TaskExecutionException(it, 112 | new Exception("Command './install-local-dependency.sh' failed. See $log for output.")) 113 | } 114 | } 115 | } 116 | } 117 | } 118 | 119 | /* 4. Alert the user that local dependencies are being used. */ 120 | 121 | gradle.buildFinished { 122 | if (substitutions) { 123 | logger.lifecycle('Applied {} local dependency substitution(s).', substitutions.size()) 124 | logger.info(substitutions.inject('') { acc, val -> 125 | acc << "\n\t* $val.group:$val.name" 126 | }.substring(1)) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /sample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion '25.0.0' 6 | 7 | defaultConfig { 8 | applicationId "com.google.android.material.motion.runtime.sample" 9 | minSdkVersion 15 10 | targetSdkVersion 25 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | 15 | lintOptions { 16 | abortOnError false 17 | 18 | // Disable gradle dependency check for new versions. 19 | // Many of these have been chosen to work with Google Tools. 20 | disable 'GradleDependency' 21 | } 22 | 23 | buildTypes { 24 | release { 25 | minifyEnabled true 26 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 27 | } 28 | } 29 | } 30 | 31 | dependencies { 32 | // If you are developing any dependencies locally, also list them in local.dependencies. 33 | compile project(':library') 34 | compile 'com.android.support:appcompat-v7:25.1.0' 35 | } 36 | -------------------------------------------------------------------------------- /sample/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | -------------------------------------------------------------------------------- /sample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /sample/src/main/java/com/google/android/material/motion/runtime/sample/MainActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-present The Material Motion Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.android.material.motion.runtime.sample; 17 | 18 | import android.animation.Animator; 19 | import android.animation.AnimatorListenerAdapter; 20 | import android.os.Bundle; 21 | import android.support.v7.app.AppCompatActivity; 22 | import android.view.View; 23 | import android.widget.TextView; 24 | 25 | import com.google.android.material.motion.runtime.MotionRuntime; 26 | import com.google.android.material.motion.runtime.NamedPerformer; 27 | import com.google.android.material.motion.runtime.NamedPlan; 28 | import com.google.android.material.motion.runtime.Performer; 29 | import com.google.android.material.motion.runtime.PerformerFeatures.ContinuousPerforming; 30 | import com.google.android.material.motion.runtime.Plan; 31 | 32 | /** 33 | * Material Motion Android Runtime sample Activity. 34 | */ 35 | public class MainActivity extends AppCompatActivity { 36 | 37 | @Override 38 | protected void onCreate(Bundle savedInstanceState) { 39 | super.onCreate(savedInstanceState); 40 | 41 | setContentView(R.layout.main_activity); 42 | 43 | TextView text1 = (TextView) findViewById(R.id.text1); 44 | TextView text2 = (TextView) findViewById(R.id.text2); 45 | 46 | text1.setText(""); 47 | text2.setAlpha(0f); 48 | 49 | MotionRuntime runtime = new MotionRuntime(); 50 | 51 | runtime.addNamedPlan(new DemoPlan1("trash"), "cd", text1); 52 | runtime.addPlan(new DemoPlan1("get"), text1); 53 | runtime.addNamedPlan(new DemoPlan1("real"), "cd", text1); 54 | 55 | runtime.addPlan(new DemoPlan2(.5f), text2); 56 | } 57 | 58 | private static class DemoPlan1 extends NamedPlan { 59 | 60 | private final String text; 61 | 62 | private DemoPlan1(String text) { 63 | this.text = text; 64 | } 65 | 66 | @Override 67 | public Class> getPerformerClass() { 68 | return DemoPerformer1.class; 69 | } 70 | } 71 | 72 | public static class DemoPerformer1 extends NamedPerformer { 73 | 74 | @Override 75 | public void addPlan(Plan plan) { 76 | DemoPlan1 demoPlan = (DemoPlan1) plan; 77 | TextView target = getTarget(); 78 | 79 | target.setText(target.getText() + " " + demoPlan.text); 80 | } 81 | 82 | @Override 83 | public void addPlan(NamedPlan plan, String name) { 84 | addPlan(plan); 85 | } 86 | 87 | @Override 88 | public void removePlan(String name) { 89 | } 90 | } 91 | 92 | private static class DemoPlan2 extends Plan { 93 | 94 | private final float alpha; 95 | 96 | private DemoPlan2(float alpha) { 97 | this.alpha = alpha; 98 | } 99 | 100 | @Override 101 | public Class> getPerformerClass() { 102 | return DemoPerformer2.class; 103 | } 104 | } 105 | 106 | public static class DemoPerformer2 extends Performer implements ContinuousPerforming { 107 | 108 | private IsActiveTokenGenerator isActiveTokenGenerator; 109 | 110 | @Override 111 | public void setIsActiveTokenGenerator(IsActiveTokenGenerator isActiveTokenGenerator) { 112 | this.isActiveTokenGenerator = isActiveTokenGenerator; 113 | } 114 | 115 | @Override 116 | public void addPlan(Plan plan) { 117 | DemoPlan2 demoPlan = (DemoPlan2) plan; 118 | View target = getTarget(); 119 | 120 | target 121 | .animate() 122 | .alpha(demoPlan.alpha) 123 | .setDuration(2000) 124 | .setListener( 125 | new AnimatorListenerAdapter() { 126 | private IsActiveToken token; 127 | 128 | @Override 129 | public void onAnimationStart(Animator animation) { 130 | token = isActiveTokenGenerator.generate(); 131 | } 132 | 133 | @Override 134 | public void onAnimationEnd(Animator animation) { 135 | token.terminate(); 136 | } 137 | }); 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/main_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 21 | 26 | 31 | 32 | -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/material-motion-archive/runtime-android/61efbbc62d4a24805f1cbfe834814bdf0592ef05/sample/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/material-motion-archive/runtime-android/61efbbc62d4a24805f1cbfe834814bdf0592ef05/sample/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/material-motion-archive/runtime-android/61efbbc62d4a24805f1cbfe834814bdf0592ef05/sample/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/material-motion-archive/runtime-android/61efbbc62d4a24805f1cbfe834814bdf0592ef05/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/material-motion-archive/runtime-android/61efbbc62d4a24805f1cbfe834814bdf0592ef05/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | Material Motion Android Runtime Sample 19 | 20 | -------------------------------------------------------------------------------- /sample/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':library', ':sample' 2 | --------------------------------------------------------------------------------