├── .github
├── scripts
│ └── gradlew_recursive.sh
└── workflows
│ ├── android.yml
│ └── copy-branch.yml
├── .gitignore
├── .google
└── packaging.yaml
├── .idea
├── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
└── vcs.xml
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── TODO.md
├── app
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── ic_launcher-web.png
│ ├── java
│ └── com
│ │ └── example
│ │ └── android
│ │ └── uamp
│ │ ├── MainActivity.kt
│ │ ├── MediaItemAdapter.kt
│ │ ├── MediaItemData.kt
│ │ ├── cast
│ │ └── UampCastOptionsProvider.kt
│ │ ├── fragments
│ │ ├── MediaItemFragment.kt
│ │ └── NowPlayingFragment.kt
│ │ ├── utils
│ │ ├── Event.kt
│ │ └── InjectorUtils.kt
│ │ └── viewmodels
│ │ ├── MainActivityViewModel.kt
│ │ ├── MediaItemFragmentViewModel.kt
│ │ └── NowPlayingFragmentViewModel.kt
│ └── res
│ ├── drawable-v24
│ └── ic_launcher_foreground.xml
│ ├── drawable
│ ├── ic_album_black_24dp.xml
│ ├── ic_launcher_background.xml
│ ├── ic_pause_black_24dp.xml
│ ├── ic_play_arrow_black_24dp.xml
│ ├── ic_signal_wifi_off_black_24dp.xml
│ ├── media_item_background.xml
│ ├── media_item_mask.xml
│ └── media_overlay_background.xml
│ ├── layout
│ ├── activity_main.xml
│ ├── cast_context_error.xml
│ ├── fragment_mediaitem.xml
│ ├── fragment_mediaitem_list.xml
│ └── fragment_nowplaying.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.png
│ ├── ic_launcher_foreground.png
│ └── ic_launcher_round.png
│ ├── mipmap-mdpi
│ ├── ic_launcher.png
│ ├── ic_launcher_foreground.png
│ └── ic_launcher_round.png
│ ├── mipmap-xhdpi
│ ├── ic_launcher.png
│ ├── ic_launcher_foreground.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ ├── ic_launcher_foreground.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ ├── ic_launcher_foreground.png
│ └── ic_launcher_round.png
│ ├── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
│ └── xml
│ └── automotive_app_desc.xml
├── automotive
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── example
│ │ └── android
│ │ └── uamp
│ │ └── automotive
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── example
│ │ │ └── android
│ │ │ └── uamp
│ │ │ └── automotive
│ │ │ ├── AutomotiveMusicService.kt
│ │ │ ├── PhoneSignInFragment.kt
│ │ │ ├── PinCodeSignInFragment.kt
│ │ │ ├── QrCodeSignInFragment.kt
│ │ │ ├── SettingsActivity.kt
│ │ │ ├── SettingsFragment.kt
│ │ │ ├── SignInActivity.kt
│ │ │ ├── SignInActivityViewModel.kt
│ │ │ ├── SignInLandingPageFragment.kt
│ │ │ └── UsernameAndPasswordSignInFragment.kt
│ └── res
│ │ ├── color
│ │ ├── car_text_dark.xml
│ │ └── car_text_light.xml
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ ├── aural_logo.png
│ │ ├── default_button_background.xml
│ │ ├── google_logo.png
│ │ ├── google_sign_in_button_background.xml
│ │ ├── google_sign_in_button_logo.xml
│ │ ├── ic_launcher_background.xml
│ │ ├── pin_background.xml
│ │ ├── sign_in_button_background.xml
│ │ ├── sign_in_toolbar_back_icon.xml
│ │ └── sign_in_toolbar_back_ripple_background.xml
│ │ ├── layout-h900dp
│ │ ├── phone_sign_in.xml
│ │ ├── pin_sign_in.xml
│ │ ├── qr_sign_in.xml
│ │ ├── sign_in_landing_page.xml
│ │ ├── sign_in_landing_page_with_username_and_password.xml
│ │ └── username_and_password_sign_in.xml
│ │ ├── layout
│ │ ├── activity_login.xml
│ │ ├── activity_settings.xml
│ │ ├── activity_sign_in.xml
│ │ ├── phone_sign_in.xml
│ │ ├── pin_item.xml
│ │ ├── pin_sign_in.xml
│ │ ├── preference.xml
│ │ ├── preference_category.xml
│ │ ├── qr_sign_in.xml
│ │ ├── sign_in_landing_page.xml
│ │ ├── sign_in_landing_page_with_username_and_password.xml
│ │ └── username_and_password_sign_in.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── values-h1060dp
│ │ └── dimens.xml
│ │ ├── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ │ └── xml
│ │ ├── automotive_app_desc.xml
│ │ └── preferences.xml
│ └── test
│ └── java
│ └── com
│ └── example
│ └── android
│ └── uamp
│ └── automotive
│ └── ExampleUnitTest.java
├── build.gradle
├── common
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── main
│ ├── AndroidManifest.xml
│ ├── assets
│ │ └── default_art.png
│ ├── java
│ │ └── com
│ │ │ └── example
│ │ │ └── android
│ │ │ └── uamp
│ │ │ ├── common
│ │ │ └── MusicServiceConnection.kt
│ │ │ └── media
│ │ │ ├── CastMediaItemConverter.kt
│ │ │ ├── MusicService.kt
│ │ │ ├── PackageValidator.kt
│ │ │ ├── PersistentStorage.kt
│ │ │ ├── UampNotificationManager.kt
│ │ │ ├── extensions
│ │ │ ├── FileExt.kt
│ │ │ ├── JavaLangExt.kt
│ │ │ ├── MediaMetadataCompatExt.kt
│ │ │ └── PlaybackStateCompatExt.kt
│ │ │ └── library
│ │ │ ├── AlbumArtContentProvider.kt
│ │ │ ├── BrowseTree.kt
│ │ │ ├── JsonSource.kt
│ │ │ └── MusicSource.kt
│ └── res
│ │ ├── drawable-hdpi
│ │ └── ic_notification.png
│ │ ├── drawable-mdpi
│ │ └── ic_notification.png
│ │ ├── drawable-nodpi
│ │ └── default_art.png
│ │ ├── drawable-xhdpi
│ │ └── ic_notification.png
│ │ ├── drawable-xxhdpi
│ │ └── ic_notification.png
│ │ ├── drawable-xxxhdpi
│ │ └── ic_notification.png
│ │ ├── drawable
│ │ ├── ic_album.xml
│ │ └── ic_recommended.xml
│ │ ├── ic_notification.png
│ │ ├── menu
│ │ └── main_activity_menu.xml
│ │ ├── values
│ │ └── strings.xml
│ │ └── xml
│ │ └── allowed_media_browser_callers.xml
│ └── test
│ └── java
│ └── com
│ └── example
│ └── android
│ └── uamp
│ └── media
│ └── library
│ └── MusicSourceTest.kt
├── docs
├── FAQs.md
├── FullGuide.md
└── images
│ ├── 1-browse-albums-screenshot.png
│ ├── 12-ui-class-diagram.png
│ ├── 2-play-song-screenshot.png
│ ├── 3-architecture-overview.png
│ ├── 4-MusicService.png
│ ├── 5-MediaController.png
│ ├── 6-notification.png
│ └── 9-mvvm.png
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.github/scripts/gradlew_recursive.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Copyright (C) 2020 The Android Open Source Project
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 | # https://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 | set -xe
18 |
19 | # Default Gradle settings are not optimal for Android builds, override them
20 | # here to make the most out of the GitHub Actions build servers
21 | GRADLE_OPTS="$GRADLE_OPTS -Xms4g -Xmx4g"
22 | GRADLE_OPTS="$GRADLE_OPTS -XX:+HeapDumpOnOutOfMemoryError"
23 | GRADLE_OPTS="$GRADLE_OPTS -Dorg.gradle.daemon=false"
24 | GRADLE_OPTS="$GRADLE_OPTS -Dorg.gradle.workers.max=2"
25 | GRADLE_OPTS="$GRADLE_OPTS -Dkotlin.incremental=false"
26 | GRADLE_OPTS="$GRADLE_OPTS -Dkotlin.compiler.execution.strategy=in-process"
27 | GRADLE_OPTS="$GRADLE_OPTS -Dfile.encoding=UTF-8"
28 | export GRADLE_OPTS
29 |
30 | # Crawl all gradlew files which indicate an Android project
31 | # You may edit this if your repo has a different project structure
32 | for GRADLEW in `find . -name "gradlew"` ; do
33 | SAMPLE=$(dirname "${GRADLEW}")
34 | # Tell Gradle that this is a CI environment and disable parallel compilation
35 | bash "$GRADLEW" -p "$SAMPLE" -Pci --no-parallel --stacktrace $@
36 | done
37 |
--------------------------------------------------------------------------------
/.github/workflows/android.yml:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2020 The Android Open Source Project
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | name: Android CI
16 |
17 | on:
18 | workflow_dispatch:
19 | push:
20 | branches: [ main ]
21 | pull_request:
22 | branches: [ main ]
23 |
24 | jobs:
25 |
26 | build:
27 | name: Build
28 | runs-on: ubuntu-18.04
29 |
30 | steps:
31 | - uses: actions/checkout@v1
32 | - name: set up JDK 1.8
33 | uses: actions/setup-java@v1
34 | with:
35 | java-version: 1.8
36 | - name: Build project
37 | run: .github/scripts/gradlew_recursive.sh assembleDebug
38 | - name: Zip artifacts
39 | run: zip -r assemble.zip . -i '**/build/*.apk' '**/build/*.aab' '**/build/*.aar' '**/build/*.so'
40 | - name: Upload artifacts
41 | uses: actions/upload-artifact@v1
42 | with:
43 | name: assemble
44 | path: assemble.zip
45 |
--------------------------------------------------------------------------------
/.github/workflows/copy-branch.yml:
--------------------------------------------------------------------------------
1 | # Duplicates default main branch to the old master branch
2 |
3 | name: Duplicates main to old master branch
4 |
5 | # Controls when the action will run. Triggers the workflow on push or pull request
6 | # events but only for the main branch
7 | on:
8 | workflow_dispatch:
9 | push:
10 | branches: [ main ]
11 |
12 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel
13 | jobs:
14 | # This workflow contains a single job called "copy-branch"
15 | copy-branch:
16 | # The type of runner that the job will run on
17 | runs-on: ubuntu-latest
18 |
19 | # Steps represent a sequence of tasks that will be executed as part of the job
20 | steps:
21 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it,
22 | # but specifies master branch (old default).
23 | - uses: actions/checkout@v2
24 | with:
25 | fetch-depth: 0
26 | ref: master
27 |
28 | - run: |
29 | git config user.name github-actions
30 | git config user.email github-actions@github.com
31 | git merge origin/main
32 | git push
33 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea
5 | .DS_Store
6 | /captures
7 | .externalNativeBuild
8 |
9 | # Generated files
10 | build/
11 |
12 | # Extra (custom) settings
13 | extra-settings.gradle
14 |
--------------------------------------------------------------------------------
/.google/packaging.yaml:
--------------------------------------------------------------------------------
1 | # GOOGLE SAMPLE PACKAGING DATA
2 | #
3 | # This file is used by Google as part of our samples packaging process.
4 | # End users may safely ignore this file. It has no relevance to other systems.
5 | ---
6 | status: PUBLISHED
7 | technologies: [Android, Android Auto, Android Automotive OS, Android Wear]
8 | categories: [Getting Started, Media, UI]
9 | languages: [Kotlin]
10 | solutions: [Mobile]
11 |
12 | github: googlesamples/android-UniversalMusicPlayer
13 |
14 | level: INTERMEDIATE
15 |
16 | icon: screenshots/icon-web.png
17 |
18 | apiRefs:
19 | - android:android.support.v4.media.session.MediaSessionCompat
20 | - android:android.support.v4.media.session.MediaControllerCompat
21 | - androidx.media.MediaBrowserServiceCompat
22 | - android:android.support.v4.media.MediaBrowserCompat
23 | - androidx.media.app.NotificationCompat.MediaStyle
24 | - android:com.google.android.exoplayer2.SimpleExoPlayer
25 | - android:com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
26 |
27 | license: apache2-android
28 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | xmlns:android
31 |
32 | ^$
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | xmlns:.*
42 |
43 | ^$
44 |
45 |
46 | BY_NAME
47 |
48 |
49 |
50 |
51 |
52 |
53 | .*:id
54 |
55 | http://schemas.android.com/apk/res/android
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | .*:name
65 |
66 | http://schemas.android.com/apk/res/android
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | name
76 |
77 | ^$
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 | style
87 |
88 | ^$
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 | .*
98 |
99 | ^$
100 |
101 |
102 | BY_NAME
103 |
104 |
105 |
106 |
107 |
108 |
109 | .*
110 |
111 | http://schemas.android.com/apk/res/android
112 |
113 |
114 | ANDROID_ATTRIBUTE_ORDER
115 |
116 |
117 |
118 |
119 |
120 |
121 | .*
122 |
123 | .*
124 |
125 |
126 | BY_NAME
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to become a contributor and submit your own code
2 |
3 | ## Contributor License Agreements
4 |
5 | We'd love to accept your sample apps and patches! Before we can take them, we
6 | have to jump a couple of legal hurdles.
7 |
8 | Please fill out either the individual or corporate Contributor License Agreement (CLA).
9 |
10 | * If you are an individual writing original source code and you're sure you
11 | own the intellectual property, then you'll need to sign an [individual CLA]
12 | (https://developers.google.com/open-source/cla/individual).
13 | * If you work for a company that wants to allow you to contribute your work,
14 | then you'll need to sign a [corporate CLA]
15 | (https://developers.google.com/open-source/cla/corporate).
16 |
17 | Follow either of the two links above to access the appropriate CLA and
18 | instructions for how to sign and return it. Once we receive it, we'll be able to
19 | accept your pull requests.
20 |
21 | ## Contributing A Patch
22 |
23 | 1. Submit an issue describing your proposed change to the repo in question.
24 | 1. The repo owner will respond to your issue promptly.
25 | 1. If your proposed change is accepted, and you haven't already done so, sign a
26 | Contributor License Agreement (see details above).
27 | 1. Fork the desired repo, develop and test your code changes.
28 | 1. Ensure that your code adheres to the existing style in the sample to which
29 | you are contributing. Refer to the
30 | [Android Code Style Guide]
31 | (https://source.android.com/source/code-style.html) for the
32 | recommended coding standards for this organization.
33 | 1. Ensure that your code has an appropriate set of unit tests which all pass.
34 | 1. Submit a pull request.
35 |
36 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Universal Android Music Player Sample
2 | =====================================
3 | The goal of this sample is to show how to implement an audio media app that works
4 | across multiple form factors and provides a consistent user experience
5 | on Android phones, tablets, Android Auto, Android Wear, Android TV, Google Cast devices,
6 | and with the Google Assistant.
7 |
8 | To get started with UAMP please read the [full guide](docs/FullGuide.md).
9 |
10 | 
11 | 
12 |
13 | Pre-requisites
14 | --------------
15 |
16 | - Android Studio 3.x
17 |
18 | Getting Started
19 | ---------------
20 |
21 | This sample uses the Gradle build system. To build this project, use the
22 | "gradlew build" command or use "Import Project" in Android Studio.
23 |
24 | Support
25 | -------
26 |
27 | - Check out the [FAQs page](docs/FAQs.md)
28 | - Stack Overflow: http://stackoverflow.com/questions/tagged/android
29 |
30 | If you've found an error in this sample, please
31 | [file an issue](https://github.com/android/UAMP/issues)
32 |
33 | Patches are encouraged and may be submitted by forking this project and
34 | submitting a pull request through GitHub. Please see [CONTRIBUTING.md](CONTRIBUTING.md) for more
35 | details.
36 |
37 | Audio
38 | -----
39 |
40 | Music provided by the [Free Music Archive](http://freemusicarchive.org/).
41 |
42 | - [Wake Up](http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/) by
43 | [The Kyoto Connection](http://freemusicarchive.org/music/The_Kyoto_Connection/).
44 |
45 | Recordings provided by the [Ambisonic Sound Library](https://library.soundfield.com/).
46 |
47 | - [Pre Game Marching Band](https://library.soundfield.com/track/163) by Watson Wu
48 | - [Chickens on a Farm](https://library.soundfield.com/track/129) by Watson Wu
49 | - [Rural Market Busker](https://library.soundfield.com/track/55) by Stephan Schutze
50 | - [Steamtrain Interior](https://library.soundfield.com/track/65) by Stephan Schutze
51 | - [Rural Road Car Pass](https://library.soundfield.com/track/57) by Stephan Schutze
52 | - [10 Feet from Shore](https://library.soundfield.com/track/114) by Watson Wu
53 |
54 | License
55 | -------
56 |
57 | Copyright 2017 Google Inc.
58 |
59 | Licensed to the Apache Software Foundation (ASF) under one or more contributor
60 | license agreements. See the NOTICE file distributed with this work for
61 | additional information regarding copyright ownership. The ASF licenses this
62 | file to you under the Apache License, Version 2.0 (the "License"); you may not
63 | use this file except in compliance with the License. You may obtain a copy of
64 | the License at
65 |
66 | http://www.apache.org/licenses/LICENSE-2.0
67 |
68 | Unless required by applicable law or agreed to in writing, software
69 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
70 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
71 | License for the specific language governing permissions and limitations under
72 | the License.
73 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | TODOs
2 | =====
3 |
4 | This file captures the high level goals of the project. This provides guidance for anyone who wants
5 | to contribute. If you see something in the list that you'd like to work on,
6 | the best approach would be to [create an
7 | issue](https://github.com/googlesamples/android-UniversalMusicPlayer/issues) first,
8 | and then provide a pull request once completed to have your work merged into the project.
9 |
10 | Service Side Tasks
11 | ------------------
12 |
13 | - Implement rating (ideally "favorite" vs "thumbs up/down").
14 | - Improve integration with the Google Assistant.
15 |
16 | UI Tasks
17 | --------
18 |
19 | - Implement a "now playing" UI with current position and skip forward/back 30s ([BottomSheet](https://material.io/guidelines/components/bottom-sheets.html#bottom-sheets-persistent-bottom-sheets)).
20 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 Google Inc. 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 | apply plugin: 'com.android.application'
18 | apply plugin: 'kotlin-android'
19 | apply plugin: 'kotlin-kapt'
20 | apply plugin: 'kotlin-android-extensions'
21 |
22 | android {
23 | compileSdkVersion rootProject.compileSdkVersion
24 |
25 | defaultConfig {
26 | applicationId "com.example.android.uamp.next"
27 | versionCode 1
28 | versionName "1.0"
29 |
30 | minSdkVersion rootProject.minSdkVersion
31 | targetSdkVersion rootProject.targetSdkVersion
32 | multiDexEnabled true
33 |
34 | compileOptions {
35 | sourceCompatibility JavaVersion.VERSION_1_8
36 | targetCompatibility JavaVersion.VERSION_1_8
37 | }
38 | kotlinOptions {
39 | jvmTarget = "1.8"
40 | }
41 |
42 | vectorDrawables {
43 | useSupportLibrary true
44 | }
45 | }
46 |
47 | buildFeatures {
48 | viewBinding true
49 | dataBinding true
50 | }
51 |
52 | buildTypes {
53 | release {
54 | minifyEnabled false
55 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
56 | }
57 | }
58 | }
59 |
60 | dependencies {
61 | implementation "com.android.support:multidex:$multidex_version"
62 |
63 | implementation project(':common')
64 |
65 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
66 |
67 | implementation "androidx.appcompat:appcompat:$androidx_app_compat_version"
68 | implementation "androidx.fragment:fragment-ktx:$fragment_version"
69 | implementation "androidx.recyclerview:recyclerview:$recycler_view_version"
70 |
71 | implementation "androidx.constraintlayout:constraintlayout:$constraint_layout_version"
72 | implementation "androidx.lifecycle:lifecycle-extensions:$arch_lifecycle_version"
73 |
74 | // Glide dependencies
75 | implementation "com.github.bumptech.glide:glide:$glide_version"
76 | kapt "com.github.bumptech.glide:compiler:$glide_version"
77 | }
78 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
21 |
22 |
30 |
31 |
32 |
33 |
34 |
35 |
38 |
39 |
40 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
84 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/app/src/main/ic_launcher-web.png
--------------------------------------------------------------------------------
/app/src/main/java/com/example/android/uamp/MediaItemAdapter.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 Google Inc. 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.example.android.uamp
18 |
19 | import android.view.LayoutInflater
20 | import android.view.ViewGroup
21 | import android.widget.ImageView
22 | import android.widget.TextView
23 | import androidx.recyclerview.widget.ListAdapter
24 | import androidx.recyclerview.widget.RecyclerView
25 | import com.bumptech.glide.Glide
26 | import com.example.android.uamp.MediaItemData.Companion.PLAYBACK_RES_CHANGED
27 | import com.example.android.uamp.databinding.FragmentMediaitemBinding
28 | import com.example.android.uamp.fragments.MediaItemFragment
29 |
30 | /**
31 | * [RecyclerView.Adapter] of [MediaItemData]s used by the [MediaItemFragment].
32 | */
33 | class MediaItemAdapter(
34 | private val itemClickedListener: (MediaItemData) -> Unit
35 | ) : ListAdapter(MediaItemData.diffCallback) {
36 |
37 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MediaViewHolder {
38 | val inflater = LayoutInflater.from(parent.context)
39 | val binding = FragmentMediaitemBinding.inflate(inflater, parent, false)
40 | return MediaViewHolder(binding, itemClickedListener)
41 | }
42 |
43 | override fun onBindViewHolder(
44 | holder: MediaViewHolder,
45 | position: Int,
46 | payloads: MutableList
47 | ) {
48 |
49 | val mediaItem = getItem(position)
50 | var fullRefresh = payloads.isEmpty()
51 |
52 | if (payloads.isNotEmpty()) {
53 | payloads.forEach { payload ->
54 | when (payload) {
55 | PLAYBACK_RES_CHANGED -> {
56 | holder.playbackState.setImageResource(mediaItem.playbackRes)
57 | }
58 | // If the payload wasn't understood, refresh the full item (to be safe).
59 | else -> fullRefresh = true
60 | }
61 | }
62 | }
63 |
64 | // Normally we only fully refresh the list item if it's being initially bound, but
65 | // we might also do it if there was a payload that wasn't understood, just to ensure
66 | // there isn't a stale item.
67 | if (fullRefresh) {
68 | holder.item = mediaItem
69 | holder.titleView.text = mediaItem.title
70 | holder.subtitleView.text = mediaItem.subtitle
71 | holder.playbackState.setImageResource(mediaItem.playbackRes)
72 |
73 | Glide.with(holder.albumArt)
74 | .load(mediaItem.albumArtUri)
75 | .placeholder(R.drawable.default_art)
76 | .into(holder.albumArt)
77 | }
78 | }
79 |
80 | override fun onBindViewHolder(holder: MediaViewHolder, position: Int) {
81 | onBindViewHolder(holder, position, mutableListOf())
82 | }
83 | }
84 |
85 | class MediaViewHolder(
86 | binding: FragmentMediaitemBinding,
87 | itemClickedListener: (MediaItemData) -> Unit
88 | ) : RecyclerView.ViewHolder(binding.root) {
89 |
90 | val titleView: TextView = binding.title
91 | val subtitleView: TextView = binding.subtitle
92 | val albumArt: ImageView = binding.albumArt
93 | val playbackState: ImageView = binding.itemState
94 |
95 | var item: MediaItemData? = null
96 |
97 | init {
98 | binding.root.setOnClickListener {
99 | item?.let { itemClickedListener(it) }
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/android/uamp/MediaItemData.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Google Inc. 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.example.android.uamp
18 |
19 | import android.net.Uri
20 | import android.support.v4.media.MediaBrowserCompat
21 | import android.support.v4.media.MediaBrowserCompat.MediaItem
22 | import androidx.recyclerview.widget.DiffUtil
23 | import com.example.android.uamp.viewmodels.MediaItemFragmentViewModel
24 |
25 | /**
26 | * Data class to encapsulate properties of a [MediaItem].
27 | *
28 | * If an item is [browsable] it means that it has a list of child media items that
29 | * can be retrieved by passing the mediaId to [MediaBrowserCompat.subscribe].
30 | *
31 | * Objects of this class are built from [MediaItem]s in
32 | * [MediaItemFragmentViewModel.subscriptionCallback].
33 | */
34 | data class MediaItemData(
35 | val mediaId: String,
36 | val title: String,
37 | val subtitle: String,
38 | val albumArtUri: Uri,
39 | val browsable: Boolean,
40 | var playbackRes: Int
41 | ) {
42 |
43 | companion object {
44 | /**
45 | * Indicates [playbackRes] has changed.
46 | */
47 | const val PLAYBACK_RES_CHANGED = 1
48 |
49 | /**
50 | * [DiffUtil.ItemCallback] for a [MediaItemData].
51 | *
52 | * Since all [MediaItemData]s have a unique ID, it's easiest to check if two
53 | * items are the same by simply comparing that ID.
54 | *
55 | * To check if the contents are the same, we use the same ID, but it may be the
56 | * case that it's only the play state itself which has changed (from playing to
57 | * paused, or perhaps a different item is the active item now). In this case
58 | * we check both the ID and the playback resource.
59 | *
60 | * To calculate the payload, we use the simplest method possible:
61 | * - Since the title, subtitle, and albumArtUri are constant (with respect to mediaId),
62 | * there's no reason to check if they've changed. If the mediaId is the same, none of
63 | * those properties have changed.
64 | * - If the playback resource (playbackRes) has changed to reflect the change in playback
65 | * state, that's all that needs to be updated. We return [PLAYBACK_RES_CHANGED] as
66 | * the payload in this case.
67 | * - If something else changed, then refresh the full item for simplicity.
68 | */
69 | val diffCallback = object : DiffUtil.ItemCallback() {
70 | override fun areItemsTheSame(
71 | oldItem: MediaItemData,
72 | newItem: MediaItemData
73 | ): Boolean =
74 | oldItem.mediaId == newItem.mediaId
75 |
76 | override fun areContentsTheSame(oldItem: MediaItemData, newItem: MediaItemData) =
77 | oldItem.mediaId == newItem.mediaId && oldItem.playbackRes == newItem.playbackRes
78 |
79 | override fun getChangePayload(oldItem: MediaItemData, newItem: MediaItemData) =
80 | if (oldItem.playbackRes != newItem.playbackRes) {
81 | PLAYBACK_RES_CHANGED
82 | } else null
83 | }
84 | }
85 | }
86 |
87 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/android/uamp/cast/UampCastOptionsProvider.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Google Inc. 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.example.android.uamp.cast
18 |
19 | import android.content.Context
20 | import com.google.android.exoplayer2.ext.cast.DefaultCastOptionsProvider.APP_ID_DEFAULT_RECEIVER_WITH_DRM
21 | import com.google.android.gms.cast.framework.CastOptions
22 | import com.google.android.gms.cast.framework.OptionsProvider
23 | import com.google.android.gms.cast.framework.SessionProvider
24 | import com.google.android.gms.cast.framework.media.CastMediaOptions
25 |
26 |
27 | class UampCastOptionsProvider : OptionsProvider {
28 |
29 | override fun getCastOptions(context: Context?): CastOptions? {
30 | return CastOptions.Builder()
31 | // Use the Default Media Receiver with DRM support.
32 | .setReceiverApplicationId(APP_ID_DEFAULT_RECEIVER_WITH_DRM)
33 | .setCastMediaOptions(
34 | CastMediaOptions.Builder()
35 | // We manage the media session and the notifications ourselves.
36 | .setMediaSessionEnabled(false)
37 | .setNotificationOptions(null)
38 | .build()
39 | )
40 | .setStopReceiverApplicationWhenEndingSession(true).build()
41 | }
42 |
43 | override fun getAdditionalSessionProviders(context: Context?): List? {
44 | return null
45 | }
46 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/android/uamp/fragments/MediaItemFragment.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 Google Inc. 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.example.android.uamp.fragments
18 |
19 | import android.os.Bundle
20 | import android.view.LayoutInflater
21 | import android.view.View
22 | import android.view.ViewGroup
23 | import androidx.fragment.app.Fragment
24 | import androidx.fragment.app.activityViewModels
25 | import androidx.fragment.app.viewModels
26 | import androidx.lifecycle.Observer
27 | import com.example.android.uamp.MediaItemAdapter
28 | import com.example.android.uamp.databinding.FragmentMediaitemListBinding
29 | import com.example.android.uamp.utils.InjectorUtils
30 | import com.example.android.uamp.viewmodels.MainActivityViewModel
31 | import com.example.android.uamp.viewmodels.MediaItemFragmentViewModel
32 |
33 | /**
34 | * A fragment representing a list of MediaItems.
35 | */
36 | class MediaItemFragment : Fragment() {
37 | private val mainActivityViewModel by activityViewModels {
38 | InjectorUtils.provideMainActivityViewModel(requireContext())
39 | }
40 | private val mediaItemFragmentViewModel by viewModels {
41 | InjectorUtils.provideMediaItemFragmentViewModel(requireContext(), mediaId)
42 | }
43 |
44 | private lateinit var mediaId: String
45 | private lateinit var binding: FragmentMediaitemListBinding
46 |
47 | private val listAdapter = MediaItemAdapter { clickedItem ->
48 | mainActivityViewModel.mediaItemClicked(clickedItem)
49 | }
50 |
51 | companion object {
52 | fun newInstance(mediaId: String): MediaItemFragment {
53 |
54 | return MediaItemFragment().apply {
55 | arguments = Bundle().apply {
56 | putString(MEDIA_ID_ARG, mediaId)
57 | }
58 | }
59 | }
60 | }
61 |
62 | override fun onCreateView(
63 | inflater: LayoutInflater, container: ViewGroup?,
64 | savedInstanceState: Bundle?
65 | ): View? {
66 | binding = FragmentMediaitemListBinding.inflate(inflater, container, false)
67 | return binding.root
68 | }
69 |
70 | override fun onActivityCreated(savedInstanceState: Bundle?) {
71 | super.onActivityCreated(savedInstanceState)
72 |
73 | // Always true, but lets lint know that as well.
74 | mediaId = arguments?.getString(MEDIA_ID_ARG) ?: return
75 |
76 | mediaItemFragmentViewModel.mediaItems.observe(viewLifecycleOwner,
77 | Observer { list ->
78 | binding.loadingSpinner.visibility =
79 | if (list?.isNotEmpty() == true) View.GONE else View.VISIBLE
80 | listAdapter.submitList(list)
81 | })
82 | mediaItemFragmentViewModel.networkError.observe(viewLifecycleOwner,
83 | Observer { error ->
84 | if (error) {
85 | binding.loadingSpinner.visibility = View.GONE
86 | binding.networkError.visibility = View.VISIBLE
87 | } else {
88 | binding.networkError.visibility = View.GONE
89 | }
90 | })
91 |
92 | // Set the adapter
93 | binding.list.adapter = listAdapter
94 | }
95 | }
96 |
97 | private const val MEDIA_ID_ARG = "com.example.android.uamp.fragments.MediaItemFragment.MEDIA_ID"
98 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/android/uamp/fragments/NowPlayingFragment.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google Inc. 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.example.android.uamp.fragments
18 |
19 | import android.net.Uri
20 | import android.os.Bundle
21 | import android.view.LayoutInflater
22 | import android.view.View
23 | import android.view.ViewGroup
24 | import androidx.fragment.app.Fragment
25 | import androidx.fragment.app.activityViewModels
26 | import androidx.fragment.app.viewModels
27 | import androidx.lifecycle.Observer
28 | import com.bumptech.glide.Glide
29 | import com.example.android.uamp.R
30 | import com.example.android.uamp.databinding.FragmentNowplayingBinding
31 | import com.example.android.uamp.utils.InjectorUtils
32 | import com.example.android.uamp.viewmodels.MainActivityViewModel
33 | import com.example.android.uamp.viewmodels.NowPlayingFragmentViewModel
34 | import com.example.android.uamp.viewmodels.NowPlayingFragmentViewModel.NowPlayingMetadata
35 |
36 | /**
37 | * A fragment representing the current media item being played.
38 | */
39 | class NowPlayingFragment : Fragment() {
40 | private val mainActivityViewModel by activityViewModels {
41 | InjectorUtils.provideMainActivityViewModel(requireContext())
42 | }
43 | private val nowPlayingViewModel by viewModels {
44 | InjectorUtils.provideNowPlayingFragmentViewModel(requireContext())
45 | }
46 |
47 | lateinit var binding: FragmentNowplayingBinding
48 |
49 | companion object {
50 | fun newInstance() = NowPlayingFragment()
51 | }
52 |
53 | override fun onCreateView(
54 | inflater: LayoutInflater, container: ViewGroup?,
55 | savedInstanceState: Bundle?
56 | ): View? {
57 | binding = FragmentNowplayingBinding.inflate(inflater, container, false)
58 | return binding.root
59 | }
60 |
61 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
62 | super.onViewCreated(view, savedInstanceState)
63 |
64 | // Always true, but lets lint know that as well.
65 | val context = activity ?: return
66 |
67 | // Attach observers to the LiveData coming from this ViewModel
68 | nowPlayingViewModel.mediaMetadata.observe(viewLifecycleOwner,
69 | Observer { mediaItem -> updateUI(view, mediaItem) })
70 | nowPlayingViewModel.mediaButtonRes.observe(viewLifecycleOwner,
71 | Observer { res ->
72 | binding.mediaButton.setImageResource(res)
73 | })
74 | nowPlayingViewModel.mediaPosition.observe(viewLifecycleOwner,
75 | Observer { pos ->
76 | binding.position.text = NowPlayingMetadata.timestampToMSS(context, pos)
77 | })
78 |
79 | // Setup UI handlers for buttons
80 | binding.mediaButton.setOnClickListener {
81 | nowPlayingViewModel.mediaMetadata.value?.let { mainActivityViewModel.playMediaId(it.id) }
82 | }
83 |
84 | // Initialize playback duration and position to zero
85 | binding.duration.text = NowPlayingMetadata.timestampToMSS(context, 0L)
86 | binding.position.text = NowPlayingMetadata.timestampToMSS(context, 0L)
87 | }
88 |
89 | /**
90 | * Internal function used to update all UI elements except for the current item playback
91 | */
92 | private fun updateUI(view: View, metadata: NowPlayingMetadata) = with(binding) {
93 | if (metadata.albumArtUri == Uri.EMPTY) {
94 | albumArt.setImageResource(R.drawable.ic_album_black_24dp)
95 | } else {
96 | Glide.with(view)
97 | .load(metadata.albumArtUri)
98 | .into(albumArt)
99 | }
100 | title.text = metadata.title
101 | subtitle.text = metadata.subtitle
102 | duration.text = metadata.duration
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/android/uamp/utils/Event.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Google Inc. 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.example.android.uamp.utils
18 |
19 | /**
20 | * Used as a wrapper for data that is exposed via a LiveData that represents an event.
21 | *
22 | * For more information, see:
23 | * https://medium.com/google-developers/livedata-with-events-ac2622673150
24 | */
25 | class Event(private val content: T) {
26 |
27 | var hasBeenHandled = false
28 | private set // Allow external read but not write
29 |
30 | /**
31 | * Returns the content and prevents its use again.
32 | */
33 | fun getContentIfNotHandled(): T? {
34 | return if (hasBeenHandled) {
35 | null
36 | } else {
37 | hasBeenHandled = true
38 | content
39 | }
40 | }
41 |
42 | /**
43 | * Returns the content, even if it's already been handled.
44 | */
45 | fun peekContent(): T = content
46 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/android/uamp/utils/InjectorUtils.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Google Inc. 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.example.android.uamp.utils
18 |
19 | import android.app.Application
20 | import android.content.ComponentName
21 | import android.content.Context
22 | import com.example.android.uamp.common.MusicServiceConnection
23 | import com.example.android.uamp.media.MusicService
24 | import com.example.android.uamp.viewmodels.MainActivityViewModel
25 | import com.example.android.uamp.viewmodels.MediaItemFragmentViewModel
26 | import com.example.android.uamp.viewmodels.NowPlayingFragmentViewModel
27 |
28 | /**
29 | * Static methods used to inject classes needed for various Activities and Fragments.
30 | */
31 | object InjectorUtils {
32 | private fun provideMusicServiceConnection(context: Context): MusicServiceConnection {
33 | return MusicServiceConnection.getInstance(
34 | context,
35 | ComponentName(context, MusicService::class.java)
36 | )
37 | }
38 |
39 | fun provideMainActivityViewModel(context: Context): MainActivityViewModel.Factory {
40 | val applicationContext = context.applicationContext
41 | val musicServiceConnection = provideMusicServiceConnection(applicationContext)
42 | return MainActivityViewModel.Factory(musicServiceConnection)
43 | }
44 |
45 | fun provideMediaItemFragmentViewModel(context: Context, mediaId: String)
46 | : MediaItemFragmentViewModel.Factory {
47 | val applicationContext = context.applicationContext
48 | val musicServiceConnection = provideMusicServiceConnection(applicationContext)
49 | return MediaItemFragmentViewModel.Factory(mediaId, musicServiceConnection)
50 | }
51 |
52 | fun provideNowPlayingFragmentViewModel(context: Context)
53 | : NowPlayingFragmentViewModel.Factory {
54 | val applicationContext = context.applicationContext
55 | val musicServiceConnection = provideMusicServiceConnection(applicationContext)
56 | return NowPlayingFragmentViewModel.Factory(
57 | applicationContext as Application, musicServiceConnection
58 | )
59 | }
60 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
23 |
28 |
29 |
35 |
38 |
41 |
42 |
43 |
44 |
50 |
51 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_album_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
22 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_pause_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
22 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_play_arrow_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
22 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_signal_wifi_off_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
22 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/media_item_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
23 |
24 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/media_item_mask.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
23 |
24 |
27 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/media_overlay_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
19 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/cast_context_error.xml:
--------------------------------------------------------------------------------
1 |
2 |
16 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_mediaitem.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
24 |
25 |
34 |
35 |
46 |
47 |
56 |
57 |
70 |
71 |
83 |
84 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_mediaitem_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
22 |
23 |
33 |
34 |
39 |
40 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 | #840255
19 | #710144
20 | #14A5A1
21 |
22 | #00000000
23 |
24 | #F1F1F1
25 |
26 | #f8f8f8
27 |
28 | #eff0f0
29 | #fdfdfd
30 |
31 | #ffffff
32 |
33 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 | 16dp
19 |
20 | 72dp
21 | 4dp
22 | 12dp
23 | 4dp
24 |
25 | 72dp
26 |
27 | 4dp
28 | 2dp
29 | -2dp
30 |
31 | 72dp
32 | 42dp
33 | 8dp
34 | 42dp
35 | 52dp
36 |
37 | 32dp
38 | 8dp
39 | 32dp
40 | 16dp
41 |
42 | 32sp
43 | 18sp
44 |
45 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 | UAMP
19 |
20 | Skip back 10s
21 | Skip forward 10s
22 | Play
23 | Pause
24 |
25 | Queue
26 | Album art
27 | --:--
28 | %d:%02d
29 |
30 | Failed to get Cast context. Try updating Google Play Services and restart the app.
31 |
32 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
25 |
26 |
29 |
30 |
33 |
34 |
38 |
39 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/automotive_app_desc.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
22 |
23 |
--------------------------------------------------------------------------------
/automotive/build.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
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 | * https://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 | apply plugin: 'com.android.application'
18 | apply plugin: 'kotlin-android'
19 | apply plugin: 'kotlin-kapt'
20 | apply plugin: 'kotlin-android-extensions'
21 |
22 | android {
23 | compileSdkVersion rootProject.compileSdkVersion
24 |
25 | defaultConfig {
26 | applicationId "com.example.android.uamp.next"
27 |
28 | minSdkVersion 21
29 | targetSdkVersion rootProject.targetSdkVersion
30 |
31 | versionCode 1
32 | versionName "1.0"
33 |
34 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
35 | }
36 |
37 | buildFeatures {
38 | viewBinding true
39 | }
40 |
41 | compileOptions {
42 | sourceCompatibility 1.8
43 | targetCompatibility 1.8
44 | }
45 | kotlinOptions {
46 | jvmTarget = "1.8"
47 | }
48 |
49 | buildTypes {
50 | release {
51 | minifyEnabled false
52 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
53 | }
54 | }
55 |
56 | }
57 |
58 | dependencies {
59 | implementation project(':common')
60 |
61 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
62 |
63 | implementation "androidx.core:core-ktx:$androidx_core_ktx_version"
64 | implementation "androidx.preference:preference:$androidx_preference_version"
65 | implementation "androidx.car:car:$androidx_car_version"
66 | implementation "androidx.constraintlayout:constraintlayout:$constraint_layout_version"
67 | implementation "androidx.appcompat:appcompat:$androidx_app_compat_version"
68 | implementation "androidx.lifecycle:lifecycle-extensions:$arch_lifecycle_version"
69 |
70 | implementation "com.google.android.gms:play-services-auth:$play_services_auth_version"
71 |
72 | testImplementation "junit:junit:$junit_version"
73 |
74 | androidTestImplementation "androidx.test:runner:$androidx_test_runner_version"
75 | androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_version"
76 | }
77 |
--------------------------------------------------------------------------------
/automotive/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/automotive/src/androidTest/java/com/example/android/uamp/automotive/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
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 | * https://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.example.android.uamp.automotive;
18 |
19 | import android.content.Context;
20 |
21 | import androidx.test.InstrumentationRegistry;
22 | import androidx.test.runner.AndroidJUnit4;
23 |
24 | import org.junit.Test;
25 | import org.junit.runner.RunWith;
26 |
27 | import static org.junit.Assert.assertEquals;
28 |
29 | /**
30 | * Instrumented test, which will execute on an Android device.
31 | *
32 | * @see Testing documentation
33 | */
34 | @RunWith(AndroidJUnit4.class)
35 | public class ExampleInstrumentedTest {
36 | @Test
37 | public void useAppContext() {
38 | // Context of the app under test.
39 | Context appContext = InstrumentationRegistry.getTargetContext();
40 |
41 | assertEquals("com.example.automotive", appContext.getPackageName());
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/automotive/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
20 |
21 |
27 |
30 |
31 |
32 |
33 |
36 |
39 |
42 |
43 |
52 |
53 |
56 |
57 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
70 |
71 |
72 |
73 |
74 |
75 |
79 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
--------------------------------------------------------------------------------
/automotive/src/main/java/com/example/android/uamp/automotive/PhoneSignInFragment.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.example.android.uamp.automotive
18 |
19 | import android.os.Bundle
20 | import android.text.method.LinkMovementMethod
21 | import android.view.View
22 | import androidx.core.content.ContextCompat
23 | import androidx.core.text.HtmlCompat
24 | import androidx.fragment.app.Fragment
25 | import com.example.android.uamp.automotive.databinding.PhoneSignInBinding
26 |
27 | /**
28 | * Fragment that is used to facilitate phone sign-in. The fragment allows users to choose between
29 | * either the PIN or QR code sign-in flow.
30 | */
31 | class PhoneSignInFragment : Fragment(R.layout.phone_sign_in) {
32 |
33 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
34 | super.onViewCreated(view, savedInstanceState)
35 | val context = requireContext()
36 |
37 | val binding = PhoneSignInBinding.bind(view)
38 |
39 | binding.toolbar.setNavigationOnClickListener {
40 | requireActivity().supportFragmentManager.popBackStack()
41 | }
42 |
43 | // Set up PIN sign in button.
44 | binding.appIcon.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.aural_logo))
45 | binding.primaryMessage.text = getString(R.string.phone_sign_in_primary_text)
46 | binding.pinSignInButton.text = getString(R.string.pin_sign_in_button_label)
47 | binding.pinSignInButton.setOnClickListener {
48 | requireActivity().supportFragmentManager.beginTransaction()
49 | .replace(R.id.sign_in_container, PinCodeSignInFragment())
50 | .addToBackStack("landingPage")
51 | .commit()
52 | }
53 |
54 | // Set up QR code sign in button.
55 | binding.qrSignInButton.text = getString(R.string.qr_sign_in_button_label)
56 | binding.qrSignInButton.setOnClickListener {
57 | requireActivity().supportFragmentManager.beginTransaction()
58 | .replace(R.id.sign_in_container, QrCodeSignInFragment())
59 | .addToBackStack("landingPage")
60 | .commit()
61 | }
62 |
63 | // Links in footer text should be clickable.
64 | binding.footer.text = HtmlCompat.fromHtml(
65 | context.getString(R.string.sign_in_footer),
66 | HtmlCompat.FROM_HTML_MODE_LEGACY
67 | )
68 | binding.footer.movementMethod = LinkMovementMethod.getInstance()
69 | }
70 | }
--------------------------------------------------------------------------------
/automotive/src/main/java/com/example/android/uamp/automotive/PinCodeSignInFragment.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.example.android.uamp.automotive
18 |
19 | import android.os.Bundle
20 | import android.text.method.LinkMovementMethod
21 | import android.view.LayoutInflater
22 | import android.view.View
23 | import android.widget.TextView
24 | import androidx.core.content.ContextCompat
25 | import androidx.core.text.HtmlCompat
26 | import androidx.fragment.app.Fragment
27 | import androidx.lifecycle.ViewModelProvider
28 | import com.example.android.uamp.automotive.databinding.PinSignInBinding
29 |
30 | /**
31 | * Fragment that is used to facilitate PIN code sign-in. This fragment displayed a configurable
32 | * PIN code that users enter in a secondary device to perform sign-in.
33 | *
34 | *
This screen serves as a demo for UI best practices for PIN code sign in. Sign in implementation
35 | * will be app specific and is not included.
36 | */
37 | class PinCodeSignInFragment : Fragment(R.layout.pin_sign_in) {
38 |
39 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
40 | super.onViewCreated(view, savedInstanceState)
41 | val context = requireContext()
42 |
43 | val binding = PinSignInBinding.bind(view)
44 |
45 | binding.toolbar.setNavigationOnClickListener {
46 | requireActivity().supportFragmentManager.popBackStack()
47 | }
48 |
49 | binding.appIcon.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.aural_logo))
50 | binding.primaryMessage.text = getString(R.string.pin_sign_in_primary_text)
51 | binding.secondaryMessage.text = getString(R.string.pin_sign_in_secondary_text)
52 |
53 | // Links in footer text should be clickable.
54 | binding.footer.text = HtmlCompat.fromHtml(
55 | context.getString(R.string.sign_in_footer),
56 | HtmlCompat.FROM_HTML_MODE_LEGACY
57 | )
58 | binding.footer.movementMethod = LinkMovementMethod.getInstance()
59 |
60 | val pin = ViewModelProvider(requireActivity())
61 | .get(SignInActivityViewModel::class.java)
62 | .generatePin()
63 |
64 | // Remove existing PIN characters.
65 | if (binding.pinCodeContainer.childCount > 0) {
66 | binding.pinCodeContainer.removeAllViews()
67 | }
68 |
69 | for (element in pin) {
70 | val pinItem = LayoutInflater.from(context).inflate(
71 | R.layout.pin_item,
72 | binding.pinCodeContainer,
73 | false
74 | ) as TextView
75 | pinItem.text = element.toString()
76 | binding.pinCodeContainer.addView(pinItem)
77 | }
78 | }
79 | }
--------------------------------------------------------------------------------
/automotive/src/main/java/com/example/android/uamp/automotive/QrCodeSignInFragment.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.example.android.uamp.automotive
18 |
19 | import android.os.Bundle
20 | import android.text.method.LinkMovementMethod
21 | import android.view.View
22 | import androidx.core.content.ContextCompat.getDrawable
23 | import androidx.core.text.HtmlCompat
24 | import androidx.fragment.app.Fragment
25 | import com.bumptech.glide.Glide
26 | import com.example.android.uamp.automotive.databinding.QrSignInBinding
27 |
28 | /**
29 | * Fragment that is used to facilitate QR code sign-in. Users scan a QR code rendered by this
30 | * fragment with their phones, which performs the authentication required for sign-in
31 | *
32 | *
This screen serves as a demo for UI best practices for QR code sign in. Sign in implementation
33 | * will be app specific and is not included.
34 | */
35 | class QrCodeSignInFragment : Fragment(R.layout.qr_sign_in) {
36 |
37 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
38 | super.onViewCreated(view, savedInstanceState)
39 | val binding = QrSignInBinding.bind(view)
40 |
41 | binding.toolbar.setNavigationOnClickListener {
42 | requireActivity().supportFragmentManager.popBackStack()
43 | }
44 |
45 | binding.appIcon.setImageDrawable(getDrawable(requireContext(), R.drawable.aural_logo))
46 | binding.primaryMessage.text = getString(R.string.qr_sign_in_primary_text)
47 | binding.secondaryMessage.text = getString(R.string.qr_sign_in_secondary_text)
48 |
49 | // Links in footer text should be clickable.
50 | binding.footer.text = HtmlCompat.fromHtml(
51 | requireContext().getString(R.string.sign_in_footer),
52 | HtmlCompat.FROM_HTML_MODE_LEGACY
53 | )
54 | binding.footer.movementMethod = LinkMovementMethod.getInstance()
55 |
56 | Glide.with(this).load(getString(R.string.qr_code_url)).into(binding.qrCode)
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/automotive/src/main/java/com/example/android/uamp/automotive/SettingsActivity.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
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 | * https://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.example.android.uamp.automotive
18 |
19 | import android.os.Bundle
20 | import androidx.appcompat.app.AppCompatActivity
21 | import com.example.android.uamp.automotive.databinding.ActivitySettingsBinding
22 |
23 | /**
24 | * This class exposes application settings
25 | * for integration with MediaCenter in Android Automotive.
26 | */
27 | class SettingsActivity : AppCompatActivity() {
28 |
29 | override fun onCreate(savedInstanceState: Bundle?) {
30 | super.onCreate(savedInstanceState)
31 | val binding = ActivitySettingsBinding.inflate(layoutInflater)
32 | setContentView(binding.root)
33 |
34 | setSupportActionBar(binding.toolbar)
35 | supportActionBar?.setHomeButtonEnabled(true)
36 | supportActionBar?.setDisplayHomeAsUpEnabled(true)
37 |
38 | supportFragmentManager
39 | .beginTransaction()
40 | .replace(R.id.settings_container, SettingsFragment())
41 | .commit()
42 | }
43 |
44 | override fun onBackPressed() {
45 | super.onBackPressed()
46 | finish()
47 | }
48 |
49 | override fun onSupportNavigateUp(): Boolean {
50 | onBackPressed()
51 | return true
52 | }
53 | }
--------------------------------------------------------------------------------
/automotive/src/main/java/com/example/android/uamp/automotive/SettingsFragment.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google Inc. 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.example.android.uamp.automotive
18 |
19 | import android.app.Application
20 | import android.content.ComponentName
21 | import android.os.Bundle
22 | import androidx.lifecycle.AndroidViewModel
23 | import androidx.lifecycle.ViewModelProvider
24 | import androidx.preference.Preference
25 | import androidx.preference.PreferenceFragmentCompat
26 | import com.example.android.uamp.common.MusicServiceConnection
27 |
28 | /**
29 | * Preference fragment hosted by [SettingsActivity]. Handles events to various preference changes.
30 | */
31 | class SettingsFragment : PreferenceFragmentCompat() {
32 | private lateinit var viewModel: SettingsFragmentViewModel
33 |
34 | override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
35 | setPreferencesFromResource(R.xml.preferences, rootKey)
36 |
37 | viewModel = ViewModelProvider(this)
38 | .get(SettingsFragmentViewModel::class.java)
39 | }
40 |
41 | override fun onPreferenceTreeClick(preference: Preference?): Boolean {
42 | return when (preference?.key) {
43 | "logout" -> {
44 | viewModel.logout()
45 | requireActivity().finish()
46 | true
47 | }
48 | else -> {
49 | super.onPreferenceTreeClick(preference)
50 | }
51 | }
52 | }
53 | }
54 |
55 | /**
56 | * Basic ViewModel for [SettingsFragment].
57 | */
58 | class SettingsFragmentViewModel(application: Application) : AndroidViewModel(application) {
59 | private val applicationContext = application.applicationContext
60 | private val musicServiceConnection = MusicServiceConnection(
61 | applicationContext,
62 | ComponentName(applicationContext, AutomotiveMusicService::class.java)
63 | )
64 |
65 | fun logout() {
66 | // Logout is fire and forget.
67 | musicServiceConnection.sendCommand(LOGOUT, null)
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/automotive/src/main/java/com/example/android/uamp/automotive/SignInActivity.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
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 | * https://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.example.android.uamp.automotive
18 |
19 | import android.os.Bundle
20 | import android.widget.Toast
21 | import androidx.appcompat.app.AppCompatActivity
22 | import androidx.lifecycle.Observer
23 | import androidx.lifecycle.ViewModelProvider
24 |
25 | class SignInActivity : AppCompatActivity() {
26 |
27 | private lateinit var viewModel: SignInActivityViewModel
28 |
29 | override fun onCreate(savedInstanceState: Bundle?) {
30 | super.onCreate(savedInstanceState)
31 | setContentView(R.layout.activity_sign_in)
32 |
33 | viewModel = ViewModelProvider(this)
34 | .get(SignInActivityViewModel::class.java)
35 |
36 | viewModel.loggedIn.observe(this, Observer { loggedIn ->
37 | if (loggedIn == true) {
38 | Toast.makeText(this, R.string.sign_in_success_message, Toast.LENGTH_SHORT).show()
39 | finish()
40 | }
41 | })
42 |
43 | supportFragmentManager.beginTransaction()
44 | .add(R.id.sign_in_container, SignInLandingPageFragment())
45 | .commit()
46 | }
47 | }
--------------------------------------------------------------------------------
/automotive/src/main/java/com/example/android/uamp/automotive/SignInActivityViewModel.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
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 | * https://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.example.android.uamp.automotive
18 |
19 | import android.app.Activity
20 | import android.app.Application
21 | import android.content.ComponentName
22 | import android.os.Bundle
23 | import android.text.TextUtils
24 | import android.widget.Toast
25 | import androidx.lifecycle.AndroidViewModel
26 | import androidx.lifecycle.LiveData
27 | import androidx.lifecycle.MutableLiveData
28 | import com.example.android.uamp.common.MusicServiceConnection
29 | import java.util.Random
30 |
31 | /**
32 | * Basic ViewModel for [SignInActivity].
33 | */
34 | class SignInActivityViewModel(application: Application) : AndroidViewModel(application) {
35 | private val applicationContext = application.applicationContext
36 | private val musicServiceConnection = MusicServiceConnection(
37 | applicationContext,
38 | ComponentName(applicationContext, AutomotiveMusicService::class.java)
39 | )
40 |
41 | private val _loggedIn = MutableLiveData()
42 | val loggedIn: LiveData = _loggedIn
43 |
44 | fun login(email: String, password: String) {
45 | if (TextUtils.isEmpty(email) or TextUtils.isEmpty(password)) {
46 | Toast.makeText(
47 | applicationContext,
48 | applicationContext.getString(R.string.missing_fields_error),
49 | Toast.LENGTH_SHORT
50 | ).show()
51 | } else {
52 | val loginParams = Bundle().apply {
53 | putString(LOGIN_EMAIL, email)
54 | putString(LOGIN_PASSWORD, password)
55 | }
56 | musicServiceConnection.sendCommand(LOGIN, loginParams) { resultCode, _ ->
57 | _loggedIn.postValue(resultCode == Activity.RESULT_OK)
58 | }
59 | }
60 | }
61 |
62 | fun generatePin(): CharSequence {
63 | return String.format("%08d", Random().nextInt(99999999))
64 | }
65 | }
--------------------------------------------------------------------------------
/automotive/src/main/java/com/example/android/uamp/automotive/UsernameAndPasswordSignInFragment.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.example.android.uamp.automotive
18 |
19 | import android.os.Build
20 | import android.os.Bundle
21 | import android.text.method.LinkMovementMethod
22 | import android.view.LayoutInflater
23 | import android.view.View
24 | import android.view.ViewGroup
25 | import android.widget.Button
26 | import android.widget.ImageView
27 | import android.widget.TextView
28 | import androidx.appcompat.widget.Toolbar
29 | import androidx.core.content.ContextCompat
30 | import androidx.core.text.HtmlCompat
31 | import androidx.fragment.app.Fragment
32 | import androidx.lifecycle.ViewModelProvider
33 | import com.google.android.material.textfield.TextInputEditText
34 | import com.google.android.material.textfield.TextInputLayout
35 |
36 | /**
37 | * Fragment that is used to facilitates username and password sign-in.
38 | */
39 | class UsernameAndPasswordSignInFragment : Fragment() {
40 |
41 | private lateinit var toolbar: Toolbar
42 | private lateinit var appIcon: ImageView
43 | private lateinit var primaryTextView: TextView
44 | private lateinit var passwordContainer: TextInputLayout
45 | private lateinit var passwordInput: TextInputEditText
46 | private lateinit var submitButton: Button
47 | private lateinit var footerTextView: TextView
48 |
49 | override fun onCreateView(
50 | inflater: LayoutInflater, container: ViewGroup?,
51 | savedInstanceState: Bundle?
52 | ): View? {
53 | return inflater.inflate(R.layout.username_and_password_sign_in, container, false)
54 | }
55 |
56 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
57 | super.onViewCreated(view, savedInstanceState)
58 | val context = requireContext()
59 |
60 | toolbar = view.findViewById(R.id.toolbar)
61 | appIcon = view.findViewById(R.id.app_icon)
62 | primaryTextView = view.findViewById(R.id.primary_message)
63 | passwordContainer = view.findViewById(R.id.password_container)
64 | passwordInput = view.findViewById(R.id.password_input)
65 | submitButton = view.findViewById(R.id.submit_button)
66 | footerTextView = view.findViewById(R.id.footer)
67 |
68 | toolbar.setNavigationOnClickListener {
69 | requireActivity().supportFragmentManager.popBackStack()
70 | }
71 |
72 | appIcon.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.aural_logo))
73 | primaryTextView.text = getString(R.string.username_and_password_sign_in_primary_text)
74 | passwordContainer.hint = getString(R.string.password_hint)
75 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
76 | passwordInput.setAutofillHints(View.AUTOFILL_HINT_PASSWORD)
77 | }
78 |
79 | // Links in footer text should be clickable.
80 | footerTextView.text = HtmlCompat.fromHtml(
81 | context.getString(R.string.sign_in_footer),
82 | HtmlCompat.FROM_HTML_MODE_LEGACY
83 | )
84 | footerTextView.movementMethod = LinkMovementMethod.getInstance()
85 |
86 | // Get user identifier from previous screen.
87 | val userId = arguments?.getString(SignInLandingPageFragment.CAR_SIGN_IN_IDENTIFIER_KEY)
88 |
89 | submitButton.text = getString(R.string.sign_in_submit_button_label)
90 | submitButton.setOnClickListener {
91 | onSignIn(userId!!, passwordInput.text.toString())
92 | }
93 | }
94 |
95 | private fun onSignIn(userIdentifier: CharSequence, password: CharSequence) {
96 | ViewModelProvider(requireActivity())
97 | .get(SignInActivityViewModel::class.java)
98 | .login(userIdentifier.toString(), password.toString())
99 | }
100 | }
--------------------------------------------------------------------------------
/automotive/src/main/res/color/car_text_dark.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/automotive/src/main/res/color/car_text_light.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/automotive/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
22 |
25 |
26 |
--------------------------------------------------------------------------------
/automotive/src/main/res/drawable/aural_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/automotive/src/main/res/drawable/aural_logo.png
--------------------------------------------------------------------------------
/automotive/src/main/res/drawable/default_button_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/automotive/src/main/res/drawable/google_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/automotive/src/main/res/drawable/google_logo.png
--------------------------------------------------------------------------------
/automotive/src/main/res/drawable/google_sign_in_button_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/automotive/src/main/res/drawable/google_sign_in_button_logo.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
22 |
--------------------------------------------------------------------------------
/automotive/src/main/res/drawable/pin_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
19 |
20 |
23 |
24 |
--------------------------------------------------------------------------------
/automotive/src/main/res/drawable/sign_in_button_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/automotive/src/main/res/drawable/sign_in_toolbar_back_icon.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
22 |
23 |
24 |
27 |
28 |
--------------------------------------------------------------------------------
/automotive/src/main/res/drawable/sign_in_toolbar_back_ripple_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
20 |
--------------------------------------------------------------------------------
/automotive/src/main/res/layout-h900dp/pin_sign_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
21 |
22 |
26 |
27 |
39 |
40 |
52 |
53 |
65 |
66 |
76 |
77 |
89 |
90 |
91 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/automotive/src/main/res/layout-h900dp/qr_sign_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
21 |
22 |
26 |
27 |
39 |
40 |
52 |
53 |
65 |
66 |
78 |
79 |
91 |
92 |
93 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/automotive/src/main/res/layout/activity_login.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
23 |
24 |
33 |
34 |
46 |
47 |
63 |
64 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/automotive/src/main/res/layout/activity_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
22 |
23 |
26 |
27 |
32 |
33 |
34 |
35 |
40 |
41 |
--------------------------------------------------------------------------------
/automotive/src/main/res/layout/activity_sign_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
21 |
--------------------------------------------------------------------------------
/automotive/src/main/res/layout/pin_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
27 |
--------------------------------------------------------------------------------
/automotive/src/main/res/layout/preference.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
21 |
22 |
30 |
31 |
--------------------------------------------------------------------------------
/automotive/src/main/res/layout/preference_category.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
21 |
22 |
30 |
31 |
--------------------------------------------------------------------------------
/automotive/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/automotive/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/automotive/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/automotive/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/automotive/src/main/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/automotive/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/automotive/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/automotive/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/automotive/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/automotive/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/automotive/src/main/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/automotive/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/automotive/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/automotive/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/automotive/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/automotive/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/automotive/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/automotive/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/automotive/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/automotive/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/automotive/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/automotive/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/automotive/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/automotive/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/automotive/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/automotive/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/automotive/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/automotive/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/automotive/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/automotive/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/automotive/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/automotive/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/automotive/src/main/res/values-h1060dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 | 64dp
19 | 116dp
20 | 64dp
21 |
22 |
--------------------------------------------------------------------------------
/automotive/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 | #840255
19 | #710144
20 | #14A5A1
21 | #202020
22 |
23 | #fff1f3f4
24 | #ffdadce0
25 |
26 | #ffffffff
27 |
28 |
30 | @color/grey_300
31 |
32 |
--------------------------------------------------------------------------------
/automotive/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 | 16dp
19 | 16dp
20 |
21 | 64sp
22 | 32sp
23 | 26sp
24 |
25 | 158dp
26 | 56dp
27 | 56dp
28 |
29 | 24dp
30 | 48dp
31 | 96dp
32 |
33 | 4dp
34 | 2dp
35 |
36 | 28dp
37 |
38 | 48dp
39 | 96dp
40 |
41 | 32dp
42 | 24dp
43 |
44 | 642dp
45 | 420dp
46 | 48dp
47 |
48 | 48dp
49 | 32dp
50 | 64dp
51 | 48dp
52 |
53 | 16dp
54 | 8dp
55 | 4dp
56 |
57 | 96dp
58 | 32dp
59 | 0.1
60 | 0.9
61 |
62 |
--------------------------------------------------------------------------------
/automotive/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 | UAMP
19 | UAMP Settings
20 |
21 |
22 | Login
23 | Authentication required
24 | Enter password
25 | Email
26 | Login
27 | Please fill in missing fields
28 | Forgot username? | Use phone app to sign in
29 | Forgot password? | Reset with phone app
30 | Next
31 | Next
32 | Enter username
33 | Invalid input
34 | Sign in with Aural
35 | Sign in with Aural
36 | Sign in with phone
37 | Choose phone sign-in method
38 | PIN
39 | Sign in with PIN
40 | Go to example.com/fast-pair on your phone
41 | QR code
42 | Sign in with QR code
43 | Use your phone\'s camera to capture this code
44 | Sign in with Google
45 | By signing in with Aural, you agree to the <a href="http://www.google.com">Terms & Conditions</a> and <a href="http://www.google.com">Privacy policy</a>
46 | https://chart.apis.google.com/chart?cht=qr&chs=392x392&chl=https://www.youtube.com/watch?v=8I8mCFYYfdk
47 | Sign in successful
48 | Sign in failed with error code: %1$d
49 |
50 | YOUR_SERVER_CLIENT_ID
51 |
52 |
--------------------------------------------------------------------------------
/automotive/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
31 |
32 |
39 |
40 |
45 |
46 |
49 |
50 |
62 |
63 |
66 |
67 |
70 |
71 |
74 |
75 |
78 |
79 |
82 |
83 |
86 |
87 |
95 |
96 |
--------------------------------------------------------------------------------
/automotive/src/main/res/xml/automotive_app_desc.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/automotive/src/main/res/xml/preferences.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
22 |
23 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/automotive/src/test/java/com/example/android/uamp/automotive/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
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 | * https://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.example.android.uamp.automotive;
18 |
19 | import org.junit.Test;
20 |
21 | import static org.junit.Assert.assertEquals;
22 |
23 | /**
24 | * Example local unit test, which will execute on the development machine (host).
25 | *
26 | * @see Testing documentation
27 | */
28 | public class ExampleUnitTest {
29 | @Test
30 | public void addition_isCorrect() {
31 | assertEquals(4, 2 + 2);
32 | }
33 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 Google Inc. 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 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
18 |
19 | buildscript {
20 | ext {
21 | // App SDK versions.
22 | compileSdkVersion = 30
23 | minSdkVersion = 19
24 | targetSdkVersion = 30
25 |
26 | // Dependency versions.
27 | androidx_app_compat_version = '1.2.0'
28 | androidx_car_version = '1.0.0-alpha7'
29 | androidx_core_ktx_version = '1.3.1'
30 | androidx_media_version = '1.0.1'
31 | androidx_preference_version = '1.1.1'
32 | androidx_test_runner_version = '1.3.0'
33 | arch_lifecycle_version = '2.2.0'
34 | constraint_layout_version = '2.0.1'
35 | espresso_version = '3.3.0'
36 | exoplayer_version = '2.16.0'
37 | fragment_version = '1.2.5'
38 | glide_version = '4.11.0'
39 | gms_strict_version_matcher_version = '1.0.3'
40 | gradle_version = '3.1.4'
41 | gson_version = '2.8.5'
42 | junit_version = '4.13'
43 | kotlin_version = '1.3.72'
44 | kotlin_coroutines_version = '1.1.0'
45 | multidex_version = '1.0.3'
46 | play_services_auth_version = '18.1.0'
47 | recycler_view_version = '1.1.0'
48 | robolectric_version = '4.2'
49 | test_runner_version = '1.1.0'
50 | }
51 |
52 | repositories {
53 | google()
54 | mavenCentral()
55 | }
56 | dependencies {
57 | classpath 'com.android.tools.build:gradle:4.0.1'
58 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
59 | classpath "com.google.android.gms:strict-version-matcher-plugin:$gms_strict_version_matcher_version"
60 |
61 | // NOTE: Do not place your application dependencies here; they belong
62 | // in the individual module build.gradle files
63 | }
64 | }
65 |
66 | allprojects {
67 | repositories {
68 | google()
69 | mavenCentral()
70 | }
71 | }
72 |
73 | task clean(type: Delete) {
74 | delete rootProject.buildDir
75 | }
76 |
--------------------------------------------------------------------------------
/common/build.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 Google Inc. 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 | apply plugin: 'com.android.library'
18 | apply plugin: 'kotlin-android'
19 | apply plugin: 'kotlin-kapt'
20 | apply plugin: 'kotlin-android-extensions'
21 |
22 | android {
23 | compileSdkVersion rootProject.compileSdkVersion
24 |
25 | defaultConfig {
26 | versionCode 1
27 | versionName "1.0"
28 |
29 | minSdkVersion rootProject.minSdkVersion
30 | targetSdkVersion rootProject.targetSdkVersion
31 |
32 | testOptions.unitTests.includeAndroidResources = true
33 | testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
34 | }
35 |
36 | buildTypes {
37 | release {
38 | minifyEnabled false
39 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
40 | }
41 | }
42 | compileOptions {
43 | targetCompatibility = '1.8'
44 | }
45 |
46 | }
47 |
48 | dependencies {
49 | api "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
50 | api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
51 | api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
52 |
53 | api "androidx.media:media:$androidx_media_version"
54 |
55 | api "com.google.code.gson:gson:$gson_version"
56 |
57 | // ExoPlayer dependencies
58 |
59 | // This allows UAMP to utilize a local version of ExoPlayer, which is particularly
60 | // useful for extending the MediaSession extension, as well as for testing and
61 | // customization. If the ":exoplayer-library-core" project is included, we assume
62 | // the others are included as well.
63 | if (findProject(':exoplayer-library-core') != null) {
64 | api project(':exoplayer-library-core')
65 | api project(':exoplayer-library-ui')
66 | api project(':exoplayer-extension-mediasession')
67 | api project(':exoplayer-extension-cast')
68 | } else {
69 | api "com.google.android.exoplayer:exoplayer-core:$exoplayer_version"
70 | api "com.google.android.exoplayer:exoplayer-ui:$exoplayer_version"
71 | api "com.google.android.exoplayer:extension-mediasession:$exoplayer_version"
72 | api "com.google.android.exoplayer:extension-cast:$exoplayer_version"
73 | }
74 |
75 | // Glide dependencies
76 | api "com.github.bumptech.glide:glide:$glide_version"
77 | kapt "com.github.bumptech.glide:compiler:$glide_version"
78 |
79 | // Testing
80 | testImplementation "junit:junit:$junit_version"
81 | testImplementation "org.robolectric:robolectric:$robolectric_version"
82 | }
83 |
--------------------------------------------------------------------------------
/common/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/common/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/common/src/main/assets/default_art.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/common/src/main/assets/default_art.png
--------------------------------------------------------------------------------
/common/src/main/java/com/example/android/uamp/media/CastMediaItemConverter.kt:
--------------------------------------------------------------------------------
1 | package com.example.android.uamp.media
2 |
3 | import android.net.Uri
4 | import android.support.v4.media.MediaMetadataCompat
5 | import com.example.android.uamp.media.library.JsonSource
6 | import com.google.android.exoplayer2.MediaItem
7 | import com.google.android.exoplayer2.ext.cast.DefaultMediaItemConverter
8 | import com.google.android.exoplayer2.ext.cast.MediaItemConverter
9 | import com.google.android.exoplayer2.util.MimeTypes
10 | import com.google.android.gms.cast.MediaInfo
11 | import com.google.android.gms.cast.MediaMetadata
12 | import com.google.android.gms.cast.MediaQueueItem
13 | import com.google.android.gms.common.images.WebImage
14 |
15 | /**
16 | * A [MediaItemConverter] to convert from a [MediaItem] to a Cast [MediaQueueItem].
17 | *
18 | * It adds all audio specific metadata properties and creates a Cast metadata object of type
19 | * [MediaMetadata.MEDIA_TYPE_MUSIC_TRACK].
20 | *
21 | * To create an artwork for Cast we can't use the standard [MediaItem#mediaMetadata#artworkUri]
22 | * because UAMP uses a content provider to serve cached bitmaps. The URIs starting with `content://`
23 | * are useless on a Cast device, so we need to use the original HTTP URI that the [JsonSource]
24 | * stores in the metadata extra with key `JsonSource.ORIGINAL_ARTWORK_URI_KEY`.
25 | */
26 | internal class CastMediaItemConverter : MediaItemConverter {
27 |
28 | private val defaultMediaItemConverter = DefaultMediaItemConverter()
29 |
30 | override fun toMediaQueueItem(mediaItem: MediaItem): MediaQueueItem {
31 | val castMediaMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MUSIC_TRACK)
32 | mediaItem.mediaMetadata.title?.let {
33 | castMediaMetadata.putString(MediaMetadata.KEY_TITLE, it.toString() )
34 | }
35 | mediaItem.mediaMetadata.subtitle?.let {
36 | castMediaMetadata.putString(MediaMetadata.KEY_SUBTITLE, it.toString())
37 | }
38 | mediaItem.mediaMetadata.artist?.let {
39 | castMediaMetadata.putString(MediaMetadata.KEY_ARTIST, it.toString())
40 | }
41 | mediaItem.mediaMetadata.albumTitle?.let {
42 | castMediaMetadata.putString(MediaMetadata.KEY_ALBUM_TITLE, it.toString())
43 | }
44 | mediaItem.mediaMetadata.albumArtist?.let {
45 | castMediaMetadata.putString(MediaMetadata.KEY_ALBUM_ARTIST, it.toString())
46 | }
47 | mediaItem.mediaMetadata.composer?.let {
48 | castMediaMetadata.putString(MediaMetadata.KEY_COMPOSER, it.toString())
49 | }
50 | mediaItem.mediaMetadata.trackNumber?.let{
51 | castMediaMetadata.putInt(MediaMetadata.KEY_TRACK_NUMBER, it)
52 | }
53 | mediaItem.mediaMetadata.discNumber?.let {
54 | castMediaMetadata.putInt(MediaMetadata.KEY_DISC_NUMBER, it)
55 | }
56 | val mediaInfo = MediaInfo.Builder(mediaItem.localConfiguration!!.uri.toString())
57 | .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
58 | .setContentType(MimeTypes.AUDIO_MPEG)
59 | mediaItem.localConfiguration?.let {
60 | mediaInfo.setContentUrl(it.uri.toString())
61 | }
62 | mediaItem.mediaMetadata.extras?.let { bundle ->
63 | // Use the original artwork URI for Cast.
64 | bundle.getString(JsonSource.ORIGINAL_ARTWORK_URI_KEY)?.let {
65 | castMediaMetadata.addImage(WebImage(Uri.parse(it)))
66 | }
67 | mediaInfo.setStreamDuration(
68 | bundle.getLong(MediaMetadataCompat.METADATA_KEY_DURATION,0))
69 | }
70 | mediaInfo.setMetadata(castMediaMetadata)
71 | return MediaQueueItem.Builder(mediaInfo.build()).build()
72 | }
73 |
74 | override fun toMediaItem(mediaQueueItem: MediaQueueItem): MediaItem {
75 | return defaultMediaItemConverter.toMediaItem(mediaQueueItem)
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/common/src/main/java/com/example/android/uamp/media/PersistentStorage.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Google Inc. 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.example.android.uamp.media
18 |
19 | import android.content.Context
20 | import android.content.SharedPreferences
21 | import android.net.Uri
22 | import android.os.Bundle
23 | import android.support.v4.media.MediaBrowserCompat
24 | import android.support.v4.media.MediaBrowserCompat.MediaItem.FLAG_PLAYABLE
25 | import android.support.v4.media.MediaDescriptionCompat
26 | import com.bumptech.glide.Glide
27 | import com.example.android.uamp.media.extensions.asAlbumArtContentUri
28 | import kotlinx.coroutines.Dispatchers
29 | import kotlinx.coroutines.withContext
30 |
31 | internal class PersistentStorage private constructor(val context: Context) {
32 |
33 | /**
34 | * Store any data which must persist between restarts, such as the most recently played song.
35 | */
36 | private var preferences: SharedPreferences = context.getSharedPreferences(
37 | PREFERENCES_NAME,
38 | Context.MODE_PRIVATE
39 | )
40 |
41 | companion object {
42 |
43 | @Volatile
44 | private var instance: PersistentStorage? = null
45 |
46 | fun getInstance(context: Context) =
47 | instance ?: synchronized(this) {
48 | instance ?: PersistentStorage(context).also { instance = it }
49 | }
50 | }
51 |
52 | suspend fun saveRecentSong(description: MediaDescriptionCompat, position: Long) {
53 |
54 | withContext(Dispatchers.IO) {
55 |
56 | /**
57 | * After booting, Android will attempt to build static media controls for the most
58 | * recently played song. Artwork for these media controls should not be loaded
59 | * from the network as it may be too slow or unavailable immediately after boot. Instead
60 | * we convert the iconUri to point to the Glide on-disk cache.
61 | */
62 | val localIconUri = Glide.with(context).asFile().load(description.iconUri)
63 | .submit(NOTIFICATION_LARGE_ICON_SIZE, NOTIFICATION_LARGE_ICON_SIZE).get()
64 | .asAlbumArtContentUri()
65 |
66 | preferences.edit()
67 | .putString(RECENT_SONG_MEDIA_ID_KEY, description.mediaId)
68 | .putString(RECENT_SONG_TITLE_KEY, description.title.toString())
69 | .putString(RECENT_SONG_SUBTITLE_KEY, description.subtitle.toString())
70 | .putString(RECENT_SONG_ICON_URI_KEY, localIconUri.toString())
71 | .putLong(RECENT_SONG_POSITION_KEY, position)
72 | .apply()
73 | }
74 | }
75 |
76 | fun loadRecentSong(): MediaBrowserCompat.MediaItem? {
77 | val mediaId = preferences.getString(RECENT_SONG_MEDIA_ID_KEY, null)
78 | return if (mediaId == null) {
79 | null
80 | } else {
81 | val extras = Bundle().also {
82 | val position = preferences.getLong(RECENT_SONG_POSITION_KEY, 0L)
83 | it.putLong(MEDIA_DESCRIPTION_EXTRAS_START_PLAYBACK_POSITION_MS, position)
84 | }
85 |
86 | MediaBrowserCompat.MediaItem(
87 | MediaDescriptionCompat.Builder()
88 | .setMediaId(mediaId)
89 | .setTitle(preferences.getString(RECENT_SONG_TITLE_KEY, ""))
90 | .setSubtitle(preferences.getString(RECENT_SONG_SUBTITLE_KEY, ""))
91 | .setIconUri(Uri.parse(preferences.getString(RECENT_SONG_ICON_URI_KEY, "")))
92 | .setExtras(extras)
93 | .build(), FLAG_PLAYABLE
94 | )
95 | }
96 | }
97 | }
98 |
99 | private const val PREFERENCES_NAME = "uamp"
100 | private const val RECENT_SONG_MEDIA_ID_KEY = "recent_song_media_id"
101 | private const val RECENT_SONG_TITLE_KEY = "recent_song_title"
102 | private const val RECENT_SONG_SUBTITLE_KEY = "recent_song_subtitle"
103 | private const val RECENT_SONG_ICON_URI_KEY = "recent_song_icon_uri"
104 | private const val RECENT_SONG_POSITION_KEY = "recent_song_position"
--------------------------------------------------------------------------------
/common/src/main/java/com/example/android/uamp/media/extensions/FileExt.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google Inc. 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.example.android.uamp.media.extensions
18 |
19 | import android.content.ContentResolver
20 | import android.net.Uri
21 | import java.io.File
22 |
23 | /**
24 | * This file contains extension methods for the java.io.File class.
25 | */
26 |
27 | /**
28 | * Returns a Content Uri for the AlbumArtContentProvider
29 | */
30 | fun File.asAlbumArtContentUri(): Uri {
31 | return Uri.Builder()
32 | .scheme(ContentResolver.SCHEME_CONTENT)
33 | .authority(AUTHORITY)
34 | .appendPath(this.path)
35 | .build()
36 | }
37 |
38 | private const val AUTHORITY = "com.example.android.uamp"
39 |
--------------------------------------------------------------------------------
/common/src/main/java/com/example/android/uamp/media/extensions/JavaLangExt.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Google Inc. 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.example.android.uamp.media.extensions
18 |
19 | import android.net.Uri
20 | import java.net.URLEncoder
21 | import java.nio.charset.Charset
22 | import java.util.Locale
23 |
24 | /**
25 | * This file contains extension methods for the java.lang package.
26 | */
27 |
28 | /**
29 | * Helper method to check if a [String] contains another in a case insensitive way.
30 | */
31 | fun String?.containsCaseInsensitive(other: String?) =
32 | if (this != null && other != null) {
33 | toLowerCase(Locale.getDefault()).contains(other.toLowerCase(Locale.getDefault()))
34 | } else {
35 | this == other
36 | }
37 |
38 | /**
39 | * Helper extension to URL encode a [String]. Returns an empty string when called on null.
40 | */
41 | inline val String?.urlEncoded: String
42 | get() = if (Charset.isSupported("UTF-8")) {
43 | URLEncoder.encode(this ?: "", "UTF-8")
44 | } else {
45 | // If UTF-8 is not supported, use the default charset.
46 | @Suppress("deprecation")
47 | URLEncoder.encode(this ?: "")
48 | }
49 |
50 | /**
51 | * Helper extension to convert a potentially null [String] to a [Uri] falling back to [Uri.EMPTY]
52 | */
53 | fun String?.toUri(): Uri = this?.let { Uri.parse(it) } ?: Uri.EMPTY
54 |
--------------------------------------------------------------------------------
/common/src/main/java/com/example/android/uamp/media/extensions/PlaybackStateCompatExt.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Google Inc. 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.example.android.uamp.media.extensions
18 |
19 | import android.os.SystemClock
20 | import android.support.v4.media.session.PlaybackStateCompat
21 |
22 | /**
23 | * Useful extension methods for [PlaybackStateCompat].
24 | */
25 | inline val PlaybackStateCompat.isPrepared
26 | get() = (state == PlaybackStateCompat.STATE_BUFFERING) ||
27 | (state == PlaybackStateCompat.STATE_PLAYING) ||
28 | (state == PlaybackStateCompat.STATE_PAUSED)
29 |
30 | inline val PlaybackStateCompat.isPlaying
31 | get() = (state == PlaybackStateCompat.STATE_BUFFERING) ||
32 | (state == PlaybackStateCompat.STATE_PLAYING)
33 |
34 | inline val PlaybackStateCompat.isPlayEnabled
35 | get() = (actions and PlaybackStateCompat.ACTION_PLAY != 0L) ||
36 | ((actions and PlaybackStateCompat.ACTION_PLAY_PAUSE != 0L) &&
37 | (state == PlaybackStateCompat.STATE_PAUSED))
38 |
39 | inline val PlaybackStateCompat.isPauseEnabled
40 | get() = (actions and PlaybackStateCompat.ACTION_PAUSE != 0L) ||
41 | ((actions and PlaybackStateCompat.ACTION_PLAY_PAUSE != 0L) &&
42 | (state == PlaybackStateCompat.STATE_BUFFERING ||
43 | state == PlaybackStateCompat.STATE_PLAYING))
44 |
45 | inline val PlaybackStateCompat.isSkipToNextEnabled
46 | get() = actions and PlaybackStateCompat.ACTION_SKIP_TO_NEXT != 0L
47 |
48 | inline val PlaybackStateCompat.isSkipToPreviousEnabled
49 | get() = actions and PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS != 0L
50 |
51 | inline val PlaybackStateCompat.stateName
52 | get() = when (state) {
53 | PlaybackStateCompat.STATE_NONE -> "STATE_NONE"
54 | PlaybackStateCompat.STATE_STOPPED -> "STATE_STOPPED"
55 | PlaybackStateCompat.STATE_PAUSED -> "STATE_PAUSED"
56 | PlaybackStateCompat.STATE_PLAYING -> "STATE_PLAYING"
57 | PlaybackStateCompat.STATE_FAST_FORWARDING -> "STATE_FAST_FORWARDING"
58 | PlaybackStateCompat.STATE_REWINDING -> "STATE_REWINDING"
59 | PlaybackStateCompat.STATE_BUFFERING -> "STATE_BUFFERING"
60 | PlaybackStateCompat.STATE_ERROR -> "STATE_ERROR"
61 | else -> "UNKNOWN_STATE"
62 | }
63 |
64 | /**
65 | * Calculates the current playback position based on last update time along with playback
66 | * state and speed.
67 | */
68 | inline val PlaybackStateCompat.currentPlayBackPosition: Long
69 | get() = if (state == PlaybackStateCompat.STATE_PLAYING) {
70 | val timeDelta = SystemClock.elapsedRealtime() - lastPositionUpdateTime
71 | (position + (timeDelta * playbackSpeed)).toLong()
72 | } else {
73 | position
74 | }
75 |
76 |
--------------------------------------------------------------------------------
/common/src/main/java/com/example/android/uamp/media/library/AlbumArtContentProvider.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google Inc. 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.example.android.uamp.media.library
18 |
19 | import android.content.ContentProvider
20 | import android.content.ContentResolver
21 | import android.content.ContentValues
22 | import android.database.Cursor
23 | import android.net.Uri
24 | import android.os.ParcelFileDescriptor
25 | import com.bumptech.glide.Glide
26 | import java.io.File
27 | import java.io.FileNotFoundException
28 | import java.util.concurrent.TimeUnit
29 |
30 | // The amount of time to wait for the album art file to download before timing out.
31 | const val DOWNLOAD_TIMEOUT_SECONDS = 30L
32 |
33 | internal class AlbumArtContentProvider : ContentProvider() {
34 |
35 | companion object {
36 | private val uriMap = mutableMapOf()
37 |
38 | fun mapUri(uri: Uri): Uri {
39 | val path = uri.encodedPath?.substring(1)?.replace('/', ':') ?: return Uri.EMPTY
40 | val contentUri = Uri.Builder()
41 | .scheme(ContentResolver.SCHEME_CONTENT)
42 | .authority("com.example.android.uamp")
43 | .path(path)
44 | .build()
45 | uriMap[contentUri] = uri
46 | return contentUri
47 | }
48 | }
49 |
50 | override fun onCreate() = true
51 |
52 | override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
53 | val context = this.context ?: return null
54 | val remoteUri = uriMap[uri] ?: throw FileNotFoundException(uri.path)
55 |
56 | var file = File(context.cacheDir, uri.path)
57 |
58 | if (!file.exists()) {
59 | // Use Glide to download the album art.
60 | val cacheFile = Glide.with(context)
61 | .asFile()
62 | .load(remoteUri)
63 | .submit()
64 | .get(DOWNLOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS)
65 |
66 | // Rename the file Glide created to match our own scheme.
67 | cacheFile.renameTo(file)
68 |
69 | file = cacheFile
70 | }
71 | return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)
72 | }
73 |
74 | override fun insert(uri: Uri, values: ContentValues?): Uri? = null
75 |
76 | override fun query(
77 | uri: Uri,
78 | projection: Array?,
79 | selection: String?,
80 | selectionArgs: Array?,
81 | sortOrder: String?
82 | ): Cursor? = null
83 |
84 | override fun update(
85 | uri: Uri,
86 | values: ContentValues?,
87 | selection: String?,
88 | selectionArgs: Array?
89 | ) = 0
90 |
91 | override fun delete(uri: Uri, selection: String?, selectionArgs: Array?) = 0
92 |
93 | override fun getType(uri: Uri): String? = null
94 |
95 | }
96 |
--------------------------------------------------------------------------------
/common/src/main/res/drawable-hdpi/ic_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/common/src/main/res/drawable-hdpi/ic_notification.png
--------------------------------------------------------------------------------
/common/src/main/res/drawable-mdpi/ic_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/common/src/main/res/drawable-mdpi/ic_notification.png
--------------------------------------------------------------------------------
/common/src/main/res/drawable-nodpi/default_art.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/common/src/main/res/drawable-nodpi/default_art.png
--------------------------------------------------------------------------------
/common/src/main/res/drawable-xhdpi/ic_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/common/src/main/res/drawable-xhdpi/ic_notification.png
--------------------------------------------------------------------------------
/common/src/main/res/drawable-xxhdpi/ic_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/common/src/main/res/drawable-xxhdpi/ic_notification.png
--------------------------------------------------------------------------------
/common/src/main/res/drawable-xxxhdpi/ic_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/common/src/main/res/drawable-xxxhdpi/ic_notification.png
--------------------------------------------------------------------------------
/common/src/main/res/drawable/ic_album.xml:
--------------------------------------------------------------------------------
1 |
16 |
21 |
24 |
25 |
--------------------------------------------------------------------------------
/common/src/main/res/drawable/ic_recommended.xml:
--------------------------------------------------------------------------------
1 |
16 |
21 |
24 |
25 |
--------------------------------------------------------------------------------
/common/src/main/res/ic_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/common/src/main/res/ic_notification.png
--------------------------------------------------------------------------------
/common/src/main/res/menu/main_activity_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
16 |
--------------------------------------------------------------------------------
/common/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 | Now Playing
20 | Shows what music is currently playing in UAMP
21 |
22 |
23 | Skip to previous track
24 | Skip to next track
25 | Play
26 | Pause
27 |
28 |
29 | Could not locate requested media.
30 | The application has encountered an unexpected error.
31 |
32 |
33 | Caller has a valid certificate, but its package doesn\'t match any expected package for the
34 | given certificate. To allow this caller, add the following to the allowed callers list:\n
35 | \n\t%3$s\n\n
37 | ]]>
38 |
39 |
40 |
41 | Cast
42 |
43 |
44 | Recommended
45 | Albums
46 |
--------------------------------------------------------------------------------
/common/src/main/res/xml/allowed_media_browser_callers.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
56 |
57 |
60 |
61 | 19:75:b2:f1:71:77:bc:89:a5:df:f3:1f:9e:64:a6:ca:e2:81:a5:3d:c1:d1:d5:9b:1d:14:7f:e1:c8:2a:fa:00
62 |
63 |
64 | 70:81:1a:3e:ac:fd:2e:83:e1:8d:a9:bf:ed:e5:2d:f1:6c:e9:1f:2e:69:a4:4d:21:f1:8a:b6:69:91:13:07:71
65 |
66 |
67 |
68 | fd:b0:0c:43:db:de:8b:51:cb:31:2a:a8:1d:3b:5f:a1:77:13:ad:b9:4b:28:f5:98:d7:7f:8e:b8:9d:ac:ee:df
69 |
70 |
71 |
72 | 1c:a8:dc:c0:be:d3:cb:d8:72:d2:cb:79:12:00:c0:29:2c:a9:97:57:68:a8:2d:67:6b:8b:42:4f:b6:5b:52:95
73 |
74 |
75 |
76 |
79 |
80 | 69:d0:72:16:9a:2c:6b:2f:5a:cc:59:0c:e4:33:a1:1a:c3:df:55:1a:df:ee:5d:5f:63:c0:83:b7:22:76:2e:19
81 |
82 |
83 | 85:cd:59:73:54:1b:e6:f4:77:d8:47:a0:bc:c6:aa:25:27:68:4b:81:9c:d5:96:85:29:66:4c:b0:71:57:b6:fe
84 |
85 |
86 |
87 |
90 |
91 | 19:75:b2:f1:71:77:bc:89:a5:df:f3:1f:9e:64:a6:ca:e2:81:a5:3d:c1:d1:d5:9b:1d:14:7f:e1:c8:2a:fa:00
92 |
93 |
94 |
95 |
98 |
99 | 19:75:b2:f1:71:77:bc:89:a5:df:f3:1f:9e:64:a6:ca:e2:81:a5:3d:c1:d1:d5:9b:1d:14:7f:e1:c8:2a:fa:00
100 |
101 |
102 | f0:fd:6c:5b:41:0f:25:cb:25:c3:b5:33:46:c8:97:2f:ae:30:f8:ee:74:11:df:91:04:80:ad:6b:2d:60:db:83
103 |
104 |
105 |
106 |
109 |
110 | 17:E2:81:11:06:2F:97:A8:60:79:7A:83:70:5B:F8:2C:7C:C0:29:35:56:6D:46:22:BC:4E:CF:EE:1B:EB:F8:15
111 |
112 |
113 | 74:B6:FB:F7:10:E8:D9:0D:44:D3:40:12:58:89:B4:23:06:A6:2C:43:79:D0:E5:A6:62:20:E3:A6:8A:BF:90:E2
114 |
115 |
116 |
117 |
--------------------------------------------------------------------------------
/docs/FAQs.md:
--------------------------------------------------------------------------------
1 | # Frequently Asked Questions
2 |
3 | ## How can I change the music which UAMP plays?
4 | UAMP reads its [music catalog](https://storage.googleapis.com/uamp/catalog.json) from a server.
5 | This contains a list of songs and their metadata in JSON format. To change the music you can create your own
6 | music catalog file, host it on a server and update the catalog URL in
7 | [`MusicService`](https://github.com/android/uamp/blob/6c3ff3779d02f55661c5b9d6032cfac507a8415e/common/src/main/java/com/example/android/uamp/media/MusicService.kt#L127).
8 |
9 | Alternatively, you could package your own music catalog and songs inside the app so they can be played without needing an internet connection.
--------------------------------------------------------------------------------
/docs/images/1-browse-albums-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/docs/images/1-browse-albums-screenshot.png
--------------------------------------------------------------------------------
/docs/images/12-ui-class-diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/docs/images/12-ui-class-diagram.png
--------------------------------------------------------------------------------
/docs/images/2-play-song-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/docs/images/2-play-song-screenshot.png
--------------------------------------------------------------------------------
/docs/images/3-architecture-overview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/docs/images/3-architecture-overview.png
--------------------------------------------------------------------------------
/docs/images/4-MusicService.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/docs/images/4-MusicService.png
--------------------------------------------------------------------------------
/docs/images/5-MediaController.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/docs/images/5-MediaController.png
--------------------------------------------------------------------------------
/docs/images/6-notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/docs/images/6-notification.png
--------------------------------------------------------------------------------
/docs/images/9-mvvm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/docs/images/9-mvvm.png
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2017 Google Inc. 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 | # Project-wide Gradle settings.
17 | # IDE (e.g. Android Studio) users:
18 | # Gradle settings configured through the IDE *will override*
19 | # any settings specified in this file.
20 | # For more details on how to configure your build environment visit
21 | # http://www.gradle.org/docs/current/userguide/build_environment.html
22 | # Specifies the JVM arguments used for the daemon process.
23 | # The setting is particularly useful for tweaking memory settings.
24 | android.enableJetifier=true
25 | android.useAndroidX=true
26 | org.gradle.jvmargs=-Xmx1536m
27 | # Required by Robolectric.
28 | #android.enableUnitTestBinaryResources=true
29 | # When configured, Gradle will run in incubating parallel mode.
30 | # This option should only be used with decoupled projects. More details, visit
31 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
32 | # org.gradle.parallel=true
33 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/android/uamp/31769898337d0228af7457a7a475d1b755263fdd/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Jun 18 22:31:29 CEST 2020
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-6.1.1-all.zip
7 |
--------------------------------------------------------------------------------
/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 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
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 Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 Google Inc. 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 | * Allow for customization of the modules to include through an extra file that is not included
19 | * in source control.
20 | *
21 | * *** THERE IS USUALLY NO REASON TO USE THIS ***
22 | *
23 | * The purpose of "extra-settings.gradle" is to allow the use of a local version of the ExoPlayer
24 | * library to be used to build the project. This is useful for testing experimental features
25 | * which have not yet been released, extending the library yourself, or for debugging issues
26 | * with either ExoPlayer or the MediaSession extension.
27 | *
28 | * To make use of this, create a file named "extra-settings.gradle" in the root of the
29 | * project (along side settings.gradle) with the following contents:
30 | *
31 | * gradle.ext.exoplayerRoot = ''
32 | * gradle.ext.exoplayerModulePrefix = 'exoplayer-'
33 | * apply from: new File(gradle.ext.exoplayerRoot, 'core_settings.gradle')
34 | *
35 | * For more information, see: https://github.com/google/ExoPlayer/blob/release-v2/README.md
36 | */
37 | File extraSettings = new File(rootDir, 'extra-settings.gradle')
38 | if (extraSettings.exists()) {
39 | apply from: extraSettings
40 | }
41 |
42 | include ':app', ':common', ':automotive'
43 |
--------------------------------------------------------------------------------