Skip to content

Commit

Permalink
Starts adding a Screen Mirroring addon
Browse files Browse the repository at this point in the history
  • Loading branch information
hufman committed Sep 26, 2021
1 parent 350d00a commit df57340
Show file tree
Hide file tree
Showing 40 changed files with 1,429 additions and 1 deletion.
1 change: 1 addition & 0 deletions screen_mirror/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
58 changes: 58 additions & 0 deletions screen_mirror/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
}

repositories {
maven { url 'https://jitpack.io' }
}

android {
compileSdkVersion 30
buildToolsVersion "31.0.0"

defaultConfig {
applicationId "me.hufman.idriveconnectaddons.screen_mirror"
minSdkVersion 23
targetSdkVersion 30
versionCode 1
versionName "1.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

buildFeatures {
dataBinding true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}

dependencies {

implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.6.0'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.activity:activity-ktx:1.3.1'

implementation 'io.bimmergestalt:IDriveConnectKit:jitpack_publish-SNAPSHOT'
implementation 'io.bimmergestalt:IDriveConnectKitAndroid:jitpack_publish_2-SNAPSHOT'
implementation project(path: ':lib')

testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
21 changes: 21 additions & 0 deletions screen_mirror/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.bimmergestalt.idriveconnectaddons.screenmirror

import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4

import org.junit.Test
import org.junit.runner.RunWith

import org.junit.Assert.*

/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("me.hufman.idriveconnectaddons.screen_mirror", appContext.packageName)
}
}
32 changes: 32 additions & 0 deletions screen_mirror/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="io.bimmergestalt.idriveconnectaddons.screenmirror">

<!-- Connect to local etch port -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- Provide a foreground service to indicate mirroring status -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.IDriveConnectAddons">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
<activity android:name=".RequestActivity" />
<service android:name=".CarAppService">
<intent-filter>
<action android:name="io.bimmergestalt.carconnection.service" />
</intent-filter>
</service>
<service android:name=".NotificationService"
android:foregroundServiceType="mediaProjection" />
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package io.bimmergestalt.idriveconnectaddons.screenmirror

import android.content.Context
import io.bimmergestalt.idriveconnectkit.android.CarAppResources
import io.bimmergestalt.idriveconnectkit.android.CertMangling
import io.bimmergestalt.idriveconnectkit.android.security.SecurityAccess
import java.io.ByteArrayInputStream
import java.io.FileNotFoundException
import java.io.InputStream

/**
* An implementation of CarAppResources for easy use in Android apps
*
* Loads resource files from the app context's assets
*
* It expects to find the App Certificate at:
* - carapplications/$name/rhmi/$brand/$name.p7b
* - carapplications/$name/$name.p7b
*
* It expects to find the ui_description.xml at:
* - carapplications/$name/rhmi/$brand/ui_description.xml
* - carapplications/$name/rhmi/common/ui_description.xml
* - carapplications/$name/rhmi/ui_description.xml
*
* It expects to find the images.zip file at:
* - carapplications/$name/rhmi/$brand/images.zip
* - carapplications/$name/rhmi/common/images.zip
*
* It expects to find the textx.zip file at:
* - carapplications/$name/rhmi/$brand/texts.zip
* - carapplications/$name/rhmi/common/texts.zip
*
* The $brand is always lower-cased
*
* This module uses SecurityAccess to automatically alter the App Certificate
* to include the necessary extra certs from the Security Module.
*
* todo: Move to IDriveConnectKitAndroid
* */
class CarAppAssetResources(val context: Context, val name: String): CarAppResources {
fun loadFile(path: String): InputStream? {
try {
return context.assets.open(path)
} catch (e: FileNotFoundException) {
return null
}
}

fun getAppCertificateRaw(brand: String): InputStream? {
return loadFile("carapplications/$name/rhmi/${brand.toLowerCase()}/$name.p7b") ?:
loadFile("carapplications/$name/$name.p7b")
}

override fun getAppCertificate(brand: String): InputStream? {
val appCert = getAppCertificateRaw(brand)?.readBytes() as ByteArray
val signedCert = CertMangling.mergeBMWCert(appCert, SecurityAccess.getInstance(context).fetchBMWCerts(brandHint= brand))
return ByteArrayInputStream(signedCert)
}

override fun getUiDescription(brand: String): InputStream? {
return loadFile("carapplications/$name/rhmi/${brand.toLowerCase()}/ui_description.xml") ?:
loadFile("carapplications/$name/rhmi/common/ui_description.xml") ?:
loadFile("carapplications/$name/rhmi/ui_description.xml")
}

override fun getImagesDB(brand: String): InputStream? {
return loadFile("carapplications/$name/rhmi/${brand.toLowerCase()}/images.zip") ?:
loadFile("carapplications/$name/rhmi/common/images.zip")
}

override fun getTextsDB(brand: String): InputStream? {
return loadFile("carapplications/$name/rhmi/${brand.toLowerCase()}/texts.zip") ?:
loadFile("carapplications/$name/rhmi/common/texts.zip")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package io.bimmergestalt.idriveconnectaddons.screenmirror

import android.app.Service
import android.content.Intent
import android.os.Build
import android.os.IBinder
import android.util.Log
import io.bimmergestalt.idriveconnectaddons.screenmirror.carapp.CarApp
import io.bimmergestalt.idriveconnectkit.android.IDriveConnectionReceiver
import io.bimmergestalt.idriveconnectkit.android.IDriveConnectionStatus
import io.bimmergestalt.idriveconnectkit.android.security.SecurityAccess

class CarAppService: Service() {
var thread: CarThread? = null
var app: CarApp? = null

override fun onCreate() {
super.onCreate()
SecurityAccess.getInstance(applicationContext).connect()
}

/**
* When a car is connected, it will bind the Addon Service
*/
override fun onBind(intent: Intent?): IBinder? {
intent ?: return null
setConnection(intent)
return null
}

/**
* If the thread crashes for any reason,
* opening the main app will trigger a Start on the Addon Services
* as a chance to reconnect
*/
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
intent ?: return START_NOT_STICKY
setConnection(intent)
startThread()
return START_STICKY
}

/**
* The car has disconnected, so forget the previous details
*/
override fun onUnbind(intent: Intent?): Boolean {
IDriveConnectionStatus.reset()
return super.onUnbind(intent)
}

/**
* Parses the connection intent and sets the connection details
* todo: Move to IDriveConnectionListener
*/
fun setConnection(intent: Intent) {
val brand = intent.getStringExtra("EXTRA_BRAND")
val host = intent.getStringExtra("EXTRA_HOST")
val port = intent.getIntExtra("EXTRA_PORT", -1)
val instanceId = intent.getIntExtra("EXTRA_INSTANCE_ID", -1)
Log.i(TAG, "Received connection details: $brand at $host:$port($instanceId)")
brand ?: return
host ?: return
if (port == -1) {
return
}
IDriveConnectionStatus.setConnection(brand, host, port, instanceId)
startThread()
}

/**
* Starts the thread for the car app, if it isn't running
*/
fun startThread() {
val iDriveConnectionStatus = IDriveConnectionReceiver()
val securityAccess = SecurityAccess.getInstance(applicationContext)
if (iDriveConnectionStatus.isConnected &&
securityAccess.isConnected() &&
thread?.isAlive != true) {

L.loadResources(applicationContext)
thread = CarThread("ScreenMirroring") {
app = CarApp(
iDriveConnectionStatus,
securityAccess,
CarAppAssetResources(applicationContext, "smartthings"),
ScreenMirrorProvider(thread?.handler!!)
) {
// start up the notification when we enter the app
val foreground = NotificationService.shouldBeForeground()
NotificationService.startNotification(applicationContext, foreground)
}
}
thread?.start()
}
}

override fun onDestroy() {
super.onDestroy()

app?.onDestroy()
NotificationService.stopNotification(applicationContext)
}
}
Loading

0 comments on commit df57340

Please sign in to comment.