mirror of
https://github.com/home-assistant/android
synced 2024-10-02 22:34:46 +00:00
Add 'Sign in on phone' button to Wear OS login (#2154)
* Add 'Sign in on phone' button to Wear OS login - Detects a compatible app and shows a button to sign in on your phone instead, which is a lot easier than typing everything on the watch * Include exception for phone sign in in log
This commit is contained in:
parent
9a28be2699
commit
e1e12dc2a2
|
@ -5,6 +5,7 @@
|
|||
<item>verify_phone_app</item>
|
||||
<item>request_authentication_token</item>
|
||||
<item>request_home_assistant_instance</item>
|
||||
<item>sign_in_to_home_assistant_instance</item>
|
||||
<item>send_home_favorites</item>
|
||||
<item>save_home_favorites</item>
|
||||
</string-array>
|
||||
|
|
|
@ -261,7 +261,18 @@
|
|||
<activity
|
||||
android:name=".settings.wear.SettingsWearActivity"
|
||||
android:parentActivityName=".settings.SettingsActivity"
|
||||
android:configChanges="orientation|screenSize" />
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:scheme="homeassistant"
|
||||
android:host="wear-phone-signin" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".settings.wear.views.SettingsWearMainView"
|
||||
|
|
|
@ -110,6 +110,7 @@
|
|||
<string name="confirm_delete_this_widget_title">Confirm deleting this widget</string>
|
||||
<string name="confirm_negative">NO</string>
|
||||
<string name="confirm_positive">YES</string>
|
||||
<string name="continue_on_phone">Continue on your phone</string>
|
||||
<string name="connect_to_home_assistant">Connect to Home Assistant</string>
|
||||
<string name="connect_to_home_internet">Make sure your phone is connected\nto your home internet.</string>
|
||||
<string name="connect">Connect</string>
|
||||
|
@ -280,7 +281,7 @@
|
|||
<string name="manage_widgets_summary">Edit your widgets, adding/deleting can only be done from the home screen</string>
|
||||
<string name="manage_widgets">Manage Widgets</string>
|
||||
<string name="manual_desc">Enter the URL of your Home Assistant server. Make sure the URL includes the protocol and port. For example:\n\nhttp://homeassistant.local:8123 or \nhttps://example.duckdns.org.</string>
|
||||
<string name="manual_setup">enter address manually</string>
|
||||
<string name="manual_setup">Enter address manually</string>
|
||||
<string name="manual_title">What is your Home Assistant URL?</string>
|
||||
<string name="map">Map</string>
|
||||
<string name="maximum">Maximum</string>
|
||||
|
@ -584,6 +585,7 @@
|
|||
<string name="shortcuts">Shortcuts</string>
|
||||
<string name="show_share_logs_summary">Sharing logs with the Home Assistant team will help to solve issues. Please share the logs only if you have been asked to do so by a Home Assistant developer</string>
|
||||
<string name="show_share_logs">Show and Share Logs</string>
|
||||
<string name="sign_in_on_phone">Sign in on phone</string>
|
||||
<string name="skip">Skip</string>
|
||||
<string name="state_auto">Auto</string>
|
||||
<string name="state_cleaning">Cleaning</string>
|
||||
|
|
|
@ -95,6 +95,7 @@ dependencies {
|
|||
implementation("com.google.android.support:wearable:2.8.1")
|
||||
implementation("com.google.android.gms:play-services-wearable:17.1.0")
|
||||
implementation("androidx.wear:wear-input:1.2.0-alpha02")
|
||||
implementation("androidx.wear:wear-remote-interactions:1.0.0")
|
||||
compileOnly("com.google.android.wearable:wearable:2.8.1")
|
||||
|
||||
implementation("com.google.dagger:hilt-android:2.40.5")
|
||||
|
|
|
@ -9,6 +9,7 @@ import android.view.View
|
|||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.wear.activity.ConfirmationActivity
|
||||
import androidx.wear.remote.interactions.RemoteActivityHelper
|
||||
import androidx.wear.widget.WearableRecyclerView
|
||||
import com.google.android.gms.tasks.Tasks
|
||||
import com.google.android.gms.wearable.CapabilityClient
|
||||
|
@ -28,6 +29,9 @@ class OnboardingActivity : AppCompatActivity(), OnboardingView {
|
|||
|
||||
private lateinit var adapter: ServerListAdapter
|
||||
|
||||
private lateinit var capabilityClient: CapabilityClient
|
||||
private lateinit var remoteActivityHelper: RemoteActivityHelper
|
||||
|
||||
companion object {
|
||||
private const val TAG = "OnboardingActivity"
|
||||
|
||||
|
@ -45,11 +49,15 @@ class OnboardingActivity : AppCompatActivity(), OnboardingView {
|
|||
|
||||
setContentView(R.layout.activity_onboarding)
|
||||
|
||||
loadingView = findViewById<LoadingView>(R.id.loading_view)
|
||||
loadingView = findViewById(R.id.loading_view)
|
||||
|
||||
adapter = ServerListAdapter(ArrayList())
|
||||
adapter.onInstanceClicked = { instance -> presenter.onAdapterItemClick(instance) }
|
||||
adapter.onManualSetupClicked = { this.startManualSetup() }
|
||||
adapter.onPhoneSignInClicked = { this.startPhoneSignIn() }
|
||||
|
||||
capabilityClient = Wearable.getCapabilityClient(this)
|
||||
remoteActivityHelper = RemoteActivityHelper(this)
|
||||
|
||||
findViewById<WearableRecyclerView>(R.id.server_list)?.apply {
|
||||
layoutManager = LinearLayoutManager(this@OnboardingActivity)
|
||||
|
@ -71,6 +79,9 @@ class OnboardingActivity : AppCompatActivity(), OnboardingView {
|
|||
|
||||
// Request authentication token in separate task
|
||||
Thread { requestInstances() }.start()
|
||||
|
||||
// Check if there is a phone connected that supports sign in
|
||||
Thread { requestPhoneSignIn() }.start()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
|
@ -87,6 +98,28 @@ class OnboardingActivity : AppCompatActivity(), OnboardingView {
|
|||
startActivity(ManualSetupActivity.newInstance(this))
|
||||
}
|
||||
|
||||
override fun startPhoneSignIn() {
|
||||
try {
|
||||
remoteActivityHelper.startRemoteActivity(
|
||||
Intent(Intent.ACTION_VIEW).apply {
|
||||
addCategory(Intent.CATEGORY_DEFAULT)
|
||||
addCategory(Intent.CATEGORY_BROWSABLE)
|
||||
data = Uri.parse("homeassistant://wear-phone-signin")
|
||||
},
|
||||
null // a Wear device only has one companion device so this is not needed
|
||||
)
|
||||
val confirmation = Intent(this, ConfirmationActivity::class.java).apply {
|
||||
putExtra(ConfirmationActivity.EXTRA_ANIMATION_TYPE, ConfirmationActivity.OPEN_ON_PHONE_ANIMATION)
|
||||
putExtra(ConfirmationActivity.EXTRA_ANIMATION_DURATION_MILLIS, 2500)
|
||||
putExtra(ConfirmationActivity.EXTRA_MESSAGE, getString(commonR.string.continue_on_phone))
|
||||
}
|
||||
startActivity(confirmation)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to open sign in activity on phone", e)
|
||||
showError()
|
||||
}
|
||||
}
|
||||
|
||||
override fun showLoading() {
|
||||
loadingView.visibility = View.VISIBLE
|
||||
}
|
||||
|
@ -135,11 +168,10 @@ class OnboardingActivity : AppCompatActivity(), OnboardingView {
|
|||
|
||||
// Find all nodes that are capable
|
||||
val capabilityInfo: CapabilityInfo = Tasks.await(
|
||||
Wearable.getCapabilityClient(this)
|
||||
.getCapability(
|
||||
"request_home_assistant_instance",
|
||||
CapabilityClient.FILTER_REACHABLE
|
||||
)
|
||||
capabilityClient.getCapability(
|
||||
"request_home_assistant_instance",
|
||||
CapabilityClient.FILTER_REACHABLE
|
||||
)
|
||||
)
|
||||
|
||||
if (capabilityInfo.nodes.size == 0) {
|
||||
|
@ -158,6 +190,24 @@ class OnboardingActivity : AppCompatActivity(), OnboardingView {
|
|||
}
|
||||
}
|
||||
|
||||
private fun requestPhoneSignIn() {
|
||||
Log.d(TAG, "requestPhoneSignIn")
|
||||
|
||||
// Find all nodes that are capable
|
||||
val capabilityInfo: CapabilityInfo = Tasks.await(
|
||||
capabilityClient.getCapability(
|
||||
"sign_in_to_home_assistant_instance",
|
||||
CapabilityClient.FILTER_REACHABLE
|
||||
)
|
||||
)
|
||||
|
||||
Log.d(TAG, "requestPhoneSignIn: found ${capabilityInfo.nodes.size} nodes")
|
||||
runOnUiThread {
|
||||
adapter.phoneSignInAvailable = capabilityInfo.nodes.size > 0
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
presenter.onFinish()
|
||||
super.onDestroy()
|
||||
|
|
|
@ -3,6 +3,7 @@ package io.homeassistant.companion.android.onboarding
|
|||
interface OnboardingView {
|
||||
fun startAuthentication(flowId: String)
|
||||
fun startManualSetup()
|
||||
fun startPhoneSignIn()
|
||||
|
||||
fun onInstanceFound(instance: HomeAssistantInstance)
|
||||
fun onInstanceLost(instance: HomeAssistantInstance)
|
||||
|
|
|
@ -9,6 +9,7 @@ import io.homeassistant.companion.android.viewHolders.HeaderViewHolder
|
|||
import io.homeassistant.companion.android.viewHolders.InstanceViewHolder
|
||||
import io.homeassistant.companion.android.viewHolders.LoadingViewHolder
|
||||
import io.homeassistant.companion.android.viewHolders.ManualSetupViewHolder
|
||||
import io.homeassistant.companion.android.viewHolders.PhoneSignInViewHolder
|
||||
import kotlin.math.min
|
||||
import io.homeassistant.companion.android.common.R as commonR
|
||||
|
||||
|
@ -18,12 +19,16 @@ class ServerListAdapter(
|
|||
|
||||
lateinit var onInstanceClicked: (HomeAssistantInstance) -> Unit
|
||||
lateinit var onManualSetupClicked: () -> Unit
|
||||
lateinit var onPhoneSignInClicked: () -> Unit
|
||||
|
||||
var phoneSignInAvailable = false
|
||||
|
||||
companion object {
|
||||
private const val TYPE_INSTANCE = 1
|
||||
private const val TYPE_HEADER = 2
|
||||
private const val TYPE_LOADING = 3
|
||||
private const val TYPE_MANUAL = 4
|
||||
private const val TYPE_PHONE_SIGNIN = 5
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(
|
||||
|
@ -46,6 +51,11 @@ class ServerListAdapter(
|
|||
.inflate(R.layout.listitem_instance, parent, false)
|
||||
ManualSetupViewHolder(view, onManualSetupClicked)
|
||||
}
|
||||
TYPE_PHONE_SIGNIN -> {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.listitem_instance, parent, false)
|
||||
PhoneSignInViewHolder(view, onPhoneSignInClicked)
|
||||
}
|
||||
else -> {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.listitem_loading, parent, false)
|
||||
|
@ -59,6 +69,8 @@ class ServerListAdapter(
|
|||
holder.server = servers[position - 1]
|
||||
} else if (holder is ManualSetupViewHolder) {
|
||||
holder.text.setText(commonR.string.manual_setup)
|
||||
} else if (holder is PhoneSignInViewHolder) {
|
||||
holder.text.setText(commonR.string.sign_in_on_phone)
|
||||
} else if (holder is HeaderViewHolder) {
|
||||
if (position == 0) {
|
||||
holder.headerTextView.setText(commonR.string.list_header_instances)
|
||||
|
@ -68,11 +80,15 @@ class ServerListAdapter(
|
|||
}
|
||||
}
|
||||
|
||||
override fun getItemCount() = min(servers.size + 3, 4)
|
||||
override fun getItemCount() = min(
|
||||
servers.size + (if (phoneSignInAvailable) 4 else 3),
|
||||
if (phoneSignInAvailable) 5 else 4
|
||||
)
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return when {
|
||||
position == 0 || position == this.itemCount - 2 -> TYPE_HEADER
|
||||
position == 0 || position == this.itemCount - (if (phoneSignInAvailable) 3 else 2) -> TYPE_HEADER
|
||||
position == this.itemCount - 2 && phoneSignInAvailable -> TYPE_PHONE_SIGNIN
|
||||
position == this.itemCount - 1 -> TYPE_MANUAL
|
||||
servers.size > 0 -> TYPE_INSTANCE
|
||||
else -> TYPE_LOADING
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package io.homeassistant.companion.android.viewHolders
|
||||
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import io.homeassistant.companion.android.R
|
||||
|
||||
class PhoneSignInViewHolder(v: View, val onClick: () -> Unit) :
|
||||
RecyclerView.ViewHolder(v) {
|
||||
|
||||
val text: TextView = v.findViewById(R.id.txt_name)
|
||||
|
||||
init {
|
||||
// Set onclick listener
|
||||
v.setOnClickListener {
|
||||
onClick()
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue