Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wear os authentication #1691

Merged
merged 13 commits into from
Oct 1, 2021
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ google-services.json
.idea/
.gradle/
build/
*.keystore
2 changes: 2 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ dependencies {
implementation("androidx.navigation:navigation-ui-ktx:2.3.5")
implementation("com.google.android.material:material:1.4.0")

implementation("com.google.android.gms:play-services-wearable:17.1.0")

implementation("androidx.room:room-runtime:2.3.0")
implementation("androidx.room:room-ktx:2.3.0")
kapt("androidx.room:room-compiler:2.3.0")
Expand Down
9 changes: 9 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,15 @@
<activity
android:name=".onboarding.OnboardingActivity"
android:configChanges="orientation|screenSize|keyboardHidden" />

<service android:name=".onboarding.WearOnboardingListener">
<intent-filter>
<action android:name="com.google.android.gms.wearable.MESSAGE_RECEIVED" />
<action android:name="com.google.android.gms.wearable.DATA_CHANGED" />
<data android:scheme="wear" android:host="*"
android:path="/request_home_assistant_instance" />
</intent-filter>
</service>

<activity
android:name=".webview.WebViewActivity"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.homeassistant.companion.android.onboarding

import dagger.Component
import io.homeassistant.companion.android.common.dagger.AppComponent

@Component(dependencies = [AppComponent::class])
interface OnboardingListenerComponent {

fun inject(listener: WearOnboardingListener)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package io.homeassistant.companion.android.onboarding

import android.util.Log
import com.google.android.gms.wearable.MessageEvent
import com.google.android.gms.wearable.PutDataMapRequest
import com.google.android.gms.wearable.PutDataRequest
import com.google.android.gms.wearable.Wearable
import com.google.android.gms.wearable.WearableListenerService
import io.homeassistant.companion.android.common.dagger.GraphComponentAccessor
import io.homeassistant.companion.android.common.data.authentication.AuthenticationRepository
import io.homeassistant.companion.android.common.data.url.UrlRepository
import kotlinx.coroutines.runBlocking
import javax.inject.Inject

class WearOnboardingListener : WearableListenerService() {

@Inject
lateinit var authenticationUseCase: AuthenticationRepository

@Inject
lateinit var urlUseCase: UrlRepository

override fun onCreate() {
super.onCreate()
DaggerOnboardingListenerComponent.builder()
.appComponent((applicationContext.applicationContext as GraphComponentAccessor).appComponent)
.build()
.inject(this)
}

override fun onMessageReceived(event: MessageEvent) {
Log.d("WearOnboardingListener", "onMessageReceived: $event")

if (event.path == "/request_home_assistant_instance") {
val nodeId = event.sourceNodeId
sendHomeAssistantInstance(nodeId)
}
}

private fun sendHomeAssistantInstance(nodeId: String) = runBlocking {
Log.d("WearOnboardingListener", "sendHomeAssistantInstance: $nodeId")
// Retrieve current instance
val url = urlUseCase.getUrl()

// Put as DataMap in data layer
val putDataReq: PutDataRequest = PutDataMapRequest.create("/home_assistant_instance").run {
dataMap.putString("name", url?.host.toString())
dataMap.putString("url", url.toString())
setUrgent()
asPutDataRequest()
}
Wearable.getDataClient(this@WearOnboardingListener).putDataItem(putDataReq).apply {
addOnSuccessListener { Log.d("WearOnboardingListener", "sendHomeAssistantInstance: success") }
addOnFailureListener { Log.d("WearOnboardingListener", "sendHomeAssistantInstance: failed") }
}
}
}
3 changes: 1 addition & 2 deletions app/src/main/res/layout/fragment_mobile_app_integration.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/deviceName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
android:layout_height="wrap_content" />

</com.google.android.material.textfield.TextInputLayout>

Expand Down
8 changes: 8 additions & 0 deletions app/src/main/res/values/wear.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@array/android_wear_capabilities">
<string-array name="android_wear_capabilities">
<item>request_authentication_token</item>
<item>request_home_assistant_instance</item>
</string-array>
</resources>
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
package io.homeassistant.companion.android.common.data.authentication

import io.homeassistant.companion.android.common.data.authentication.impl.entities.LoginFlowCreateEntry
import io.homeassistant.companion.android.common.data.authentication.impl.entities.LoginFlowInit
import java.net.URL

interface AuthenticationRepository {

suspend fun initiateLoginFlow(): LoginFlowInit

suspend fun loginAuthentication(flowId: String, username: String, password: String): LoginFlowCreateEntry

suspend fun registerAuthorizationCode(authorizationCode: String)

suspend fun retrieveExternalAuthentication(forceRefresh: Boolean): String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import io.homeassistant.companion.android.common.data.LocalStorage
import io.homeassistant.companion.android.common.data.authentication.AuthenticationRepository
import io.homeassistant.companion.android.common.data.authentication.AuthorizationException
import io.homeassistant.companion.android.common.data.authentication.SessionState
import io.homeassistant.companion.android.common.data.authentication.impl.entities.LoginFlowAuthentication
import io.homeassistant.companion.android.common.data.authentication.impl.entities.LoginFlowCreateEntry
import io.homeassistant.companion.android.common.data.authentication.impl.entities.LoginFlowInit
import io.homeassistant.companion.android.common.data.authentication.impl.entities.LoginFlowRequest
import io.homeassistant.companion.android.common.data.url.UrlRepository
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import java.net.URL
Expand All @@ -26,6 +30,27 @@ class AuthenticationRepositoryImpl @Inject constructor(
private const val PREF_BIOMETRIC_ENABLED = "biometric_enabled"
}

override suspend fun initiateLoginFlow(): LoginFlowInit {
return authenticationService.initializeLogin(
LoginFlowRequest(
AuthenticationService.CLIENT_ID,
AuthenticationService.AUTH_CALLBACK,
AuthenticationService.HANDLER
)
)
}

override suspend fun loginAuthentication(flowId: String, username: String, password: String): LoginFlowCreateEntry {
return authenticationService.authenticate(
AuthenticationService.AUTHENTICATE_BASE_PATH + flowId,
LoginFlowAuthentication(
AuthenticationService.CLIENT_ID,
username,
password
)
)
}

override suspend fun registerAuthorizationCode(authorizationCode: String) {
authenticationService.getToken(
AuthenticationService.GRANT_TYPE_CODE,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
package io.homeassistant.companion.android.common.data.authentication.impl

import io.homeassistant.companion.android.common.data.authentication.impl.entities.LoginFlowAuthentication
import io.homeassistant.companion.android.common.data.authentication.impl.entities.LoginFlowCreateEntry
import io.homeassistant.companion.android.common.data.authentication.impl.entities.LoginFlowInit
import io.homeassistant.companion.android.common.data.authentication.impl.entities.LoginFlowRequest
import io.homeassistant.companion.android.common.data.authentication.impl.entities.Token
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded
import retrofit2.http.POST
import retrofit2.http.Url

interface AuthenticationService {

Expand All @@ -13,6 +19,9 @@ interface AuthenticationService {
const val GRANT_TYPE_CODE = "authorization_code"
const val GRANT_TYPE_REFRESH = "refresh_token"
const val REVOKE_ACTION = "revoke"
val HANDLER = listOf("homeassistant", null)
const val AUTHENTICATE_BASE_PATH = "auth/login_flow/"
const val AUTH_CALLBACK = "homeassistant://auth-callback"
}

@FormUrlEncoded
Expand All @@ -37,4 +46,10 @@ interface AuthenticationService {
@Field("token") refreshToken: String,
@Field("action") action: String
)

@POST("auth/login_flow")
suspend fun initializeLogin(@Body body: LoginFlowRequest): LoginFlowInit

@POST
suspend fun authenticate(@Url url: String, @Body body: LoginFlowAuthentication): LoginFlowCreateEntry
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.homeassistant.companion.android.common.data.authentication.impl.entities

import com.fasterxml.jackson.annotation.JsonProperty

data class LoginFlowAuthentication(
@JsonProperty("client_id")
val clientId: String,
@JsonProperty("username")
val userName: String,
@JsonProperty("password")
val password: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.homeassistant.companion.android.common.data.authentication.impl.entities

import com.fasterxml.jackson.annotation.JsonProperty

data class LoginFlowCreateEntry(
@JsonProperty("version")
val version: Int,
@JsonProperty("type")
val type: String,
@JsonProperty("flow_id")
val flowId: String,
@JsonProperty("result")
val result: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.homeassistant.companion.android.common.data.authentication.impl.entities

import com.fasterxml.jackson.annotation.JsonProperty

data class LoginFlowInit(
@JsonProperty("type")
val type: String,
@JsonProperty("flow_id")
val flowId: String,
@JsonProperty("step_id")
val stepId: String,
@JsonProperty("errors")
val errors: Map<String, String>
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.homeassistant.companion.android.common.data.authentication.impl.entities

import com.fasterxml.jackson.annotation.JsonProperty

data class LoginFlowRequest(
@JsonProperty("client_id")
val clientId: String,
@JsonProperty("redirect_uri")
val redirectUri: String,
@JsonProperty("handler")
val handler: List<String?>
)
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ class IntegrationRepositoryImpl @Inject constructor(
}

override suspend fun isRegistered(): Boolean {
Log.d(TAG, urlRepository.getApiUrls().toString())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra logging I don't think we need.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, I used this for debugging. Have removed it now

return urlRepository.getApiUrls().isNotEmpty()
}

Expand Down
25 changes: 25 additions & 0 deletions wear/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,25 @@ android {
signingConfig = signingConfigs.getByName("release")
}
}
flavorDimensions("version")
productFlavors {
create("minimal") {
applicationIdSuffix = ".minimal"
versionNameSuffix = "-minimal"
}
create("full") {
applicationIdSuffix = ""
versionNameSuffix = "-full"
}

// Generate a list of application ids into BuildConfig
val values = productFlavors.joinToString {
"\"${it.applicationId ?: defaultConfig.applicationId}${it.applicationIdSuffix}\""
}

defaultConfig.buildConfigField("String[]", "APPLICATION_IDS", "{$values}")
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need multiple flavors since there aren't any de-googled wear os devices.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right indeed, removed product flavors


kotlinOptions {
jvmTarget = "11"
}
Expand All @@ -65,9 +84,15 @@ android {
dependencies {
implementation(project(":common"))

implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1")

implementation("com.google.android.material:material:1.4.0")

implementation("androidx.wear:wear:1.1.0")
implementation("com.google.android.support:wearable:2.8.1")
implementation("com.google.android.gms:play-services-wearable:17.1.0")
compileOnly("com.google.android.wearable:wearable:2.8.1")

implementation("com.google.dagger:dagger:2.38.1")
kapt("com.google.dagger:dagger-compiler:2.38.1")
}
21 changes: 13 additions & 8 deletions wear/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="io.homeassistant.companion.android">

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />

<uses-feature android:name="android.hardware.type.watch" />

<application
android:name=".HomeAssistantApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
Expand All @@ -17,23 +19,26 @@
android:name="com.google.android.wearable"
android:required="true" />

<!--
Set to true if your app is Standalone, that is, it does not require the handheld
app to run.
-->
<!-- The app can run without a connected phone -->
<meta-data
android:name="com.google.android.wearable.standalone"
android:value="true" />

<activity
android:name=".Home"
android:label="@string/app_name">
<activity android:name=".home.HomeActivity"
android:label="@string/app_name"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".onboarding.OnboardingActivity" />
<activity android:name=".onboarding.integration.MobileAppIntegrationActivity" />
<activity android:name=".onboarding.authentication.AuthenticationActivity" />
<activity android:name=".onboarding.manual_setup.ManualSetupActivity" />

<!-- To show confirmations and failures -->
<activity android:name="androidx.wear.activity.ConfirmationActivity" />
</application>

</manifest>
15 changes: 0 additions & 15 deletions wear/src/main/java/io/homeassistant/companion/android/Home.kt

This file was deleted.