Last active
February 2, 2021 18:54
-
-
Save Kaspic/82dd2b9f26e4bdf80933fe577c67ce4d to your computer and use it in GitHub Desktop.
Samsung Galaxy Watch Companion App Tutorial - Part 1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE resources [ | |
| <!ELEMENT resources (application)> | |
| <!ELEMENT application (serviceProfile)+> | |
| <!ATTLIST application name CDATA #REQUIRED> | |
| <!ELEMENT serviceProfile (supportedTransports, serviceChannel+) > | |
| <!ATTLIST application xmlns:android CDATA #IMPLIED> | |
| <!ATTLIST serviceProfile xmlns:android CDATA #IMPLIED> | |
| <!ATTLIST serviceProfile serviceImpl CDATA #REQUIRED> | |
| <!ATTLIST serviceProfile role (PROVIDER | CONSUMER | provider | consumer) #REQUIRED> | |
| <!ATTLIST serviceProfile name CDATA #REQUIRED> | |
| <!ATTLIST serviceProfile id CDATA #REQUIRED> | |
| <!ATTLIST serviceProfile version CDATA #REQUIRED> | |
| <!ATTLIST serviceProfile serviceLimit (ANY | ONE_ACCESSORY | ONE_PEERAGENT | any | one_accessory | one_peeragent) #IMPLIED> | |
| <!ATTLIST serviceProfile serviceTimeout CDATA #IMPLIED> | |
| <!ELEMENT supportedTransports (transport)+> | |
| <!ATTLIST supportedTransports xmlns:android CDATA #IMPLIED> | |
| <!ELEMENT transport EMPTY> | |
| <!ATTLIST transport xmlns:android CDATA #IMPLIED> | |
| <!ATTLIST transport type (TRANSPORT_WIFI | TRANSPORT_BT | TRANSPORT_BLE | TRANSPORT_USB | | |
| transport_wifi | transport_bt | transport_ble | transport_usb) #REQUIRED> | |
| <!ELEMENT serviceChannel EMPTY> | |
| <!ATTLIST serviceChannel xmlns:android CDATA #IMPLIED> | |
| <!ATTLIST serviceChannel id CDATA #REQUIRED> | |
| <!ATTLIST serviceChannel dataRate (LOW | HIGH | low | high) #REQUIRED> | |
| <!ATTLIST serviceChannel priority (LOW | MEDIUM | HIGH | low | medium | high) #REQUIRED> | |
| <!ATTLIST serviceChannel reliability (ENABLE | DISABLE | enable | disable ) #REQUIRED> | |
| ]> | |
| <resources> | |
| <application name="ExampleApp" > | |
| <serviceProfile | |
| id="/com/example" | |
| name="GalaxyProvider" | |
| role="provider" | |
| serviceImpl="com.example.galaxyprovider.GalaxyProviderService" | |
| version="1.0" | |
| serviceLimit="ANY" | |
| serviceTimeout="15"> | |
| <supportedTransports> | |
| <transport type="TRANSPORT_BT" /> | |
| </supportedTransports> | |
| <serviceChannel | |
| id="333" | |
| dataRate="low" | |
| priority="low" | |
| reliability= "enable"/> | |
| </serviceProfile> | |
| </application> | |
| </resources> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?xml version="1.0" encoding="utf-8"?> | |
| <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
| xmlns:app="http://schemas.android.com/apk/res-auto" | |
| xmlns:tools="http://schemas.android.com/tools" | |
| android:layout_width="match_parent" | |
| android:layout_height="match_parent" | |
| tools:context=".MainActivity"> | |
| <com.google.android.material.textfield.TextInputEditText | |
| android:id="@+id/textInputEditText" | |
| android:layout_width="0dp" | |
| android:layout_height="wrap_content" | |
| android:layout_margin="16dp" | |
| app:layout_constraintBottom_toBottomOf="parent" | |
| app:layout_constraintEnd_toEndOf="parent" | |
| app:layout_constraintStart_toStartOf="parent" | |
| app:layout_constraintTop_toTopOf="parent" /> | |
| <MaterialButton | |
| android:layout_width="wrap_content" | |
| android:layout_height="wrap_content" | |
| android:layout_marginTop="8dp" | |
| app:layout_constraintEnd_toEndOf="parent" | |
| app:layout_constraintStart_toStartOf="parent" | |
| app:layout_constraintTop_toBottomOf="@+id/textInputEditText" /> | |
| </androidx.constraintlayout.widget.ConstraintLayout> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> | |
| <uses-permission android:name="com.samsung.accessory.permission.ACCESSORY_FRAMEWORK" /> | |
| <application> | |
| <service android:name="GalaxyProviderService" /> | |
| <receiver android:name="com.samsung.android.sdk.accessory.RegisterUponInstallReceiver"> | |
| <intent-filter> | |
| <action android:name="com.samsung.accessory.action.REGISTER_AGENT" /> | |
| </intent-filter> | |
| </receiver> | |
| <receiver android:name="com.samsung.android.sdk.accessory.ServiceConnectionIndicationBroadcastReceiver"> | |
| <intent-filter> | |
| <action android:name="com.samsung.accessory.action.SERVICE_CONNECTION_REQUESTED" /> | |
| </intent-filter> | |
| </receiver> | |
| </application |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| dependencies { | |
| ... | |
| implementation fileTree(dir: 'libs', include: ['*.jar']) | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import android.app.Notification | |
| import android.app.NotificationChannel | |
| import android.app.NotificationManager | |
| import android.content.Intent | |
| import android.os.Binder | |
| import android.os.Build | |
| import android.os.IBinder | |
| import android.util.Log | |
| import android.widget.Toast | |
| import androidx.core.app.NotificationCompat | |
| import com.samsung.android.sdk.SsdkUnsupportedException | |
| import com.samsung.android.sdk.accessory.SA | |
| import com.samsung.android.sdk.accessory.SAAgent | |
| import com.samsung.android.sdk.accessory.SAPeerAgent | |
| class GalaxyProviderService : SAAgent(TAG, SASOCKET_CLASS), GalaxyServiceConnection.GalaxyServiceConnectionListener { | |
| companion object { | |
| private const val TAG = "GalaxyProviderService" | |
| private val SASOCKET_CLASS: Class<GalaxyServiceConnection> = GalaxyServiceConnection::class.java | |
| private const val CHANNEL_ID = "ExampleCompanionApp" | |
| private const val CHANNEL_NAME = "ExampleCompanionApp" | |
| private const val NOTIFICATION_ID = 1234 | |
| private const val SAP_CONNECTION_CHANNEL_ID = 333 | |
| } | |
| private val binder: IBinder = LocalBinder() | |
| private var connectionHandler: GalaxyServiceConnection? = null | |
| private var notificationManager: NotificationManager? = null | |
| private val requiresForegroundService = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O | |
| override fun onCreate() { | |
| super.onCreate() | |
| if (requiresForegroundService) { | |
| if (notificationManager == null) { | |
| notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager | |
| val notificationChannel = NotificationChannel( | |
| CHANNEL_ID, | |
| CHANNEL_NAME, | |
| NotificationManager.IMPORTANCE_LOW | |
| ) | |
| notificationManager?.createNotificationChannel(notificationChannel) | |
| } | |
| val initialNotificationMessage = "Galaxy connection ready" | |
| val notification = createNotification(initialNotificationMessage) | |
| startForeground(NOTIFICATION_ID, notification) | |
| } | |
| try { | |
| val accessory = SA() | |
| accessory.initialize(this) | |
| } catch (e: SsdkUnsupportedException) { | |
| if (processUnsupportedException(e)) { | |
| terminateService() | |
| return | |
| } | |
| } catch (e1: Exception) { | |
| e1.printStackTrace() | |
| stopSelf() | |
| } | |
| } | |
| private fun createNotification(notificationMessage: String): Notification { | |
| val notificationTitle = "Galaxy Connection" | |
| return NotificationCompat.Builder(applicationContext, CHANNEL_ID) | |
| .setContentTitle(notificationTitle) | |
| .setContentText(notificationMessage) | |
| .setChannelId(CHANNEL_ID) | |
| .build() | |
| } | |
| private fun processUnsupportedException(ssdkUnsupportedException: SsdkUnsupportedException): Boolean { | |
| ssdkUnsupportedException.printStackTrace() | |
| when (ssdkUnsupportedException.type) { | |
| SsdkUnsupportedException.VENDOR_NOT_SUPPORTED, SsdkUnsupportedException.DEVICE_NOT_SUPPORTED -> { | |
| stopSelf() | |
| } | |
| SsdkUnsupportedException.LIBRARY_NOT_INSTALLED -> { | |
| Log.e(TAG, "You need to install Samsung Accessory SDK to use this application.") | |
| } | |
| SsdkUnsupportedException.LIBRARY_UPDATE_IS_REQUIRED -> { | |
| Log.e(TAG, "You need to update Samsung Accessory SDK to use this application.") | |
| } | |
| } | |
| return true | |
| } | |
| fun sendMessage(currentMessage: String) { | |
| connectionHandler?.let { connectionHandler -> | |
| try { | |
| Log.d(TAG, "Message sent: $currentMessage") | |
| connectionHandler.send(SAP_CONNECTION_CHANNEL_ID, currentMessage.toByteArray()) | |
| } catch (e: Exception) { | |
| Log.e(TAG, "SendData error: $e") | |
| } | |
| } | |
| } | |
| private fun stopForegroundNotification() { | |
| if (requiresForegroundService) { | |
| notificationManager?.cancel(NOTIFICATION_ID) | |
| stopForeground(true) | |
| } | |
| } | |
| private fun terminateService() { | |
| connectionHandler?.close() | |
| stopForegroundNotification() | |
| stopSelf() | |
| } | |
| override fun onServiceConnectionRequested(peerAgent: SAPeerAgent?) { | |
| if (peerAgent != null) { | |
| Log.d(TAG, "----> onServiceConnectionRequested(): $peerAgent") | |
| Toast.makeText(baseContext, "Connection established", Toast.LENGTH_SHORT).show() | |
| createNotification("Connected") | |
| acceptServiceConnectionRequest(peerAgent) | |
| } else { | |
| terminateService() | |
| } | |
| } | |
| override fun onServiceConnectionLost() { | |
| connectionHandler?.callback = null | |
| connectionHandler?.close() | |
| connectionHandler = null | |
| mainExecutor.execute { | |
| Toast.makeText(baseContext, "Connection to galaxy watch lost", Toast.LENGTH_SHORT) | |
| .show() | |
| } | |
| } | |
| override fun onBind(intent: Intent): IBinder = binder | |
| override fun onTaskRemoved(rootIntent: Intent?) = terminateService() | |
| inner class LocalBinder : Binder() { | |
| val service: GalaxyProviderService | |
| get() = this@GalaxyProviderService | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import android.util.Log | |
| import com.samsung.android.sdk.accessory.SASocket | |
| class GalaxyServiceConnection : SASocket(GalaxyProviderService::class.java.name) { | |
| companion object { | |
| private const val TAG = "GalaxyServiceConnection" | |
| } | |
| var callback: GalaxyServiceConnectionListener? = null | |
| override fun onError(channelId: Int, errorMessage: String?, errorCode: Int) { | |
| Log.e(TAG, "----> onError: errorCode: $errorCode -- $errorMessage") | |
| } | |
| override fun onReceive(channelId: Int, data: ByteArray?) { | |
| Log.d(TAG, "----> ServiceConnection onReceive(): " + data?.let { String(it) }) | |
| } | |
| override fun onServiceConnectionLost(reason: Int) { | |
| Log.e(TAG, "----> onServiceConnectionLost: reason: $reason") | |
| callback?.onServiceConnectionLost() | |
| } | |
| interface GalaxyServiceConnectionListener { | |
| fun onServiceConnectionLost() | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?xml version="1.0" encoding="utf-8"?> | |
| <resources xmlns:tools="http://schemas.android.com/tools" | |
| tools:keep="@xml/accessoryservices" /> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import android.content.ComponentName | |
| import android.content.Context | |
| import android.content.Intent | |
| import android.content.ServiceConnection | |
| import androidx.appcompat.app.AppCompatActivity | |
| import android.os.Bundle | |
| import android.os.IBinder | |
| import android.os.PersistableBundle | |
| import com.google.android.material.button.MaterialButton | |
| import com.google.android.material.dialog.MaterialAlertDialogBuilder | |
| import com.google.android.material.textfield.TextInputEditText | |
| class MainActivity : AppCompatActivity(R.layout.activity_main) { | |
| private var boundService: GalaxyProviderService? = null | |
| private var serviceIntent = Intent(applicationContext, GalaxyProviderService::class.java) | |
| private val boundServiceConnection: ServiceConnection = object : ServiceConnection { | |
| override fun onServiceConnected(name: ComponentName, service: IBinder) { | |
| val binderBridge: GalaxyProviderService.LocalBinder = service as GalaxyProviderService.LocalBinder | |
| boundService = binderBridge.service | |
| } | |
| override fun onServiceDisconnected(name: ComponentName) { | |
| boundService = null | |
| } | |
| } | |
| override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) { | |
| super.onCreate(savedInstanceState, persistentState) | |
| setClickListeners() | |
| initializeGalaxyProviderService() | |
| } | |
| override fun onDestroy() { | |
| super.onDestroy() | |
| disconnect() | |
| } | |
| private fun setClickListeners() { | |
| findViewById<MaterialButton>(R.id.send_button).setOnClickListener { sendCurrentInputMessage() } | |
| } | |
| private fun disconnect() { | |
| boundService = null | |
| applicationContext.unbindService(boundServiceConnection) | |
| applicationContext.stopService(serviceIntent) | |
| } | |
| private fun sendCurrentInputMessage() { | |
| val inputEditText = findViewById<TextInputEditText>(R.id.input) | |
| val currentMessage = inputEditText.text.toString() | |
| boundService?.sendMessage(currentMessage) ?: kotlin.run { | |
| MaterialAlertDialogBuilder(this) | |
| .setMessage("No active connection available") | |
| .setPositiveButton(android.R.string.ok, null) | |
| .show() | |
| } | |
| inputEditText.text?.clear() | |
| } | |
| private fun initializeGalaxyProviderService() { | |
| if (boundService == null) { | |
| val serviceAlreadyExisted = applicationContext.bindService(serviceIntent, boundServiceConnection, Context.BIND_AUTO_CREATE) | |
| if (!serviceAlreadyExisted) { | |
| if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { | |
| applicationContext.startForegroundService(serviceIntent) | |
| } else { | |
| applicationContext.startService(serviceIntent) | |
| } | |
| applicationContext.bindService(serviceIntent, boundServiceConnection, Context.BIND_AUTO_CREATE) | |
| } | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| -keepclassmembers class com.samsung.** { *; } | |
| -keep class com.samsung.** { *; } | |
| -dontwarn com.samsung.** |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment