Full Code of endyrubbin/AAStream for AI

master 13806b628264 cached
97 files
199.9 KB
47.4k tokens
1 requests
Download .txt
Showing preview only (225K chars total). Download the full file or copy to clipboard to get everything.
Repository: endyrubbin/AAStream
Branch: master
Commit: 13806b628264
Files: 97
Total size: 199.9 KB

Directory structure:
gitextract_rg_4i2rc/

├── README.md
├── apk_releases/
│   ├── aa-stream-v1.0.6.apk
│   └── aa-stream-v1.1.0.27.apk
├── app/
│   ├── build.gradle
│   ├── libs/
│   │   └── aauto.aar
│   └── src/
│       └── main/
│           ├── AndroidManifest.xml
│           ├── assets/
│           │   └── libs/
│           │       ├── arm64-v8a/
│           │       │   ├── minitouch
│           │       │   └── minitouch-nopie
│           │       ├── armeabi/
│           │       │   ├── minitouch
│           │       │   └── minitouch-nopie
│           │       ├── armeabi-v7a/
│           │       │   ├── minitouch
│           │       │   └── minitouch-nopie
│           │       ├── mips/
│           │       │   ├── minitouch
│           │       │   └── minitouch-nopie
│           │       ├── mips64/
│           │       │   ├── minitouch
│           │       │   └── minitouch-nopie
│           │       ├── x86/
│           │       │   ├── minitouch
│           │       │   └── minitouch-nopie
│           │       └── x86_64/
│           │           ├── minitouch
│           │           └── minitouch-nopie
│           ├── java/
│           │   └── com/
│           │       └── garage/
│           │           └── aastream/
│           │               ├── App.kt
│           │               ├── activities/
│           │               │   ├── CarDebugActivity.kt
│           │               │   ├── CarMainActivity.kt
│           │               │   ├── KillerActivity.kt
│           │               │   ├── ResultRequestActivity.kt
│           │               │   ├── SettingsActivity.kt
│           │               │   └── controllers/
│           │               │       ├── CarActivityController.kt
│           │               │       └── TerminalController.kt
│           │               ├── adapters/
│           │               │   └── AppListAdapter.kt
│           │               ├── handlers/
│           │               │   ├── AppHandler.kt
│           │               │   ├── AudioHandler.kt
│           │               │   ├── BrightnessHandler.kt
│           │               │   ├── DisplayHandler.kt
│           │               │   ├── NotificationHandler.kt
│           │               │   ├── PreferenceHandler.kt
│           │               │   └── RotationHandler.kt
│           │               ├── injection/
│           │               │   ├── GlideModule.kt
│           │               │   ├── InjectionComponent.kt
│           │               │   └── InjectionModule.kt
│           │               ├── interfaces/
│           │               │   ├── OnAppClickedCallback.kt
│           │               │   ├── OnAppListLoadedCallback.kt
│           │               │   ├── OnLogCallback.kt
│           │               │   ├── OnMenuTapCallback.kt
│           │               │   ├── OnMinitouchCallback.kt
│           │               │   ├── OnPatchStatusCallback.kt
│           │               │   ├── OnRotationChangedCallback.kt
│           │               │   └── OnScreenLockCallback.kt
│           │               ├── minitouch/
│           │               │   ├── MiniTouchHandler.kt
│           │               │   ├── MiniTouchSocket.kt
│           │               │   └── MinitouchDaemon.kt
│           │               ├── models/
│           │               │   ├── AppItem.kt
│           │               │   └── AppItemWrapper.kt
│           │               ├── receivers/
│           │               │   ├── ScreenLockReceiver.kt
│           │               │   └── UsbStateReceiver.kt
│           │               ├── services/
│           │               │   └── CarService.kt
│           │               ├── shell/
│           │               │   └── ShellExecutor.kt
│           │               ├── utils/
│           │               │   ├── Const.kt
│           │               │   ├── DevLog.kt
│           │               │   └── PhenotypePatcher.kt
│           │               └── views/
│           │                   ├── AutoFitRecyclerView.kt
│           │                   ├── FingerTapDetector.kt
│           │                   └── MarginDecoration.kt
│           └── res/
│               ├── drawable-anydpi/
│               │   ├── bg.xml
│               │   ├── bg_input.xml
│               │   ├── ic_apps.xml
│               │   ├── ic_back.xml
│               │   ├── ic_bookmark.xml
│               │   ├── ic_bookmark_border.xml
│               │   ├── ic_check.xml
│               │   ├── ic_close.xml
│               │   └── ic_terminal.xml
│               ├── layout/
│               │   ├── activity_car.xml
│               │   ├── activity_request_result.xml
│               │   ├── activity_settings.xml
│               │   ├── row_app_item.xml
│               │   ├── view_car_terminal.xml
│               │   ├── view_settings_about.xml
│               │   ├── view_settings_audio.xml
│               │   ├── view_settings_brightness.xml
│               │   ├── view_settings_debug.xml
│               │   ├── view_settings_immersive.xml
│               │   ├── view_settings_resize.xml
│               │   ├── view_settings_rotation.xml
│               │   ├── view_settings_sidebar.xml
│               │   └── view_settings_unlock.xml
│               ├── raw/
│               │   └── sqlite3
│               ├── values/
│               │   ├── colors.xml
│               │   ├── dimens.xml
│               │   ├── ids.xml
│               │   ├── strings.xml
│               │   └── styles.xml
│               └── xml/
│                   └── automotive_app_desc.xml
├── build.gradle
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
└── settings.gradle

================================================
FILE CONTENTS
================================================

================================================
FILE: README.md
================================================
# AA Stream
![App Icon](img/logo.png "App Icon") 
## About
**AA Stream** is an unofficial and unsupported device screen mirroring application inspired by [AAMirror](https://github.com/slashmax/AAMirror) for Android Auto. 

**Use it with caution! I don't take any responsibility for the misuse of the application. You have been warned.**

Get the latest APK [here](https://github.com/endyrubbin/AAStream/tree/master/apk_releases)

## Prerequisites and usage
#### To use this application:
- Your device has to be **rooted** (You have to figure out how to do it for yourself).
- Android Auto must be installed, preferably an older version (~v3).
- Write System settings must be granted (Enable the switch for **AA Stream** once prompted) to use brightness and rotation features.
- Screen Capture permission granted (Allow it when prompted).

#### Enable Developer Mode in Android Auto.
- Install and open the `Android Auto` App
- Select the `About` section from menu.
- Click the Header `About Android Auto` a few times, until the dev mode is turned on.
- Click the `Menu` (3 dots) button and open `Developer Settings`.
- Set the `Application Mode` to `Developer`.
- Scroll down and ensure `Unknown sources` is checked.

#### Whitelist AA Stream for Android Auto
- Open **AA Stream**.
- Click on `Unlock for Android Auto`.
- If green check mark and a Toast message with success is shown - you are good to go. (If not - ensure your device is rooted).
- Restart device for the changes to take effect.
- Connect the device to your car and select **AA Stream** from all apps menu (Last icon on the right in cars display), if **AA Stream** is not there, redo the steps.

## Settings Activity guide
<img align="right" src="img/settings.gif" alt="Settings Activity" height="500">

- **Unlock for Android Auto**
  - Click here to whitelist **AA Stream** for Android Auto. Root permission is required!
  - **AA Stream** is successfully unlocked if a green check mark is visible.
- **Overwrite screen brightness**
  - Enable this setting to override device brightness when **AA Stream** is started from Android Auto.
  - Use this to save device battery as the device screen needs to be always on to mirror it in cars display.
- **Force screen rotation**
  - Enable this setting to force the device screen to be rotated to predefined degrees (0, 90, 180, 270).
  - Use this to start apps in landscape mode (If the app supports it).
- **Force screen resizing**
  - Enable this setting to force the device screen to be resized to match car display density.
- **Force immersive mode**
  - Enable this setting to force immersive mode (Hide device status bar).
- **Force audio focus**
  - Enable this setting to force audio focus when **AA Stream** is launched.
- **Show sidebar on startup**
  - Enable this to show sidebar menu on **AA Stream** startup.
  - Choose which menu option should be shown when sidebar is opened.
  - Set it to `Favorites` to show your favorite apps (To add or remove an app to favorites, press and hold an app icon for few seconds).
  - Choose how to open sidebar menu, wit two finger tap, double or triple taps.
- **About**
  - Click on the app icon for 10 times to enable debug mode.
  - This adds a new option in car display to see the logs of the app.
  
## Car Activity guide
<img src="img/android_auto.gif" alt="Car Activity" height="500">
<br/>

- **Menu close button**
  - Click here to close the sidebar.
- **Menu back button**
  - Click here to send back press command to the device.
- **Menu app drawer button**
  - Click here to show all apps available in your device.
  - Long press an app icon to add or remove the app to your favorites.
- **Menu favorite apps button**
  - Click here to list all your favorite apps.
  - Long click on an app icon to remove it from your favorites.
- **Menu debug button**
  - Visible only if debug mode is enabled.
  - Shows all app logs in real time for debugging.

## Credits
- Inspired by: [AAMirror](https://github.com/slashmax/AAMirror)
- Whitelist queries taken from: [AA-Phenotype-Patcher](https://github.com/Eselter/AA-Phenotype-Patcher)
- Wouldn't be possible without: [AAuto-SDK](https://github.com/martoreto/aauto-sdk)


================================================
FILE: app/build.gradle
================================================
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-android-extensions'

def getVersionCode = { ->
    try {
        def stdout = new ByteArrayOutputStream()
        exec {
            commandLine 'git', 'rev-list', 'HEAD', '--count'
            standardOutput = stdout
        }
        return Integer.parseInt(stdout.toString().trim())
    }
    catch (ignored) {
        return -1
    }
}

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.garage.aastream"
        minSdkVersion 21
        targetSdkVersion 28
        versionCode getVersionCode()
        versionName "1.1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        archivesBaseName = "aa-stream-v$versionName.$versionCode"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.aar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'androidx.core:core-ktx:1.0.2'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.2.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
    implementation 'com.google.code.gson:gson:2.8.5'
    implementation 'eu.chainfire:libsuperuser:1.1.0.201903290044'
    implementation 'com.google.dagger:dagger:2.21'
    annotationProcessor 'com.google.dagger:dagger-compiler:2.21'
    kapt 'com.google.dagger:dagger-compiler:2.21'
    implementation 'androidx.recyclerview:recyclerview:1.0.0'
    kapt 'com.github.bumptech.glide:glide:4.9.0'
    kapt "com.github.bumptech.glide:compiler:4.9.0"
    implementation 'com.github.bumptech.glide:glide:4.9.0'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
}



================================================
FILE: app/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:dist="http://schemas.android.com/apk/distribution"
          xmlns:tools="http://schemas.android.com/tools"
          package="com.garage.aastream">

    <dist:module dist:instant="true"/>

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

    <!-- System permissions - granted for root -->
    <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT" tools:ignore="ProtectedPermissions"/>
    <uses-permission android:name="android.permission.CAPTURE_SECURE_VIDEO_OUTPUT" tools:ignore="ProtectedPermissions"/>
    <uses-permission android:name="android.permission.WRITE_SETTINGS" tools:ignore="ProtectedPermissions"/>

    <application
            android:name="com.garage.aastream.App"
            android:allowBackup="true"
            android:fullBackupContent="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/AppTheme"
            android:hardwareAccelerated="true"
            tools:ignore="GoogleAppIndexingWarning">

        <activity
                android:name="com.garage.aastream.activities.CarDebugActivity"
                android:theme="@style/AppTheme.NoActionBar"/>

        <activity
                android:name="com.garage.aastream.activities.ResultRequestActivity"
                android:exported="false">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
            </intent-filter>
        </activity>

        <activity android:name="com.garage.aastream.activities.SettingsActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

        <activity
                android:name=".activities.KillerActivity"
                android:screenOrientation="portrait"
                android:configChanges="screenSize|density|orientation|screenLayout"
                android:launchMode="singleTop"
        />

        <service
                android:name="com.garage.aastream.services.CarService"
                android:enabled="true"
                android:exported="true"
                android:label="@string/app_name"
                tools:ignore="ExportedService">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="com.google.android.gms.car.category.CATEGORY_PROJECTION" />
                <category android:name="com.google.android.gms.car.category.CATEGORY_PROJECTION_OEM" />
            </intent-filter>
        </service>

        <meta-data
                android:name="com.google.android.gms.car.application"
                android:resource="@xml/automotive_app_desc" />
    </application>

</manifest>

================================================
FILE: app/src/main/java/com/garage/aastream/App.kt
================================================
package com.garage.aastream

import android.app.Application
import android.content.res.Configuration
import com.garage.aastream.injection.DaggerInjectionComponent
import com.garage.aastream.injection.InjectionComponent
import com.garage.aastream.injection.InjectionModule
import com.garage.aastream.interfaces.OnRotationChangedCallback
import com.garage.aastream.utils.DevLog

/**
 * Created by Endy Rubbin on 22.05.2019 10:54.
 * For project: AAStream
 */
@Suppress("unused")
class App : Application() {

    lateinit var component: InjectionComponent
    private var callback: OnRotationChangedCallback? = null

    override fun onCreate() {
        super.onCreate()
        DevLog.init(getString(R.string.app_name), BuildConfig.DEBUG)
        component = DaggerInjectionComponent.builder().injectionModule(InjectionModule(this)).build()
        component.inject(this)
    }

    override fun onConfigurationChanged(newConfig: Configuration?) {
        super.onConfigurationChanged(newConfig)
        DevLog.d("Configuration changed")
        callback?.onRotationChanged()
    }

    fun setRotationCallback(callback: OnRotationChangedCallback) {
        this.callback = callback
    }
}

================================================
FILE: app/src/main/java/com/garage/aastream/activities/CarDebugActivity.kt
================================================
package com.garage.aastream.activities

import android.content.res.Configuration
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.garage.aastream.App
import com.garage.aastream.R
import com.garage.aastream.activities.controllers.CarActivityController
import com.garage.aastream.utils.DevLog
import javax.inject.Inject

/**
 * Created by Endy Rubbin on 22.05.2019 10:44.
 * For project: AAStream
 */
class CarDebugActivity : AppCompatActivity() {

    @Inject lateinit var activityController: CarActivityController

    override fun onCreate(savedInstanceState: Bundle?) {
        setTheme(R.style.AppTheme_NoActionBar)
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_car)
        (application as App).component.inject(this)

        DevLog.d("Car debug activity created")
        activityController.onCreate(window.decorView, windowManager)
    }

    override fun onResume() {
        super.onResume()
        activityController.onResume()
    }

    override fun onStart() {
        super.onStart()
        activityController.onStart()
    }

    override fun onStop() {
        super.onStop()
        activityController.onStop()
    }

    override fun onDestroy() {
        super.onDestroy()
        activityController.onDestroy()
    }

    override fun onConfigurationChanged(newConfig: Configuration?) {
        super.onConfigurationChanged(newConfig)
        activityController.onConfigurationChanged()
    }
}


================================================
FILE: app/src/main/java/com/garage/aastream/activities/CarMainActivity.kt
================================================
package com.garage.aastream.activities

import android.content.res.Configuration
import android.os.Bundle
import com.garage.aastream.App
import com.garage.aastream.R
import com.garage.aastream.activities.controllers.CarActivityController
import com.garage.aastream.utils.DevLog
import com.google.android.apps.auto.sdk.CarActivity
import javax.inject.Inject

/**
 * Created by Endy Rubbin on 22.05.2019 10:44.
 * For project: AAStream
 */
class CarMainActivity : CarActivity() {

    @Inject lateinit var activityController: CarActivityController

    override fun onCreate(savedInstanceState: Bundle?) {
        setTheme(R.style.AppTheme_NoActionBar)
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_car)
        (applicationContext as App).component.inject(this)
        (applicationContext as App).setRotationCallback(activityController)

        DevLog.d("Car main activity created")
        activityController.onCreate(c().decorView, c().windowManager, carUiController)
        @Suppress("DEPRECATION")
        setIgnoreConfigChanges(0xFFFF)
    }

    override fun onResume() {
        super.onResume()
        activityController.onResume()
    }

    override fun onStart() {
        super.onStart()
        activityController.onStart()
    }

    override fun onStop() {
        super.onStop()
        activityController.onStop()
    }

    override fun onDestroy() {
        super.onDestroy()
        activityController.onDestroy()
    }

    override fun onConfigurationChanged(newConfig: Configuration?) {
        super.onConfigurationChanged(newConfig)
        activityController.onConfigurationChanged()
    }

    override fun onWindowFocusChanged(focus: Boolean, b1: Boolean) {
        super.onWindowFocusChanged(focus, b1)
        DevLog.d("Window focus changed $focus")
        if (focus) {
            activityController.onWindowFocusChanged()
        }
    }
}


================================================
FILE: app/src/main/java/com/garage/aastream/activities/KillerActivity.kt
================================================
package com.garage.aastream.activities

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.garage.aastream.App
import com.garage.aastream.handlers.BrightnessHandler
import com.garage.aastream.handlers.DisplayHandler
import com.garage.aastream.handlers.NotificationHandler
import com.garage.aastream.handlers.RotationHandler
import com.garage.aastream.utils.DevLog
import javax.inject.Inject

/**
 * Created by Endy Rubbin on 10.06.2019 15:37.
 * For project: AAStream
 */
class KillerActivity: AppCompatActivity() {

    @Inject lateinit var brightnessHandler: BrightnessHandler
    @Inject lateinit var rotationHandler: RotationHandler
    @Inject lateinit var notificationHandler: NotificationHandler
    @Inject lateinit var displayHandler: DisplayHandler

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        (application as App).component.inject(this)
        DevLog.d("Killer Activity launched")
        intent.removeExtra(NotificationHandler.ACTION_EXIT)
        notificationHandler.clearNotification()
        displayHandler.restoreDisplaySettings()
        brightnessHandler.restoreScreenBrightness()
        rotationHandler.restoreScreenRotation()
        DevLog.d("AAStream values reset - finishing app")
        finishAffinity()
    }
}

================================================
FILE: app/src/main/java/com/garage/aastream/activities/ResultRequestActivity.kt
================================================
package com.garage.aastream.activities

import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.Handler
import android.os.Message
import androidx.appcompat.app.AppCompatActivity
import com.garage.aastream.R
import com.garage.aastream.utils.DevLog

/**
 * Created by Endy Rubbin on 27.05.2019 16:11.
 * For project: AAStream
 */
class ResultRequestActivity: AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        DevLog.d("Request onCreate")
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_request_result)
        startActivityForResult()
    }

    override fun onDestroy() {
        DevLog.d("Request onDestroy")
        super.onDestroy()
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        DevLog.d("Request onActivityResult")
        super.onActivityResult(requestCode, resultCode, data)
        if (resultHandler != null) {
            val msg = Message.obtain(resultHandler, requestWhat, requestCode, resultCode, data)
            msg.sendToTarget()
        }
        finish()
    }

    private fun startActivityForResult() {
        DevLog.d("Request startActivityForResult")
        if (resultHandler != null && requestIntent != null) {
            startActivityForResult(requestIntent, requestCode)
        } else {
            finish()
        }
    }

    companion object {
        private var resultHandler: Handler? = null
        private var requestWhat: Int = 0
        private var requestIntent: Intent? = null
        private var requestCode: Int = 0

        fun startActivityForResult(context: Context, handler: Handler, what: Int, intent: Intent, requestCod: Int) {
            DevLog.d("startActivityForResult")
            resultHandler = handler
            requestWhat = what
            requestIntent = intent
            requestCode = requestCod

            val request = Intent(context, ResultRequestActivity::class.java)
            request.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            context.startActivity(request)
        }
    }
}

================================================
FILE: app/src/main/java/com/garage/aastream/activities/SettingsActivity.kt
================================================
package com.garage.aastream.activities

import android.annotation.TargetApi
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.provider.Settings.ACTION_MANAGE_WRITE_SETTINGS
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.SeekBar
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.garage.aastream.App
import com.garage.aastream.BuildConfig
import com.garage.aastream.R
import com.garage.aastream.handlers.BrightnessHandler
import com.garage.aastream.handlers.PreferenceHandler
import com.garage.aastream.handlers.RotationHandler
import com.garage.aastream.interfaces.OnPatchStatusCallback
import com.garage.aastream.utils.Const
import com.garage.aastream.utils.DevLog
import com.garage.aastream.utils.PhenotypePatcher
import kotlinx.android.synthetic.main.activity_settings.*
import kotlinx.android.synthetic.main.view_settings_about.*
import kotlinx.android.synthetic.main.view_settings_audio.*
import kotlinx.android.synthetic.main.view_settings_brightness.*
import kotlinx.android.synthetic.main.view_settings_debug.*
import kotlinx.android.synthetic.main.view_settings_immersive.*
import kotlinx.android.synthetic.main.view_settings_resize.*
import kotlinx.android.synthetic.main.view_settings_rotation.*
import kotlinx.android.synthetic.main.view_settings_sidebar.*
import kotlinx.android.synthetic.main.view_settings_unlock.*
import javax.inject.Inject

/**
 * Created by Endy Rubbin on 22.05.2019 10:44.
 * For project: AAStream
 */
class SettingsActivity : AppCompatActivity() {

    @Inject lateinit var preferences: PreferenceHandler
    @Inject lateinit var brightnessHandler: BrightnessHandler
    @Inject lateinit var rotationHandler: RotationHandler
    @Inject lateinit var patcher: PhenotypePatcher

    private var previousTime: Long = 0
    private var count = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_settings)
        (application as App).component.inject(this)

        initViews()
    }

    /**
     * Check if app can modify System settings
     */
    @TargetApi(Build.VERSION_CODES.M)
    private fun checkForSystemWritePermission() {
        if (!Settings.System.canWrite(this)) {
            startActivity(Intent(ACTION_MANAGE_WRITE_SETTINGS))
        }
    }

    /**
     * Initialize views and set listeners
     */
    private fun initViews() {
        // Debug controller
        settings_debug_activity_holder.setOnClickListener {
            startActivity(Intent(this, CarDebugActivity::class.java))
        }
        settings_debug_switch.setOnCheckedChangeListener { _, isChecked ->
            DevLog.d("Debug switch changed: $isChecked")
            preferences.putBoolean(PreferenceHandler.KEY_DEBUG_DISABLED, isChecked)
            view_settings_debug.visibility = if (isChecked) View.GONE else View.VISIBLE
        }
        settings_debug_switch.isChecked = false
        view_settings_debug.visibility = if (preferences.getBoolean(PreferenceHandler.KEY_DEBUG_DISABLED, true)) {
            View.GONE
        } else {
            View.VISIBLE
        }

        // Unlock controller
        view_settings_unlock.setOnClickListener { unlock() }
        settings_unlock_state_icon.visibility = if (patcher.isPatched()) View.VISIBLE else View.GONE

        // Brightness controller
        settings_brightness_seek_bar.progress = brightnessHandler.getScreenBrightness()
        settings_brightness_seek_bar.max = Const.MAX_VALUE
        settings_brightness_seek_bar.setOnSeekBarChangeListener(object: SeekBar.OnSeekBarChangeListener {
            override fun onStartTrackingTouch(seekBar: SeekBar?) {}
            override fun onStopTrackingTouch(seekBar: SeekBar?) {}
            override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
                DevLog.d("Brightness value changed $progress")
                preferences.putInt(PreferenceHandler.KEY_BRIGHTNESS_VALUE, progress)
            }
        })
        settings_brightness_switch.isChecked = preferences.getBoolean(PreferenceHandler.KEY_BRIGHTNESS_SWITCH, false)
        settings_brightness_switch.setOnCheckedChangeListener { _, isChecked ->
            DevLog.d("Brightness switch changed: $isChecked")
            if (isChecked) {
                checkForSystemWritePermission()
            }
            preferences.putBoolean(PreferenceHandler.KEY_BRIGHTNESS_SWITCH, isChecked)
            settings_brightness_seek_bar.isEnabled = isChecked
        }
        settings_brightness_seek_bar.isEnabled = settings_brightness_switch.isChecked

        // Rotation controller
        val rotationAdapter = ArrayAdapter.createFromResource(this, R.array.rotation_values,
            android.R.layout.simple_spinner_item)
        rotationAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
        settings_rotation_dropdown.adapter = rotationAdapter
        settings_rotation_dropdown.setSelection(rotationHandler.getScreenRotation())
        settings_rotation_dropdown.onItemSelectedListener = object: AdapterView.OnItemSelectedListener {
            override fun onNothingSelected(parent: AdapterView<*>?) {}
            override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
                DevLog.d("Rotation selected $position")
                preferences.putInt(PreferenceHandler.KEY_ROTATION_VALUE, position)
            }
        }
        settings_rotation_switch.isChecked = preferences.getBoolean(PreferenceHandler.KEY_ROTATION_SWITCH, false)
        settings_rotation_switch.setOnCheckedChangeListener { _, isChecked ->
            DevLog.d("Rotation switch changed: $isChecked")
            if (isChecked) {
                checkForSystemWritePermission()
            }
            preferences.putBoolean(PreferenceHandler.KEY_ROTATION_SWITCH, isChecked)
            settings_rotation_dropdown.isEnabled = isChecked
        }
        settings_rotation_dropdown.isEnabled = settings_rotation_switch.isChecked

        // Resize controller
        settings_resize_switch.isChecked = preferences.getBoolean(PreferenceHandler.KEY_RESIZE_ENABLED, false)
        settings_resize_switch.setOnCheckedChangeListener { _, isChecked ->
            DevLog.d("Resize switch changed: $isChecked")
            if (isChecked) {
                checkForSystemWritePermission()
            }
            preferences.putBoolean(PreferenceHandler.KEY_RESIZE_ENABLED, isChecked)
        }

        // Immersive controller
        settings_immersive_switch.isChecked = preferences.getBoolean(PreferenceHandler.KEY_IMMERSIVE_MODE, false)
        settings_immersive_switch.setOnCheckedChangeListener { _, isChecked ->
            DevLog.d("Immersive switch changed: $isChecked")
            if (isChecked) {
                checkForSystemWritePermission()
            }
            preferences.putBoolean(PreferenceHandler.KEY_IMMERSIVE_MODE, isChecked)
        }

        // Audio controller
        settings_audio_switch.isChecked = preferences.getBoolean(PreferenceHandler.KEY_AUDIO_FOCUS, false)
        settings_audio_switch.setOnCheckedChangeListener { _, isChecked ->
            DevLog.d("Audio switch changed: $isChecked")
            if (isChecked) {
                checkForSystemWritePermission()
            }
            preferences.putBoolean(PreferenceHandler.KEY_AUDIO_FOCUS, isChecked)
        }

        // Sidebar controller
        val sidebarAdapter = ArrayAdapter.createFromResource(this, R.array.screen_values,
            android.R.layout.simple_spinner_item)
        sidebarAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
        settings_sidebar_dropdown.adapter = sidebarAdapter
        settings_sidebar_dropdown.setSelection(preferences.getInt(PreferenceHandler.KEY_STARTUP_VALUE,
            Const.DEFAULT_SCREEN))
        settings_sidebar_dropdown.onItemSelectedListener = object: AdapterView.OnItemSelectedListener {
            override fun onNothingSelected(parent: AdapterView<*>?) {}
            override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
                DevLog.d("Startup screen selected $position")
                preferences.putInt(PreferenceHandler.KEY_STARTUP_VALUE, position)
            }
        }
        val sidebarMenuAdapter = ArrayAdapter.createFromResource(this, R.array.tap_values,
            android.R.layout.simple_spinner_item)
        sidebarMenuAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
        settings_sidebar_dropdown_menu.adapter = sidebarMenuAdapter
        settings_sidebar_dropdown_menu.setSelection(preferences.getInt(PreferenceHandler.KEY_OPEN_MENU_METHOD,
            Const.DEFAULT_TAP_METHOD))
        settings_sidebar_dropdown_menu.onItemSelectedListener = object: AdapterView.OnItemSelectedListener {
            override fun onNothingSelected(parent: AdapterView<*>?) {}
            override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
                DevLog.d("Sidebar open method selected $position")
                preferences.putInt(PreferenceHandler.KEY_OPEN_MENU_METHOD, position)
            }
        }
        settings_sidebar_switch.isChecked = preferences.getBoolean(PreferenceHandler.KEY_SIDEBAR_SWITCH,
            Const.DEFAULT_SHOW_SIDEBAR)
        settings_sidebar_switch.setOnCheckedChangeListener { _, isChecked ->
            DevLog.d("Sidebar switch changed: $isChecked")
            preferences.putBoolean(PreferenceHandler.KEY_SIDEBAR_SWITCH, isChecked)
        }

        // About controller
        settings_about_version.text = getString(R.string.txt_version,
            "${BuildConfig.VERSION_NAME}.${BuildConfig.VERSION_CODE}")
        settings_about.setOnClickListener {
            val currentTime = System.currentTimeMillis()
            if (currentTime - previousTime <= Const.CLICK_INTERVAL) {
                count++
            } else {
                count = 0
            }

            previousTime = currentTime
            if (count == Const.DEBUG_CLICK_COUNT) {
                DevLog.d("Debug mode enabled")
                Toast.makeText(this@SettingsActivity, getString(R.string.toast_developer_mode_enabled),
                    Toast.LENGTH_LONG).show()
                preferences.putBoolean(PreferenceHandler.KEY_DEBUG_DISABLED, false)
                view_settings_debug.visibility = View.VISIBLE
                settings_debug_switch.isChecked = false
            } else if (count >= Const.DEBUG_CLICK_COUNT - 3 && count < Const.DEBUG_CLICK_COUNT) {
                Toast.makeText(this@SettingsActivity, getString(R.string.toast_developer_mode_click,
                    (Const.DEBUG_CLICK_COUNT - count)), Toast.LENGTH_SHORT).show()
            }
        }
    }

    /**
     * White list this app for Android Auto
     * Reference: @see <a href="https://github.com/Eselter/AA-Phenotype-Patcher">AA-Phenotype-Patcher</a>
     */
    private fun unlock() {
        settings_unlock_state_spinner.visibility = View.VISIBLE
        settings_unlock_state_icon.visibility = View.GONE
        patcher.patch(object : OnPatchStatusCallback{
            override fun onPatchSuccessful() {
                runOnUiThread {
                    Toast.makeText(this@SettingsActivity,
                        getString(R.string.toast_app_whitelisted), Toast.LENGTH_LONG).show()
                    settings_unlock_state_icon.visibility = View.VISIBLE
                    settings_unlock_state_spinner.visibility = View.GONE
                }
            }

            override fun onPatchFailed() {
                runOnUiThread {
                    Toast.makeText(this@SettingsActivity,
                        getString(R.string.toast_root_not_available), Toast.LENGTH_LONG).show()
                    settings_unlock_state_icon.visibility = View.GONE
                    settings_unlock_state_spinner.visibility = View.GONE
                }
            }
        })
    }
}


================================================
FILE: app/src/main/java/com/garage/aastream/activities/controllers/CarActivityController.kt
================================================
package com.garage.aastream.activities.controllers

import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.app.Application
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.graphics.Color
import android.hardware.display.DisplayManager
import android.hardware.display.VirtualDisplay
import android.hardware.usb.UsbManager
import android.media.projection.MediaProjection
import android.media.projection.MediaProjectionManager
import android.os.Handler
import android.os.Looper
import android.os.PowerManager
import android.os.PowerManager.ACQUIRE_CAUSES_WAKEUP
import android.os.PowerManager.SCREEN_DIM_WAKE_LOCK
import android.util.DisplayMetrics
import android.view.KeyEvent
import android.view.OrientationEventListener
import android.view.View
import android.view.WindowManager
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import com.garage.aastream.App
import com.garage.aastream.R
import com.garage.aastream.activities.ResultRequestActivity
import com.garage.aastream.adapters.AppListAdapter
import com.garage.aastream.handlers.*
import com.garage.aastream.interfaces.*
import com.garage.aastream.minitouch.MiniTouchHandler
import com.garage.aastream.minitouch.MinitouchDaemon
import com.garage.aastream.models.AppItem
import com.garage.aastream.receivers.ScreenLockReceiver
import com.garage.aastream.receivers.UsbStateReceiver
import com.garage.aastream.receivers.UsbStateReceiver.UsbStateCallback
import com.garage.aastream.shell.ShellExecutor
import com.garage.aastream.utils.Const
import com.garage.aastream.utils.DevLog
import com.garage.aastream.views.MarginDecoration
import com.google.android.apps.auto.sdk.CarUiController
import com.google.android.apps.auto.sdk.DayNightStyle
import eu.chainfire.libsuperuser.Shell
import kotlinx.android.synthetic.main.activity_car.view.*
import kotlinx.android.synthetic.main.view_car_terminal.view.*
import javax.inject.Inject

/**
 * Created by Endy Rubbin on 28.05.2019 10:54.
 * For project: AAStream
 */
class CarActivityController(val context: Application) : OnScreenLockCallback, OnAppClickedCallback,
    OnAppListLoadedCallback, OnMenuTapCallback, OnRotationChangedCallback, OnMinitouchCallback, UsbStateCallback {

    @Inject lateinit var appHandler: AppHandler
    @Inject lateinit var preferences: PreferenceHandler
    @Inject lateinit var brightnessHandler: BrightnessHandler
    @Inject lateinit var rotationHandler: RotationHandler
    @Inject lateinit var miniTouchHandler: MiniTouchHandler
    @Inject lateinit var audioHandler: AudioHandler
    @Inject lateinit var terminalController: TerminalController
    @Inject lateinit var notificationHandler: NotificationHandler
    @Inject lateinit var displayHandler: DisplayHandler

    private lateinit var rootView: View
    private lateinit var windowManager: WindowManager
    private lateinit var adapter: AppListAdapter
    private lateinit var orientationListener: OrientationEventListener

    private var carUiController: CarUiController? = null
    private var currentView = ViewType.VIEW_NONE
    private var destroyed = false
    private var initialMenuX = 0f
    private var virtualDisplay: VirtualDisplay? = null
    private var mediaProjection: MediaProjection? = null
    private var projectionCode: Int = 0
    private var projectionIntent: Intent? = null

    private val apps = ArrayList<AppItem>()
    private val screenLockReceiver = ScreenLockReceiver(this)
    private val usbStateReceiver = UsbStateReceiver(this)
    private val screenFilter = IntentFilter()
    private val usbFilter = IntentFilter()
    private var minitouchDaemon: MinitouchDaemon? = null
    @Suppress("DEPRECATION")
    private val wakeLock = (context.getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(
        SCREEN_DIM_WAKE_LOCK or ACQUIRE_CAUSES_WAKEUP, WAKELOCK_TAG)

    /**
     * Called when user has granted permission to start screen capture
     */
    private val requestHandler = Handler(Handler.Callback { msg ->
        if (msg?.what == Const.REQUEST_MEDIA_PROJECTION_PERMISSION) {
            projectionCode = msg.arg2
            projectionIntent = msg.obj as Intent
            DevLog.d("Permission granted - starting screen capture")
            startScreenCapture()
        }
        false
    })

    /**
     * Initialize broadcast receivers
     */
    init {
        (context as App).component.inject(this)
        screenFilter.addAction(Intent.ACTION_USER_PRESENT)
        screenFilter.addAction(Intent.ACTION_SCREEN_ON)
        screenFilter.addAction(Intent.ACTION_SCREEN_OFF)
        usbFilter.addAction(Intent.ACTION_POWER_DISCONNECTED)
        usbFilter.addAction(UsbManager.ACTION_USB_ACCESSORY_DETACHED)
        usbFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED)
    }

    /**
     * Called when Activity is created
     */
    fun onCreate(rootView: View, windowManager: WindowManager, carUiController: CarUiController? = null) {
        this.rootView = rootView
        this.windowManager = windowManager
        this.carUiController = carUiController
        terminalController.init(rootView)
        destroyed = false

        Thread.setDefaultUncaughtExceptionHandler { _, e ->
            e.printStackTrace()
            DevLog.d("App has crashed: ${e.localizedMessage}")
            onDestroy()
        }

        initViews()
        initCarUiController()
        requestProjectionPermission()
    }

    /**
     * Called when Activity is resumed
     */
    fun onResume() {
        Shell.SU.available()
        startMinitouch()
        miniTouchHandler.updateValues()
        loadApps()
        DevLog.d("Car activity resumed")
    }

    /**
     * Called when Activity is started
     */
    fun onStart() {
        DevLog.d("Car activity started")
        onScreenOn()
        notificationHandler.showNotification()
        wakeLock.acquire(Long.MAX_VALUE)
        audioHandler.start()
        orientationListener.enable()
        displayHandler.changeDisplaySettings()
        brightnessHandler.setScreenBrightness()
        rotationHandler.setScreenRotation()
        context.registerReceiver(screenLockReceiver, screenFilter)
        context.registerReceiver(usbStateReceiver, usbFilter)
    }

    /**
     * Called when Activity is stopped
     */
    fun onStop() {
        DevLog.d("Car activity stopped")
        if (wakeLock.isHeld) wakeLock.release()
        audioHandler.stop()
        orientationListener.disable()
        stopScreenCapture()
    }

    /**
     * Called when Activity is destroyed
     */
    fun onDestroy() {
        DevLog.d("Car activity destroyed")
        destroyed = true
        terminalController.stop()
        stopMinitouch()
        displayHandler.restoreDisplaySettings()
        brightnessHandler.restoreScreenBrightness()
        rotationHandler.restoreScreenRotation()
        try {
            context.unregisterReceiver(screenLockReceiver)
        } catch (e: Exception) {
            DevLog.d("Screen lock receiver already unregistered")
        }
        try {
            context.unregisterReceiver(usbStateReceiver)
        } catch (e: Exception) {
            DevLog.d("USB state receiver already unregistered")
        }
    }

    /**
     * Called when Activity configuration has changed
     */
    fun onConfigurationChanged() {
        DevLog.d("Configuration changed")
        miniTouchHandler.updateValues()
    }

    /**
     * Called when car activity focus has changed
     */
    fun onWindowFocusChanged() {
        DevLog.d("On focus changed $destroyed")
        if (!destroyed) {
            updateScreenSize()
            startScreenCapture()
        }
    }

    /**
     * Start mini touch daemon
     */
    private fun startMinitouch() {
        DevLog.d("Starting minitouch")
        if (minitouchDaemon == null) {
            minitouchDaemon = MinitouchDaemon(miniTouchHandler, this)
            minitouchDaemon?.execute()
        }
        miniTouchHandler.init(rootView.car_surface_view, this)
    }

    /**
     * Stop mini touch daemon
     */
    private fun stopMinitouch() {
        miniTouchHandler.clear()
        miniTouchHandler.stop()
        minitouchDaemon?.cancel(true)
    }

    /**
     * Start dummy activity to handle permission granted result
     */
    private fun startActivityForResult(what: Int, intent: Intent) {
        ResultRequestActivity.startActivityForResult(context, requestHandler, what, intent, what)
    }

    /**
     * Request permission to record screen
     */
    private fun requestProjectionPermission() {
        DevLog.d("Request projection permission")
        val mediaProjectionManager = context.getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
        startActivityForResult(
            Const.REQUEST_MEDIA_PROJECTION_PERMISSION,
            mediaProjectionManager.createScreenCaptureIntent()
        )
    }

    /**
     * Initialize views and set listeners
     */
    private fun initViews() {
        DevLog.d("Initializing views")
        orientationListener = object : OrientationEventListener(context) {
            override fun onOrientationChanged(orientation: Int) {
                miniTouchHandler.updateValues()
            }
        }

        adapter = AppListAdapter(context, this)
        rootView.car_app_grid.itemAnimator = null
        rootView.car_app_grid.addItemDecoration(MarginDecoration(context))
        rootView.car_app_grid.adapter = adapter

        rootView.car_menu_app_list.setOnClickListener { showScreen(ViewType.VIEW_APP_LIST.value) }
        rootView.car_menu_favorites.setOnClickListener { showScreen(ViewType.VIEW_FAVORITES.value) }
        rootView.car_menu_terminal.setOnClickListener { showScreen(ViewType.VIEW_TERMINAL.value) }
        rootView.car_menu_close.setOnClickListener { switchMenuVisibility(false) }
        rootView.car_menu_back.setOnClickListener {
            showScreen(ViewType.VIEW_NONE.value)
            ShellExecutor("input keyevent ${KeyEvent.KEYCODE_BACK}").start()
        }
        rootView.car_menu_holder.post {
            initialMenuX = rootView.car_menu_holder.width.toFloat()
            rootView.car_menu_holder.x = -initialMenuX
        }
        rootView.car_menu_terminal.visibility = if (preferences.getBoolean(PreferenceHandler.KEY_DEBUG_DISABLED, true)) {
            View.GONE
        } else {
            View.VISIBLE
        }
        switchMenuVisibility(preferences.getBoolean(PreferenceHandler.KEY_SIDEBAR_SWITCH, Const.DEFAULT_SHOW_SIDEBAR))
    }

    /**
     * Initialize car UI controller
     */
    private fun initCarUiController() {
        carUiController?.statusBarController?.setTitle("")
        carUiController?.statusBarController?.hideAppHeader()
        carUiController?.statusBarController?.setAppBarAlpha(0.0f)
        carUiController?.statusBarController?.setAppBarBackgroundColor(Color.WHITE)
        carUiController?.statusBarController?.setDayNightStyle(DayNightStyle.AUTO)
        carUiController?.menuController?.hideMenuButton()
    }

    /**
     * Show/hide sidebar menu
     */
    private fun switchMenuVisibility(visible: Boolean) {
        rootView.car_menu_holder.post {
            if (visible) {
                rootView.car_menu_holder.animate().cancel()
                rootView.car_menu_holder.animate()
                    .alpha(1f)
                    .x(0f)
                    .setStartDelay(Const.DEFAULT_ANIMATION_DELAY)
                    .setDuration(Const.DEFAULT_ANIMATION_DURATION)
                    .setListener(object : AnimatorListenerAdapter() {
                        override fun onAnimationEnd(animation: Animator?) {
                            super.onAnimationEnd(animation)
                            showScreen(preferences.getInt(PreferenceHandler.KEY_STARTUP_VALUE, Const.DEFAULT_SCREEN))
                        }
                    })
                    .start()
            } else {
                showScreen(ViewType.VIEW_NONE.value)
                rootView.car_menu_holder.animate()
                    .alpha(0f)
                    .x(-initialMenuX)
                    .setStartDelay(Const.DEFAULT_ANIMATION_DELAY)
                    .setDuration(Const.DEFAULT_ANIMATION_DURATION)
                    .setListener(object : AnimatorListenerAdapter() {
                        override fun onAnimationEnd(animation: Animator?) {
                            super.onAnimationEnd(animation)
                            currentView = Companion.ViewType.VIEW_NONE
                        }
                    })
                    .start()
            }
        }
    }

    /**
     * Show selected screen
     */
    private fun showScreen(index: Int) {
        if (index != currentView.value) {
            DevLog.d("Showing screen: $index")
            hideKeyboard()
            when (index) {
                ViewType.VIEW_NONE.value -> {
                    rootView.car_app_grid.visibility = View.GONE
                    rootView.view_car_terminal.visibility = View.GONE
                    rootView.car_app_favorite_empty.visibility = View.GONE
                }
                ViewType.VIEW_APP_LIST.value -> showAllApps()
                ViewType.VIEW_FAVORITES.value -> showFavorites()
                ViewType.VIEW_TERMINAL.value -> showTerminal()
            }
        }
    }

    /**
     * Show terminal view
     */
    private fun showTerminal() {
        DevLog.d("Showing terminal")
        currentView = ViewType.VIEW_TERMINAL
        rootView.view_car_terminal.visibility = View.VISIBLE
        rootView.car_app_grid.visibility = View.GONE
        rootView.car_app_favorite_empty.visibility = View.GONE
        rootView.car_app_grid_loader.visibility = View.GONE
    }

    /**
     * Shows all device apps
     */
    private fun showAllApps() {
        DevLog.d("Showing all apps")
        currentView = ViewType.VIEW_APP_LIST
        rootView.car_app_grid.visibility = View.GONE
        rootView.car_app_favorite_empty.visibility = View.GONE
        rootView.view_car_terminal.visibility = View.GONE
        if (apps.isEmpty()) {
            rootView.car_app_grid_loader.visibility = View.VISIBLE
        } else {
            rootView.car_app_grid_loader.visibility = View.GONE
            rootView.car_app_grid.visibility = View.VISIBLE
            adapter.addAll(apps)
        }
    }

    /**
     * Shows all favorite apps if exists
     */
    private fun showFavorites() {
        DevLog.d("Showing favorite apps")
        currentView = ViewType.VIEW_FAVORITES
        rootView.car_app_grid.visibility = View.VISIBLE
        rootView.view_car_terminal.visibility = View.GONE
        showFavoritePlaceholder()
        appHandler.getFavorites().let {
            adapter.addAll(it)
        }
    }

    /**
     * Show / hide favorite app placeholder
     */
    private fun showFavoritePlaceholder() {
        rootView.car_app_grid_loader.visibility = View.GONE
        appHandler.getFavorites().let {
            if (it.isEmpty()) {
                rootView.car_app_favorite_empty.visibility = View.VISIBLE
            } else {
                rootView.car_app_favorite_empty.visibility = View.GONE
            }
        }
    }

    /**
     * @return selected app from app list or null
     */
    private fun getSelectedApp(app: AppItem): AppItem? {
        return apps.firstOrNull { it.equalTo(app) }
    }

    /**
     * Query for installed device apps
     */
    private fun loadApps() {
        appHandler.loadApps(this)
    }

    /**
     * Show error Toast with provided message String
     */
    private fun showToastMessage(message: String) {
        Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
    }

    /**
     * Hide keyboard
     */
    private fun hideKeyboard() {
        rootView.terminal_input.let {
            it.postDelayed({
                val inputManager = context.getSystemService(
                    Context.INPUT_METHOD_SERVICE) as InputMethodManager
                inputManager.hideSoftInputFromWindow(it.windowToken, 0)
            }, 100)
        }
    }

    /**
     * Set current [AppItem] as favorite or not
     */
    private fun setAppFavorite(app: AppItem) {
        val wasFavorite = appHandler.isInFavorites(app)
        apps.firstOrNull { it.equalTo(app) }?.favorite = !wasFavorite
        preferences.putFavorites(apps.filter { it.favorite } as ArrayList<AppItem>)

        if (ViewType.VIEW_FAVORITES == currentView) {
            adapter.removeFavorite(app)
            showFavoritePlaceholder()
        } else {
            adapter.setFavorite(app, wasFavorite)
        }

        if (wasFavorite) {
            showToastMessage(context.getString(R.string.txt_removed_from_favorites))
        } else {
            showToastMessage(context.getString(R.string.txt_added_to_favorites))
        }
    }

    /**
     * Start screen capture
     */
    private fun startScreenCapture() {
        if (!destroyed) {
            Handler(Looper.getMainLooper()).postDelayed({
                rootView.car_surface_view.post {
                    DevLog.d("Will start screen capture if ($projectionCode) != 0 && ($projectionIntent) != null")
                    if (projectionIntent != null || projectionCode != 0) {
                        stopScreenCapture()
                        DevLog.d("Starting screen capture $projectionCode $projectionIntent")
                        miniTouchHandler.updateTouchTransformations(true)

                        val metrics = DisplayMetrics()
                        windowManager.defaultDisplay.getMetrics(metrics)
                        val screenDensity = metrics.densityDpi
                        val mediaProjectionManager = context.getSystemService(
                            Context.MEDIA_PROJECTION_SERVICE
                        ) as MediaProjectionManager

                        mediaProjection = mediaProjectionManager.getMediaProjection(projectionCode, projectionIntent!!)
                        mediaProjection?.let {
                            val width = rootView.car_surface_view.width
                            val height = rootView.car_surface_view.height

                            DevLog.d("Screen width: $width")
                            DevLog.d("Screen height: $height")
                            DevLog.d("Screen density: $screenDensity")

                            if (width > 0 && height > 0) {
                                virtualDisplay = it.createVirtualDisplay(
                                    "ScreenCapture",
                                    width, height, screenDensity,
                                    DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
                                    rootView.car_surface_view.holder.surface, null, null
                                )
                            }
                        }
                    }
                }
            }, Const.DEFAULT_ANIMATION_DELAY)
        }
    }

    /**
     * Stop screen capture
     */
    private fun stopScreenCapture() {
        DevLog.d("Stopping screen capture")
        virtualDisplay?.release()
        virtualDisplay = null
        mediaProjection?.stop()
        mediaProjection = null
    }

    /**
     * Update screen size on focus change
     */
    private fun updateScreenSize() {
        if (preferences.getBoolean(PreferenceHandler.KEY_RESIZE_ENABLED, false)) {
            val width = rootView.car_surface_view.width.toDouble()
            val height = rootView.car_surface_view.height.toDouble()
            if (width > 0 && height > 0) {
                val ratio = width / height
                val deviceWidth = miniTouchHandler.getDeviceDisplayWidth()
                val deviceHeight = (deviceWidth * ratio).toInt()
                if (deviceWidth > 0) {
                    displayHandler.updateScreenSize(deviceWidth, deviceHeight)
                }
            }
        }
    }

    /**
     * Called when device screen is unlocked
     */
    override fun onScreenUnlocked() {
        DevLog.d("Screen Unlocked")
        rootView.car_surface_view.keepScreenOn = false
        updateScreenSize()
    }

    /**
     * Called when device screen is turned on
     */
    override fun onScreenOn() {
        DevLog.d("Screen ON")
        rootView.car_surface_view.keepScreenOn = true
        updateScreenSize()
        startScreenCapture()
    }

    /**
     * Called when device screen is turned off
     */
    override fun onScreenOff() {
        DevLog.d("Screen OFF")
        rootView.car_surface_view.keepScreenOn = false
        displayHandler.restoreDisplaySettings()
        stopScreenCapture()
    }

    /**
     * Called when screen is tapped with two fingers
     */
    override fun onTapForMenu() {
        DevLog.d("Show menu from tap")
        switchMenuVisibility(true)
    }

    /**
     * Called when device rotation has changed
     */
    override fun onRotationChanged() {
        DevLog.d("Rotation changed")
        if (!destroyed) {
            miniTouchHandler.updateValues()
            onWindowFocusChanged()
        }
    }

    /**
     * Called when query for device apps is finished and results are returned
     */
    override fun onAppListLoaded(apps: ArrayList<AppItem>) {
        Handler(Looper.getMainLooper()).post {
            DevLog.d("App list loaded")
            var updated = false
            if (this.apps.size == 0 || this.apps.size != apps.size) {
                this.apps.addAll(apps)
                updated = true
            } else {
                apps.forEach { newApp ->
                    var added = false
                    this.apps.forEach { currentApp ->
                        if (newApp.equalTo(currentApp)) {
                            added = true
                        }
                    }
                    if (!added) {
                        this.apps.add(newApp)
                        updated = true
                    }
                }
            }
            // Update favorites
            apps.forEach {
                it.favorite = appHandler.isInFavorites(it)
            }
            if (updated) {
                if (ViewType.VIEW_APP_LIST == currentView) {
                    showAllApps()
                } else if (ViewType.VIEW_FAVORITES == currentView) {
                    showFavorites()
                }
            }
        }
    }

    /**
     * Called when query for device apps has failed
     */
    override fun onAppListLoadFailed() {
        DevLog.d("App list load failed")
        if (ViewType.VIEW_APP_LIST == currentView) {
            showToastMessage(context.getString(R.string.err_app_list_load_failed))
        }
    }

    /**
     * Called when an app in app list is clicked
     */
    override fun onAppClicked(app: AppItem) {
        Handler(Looper.getMainLooper()).post {
            getSelectedApp(app)?.let {
                DevLog.d("App clicked: $it")
                context.packageManager.getLaunchIntentForPackage(it.packageName)?.let { intent ->
                    switchMenuVisibility(false)
                    context.startActivity(intent)
                } ?: showToastMessage(context.getString(R.string.err_app_launch_failed))
            }
        }
    }

    /**
     * Called when an app in app list is long clicked
     */
    override fun onAppLongClicked(app: AppItem) {
        Handler(Looper.getMainLooper()).post {
            getSelectedApp(app)?.let {
                DevLog.d("App long clicked: $it")
                setAppFavorite(it)
            }
        }
    }

    /**
     * Called when USB is disconnected
     */
    override fun onUsbDisconnected() {
        DevLog.d("Usb disconnected")
        onDestroy()
    }

    /**
     * Called when minitouch installed on path
     */
    override fun onInstalled(path: String) {
        DevLog.d("Initializing minitouch on $path")
        ShellExecutor("chmod 777 $path").start()
        ShellExecutor(path).start()
        DevLog.d("Mini Touch started: $path")
        miniTouchHandler.isInstalled = true
    }

    /**
     * Called when minitouch has failed
     */
    override fun onFailed() {
        DevLog.d("Failed to install minitouch, trying again")
        minitouchDaemon?.cancel(true)
        minitouchDaemon = null
        startMinitouch()
    }

    companion object {
        const val WAKELOCK_TAG = "AAStream:WakeLock"

        enum class ViewType(val value: Int) {
            VIEW_NONE(0),
            VIEW_APP_LIST(1),
            VIEW_FAVORITES(2),
            VIEW_TERMINAL(3)
        }
    }
}

================================================
FILE: app/src/main/java/com/garage/aastream/activities/controllers/TerminalController.kt
================================================
package com.garage.aastream.activities.controllers

import android.os.Handler
import android.os.Looper
import android.view.View
import kotlinx.android.synthetic.main.view_car_terminal.view.*
import com.garage.aastream.interfaces.OnLogCallback
import com.garage.aastream.utils.DevLog

/**
 * Created by Endy Rubbin on 28.05.2019 13:32.
 * For project: AAStream
 */
class TerminalController : OnLogCallback {

    private lateinit var rootView: View

    /**
     * Initialize the controller and add listener for logs
     */
    fun init(rootView: View) {
        this.rootView = rootView
        DevLog.setCallback(this)

        this.rootView.terminal_input_button.setOnClickListener {
            this.rootView.terminal_input.text.toString().takeIf { it.isNotEmpty() }?.let {
                DevLog.d("root@aa-stream:-$ $it")
                this.rootView.terminal_input.setText("")
            }
        }
    }

    /**
     * Remove log event callbacks
     */
    fun stop() {
        DevLog.removeCallback()
    }

    /**
     * Called when logs are written
     */
    override fun onLogWritten(log: String) {
        Handler(Looper.getMainLooper()).post {
            rootView.terminal_console.append(if (rootView.terminal_console.text.isEmpty()) "" else "\n")
            rootView.terminal_console.append(log)
            rootView.terminal_scroller.post { rootView.terminal_scroller.fullScroll(View.FOCUS_DOWN) }
        }
    }
}

================================================
FILE: app/src/main/java/com/garage/aastream/adapters/AppListAdapter.kt
================================================
package com.garage.aastream.adapters

import android.content.Context
import android.graphics.drawable.Drawable
import android.net.Uri
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.OvershootInterpolator
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import com.garage.aastream.R
import com.garage.aastream.injection.GlideApp
import com.garage.aastream.interfaces.OnAppClickedCallback
import com.garage.aastream.models.AppItem
import com.garage.aastream.utils.DevLog
import kotlinx.android.synthetic.main.row_app_item.view.*
import kotlin.random.Random

/**
 * Created by Endy Rubbin on 23.05.2019 15:02.
 * For project: AAStream
 */
class AppListAdapter(
    val context: Context,
    val callback: OnAppClickedCallback
) : RecyclerView.Adapter<AppListAdapter.ViewHolder>() {

    private var currentPosition = DEFAULT_INDEX
    private val apps: ArrayList<AppItem> = ArrayList()
    val glide = GlideApp.with(context)

    /**
     * Update the list adapter with new items
     */
    fun addAll(apps: ArrayList<AppItem>) {
        DevLog.d("Notifying app list ${apps.size} $apps")
        this.apps.clear()
        this.apps.addAll(apps)
        currentPosition = DEFAULT_INDEX
        notifyDataSetChanged()
    }

    /**
     * Update list item and set it as favorite or not
     */
    fun setFavorite(app: AppItem, favorite: Boolean) {
        apps.indexOfFirst { it.equalTo(app)}.takeIf {it >= 0}?.let {
            DevLog.d("Notifying app item $it $favorite ${apps[it]}")
            apps[it].favorite = !favorite
            currentPosition = DEFAULT_INDEX
            notifyItemChanged(it)
        }
    }

    /**
     * Remove item from list
     */
    fun removeFavorite(app: AppItem) {
        apps.indexOfFirst { it.equalTo(app)}.takeIf {it >= 0}?.let {
            apps.removeAt(it)
            currentPosition = DEFAULT_INDEX
            notifyItemRemoved(it)
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.row_app_item, parent, false)
        return ViewHolder(view)
    }

    override fun getItemCount(): Int {
        return apps.size
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val animate = if (position > currentPosition) {
            currentPosition = position
            true
        } else false
        holder.bind(apps[position], animate)
    }

    override fun onViewDetachedFromWindow(holder: ViewHolder) {
        super.onViewDetachedFromWindow(holder)
        holder.clearAnimation()
    }

    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        fun bind(app: AppItem, animate: Boolean = true) {
            if (app.drawable != null) {
                glide.load(app.drawable)
                    .placeholder(android.R.drawable.sym_def_app_icon)
                    .into(itemView.item_app_icon)
            } else {
                glide.load(if (app.icon != null) Uri.parse(app.icon) else android.R.drawable.sym_def_app_icon)
                    .placeholder(android.R.drawable.sym_def_app_icon)
                    .listener(object : RequestListener<Drawable> {
                        override fun onLoadFailed(
                            e: GlideException?,
                            model: Any?,
                            target: Target<Drawable>?,
                            isFirstResource: Boolean
                        ): Boolean {
                            return false
                        }

                        override fun onResourceReady(
                            resource: Drawable?,
                            model: Any?,
                            target: Target<Drawable>?,
                            dataSource: DataSource?,
                            isFirstResource: Boolean
                        ): Boolean {
                            app.drawable = resource
                            return false
                        }
                    })
                    .into(itemView.item_app_icon)
            }

            itemView.item_app_name.text = app.label
            itemView.item_app_favorite.visibility = if (app.favorite) View.VISIBLE else View.INVISIBLE
            itemView.setOnClickListener { callback.onAppClicked(app) }
            itemView.setOnLongClickListener {
                callback.onAppLongClicked(app)
                true
            }

            if (animate) {
                val scale = Random.nextFloat() * (MAX_START_SCALE - MIN_START_SCALE) + MIN_START_SCALE
                val delay = (Random.nextInt(MAX_START_DELAY) + MIN_START_DELAY).toLong()
                val duration = (Random.nextInt(MAX_DURATION) + MIN_DURATION).toLong()

                if (itemView.alpha != 0f) {
                    itemView.alpha = MIN_ALPHA
                }
                itemView.scaleX = scale
                itemView.scaleY = scale


                itemView.animate()
                    .alpha(1f)
                    .scaleX(1f)
                    .scaleY(1f)
                    .setStartDelay(delay)
                    .setDuration(duration)
                    .setInterpolator(OvershootInterpolator())
                    .start()
            } else {
                itemView.scaleX = 1f
                itemView.scaleY = 1f
                itemView.alpha = 1f
            }
        }

        fun clearAnimation() {
            itemView.animate().cancel()
            itemView.clearAnimation()
        }
    }

    companion object {
        const val DEFAULT_INDEX = -1
        const val MIN_ALPHA = 0.5f
        const val MIN_START_SCALE = 0.4f
        const val MAX_START_SCALE = 0.8f
        const val MIN_START_DELAY = 50
        const val MAX_START_DELAY = 200
        const val MIN_DURATION = 200
        const val MAX_DURATION = 500
    }
}

================================================
FILE: app/src/main/java/com/garage/aastream/handlers/AppHandler.kt
================================================
package com.garage.aastream.handlers

import android.content.Context
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import com.garage.aastream.interfaces.OnAppListLoadedCallback
import com.garage.aastream.models.AppItem
import java.io.File

/**
 * Created by Endy Rubbin on 23.05.2019 15:25.
 * For project: AAStream
 */
class AppHandler(
    val context: Context,
    private val preferences: PreferenceHandler) {

    private val packageManager: PackageManager = context.packageManager

    /**
     * Load all apps installed on device
     */
    fun loadApps(callback: OnAppListLoadedCallback) {
        Thread(Runnable {
            packageManager.getInstalledApplications(0)?.let {
                val apps = ArrayList<AppItem>()
                it.forEach { info ->
                packageManager.getLaunchIntentForPackage(info.packageName)?.let {
                        apps.add(getAppItem(info))
                    }
                }
                if (apps.isNotEmpty()) {
                    callback.onAppListLoaded(apps)
                } else {
                    callback.onAppListLoadFailed()
                }
            }
        }).start()
    }

    /**
     * Gather app info
     *
     * @return the [AppItem] with set name and icon
     */
    @Suppress("DEPRECATION")
    private fun getAppItem(info: ApplicationInfo): AppItem {
        val file = File(info.sourceDir)
        val icon = if (info.icon != 0) "android.resource://" + info.packageName + "/" + info.icon else null
        val name = if (!file.exists()) {
            info.packageName
        } else {
            info.loadLabel(packageManager)
        }.toString()
        return AppItem(name, info.packageName, icon)
    }

    /**
     * @return all favored [AppItem]s
     */
    fun getFavorites(): ArrayList<AppItem> {
        val favorites = preferences.getFavorites()
        favorites.forEach { app -> app.favorite = false }
        return favorites
    }

    /**
     * Check if app is in favorites
     */
    fun isInFavorites(app: AppItem): Boolean {
        return getFavorites().firstOrNull { it.equalTo(app) }?.let { true } ?: false
    }
}

================================================
FILE: app/src/main/java/com/garage/aastream/handlers/AudioHandler.kt
================================================
package com.garage.aastream.handlers

import android.content.Context
import android.media.AudioManager.AUDIOFOCUS_GAIN
import android.support.car.Car
import android.support.car.CarConnectionCallback
import android.support.car.media.CarAudioManager
import android.support.car.media.CarAudioManager.CAR_AUDIO_USAGE_DEFAULT
import com.garage.aastream.utils.DevLog

/**
 * Created by Endy Rubbin on 22.05.2019 14:31.
 * For project: AAStream
 */
class AudioHandler(
    context: Context,
    private val preferences: PreferenceHandler
) {

    private var car: Car? = null

    init {
        car = Car.createCar(context, object : CarConnectionCallback() {
            override fun onConnected(car: Car) {
                requestAudioFocus(car)
            }

            override fun onDisconnected(car: Car) {
                abandonAudioFocus(car)
            }
        })
    }

    /**
     * Start audio handler
     */
    fun start() {
        car?.connect()
    }

    /**
     * Stop audio handler
     */
    fun stop() {
        car?.disconnect()
    }

    /**
     * Request car audio focus
     */
    private fun requestAudioFocus(car: Car) {
        if (!preferences.getBoolean(PreferenceHandler.KEY_AUDIO_FOCUS, false)) {
            return
        }
        DevLog.d("RequestAudioFocus")
        try {
            val carAM = car.getCarManager(CarAudioManager::class.java)
            carAM.requestAudioFocus(
                null,
                carAM.getAudioAttributesForCarUsage(CAR_AUDIO_USAGE_DEFAULT),
                AUDIOFOCUS_GAIN,
                0
            )
        } catch (e: Exception) {
            DevLog.d("RequestAudioFocus exception: $e")
        }
    }

    /**
     * Abandon car audio focus
     */
    private fun abandonAudioFocus(car: Car) {
        DevLog.d("AbandonAudioFocus")
        try {
            val carAM = car.getCarManager(CarAudioManager::class.java)
            carAM.abandonAudioFocus(null, carAM.getAudioAttributesForCarUsage(CAR_AUDIO_USAGE_DEFAULT))
        } catch (e: Exception) {
            DevLog.d("AbandonAudioFocus exception: $e")
        }
    }
}

================================================
FILE: app/src/main/java/com/garage/aastream/handlers/BrightnessHandler.kt
================================================
package com.garage.aastream.handlers

import android.content.Context
import android.os.Build
import android.provider.Settings
import android.provider.Settings.System.SCREEN_BRIGHTNESS
import com.garage.aastream.utils.DevLog

/**
 * Created by Endy Rubbin on 26.05.2019 19:41.
 * For project: AAStream
 */
class BrightnessHandler(val context: Context, val preferences: PreferenceHandler) {

    private val systemBrightness: Int

    init {
        val savedSystemBrightness = preferences.getInt(PreferenceHandler.KEY_SYSTEM_BRIGHTNESS, -1)
        val currentSystemBrightness = (Settings.System.getInt(context.contentResolver,
            SCREEN_BRIGHTNESS).toFloat() / 255 * 100).toInt()
        systemBrightness = if (savedSystemBrightness == -1) {
            preferences.putInt(PreferenceHandler.KEY_SYSTEM_BRIGHTNESS, currentSystemBrightness)
            currentSystemBrightness
        } else {
            savedSystemBrightness
        }
    }


    /**
     * @return true if brightness can be changed
     */
    private fun canChangeBrightness(): Boolean {
        return preferences.getBoolean(PreferenceHandler.KEY_BRIGHTNESS_SWITCH, false) &&
                (Build.VERSION.SDK_INT < 23 || Settings.System.canWrite(context))
    }

    /**
     * @return current / saved screen brightness
     */
    fun getScreenBrightness(): Int {
        return if (canChangeBrightness()) {
            preferences.getInt(PreferenceHandler.KEY_BRIGHTNESS_VALUE, systemBrightness)
        } else {
            systemBrightness
        }
    }

    /**
     * Update device screen brightness
     */
    fun setScreenBrightness(value: Int = getScreenBrightness()) {
        if (canChangeBrightness()) {
            DevLog.d("Changing screen brightness: $value $systemBrightness")
            Settings.System.putInt(context.contentResolver, SCREEN_BRIGHTNESS, 255 * value / 100)
        }
    }

    /**
     * Restore previous screen brightness
     */
    fun restoreScreenBrightness() {
        DevLog.d("Restoring screen brightness $systemBrightness")
        setScreenBrightness(systemBrightness)
        preferences.putInt(PreferenceHandler.KEY_SYSTEM_BRIGHTNESS, -1)
    }
}

================================================
FILE: app/src/main/java/com/garage/aastream/handlers/DisplayHandler.kt
================================================
package com.garage.aastream.handlers

import android.content.Context
import com.garage.aastream.shell.ShellExecutor
import com.garage.aastream.utils.DevLog

/**
 * Created by Endy Rubbin on 02.07.2019 10:21.
 * For project: AAStream
 */
class DisplayHandler(val context: Context, val preferences: PreferenceHandler) {

    /**
     * Change initial display settings
     */
    fun changeDisplaySettings() {
        DevLog.d("Changing display settings")
        if (preferences.getBoolean(PreferenceHandler.KEY_IMMERSIVE_MODE, false)) {
            ShellExecutor("settings put global policy_control immersive.full=*").start()
        }
    }

    /**
     * Update screen size
     */
    fun updateScreenSize(deviceWidth: Int, deviceHeight: Int) {
        DevLog.d("Setting screen size: $deviceWidth $deviceHeight")
        ShellExecutor("wm size " + deviceWidth + "x" + deviceHeight).start()
    }

    /**
     * Restore display settings
     */
    fun restoreDisplaySettings() {
        DevLog.d("Restoring display settings")
        if (preferences.getBoolean(PreferenceHandler.KEY_IMMERSIVE_MODE, false)) {
            ShellExecutor("settings put global policy_control none*").start()
        }
        ShellExecutor("wm size reset").start()
    }
}

================================================
FILE: app/src/main/java/com/garage/aastream/handlers/NotificationHandler.kt
================================================
package com.garage.aastream.handlers

import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.os.Build
import com.garage.aastream.R
import com.garage.aastream.activities.KillerActivity

/**
 * Created by Endy Rubbin on 07.06.2019 11:29.
 * For project: AAStream
 */
class NotificationHandler(val context: Context) {

    private val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
    /**
     * Show notification when car activity is started to safely exit the app and restore previous state
     */
    @Suppress("DEPRECATION")
    fun showNotification() {
        createChannel(context)
        val builder: Notification.Builder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            Notification.Builder(context, CHANNEL_ID)
        } else {
            Notification.Builder(context)
        }

        val title = context.getString(R.string.app_name)
        val message = context.getString(R.string.txt_app_running_notification)

        val notifyIntent = Intent(context, KillerActivity::class.java)
        notifyIntent.putExtra(ACTION_EXIT, true)
        notifyIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK
        notifyIntent.flags = Intent.FLAG_ACTIVITY_REORDER_TO_FRONT

        val pendingIntent = PendingIntent.getActivity(context, 0,
            notifyIntent, PendingIntent.FLAG_UPDATE_CURRENT)

        val notification = builder
            .setSmallIcon(R.drawable.ic_small_icon)
            .setOnlyAlertOnce(true)
            .setPriority(Notification.PRIORITY_MAX)
            .setContentTitle(title)
            .setWhen(0)
            .setShowWhen(true)
            .setStyle(Notification.BigTextStyle().bigText(message))
            .setContentText(message)
            .addAction(Notification
                .Action(0, context.getString(R.string.txt_notification_exit), pendingIntent))
            .build()

        notificationManager.notify(NOTIFICATION_ID, notification)
    }

    /**
     * Clear ingoing notification when app is destroyed
     */
    fun clearNotification() {
        notificationManager.cancel(NOTIFICATION_ID)
    }

    /**
     * Create notification channel for android O and up
     */
    private fun createChannel(context: Context) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
            val importance = NotificationManager.IMPORTANCE_HIGH
            val notificationChannel = NotificationChannel(CHANNEL_ID, CHANNEL_NAME, importance)

            notificationChannel.enableVibration(true)
            notificationChannel.setShowBadge(true)
            notificationChannel.enableLights(true)
            notificationChannel.lightColor = Color.parseColor("#e8334a")
            notificationChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
            notificationManager.createNotificationChannel(notificationChannel)
        }
    }

    companion object {
        const val NOTIFICATION_ID = 1337
        const val CHANNEL_ID = "AAStream Channel ID"
        const val CHANNEL_NAME = "AAStream Notification Name"
        const val ACTION_EXIT = "Action Exit"
    }
}

================================================
FILE: app/src/main/java/com/garage/aastream/handlers/PreferenceHandler.kt
================================================
package com.garage.aastream.handlers

import android.app.Application
import android.content.Context
import com.garage.aastream.models.AppItem
import com.garage.aastream.models.AppItemWrapper
import com.garage.aastream.utils.Const
import com.google.gson.Gson
import java.util.*

/**
 * Created by Endy Rubbin on 22.05.2019 14:31.
 * For project: AAStream
 */
class PreferenceHandler(context: Application) {
    private val preferences = context.getSharedPreferences(Const.PREFERENCES_NAME, Context.MODE_PRIVATE)
    private val gson: Gson = Gson()

    /**
     * Save a boolean value preferences
     */
    fun putBoolean(key: String, value: Boolean) {
        preferences.edit().putBoolean(key, value).apply()
    }

    /**
     * Load a boolean value from preferences
     */
    fun getBoolean(key: String, value: Boolean): Boolean {
        return preferences.getBoolean(key, value)
    }

    /**
     * Save a int value preferences
     */
    fun putInt(key: String, value: Int) {
        preferences.edit().putInt(key, value).apply()
    }

    /**
     * Load a int value from preferences
     */
    fun getInt(key: String, value: Int): Int {
        return preferences.getInt(key, value)
    }

    /**
     * Save a list of [AppItem]s in preferences
     */
    fun putFavorites(apps: ArrayList<AppItem>) {
        val data = gson.toJson(AppItemWrapper(apps))
        preferences.edit().putString(KEY_FAVORITE_APPS, data).apply()
    }

    /**
     * Load a list of [AppItem]s from preferences
     */
    fun getFavorites(): ArrayList<AppItem> {
        val data = preferences.getString(KEY_FAVORITE_APPS, null)
        return if (data != null) {
            gson.fromJson(data, AppItemWrapper::class.java).apps
        } else ArrayList()
    }

    companion object {
        const val KEY_AUDIO_FOCUS = "audio_focus"
        const val KEY_FAVORITE_APPS = "favorite_app_list"
        const val KEY_ROTATION_SWITCH = "rotation_switch"
        const val KEY_ROTATION_VALUE = "rotation_value"
        const val KEY_BRIGHTNESS_SWITCH = "brightness_switch"
        const val KEY_BRIGHTNESS_VALUE = "brightness_value"
        const val KEY_SYSTEM_BRIGHTNESS = "system_brightness"
        const val KEY_SIDEBAR_SWITCH = "sidebar_switch"
        const val KEY_STARTUP_VALUE = "sidebar_value"
        const val KEY_DEBUG_DISABLED = "debug_enabled"
        const val KEY_OPEN_MENU_METHOD = "menu_open_method"
        const val KEY_RESIZE_ENABLED = "resize_enabled"
        const val KEY_IMMERSIVE_MODE = "immersive_mode"
    }
}


================================================
FILE: app/src/main/java/com/garage/aastream/handlers/RotationHandler.kt
================================================
package com.garage.aastream.handlers

import android.content.Context
import android.os.Build
import android.provider.Settings
import android.provider.Settings.System.ACCELEROMETER_ROTATION
import android.provider.Settings.System.USER_ROTATION
import android.view.Surface
import com.garage.aastream.utils.DevLog

/**
 * Created by Endy Rubbin on 26.05.2019 20:27.
 * For project: AAStream
 */
class RotationHandler(val context: Context, val preferences: PreferenceHandler) {

    private val systemAutoRotation = Settings.System.getInt(context.contentResolver, ACCELEROMETER_ROTATION)

    /**
     * @return true if rotation can be changed
     */
    private fun canChangeRotation(): Boolean {
        return preferences.getBoolean(PreferenceHandler.KEY_ROTATION_SWITCH, false) &&
                (Build.VERSION.SDK_INT < 23 || Settings.System.canWrite(context))
    }

    /**
     * @return current / saved screen brightness
     */
    fun getScreenRotation(): Int {
        return if (canChangeRotation()) {
            preferences.getInt(PreferenceHandler.KEY_ROTATION_VALUE, 0)
        } else {
            0
        }
    }

    /**
     * Update device screen brightness
     */
    fun setScreenRotation(value: Int = getScreenRotation(), autoRotation: Int = 0) {
        if (canChangeRotation()) {
            DevLog.d("Changing screen rotation: $value $systemAutoRotation")
            Settings.System.putInt(context.contentResolver, ACCELEROMETER_ROTATION, autoRotation)
            Settings.System.putInt(context.contentResolver, USER_ROTATION, getOrientation(value))
        }
    }

    /**
     * @return System rotation value for selected option
     */
    private fun getOrientation(value: Int): Int {
        return when(value) {
            0 -> Surface.ROTATION_0
            1 -> Surface.ROTATION_90
            2 -> Surface.ROTATION_180
            3 -> Surface.ROTATION_270
            else -> Surface.ROTATION_0
        }
    }

    /**
     * Restore previous screen brightness
     */
    fun restoreScreenRotation() {
        DevLog.d("Restoring screen rotation")
        setScreenRotation(Surface.ROTATION_0, systemAutoRotation)
    }
}

================================================
FILE: app/src/main/java/com/garage/aastream/injection/GlideModule.kt
================================================
package com.garage.aastream.injection

import com.bumptech.glide.annotation.GlideModule
import com.bumptech.glide.module.AppGlideModule

/**
 * Created by Endy Rubbin on 24.05.2019 17:48.
 * For project: AAStream
 */
@GlideModule
class GlideModule : AppGlideModule()

================================================
FILE: app/src/main/java/com/garage/aastream/injection/InjectionComponent.kt
================================================
package com.garage.aastream.injection

import com.garage.aastream.App
import com.garage.aastream.activities.CarDebugActivity
import com.garage.aastream.activities.CarMainActivity
import com.garage.aastream.activities.KillerActivity
import com.garage.aastream.activities.SettingsActivity
import com.garage.aastream.activities.controllers.CarActivityController
import com.garage.aastream.handlers.PreferenceHandler
import dagger.Component
import javax.inject.Singleton

/**
 * Created by Endy Rubbin on 22.05.2019 14:48.
 * For project: AAStream
 */

@Singleton
@Component(modules = [InjectionModule::class])
interface InjectionComponent {
    fun inject(target: App)
    fun inject(target: SettingsActivity)
    fun inject(target: CarMainActivity)
    fun inject(target: CarDebugActivity)
    fun inject(target: KillerActivity)
    fun inject(target: CarActivityController)

    fun exposePreferenceHandler(): PreferenceHandler
}

================================================
FILE: app/src/main/java/com/garage/aastream/injection/InjectionModule.kt
================================================
package com.garage.aastream.injection

import android.app.Application
import com.garage.aastream.activities.controllers.CarActivityController
import com.garage.aastream.activities.controllers.TerminalController
import com.garage.aastream.handlers.*
import com.garage.aastream.minitouch.MiniTouchHandler
import com.garage.aastream.utils.PhenotypePatcher
import dagger.Module
import dagger.Provides
import javax.inject.Singleton

/**
 * Created by Endy Rubbin on 22.05.2019 14:44.
 * For project: AAStream
 */
@Module
class InjectionModule(private val context: Application) {

    @Singleton
    @Provides
    fun provideAudioHandler(preferences: PreferenceHandler): AudioHandler {
        return AudioHandler(context, preferences)
    }

    @Singleton
    @Provides
    fun provideMiniTouchHandler(preferences: PreferenceHandler): MiniTouchHandler {
        return MiniTouchHandler(context, preferences)
    }

    @Singleton
    @Provides
    fun providePreferenceHandler(): PreferenceHandler {
        return PreferenceHandler(context)
    }

    @Singleton
    @Provides
    fun provideAppHandler(preferences: PreferenceHandler): AppHandler {
        return AppHandler(context, preferences)
    }

    @Singleton
    @Provides
    fun provideBrightnessHandler(preferences: PreferenceHandler): BrightnessHandler {
        return BrightnessHandler(context, preferences)
    }

    @Singleton
    @Provides
    fun provideRotationHandler(preferences: PreferenceHandler): RotationHandler {
        return RotationHandler(context, preferences)
    }

    @Singleton
    @Provides
    fun providePhenotypePatcher(): PhenotypePatcher {
        return PhenotypePatcher(context)
    }

    @Singleton
    @Provides
    fun provideCarActivityController(): CarActivityController {
        return CarActivityController(context)
    }

    @Singleton
    @Provides
    fun provideTerminalController(): TerminalController {
        return TerminalController()
    }

    @Singleton
    @Provides
    fun provideNotificationHandler(): NotificationHandler {
        return NotificationHandler(context)
    }

    @Singleton
    @Provides
    fun provideDisplayHandler(preferences: PreferenceHandler): DisplayHandler {
        return DisplayHandler(context, preferences)
    }
}

================================================
FILE: app/src/main/java/com/garage/aastream/interfaces/OnAppClickedCallback.kt
================================================
package com.garage.aastream.interfaces

import com.garage.aastream.models.AppItem

/**
 * Created by Endy Rubbin on 23.05.2019 15:05.
 * For project: AAStream
 */
interface OnAppClickedCallback {

    /**
     * Called when [AppItem] is selected at adapter position
     */
    fun onAppClicked(app: AppItem)

    /**
     * Called when [AppItem] is long clicked ar adapter position to add to favorites
     */
    fun onAppLongClicked(app: AppItem)
}

================================================
FILE: app/src/main/java/com/garage/aastream/interfaces/OnAppListLoadedCallback.kt
================================================
package com.garage.aastream.interfaces

import com.garage.aastream.models.AppItem

/**
 * Created by Endy Rubbin on 23.05.2019 15:26.
 * For project: AAStream
 */
interface OnAppListLoadedCallback {

    /**
     * Called when device app list has finished loading
     */
    fun onAppListLoaded(apps: ArrayList<AppItem>)

    /**
     * Called when device app list failed to load
     */
    fun onAppListLoadFailed()
}

================================================
FILE: app/src/main/java/com/garage/aastream/interfaces/OnLogCallback.kt
================================================
package com.garage.aastream.interfaces

/**
 * Created by Endy Rubbin on 28.05.2019 13:35.
 * For project: AAStream
 */
interface OnLogCallback {

    /**
     * Called when a log line is written to console
     */
    fun onLogWritten(log: String)
}

================================================
FILE: app/src/main/java/com/garage/aastream/interfaces/OnMenuTapCallback.kt
================================================
package com.garage.aastream.interfaces

/**
 * Created by Endy Rubbin on 27.05.2019 13:35.
 * For project: AAStream
 */
interface OnMenuTapCallback {

    /**
     * Called when tap to open menu is detected
     */
    fun onTapForMenu()
}

================================================
FILE: app/src/main/java/com/garage/aastream/interfaces/OnMinitouchCallback.kt
================================================
package com.garage.aastream.interfaces

/**
 * Created by Endy Rubbin on 10.06.2019 14:20.
 * For project: AAStream
 */
interface OnMinitouchCallback {

    /**
     * Called when minitouch is installed
     */
    fun onInstalled(path: String)

    /**
     * Called when minitouch has failed
     */
    fun onFailed()
}

================================================
FILE: app/src/main/java/com/garage/aastream/interfaces/OnPatchStatusCallback.kt
================================================
package com.garage.aastream.interfaces

/**
 * Created by Endy Rubbin on 27.05.2019 10:33.
 * For project: AAStream
 */
interface OnPatchStatusCallback {

    /**
     * Called if patch was successful
     */
    fun onPatchSuccessful()

    /**
     * Called if patch has failed
     */
    fun onPatchFailed()
}

================================================
FILE: app/src/main/java/com/garage/aastream/interfaces/OnRotationChangedCallback.kt
================================================
package com.garage.aastream.interfaces

/**
 * Created by Endy Rubbin on 03.06.2019 16:38.
 * For project: AAStream
 */
interface OnRotationChangedCallback {

    /**
     * Called when device rotation has changed
     */
    fun onRotationChanged()
}

================================================
FILE: app/src/main/java/com/garage/aastream/interfaces/OnScreenLockCallback.kt
================================================
package com.garage.aastream.interfaces

/**
 * Created by Endy Rubbin on 22.05.2019 13:06.
 * For project: AAStream
 */
interface OnScreenLockCallback {

    /**
     * Called when device lock screen is unlocked
     */
    fun onScreenUnlocked()

    /**
     * Called when device screen is turned on and available for recording
     */
    fun onScreenOn()

    /**
     * Called when device screen is turned off / sleeping
     */
    fun onScreenOff()
}

================================================
FILE: app/src/main/java/com/garage/aastream/minitouch/MiniTouchHandler.kt
================================================
package com.garage.aastream.minitouch

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Point
import android.view.MotionEvent
import android.view.MotionEvent.*
import android.view.Surface.*
import android.view.SurfaceView
import android.view.View
import android.view.WindowManager
import com.garage.aastream.handlers.PreferenceHandler
import com.garage.aastream.interfaces.OnMenuTapCallback
import com.garage.aastream.interfaces.OnMinitouchCallback
import com.garage.aastream.utils.DevLog
import com.garage.aastream.views.FingerTapDetector
import eu.chainfire.libsuperuser.Shell
import java.io.File
import java.io.FileOutputStream

/**
 * Created by Endy Rubbin on 22.05.2019 13:25.
 * For project: AAStream
 */
class MiniTouchHandler(
    private val context: Context,
    private val preferenceHandler: PreferenceHandler
): View.OnTouchListener {

    private var fingerTapDetector: FingerTapDetector? = null
    private val miniTouchSocket: MiniTouchSocket = MiniTouchSocket()
    private var surfaceView: SurfaceView? = null
    private var callback: OnMinitouchCallback? = null

    private var deviceScreenSize = Point()
    private var deviceDisplaySize = Point()
    private var deviceScreenRotation: Int = ROTATION_0
    private var screenRotation: Int = ROTATION_0
    private var screenWidth = 0.0
    private var screenHeight = 0.0
    private var projectionOffsetX = 0.0
    private var projectionOffsetY = 0.0
    private var projectionWidth = 0.0
    private var projectionHeight = 0.0
    var isInstalled = false

    fun getDeviceDisplayWidth(): Int {
        return deviceDisplaySize.x
    }

    /**
     * Reads and updates current screen values
     */
    fun updateValues() {
        val windowManager = context.applicationContext.getSystemService(Context.WINDOW_SERVICE) as WindowManager
        windowManager.defaultDisplay.getRealSize(deviceScreenSize)
        deviceScreenRotation = windowManager.defaultDisplay.rotation
        DevLog.d("Updating values: $deviceScreenRotation")
        if (deviceScreenRotation == ROTATION_0 || deviceScreenRotation == ROTATION_180) {
            deviceDisplaySize.x = deviceScreenSize.x
            deviceDisplaySize.y = deviceScreenSize.y
        } else {
            deviceDisplaySize.x = deviceScreenSize.y
            deviceDisplaySize.y = deviceScreenSize.x
        }
        updateTouchTransformations(true)
    }

    @SuppressLint("ClickableViewAccessibility")
    fun init(surfaceView: SurfaceView, callback: OnMenuTapCallback) {
        this.surfaceView = surfaceView
        this.surfaceView?.setOnTouchListener(this)
        fingerTapDetector = FingerTapDetector(context, preferenceHandler, callback)
    }

    /**
     * Clear views and callbacks
     */
    fun clear() {
        surfaceView = null
        fingerTapDetector?.removeCallback()
        fingerTapDetector = null
    }

    /**
     * Start mini touch handler
     */
    fun start(callback: OnMinitouchCallback) {
        this.callback = callback
        val path = install()
        DevLog.d("Mini Touch path: $path")
        if (path?.isNotEmpty() == true) {
            callback.onInstalled(path)
        }
    }

    /**
     * Stop mini touch handler
     */
    fun stop() {
        this.callback = null
        val pid = miniTouchSocket.getPid()
        miniTouchSocket.disconnect()
        DevLog.d("Mini Touch stopped: $pid")
        if (pid != 0) {
            Shell.Pool.SU.run("kill $pid")
        }
    }

    /**
     * Install minitouch library
     */
    private fun install(): String? {
        val path = context.filesDir.absolutePath
        val target = File("$path/", "minitouch")
        val folder = File(path)
        try {
            val abi = detectAbi()
            val assetName = "libs/$abi/minitouch"
            DevLog.d("Minitouch already exists: ${folder.exists()}")
            if (!folder.exists()) folder.mkdirs()
            DevLog.d("Installing minitouch $assetName")
            context.resources.assets.open(assetName).use { input ->
                DevLog.d("Asset opened, writing to file: ${target.absolutePath}")
                if (!target.exists()) target.createNewFile()
                FileOutputStream(target.absolutePath).use { output ->
                    DevLog.d("Copying asset to file ${target.absolutePath}")
                    input.copyTo(output)
                    input.close()
                    output.flush()
                    output.close()

                    DevLog.d("Mini Touch installed ${target.absolutePath}")
                    return target.absolutePath
                }
            }
        } catch (e: Exception) {
            return if (target.exists()) {
                DevLog.d("Mini Touch installed ${target.absolutePath}")
                target.absolutePath
            } else {
                e.printStackTrace()
                DevLog.d("Failed to install Mini Touch: $e")
                null
            }
        }
    }

    /**
     * Detect device ABI
     */
    private fun detectAbi(): String {
        var abi: String? = null
        Shell.Pool.SH.run("getprop ro.product.cpu.abi", object : Shell.OnSyncCommandLineListener {
            override fun onSTDERR(line: String?) {
                DevLog.d("Failed to detect Abi: $line")
            }

            override fun onSTDOUT(line: String?) {
                DevLog.d("Shell line read: $line")
                if (abi == null) abi = line
            }
        })
        DevLog.d("Detected Abi: $abi")
        return if (abi != null && abi!!.isNotEmpty()) abi!! else "armeabi"
    }

    /**
     * Handle device screen touch events
     */
    @SuppressLint("ClickableViewAccessibility")
    override fun onTouch(v: View?, event: MotionEvent): Boolean {
        if (!miniTouchSocket.isConnected()) {
            if (!isInstalled) {
                DevLog.d("Minitouch not installed")
                callback?.onFailed()
                return false
            }
            miniTouchSocket.connect(callback)
            updateTouchTransformations(true)
        } else {
            updateTouchTransformations(false)
        }

        var isConnected = miniTouchSocket.isConnected()
        val action = event.actionMasked
        var i = 0
        while (i < event.pointerCount && isConnected) {
            val id = event.getPointerId(i)
            val x = (event.getX(i) - projectionOffsetX) / projectionWidth
            val y = (event.getY(i) - projectionOffsetY) / projectionHeight
            val pressure = event.getPressure(i).toDouble()

            var rx = x
            var ry = y
            when (screenRotation) {
                ROTATION_0 -> {
                    rx = x
                    ry = y
                }
                ROTATION_90 -> {
                    rx = 1.0 - y
                    ry = x
                }
                ROTATION_180 -> {
                    rx = 1.0 - x
                    ry = 1.0 - y
                }
                ROTATION_270 -> {
                    rx = y
                    ry = 1.0 - x
                }
            }
            when (action) {
                ACTION_DOWN, ACTION_POINTER_DOWN -> isConnected = isConnected && miniTouchSocket.touchDown(id, rx, ry, pressure)
                ACTION_MOVE -> isConnected = isConnected && miniTouchSocket.touchMove(id, rx, ry, pressure)
                ACTION_UP, ACTION_CANCEL -> isConnected = isConnected && miniTouchSocket.touchUpAll()
                ACTION_POINTER_UP -> isConnected = isConnected && miniTouchSocket.touchUp(id)
            }
            i++
        }

        if (isConnected) {
            miniTouchSocket.touchCommit()
        }

        fingerTapDetector?.onTouchEvent(event)
        return true
    }

    /**
     * Update touch coordinate transformations
     */
    fun updateTouchTransformations(force: Boolean) {
        if (surfaceView == null ||
            deviceScreenRotation == screenRotation &&
            deviceScreenSize.equals(screenWidth.toInt(), screenHeight.toInt())
            && !force) {
            return
        }

        screenRotation = deviceScreenRotation
        screenWidth = deviceScreenSize.x.toDouble()
        screenHeight = deviceScreenSize.y.toDouble()
        val surfaceWidth = surfaceView?.width?.toDouble() ?: 0.0
        val surfaceHeight = surfaceView?.height?.toDouble() ?: 0.0
        val factX = surfaceWidth / screenWidth
        val factY = surfaceHeight / screenHeight
        val fact = if (factX < factY) factX else factY

        projectionWidth = fact * screenWidth
        projectionHeight = fact * screenHeight

        projectionOffsetX = (surfaceWidth - projectionWidth) / 2.0
        projectionOffsetY = (surfaceHeight - projectionHeight) / 2.0

        if (screenRotation == ROTATION_0 || screenRotation == ROTATION_180) {
            miniTouchSocket.updateTouchTransformations(this.screenWidth, this.screenHeight, deviceDisplaySize)
        } else {
            miniTouchSocket.updateTouchTransformations(this.screenHeight, this.screenWidth, deviceDisplaySize)
        }
    }
}

================================================
FILE: app/src/main/java/com/garage/aastream/minitouch/MiniTouchSocket.kt
================================================
package com.garage.aastream.minitouch

import android.graphics.Point
import android.net.LocalSocket
import android.net.LocalSocketAddress
import com.garage.aastream.interfaces.OnMinitouchCallback
import com.garage.aastream.utils.DevLog
import java.io.InputStream
import java.io.OutputStream

/**
 * Created by Endy Rubbin on 22.05.2019 13:25.
 * For project: AAStream
 */
class MiniTouchSocket {

    private var socketLocal: LocalSocket? = null
    private var outputStream: OutputStream? = null

    private var version: Int = 0
    private var maxContact: Int = 0
    private var maxX: Double = 0.toDouble()
    private var maxY: Double = 0.toDouble()
    private var maxPressure: Double = 0.toDouble()
    private var pid: Int = 0

    private var projectionOffsetX: Double = 0.toDouble()
    private var projectionOffsetY: Double = 0.toDouble()
    private var projectionWidth: Double = 0.toDouble()
    private var projectionHeight: Double = 0.toDouble()
    private var touchXScale: Double = 0.toDouble()
    private var touchYScale: Double = 0.toDouble()

    internal fun disconnect() {
        DevLog.d("Mini Touch disconnect")
        if (isConnected()) {
            try {
                socketLocal!!.close()
            } catch (e: Exception) {
                DevLog.d("Failed to disconnect socket: $e")
            }
            outputStream = null
            socketLocal = null
        }
    }

    internal fun connect(callback: OnMinitouchCallback?): Boolean {
        DevLog.d("Mini Touch connect socket")
        disconnect()
        val socket = LocalSocket()
        try {
            socket.connect(LocalSocketAddress(DEFAULT_SOCKET_NAME))
            if (inputReadParams(socket.inputStream)) {
                outputStream = socket.outputStream
                socketLocal = socket
            } else {
                socket.close()
            }
        } catch (e: Exception) {
            DevLog.d("Failed to connect socket: $e")
            socketLocal = null
            callback?.onFailed()
        }
        return isConnected()
    }

    fun isConnected(): Boolean {
        return socketLocal != null
    }

    internal fun getPid(): Int {
        DevLog.d("Mini Touch pid: $pid")
        return pid
    }

    private fun inputReadParams(stream: InputStream): Boolean {
        DevLog.d("Mini Touch read")
        val buffer = ByteArray(128)
        pid = 0

        try {
            if (stream.read(buffer) == -1) {
                DevLog.d("Mini Touch read error")
                return false
            }
        } catch (e: Exception) {
            DevLog.d("Mini Touch read exception: $e")
            return false
        }

        val dataLine = String(buffer)
        val lines = dataLine.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()

        if (lines.size < 3) {
            DevLog.d("Error: less then 3 lines")
            return false
        }

        val versionLine = lines[0].split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
        if (versionLine.size == 2) {
            version = Integer.parseInt(versionLine[1])
        }
        val limitsLine = lines[1].split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
        if (limitsLine.size == 5) {
            maxContact = Integer.parseInt(limitsLine[1])
            maxX = Integer.parseInt(limitsLine[2]).toDouble()
            maxY = Integer.parseInt(limitsLine[3]).toDouble()
            maxPressure = Integer.parseInt(limitsLine[4]).toDouble()
        }
        val pidLine = lines[2].split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
        if (pidLine.size == 2) {
            pid = Integer.parseInt(pidLine[1])
        }

        DevLog.d("Read pid $pid")
        return true
    }

    private fun writeOutput(command: String): Boolean {
        if (outputStream == null)
            return false
        try {
            outputStream!!.write(command.toByteArray())
        } catch (e: Exception) {
            return false
        }
        return true
    }

    private fun validateBounds(x: Double, y: Double): Boolean {
        return x >= 0.0 && x < maxX && y >= 0.0 && y < maxY
    }

    internal fun updateTouchTransformations(screenWidth: Double, screenHeight: Double, displaySize: Point) {
        val displayWidth = displaySize.x.toDouble()
        val displayHeight = displaySize.y.toDouble()
        val factX = displayWidth / screenWidth
        val factY = displayHeight / screenHeight

        val fact = if (factX < factY) factX else factY

        projectionWidth = fact * screenWidth
        projectionHeight = fact * screenHeight

        projectionOffsetX = (displayWidth - projectionWidth) / 2.0
        projectionOffsetY = (displayHeight - projectionHeight) / 2.0

        touchXScale = maxX / displayWidth
        touchYScale = maxY / displayHeight
    }

    internal fun touchDown(id: Int, x: Double, y: Double, pressure: Double): Boolean {
        val touchX = (projectionOffsetX + x * projectionWidth) * touchXScale
        val touchY = (projectionOffsetY + y * projectionHeight) * touchYScale
        val touchPressure = pressure * maxPressure

        if (!validateBounds(touchX, touchY))
            return true
        return writeOutput(String.format("d %d %d %d %d\n", id, touchX.toInt(), touchY.toInt(), touchPressure.toInt()))
    }

    internal fun touchMove(id: Int, x: Double, y: Double, pressure: Double): Boolean {
        val touchX = (projectionOffsetX + x * projectionWidth) * touchXScale
        val touchY = (projectionOffsetY + y * projectionHeight) * touchYScale
        val touchPressure = pressure * maxPressure

        if (!validateBounds(touchX, touchY))
            return true
        return writeOutput(String.format("m %d %d %d %d\n", id, touchX.toInt(), touchY.toInt(), touchPressure.toInt()))
    }

    internal fun touchUp(id: Int): Boolean {
        return writeOutput(String.format("u %d\n", id))
    }

    internal fun touchUpAll(): Boolean {
        var ok = true
        for (i in 0 until maxContact)
            ok = ok && touchUp(i)
        return ok
    }

    internal fun touchCommit() {
        writeOutput("c\n")
    }

    companion object {
        private const val DEFAULT_SOCKET_NAME = "minitouch"
    }
}

================================================
FILE: app/src/main/java/com/garage/aastream/minitouch/MinitouchDaemon.kt
================================================
package com.garage.aastream.minitouch

import android.os.AsyncTask
import com.garage.aastream.interfaces.OnMinitouchCallback
import com.garage.aastream.utils.DevLog

/**
 * Created by Endy Rubbin on 10.06.2019 14:27.
 * For project: AAStream
 */
class MinitouchDaemon(
    private val miniTouchHandler: MiniTouchHandler,
    val callback: OnMinitouchCallback
) : AsyncTask<Void, Void, Void>() {
    override fun doInBackground(vararg voids: Void): Void? {
        DevLog.d("Minitouch daemon started")
        miniTouchHandler.start(callback)
        return null
    }
}

================================================
FILE: app/src/main/java/com/garage/aastream/models/AppItem.kt
================================================
package com.garage.aastream.models

import android.graphics.drawable.Drawable

/**
 * Created by Endy Rubbin on 23.05.2019 15:03.
 * For project: AAStream
 */
data class AppItem(val label: String, val packageName: String, val icon: String?, var favorite: Boolean = false) {

    @Transient var drawable: Drawable? = null

    fun equalTo(app: AppItem): Boolean {
        return this.packageName == app.packageName
    }
}

================================================
FILE: app/src/main/java/com/garage/aastream/models/AppItemWrapper.kt
================================================
package com.garage.aastream.models

/**
 * Created by Endy Rubbin on 23.05.2019 18:59.
 * For project: AAStream
 */
data class AppItemWrapper(val apps: ArrayList<AppItem>)

================================================
FILE: app/src/main/java/com/garage/aastream/receivers/ScreenLockReceiver.kt
================================================
package com.garage.aastream.receivers

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.Intent.*
import com.garage.aastream.interfaces.OnScreenLockCallback
import com.garage.aastream.utils.DevLog

/**
 * Created by Endy Rubbin on 22.05.2019 13:06.
 * For project: AAStream
 *
 * Used to listen for screen state changes
 */
class ScreenLockReceiver(
    private val callback: OnScreenLockCallback
) : BroadcastReceiver() {

    override fun onReceive(context: Context?, intent: Intent?) {
        DevLog.d("Screen state changed: ${intent?.action}")
        when(intent?.action) {
            ACTION_USER_PRESENT -> callback.onScreenUnlocked()
            ACTION_SCREEN_ON -> callback.onScreenOn()
            ACTION_SCREEN_OFF -> callback.onScreenOff()
        }
    }
}

================================================
FILE: app/src/main/java/com/garage/aastream/receivers/UsbStateReceiver.kt
================================================
package com.garage.aastream.receivers

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.hardware.usb.UsbManager
import com.garage.aastream.utils.DevLog

/**
 * Created by Endy Rubbin on 05.06.2019 10:34.
 * For project: AAStream
 */
class UsbStateReceiver(private val callback: UsbStateCallback) : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        DevLog.d("USB state changed: ${intent?.action}")
        when (intent?.action) {
            Intent.ACTION_POWER_DISCONNECTED,
            UsbManager.ACTION_USB_ACCESSORY_DETACHED,
            UsbManager.ACTION_USB_DEVICE_DETACHED -> callback.onUsbDisconnected()
        }
    }

    /**
     * Callback for Usb disconnect state
     */
    interface UsbStateCallback {

        /**
         * Called when USB is disconnected
         */
        fun onUsbDisconnected()
    }
}

================================================
FILE: app/src/main/java/com/garage/aastream/services/CarService.kt
================================================
package com.garage.aastream.services

import com.google.android.apps.auto.sdk.CarActivity
import com.google.android.apps.auto.sdk.CarActivityService
import com.garage.aastream.activities.CarMainActivity

/**
 * Created by Endy Rubbin on 23.05.2019 12:25.
 * For project: AAStream
 */
class CarService : CarActivityService() {

    /**
     * Called from Android Auto to start [CarMainActivity]
     */
    @Suppress("UNCHECKED_CAST")
    override fun getCarActivity(): Class<out CarActivity> {
        return CarMainActivity::class.java
    }
}

================================================
FILE: app/src/main/java/com/garage/aastream/shell/ShellExecutor.kt
================================================
package com.garage.aastream.shell

import com.garage.aastream.utils.DevLog
import eu.chainfire.libsuperuser.Shell

/**
 * Created by Endy Rubbin on 28.06.2019 15:35.
 * For project: AAStream
 */
class ShellExecutor(private val command: String): Thread() {
    override fun run() {
        if (Shell.SU.available()) {
            DevLog.d("Executing shell command: $command")
            Shell.Pool.SU.run(command)
            DevLog.d("Executed shell command: $command")
        }
    }
}

================================================
FILE: app/src/main/java/com/garage/aastream/utils/Const.kt
================================================
package com.garage.aastream.utils

/**
 * Created by Endy Rubbin on 22.05.2019 14:36.
 * For project: AAStream
 */
object Const {
    const val PREFERENCES_NAME = "AAStreamPrefs"
    const val MAX_VALUE = 100
    const val DEFAULT_ANIMATION_DELAY = 300L
    const val DEFAULT_ANIMATION_DURATION = 300L
    const val DEFAULT_SCREEN = 1
    const val DEFAULT_TAP_METHOD = 0
    const val DEFAULT_SHOW_SIDEBAR = true
    const val REQUEST_MEDIA_PROJECTION_PERMISSION = 1337
    const val CLICK_INTERVAL = 300
    const val DEBUG_CLICK_COUNT = 10
}

================================================
FILE: app/src/main/java/com/garage/aastream/utils/DevLog.kt
================================================
package com.garage.aastream.utils
import android.util.Log
import com.garage.aastream.interfaces.OnLogCallback

import java.lang.reflect.Array

/**
 * Created by Endy Rubbin on 22.05.2019 13:11.
 * For project: AAStream
 */
@Suppress("unused")
object DevLog {

    private var DEBUG = false
    private var TAG = "DevLog"

    private var callback: OnLogCallback? = null

    /**
     * Init logger with default TAG
     */
    fun init() {
        DEBUG = true
    }

    /**
     * Init logger with TAG and set enabled / disabled
     * @param tag   String Log TAG
     * @param debug boolean logging enabled / disabled
     */
    fun init(tag: String, debug: Boolean = true) {
        DEBUG = debug
        TAG = tag
    }

    /**
     * Set callback for log events
     */
    fun setCallback(callback: OnLogCallback) {
        this.callback = callback
    }

    /**
     * Remove callback for log events
     */
    fun removeCallback() {
        this.callback = null
    }

    /**
     * Logger - outputs log messages and delivers callback if set
     */
    fun d(vararg msg: Any?) {
        if (DEBUG) {
            val stackTraces = Thread.currentThread().stackTrace
            val stackTraceElement = stackTraces[3]
            val lineNumber = stackTraceElement.lineNumber.toString()
            var className = getClassName(stackTraceElement.className)
            val extension = getClassName(stackTraceElement.fileName)
            className =
                if (className.contains("$")) className.split("\\$".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[0] else className
            val out = StringBuilder("($className.$extension:$lineNumber): ")
            for (o in msg) {
                if (o == null) {
                    out.append("NULL, ")
                } else {
                    if (o.javaClass.isArray) {
                        out.append("[")
                        for (i in 0 until Array.getLength(o)) {
                            out.append(Array.get(o, i)).append(", ")
                        }
                        out.append("], ")
                    } else {
                        out.append(o.toString()).append(", ")
                    }
                }
            }
            val log = out.substring(0, out.length - 2)
            callback?.onLogWritten(log)
            Log.d(TAG, log)
        }
    }

    /**
     * Returns current class name
     * @param className String
     * @return String
     */
    private fun getClassName(className: String): String {
        val parts = className.split("\\.".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
        return parts[parts.size - 1]
    }
}

================================================
FILE: app/src/main/java/com/garage/aastream/utils/PhenotypePatcher.kt
================================================
package com.garage.aastream.utils

import android.content.Context
import com.garage.aastream.R
import com.garage.aastream.interfaces.OnPatchStatusCallback
import java.io.*

/**
 * Created by Endy Rubbin on 26.05.2019 22:52.
 * For project: AAStream
 *
 * Reference and credit: @see <a href="https://github.com/Eselter/AA-Phenotype-Patcher">AA-Phenotype-Patcher</a>
 */
class PhenotypePatcher(val context: Context) {

    private val path: String = context.applicationInfo.dataDir

    /**
     * Runs DB injections to whitelist AA Stream application for Android Auto
     */
    fun patch(callback: OnPatchStatusCallback) {
        object : Thread() {
            override fun run() {
                var suitableMethodFound = true
                copyAssets()

                // Preserve already whitelisted apps and append AA Stream if not already whitelisted
                var whiteList = runSuWithCmd("$path/sqlite3 /data/data/com.google.android.gms/databases/phenotype.db " +
                        "'SELECT stringVal FROM Flags WHERE packageName=\"com.google.android.gms.car#car\" LIMIT 1'"
                ).getInputStreamLog()
                if (!whiteList.contains(context.applicationInfo.packageName)) {
                    whiteList += ",${context.applicationInfo.packageName}"
                }

                DevLog.d("Whitelisting apps: $whiteList")
                DevLog.d(
                    runSuWithCmd(
                        "$path/sqlite3 /data/data/com.google.android.gms/databases/phenotype.db " +
                                "'DROP TRIGGER after_delete;'"
                    ).getStreamLogsWithLabels()
                )
                DevLog.d(
                    runSuWithCmd(
                        "$path/sqlite3 /data/data/com.google.android.gms/databases/phenotype.db " +
                                "'DELETE FROM Flags WHERE name=\"app_white_list\";'"
                    ).getStreamLogsWithLabels()
                )

                when {
                    runSuWithCmd(
                        "$path/sqlite3 /data/data/com.google.android.gms/databases/phenotype.db " +
                                "'SELECT 1 FROM Packages WHERE packageName=\"com.google.android.gms.car#car\"'"
                    ).getInputStreamLog() == "1" -> {
                        DevLog.d(
                            runSuWithCmd(
                                ("$path/sqlite3 /data/data/com.google.android.gms/databases/phenotype.db " +
                                        "'INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car\", " +
                                        "234, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car\", " +
                                        "230, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car\", " +
                                        "234, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car\", " +
                                        "230, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car_setup\", " +
                                        "234, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car_setup\", " +
                                        "230, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car\", " +
                                        "(SELECT version FROM Packages WHERE packageName=\"com.google.android.gms.car#car\"), " +
                                        "0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car\", " +
                                        "(SELECT version FROM Packages WHERE packageName=\"com.google.android.gms.car\"), " +
                                        "0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car_setup\", " +
                                        "(SELECT version FROM Packages WHERE packageName=\"com.google.android.gms.car#car\"), " +
                                        "0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);'")
                            ).getStreamLogsWithLabels()
                        )

                        DevLog.d(
                            runSuWithCmd(
                                ("$path/sqlite3 /data/data/com.google.android.gms/databases/phenotype.db " +
                                        "'CREATE TRIGGER after_delete AFTER DELETE\n" +
                                        "ON Flags\n" +
                                        "BEGIN\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car\", " +
                                        "(SELECT version FROM Packages WHERE packageName=\"com.google.android.gms.car#car\"), " +
                                        "0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car\", " +
                                        "230, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car\", " +
                                        "234, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car\", " +
                                        "(SELECT version FROM Packages WHERE packageName=\"com.google.android.gms.car\"), " +
                                        "0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car\", " +
                                        "230, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car\", " +
                                        "234, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car_setup\", " +
                                        "(SELECT version FROM Packages WHERE packageName=\"com.google.android.gms.car#car\"), " +
                                        "0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car_setup\", " +
                                        "230, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car_setup\", " +
                                        "234, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "END;'")
                            ).getStreamLogsWithLabels()
                        )
                    }
                    runSuWithCmd(
                        "$path/sqlite3 /data/data/com.google.android.gms/databases/phenotype.db " +
                                "'SELECT 1 FROM Packages WHERE packageName=\"com.google.android.gms.car\"'"
                    ).getInputStreamLog() == "1" -> {
                        DevLog.d(
                            runSuWithCmd(
                                ("$path/sqlite3 /data/data/com.google.android.gms/databases/phenotype.db " +
                                        "'INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car\", " +
                                        "234, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car\", " +
                                        "230, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car\", " +
                                        "234, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car\", " +
                                        "230, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car_setup\", " +
                                        "234, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car_setup\", " +
                                        "230, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car\", " +
                                        "(SELECT version FROM Packages WHERE packageName=\"com.google.android.gms.car\"), " +
                                        "0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car\", " +
                                        "(SELECT version FROM Packages WHERE packageName=\"com.google.android.gms.car\"), " +
                                        "0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car_setup\", " +
                                        "(SELECT version FROM Packages WHERE packageName=\"com.google.android.gms.car\"), " +
                                        "0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);'")
                            ).getStreamLogsWithLabels()
                        )

                        DevLog.d(
                            runSuWithCmd(
                                ("$path/sqlite3 /data/data/com.google.android.gms/databases/phenotype.db " +
                                        "'CREATE TRIGGER after_delete AFTER DELETE\n" +
                                        "ON Flags\n" +
                                        "BEGIN\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car\", " +
                                        "(SELECT version FROM Packages WHERE packageName=\"com.google.android.gms.car\"), " +
                                        "0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car\", " +
                                        "230, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car\", " +
                                        "234, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car\", " +
                                        "(SELECT version FROM Packages WHERE packageName=\"com.google.android.gms.car\"), " +
                                        "0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car\", " +
                                        "230, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car\", " +
                                        "234, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car_setup\", " +
                                        "(SELECT version FROM Packages WHERE packageName=\"com.google.android.gms.car\"), " +
                                        "0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car_setup\", " +
                                        "230, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car_setup\", " +
                                        "234, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "END;'")
                            ).getStreamLogsWithLabels()
                        )
                    }
                    runSuWithCmd(
                        ("$path/sqlite3 /data/data/com.google.android.gms/databases/phenotype.db " +
                                "'SELECT 1 FROM ApplicationStates WHERE packageName=\"com.google.android.gms.car#car\"'")
                    ).getInputStreamLog() == "1" -> {
                        DevLog.d(
                            runSuWithCmd(
                                ("$path/sqlite3 /data/data/com.google.android.gms/databases/phenotype.db " +
                                        "'INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car\", " +
                                        "240, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car\", " +
                                        "(SELECT version FROM ApplicationStates WHERE packageName=\"com.google.android.gms.car#car\"), " +
                                        "0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car\", " +
                                        "240, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car\", " +
                                        "(SELECT version FROM ApplicationStates WHERE packageName=\"com.google.android.gms.car\"), " +
                                        "0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);'")
                            ).getStreamLogsWithLabels()
                        )

                        DevLog.d(
                            runSuWithCmd(
                                ("$path/sqlite3 /data/data/com.google.android.gms/databases/phenotype.db " +
                                        "'CREATE TRIGGER after_delete AFTER DELETE\n" +
                                        "ON Flags\n" +
                                        "BEGIN\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car\", " +
                                        "240, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car\", " +
                                        "(SELECT version FROM ApplicationStates WHERE packageName=\"com.google.android.gms.car#car\"), " +
                                        "0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car\", " +
                                        "240, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car\", " +
                                        "(SELECT version FROM ApplicationStates WHERE packageName=\"com.google.android.gms.car\"), " +
                                        "0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "END;'")
                            ).getStreamLogsWithLabels()
                        )
                    }
                    runSuWithCmd(
                        ("$path/sqlite3 /data/data/com.google.android.gms/databases/phenotype.db " +
                                "'SELECT 1 FROM ApplicationStates WHERE packageName=\"com.google.android.gms.car\"'")
                    ).getInputStreamLog() == "1" -> {
                        DevLog.d(
                            runSuWithCmd(
                                ("$path/sqlite3 /data/data/com.google.android.gms/databases/phenotype.db " +
                                        "'INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car\", " +
                                        "240, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car\", " +
                                        "(SELECT version FROM ApplicationStates WHERE packageName=\"com.google.android.gms.car\"), " +
                                        "0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);'")
                            ).getStreamLogsWithLabels()
                        )

                        DevLog.d(
                            runSuWithCmd(
                                ("$path/sqlite3 /data/data/com.google.android.gms/databases/phenotype.db " +
                                        "'CREATE TRIGGER after_delete AFTER DELETE\n" +
                                        "ON Flags\n" +
                                        "BEGIN\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car\", " +
                                        "240, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
                                        "user, name, stringVal, committed) VALUES (\"com.google.android.gms.car\", " +
                                        "(SELECT version FROM ApplicationStates WHERE packageName=\"com.google.android.gms.car\"), " +
                                        "0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
                                        "END;'")
                            ).getStreamLogsWithLabels()
                        )
                    }
                    else -> suitableMethodFound = false
                }

                if (suitableMethodFound && isPatched()) {
                    callback.onPatchSuccessful()
                } else {
                    callback.onPatchFailed()
                }
            }
        }.start()
    }

    /**
     * Check if application is whitelisted
     */
    fun isPatched(): Boolean {
        val suAvailable = try {
            Runtime.getRuntime().exec("su")
            true
        } catch (e: java.lang.Exception) {
            false
        }

        val checkStep1 =
            runSuWithCmd(
                ("$path/sqlite3 /data/data/com.google.android.gms/databases/phenotype.db " +
                        "'SELECT * FROM Flags WHERE name=\"app_white_list\";'")
            )
        val checkStep1Sorted = checkStep1.getInputStreamLog().split("\n")
        checkStep1Sorted.sortedBy { it }

        var checkStep1SortedToString = ""
        for (s in checkStep1Sorted) {
            checkStep1SortedToString += "\n" + s
        }
        checkStep1SortedToString.replaceFirst(("\n").toRegex(), "")
        checkStep1.setInputStreamLog(checkStep1SortedToString)

        runSuWithCmd(
            "$path/sqlite3 /data/data/com.google.android.gms/databases/phenotype.db " +
                    "'DELETE FROM Flags WHERE name=\"app_white_list\";'"
        )

        val checkStep3 =
            runSuWithCmd(
                "$path/sqlite3 /data/data/com.google.android.gms/databases/phenotype.db " +
                        "'SELECT * FROM Flags WHERE name=\"app_white_list\";'"
            )
        val checkStep3Sorted = checkStep3.getInputStreamLog().split("\n")
        checkStep3Sorted.sortedBy { it }

        var checkStep3SortedToString = ""
        for (s in checkStep3Sorted) {
            checkStep3SortedToString += "\n" + s
        }
        checkStep3SortedToString.replaceFirst(("\n").toRegex(), "")
        checkStep3.setInputStreamLog(checkStep3SortedToString)

        return suAvailable && checkStep1.getInputStreamLog().isNotEmpty() && checkStep3.getInputStreamLog().isNotEmpty()
                && checkStep1.getInputStreamLog().length == checkStep3.getInputStreamLog().length
    }

    /**
     * Execute SU cmd command and handle input/output streams
     */
    fun runSuWithCmd(cmd: String): StreamLogs {
        val outputStream: DataOutputStream?
        val inputStream: InputStream?
        val errorStream: InputStream?
        val streamLogs = StreamLogs()
        streamLogs.setOutputStreamLog(cmd)

        try {
            val su = Runtime.getRuntime().exec("su")
            outputStream = DataOutputStream(su.outputStream)
            inputStream = su.inputStream
            errorStream = su.errorStream
            outputStream.writeBytes(cmd + "\n")
            outputStream.flush()

            outputStream.writeBytes("exit\n")
            outputStream.flush()

            try {
                su.waitFor()
            } catch (e: InterruptedException) {
                e.printStackTrace()
            }

            streamLogs.setInputStreamLog(readFully(inputStream))
            streamLogs.setErrorStreamLog(readFully(errorStream))
        } catch (e: IOException) {
            e.printStackTrace()
        }
        return streamLogs
    }

    /**
     * Initialize SQLite3 DB
     */
    private fun copyAssets() {
        val path = context.applicationInfo.dataDir
        val file = File(path, "sqlite3")
        if (!file.exists()) {
            try {
                val input = context.resources.openRawResource(R.raw.sqlite3)
                val outDir = context.applicationInfo.dataDir
                val outFile = File(outDir, "sqlite3")
                val buffer = ByteArray(1024)
                var length: Int
                val out = FileOutputStream(outFile)
                while (input.read(buffer).also { length = it } >= 0) {
                    out.write(buffer, 0, length)
                }
                input.close()
                out.flush()
                out.close()
            } catch (e: IOException) {
                DevLog.d("Failed to copy asset file: sqlite3", e)
            }

        }
        DevLog.d(runSuWithCmd("chmod 775 $path/sqlite3").getStreamLogsWithLabels())
    }

    /**
     * @return String value of cmd input
     */
    private fun readFully(input: InputStream): String {
        return try {
            val output = ByteArrayOutputStream()
            val buffer = ByteArray(1024)
            var length: Int
            while (input.read(buffer).also { length = it } >= 0) {
                output.write(buffer, 0, length)
            }
            output.toString("UTF-8")
        } catch (e: Exception) {
            DevLog.d("Failed to read fully")
            ""
        }
    }

    /**
     * Wrapper class for CMD streams
     */
    inner class StreamLogs {
        private var inputStreamLog: String? = null
        private var errorStreamLog: String? = null
        private var outputStreamLog: String? = null

        fun getInputStreamLog(): String {
            return inputStreamLog?.trim() ?: ""
        }

        private fun getErrorStreamLog(): String {
            return errorStreamLog?.trim() ?: ""
        }

        private fun getOutputStreamLog(): String {
            return outputStreamLog?.trim() ?: ""
        }

        fun setInputStreamLog(inputStreamLog: String) {
            this.inputStreamLog = inputStreamLog
        }

        fun setErrorStreamLog(errorStreamLog: String) {
            this.errorStreamLog = errorStreamLog
        }

        fun setOutputStreamLog(outputStreamLog: String) {
            this.outputStreamLog = outputStreamLog
        }

        private fun getInputStreamLogWithLabel(): String {
            return "\tInputStream:\n\t\t" + getInputStreamLog().replace("\n".toRegex(), "\n\t\t")
        }

        private fun getErrorStreamLogWithLabel(): String {
            return "\tErrorStream:\n\t\t" + getErrorStreamLog().replace("\n".toRegex(), "\n\t\t")
        }

        private fun getOutputStreamLogWithLabel(): String {
            return "\tOutputStream:\n\t\t" + getOutputStreamLog().replace("\n".toRegex(), "\n\t\t")
        }

        fun getStreamLogsWithLabels(): String {
            var result = "\n" + getOutputStreamLogWithLabel()

            if (getInputStreamLog().isNotEmpty()) {
                result += "\n" + getInputStreamLogWithLabel()
            }

            if (getErrorStreamLog().isNotEmpty()) {
                result += "\n" + getErrorStreamLogWithLabel()
            }

            return result
        }
    }
}

================================================
FILE: app/src/main/java/com/garage/aastream/views/AutoFitRecyclerView.kt
================================================
package com.garage.aastream.views

import android.content.Context
import android.util.AttributeSet
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView

/**
 * Created by Endy Rubbin on 23.05.2019 14:40.
 * For project: AAStream
 */
class AutoFitRecyclerView : RecyclerView {

    private var manager: GridLayoutManager? = null
    private var columnWidth = -1

    constructor(context: Context) : super(context) {
        init(context, null)
    }

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
        init(context, attrs)
    }

    constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) {
        init(context, attrs)
    }

    private fun init(context: Context, attrs: AttributeSet?) {
        if (attrs != null) {
            val attrsArray = intArrayOf(android.R.attr.columnWidth)
            val array = context.obtainStyledAttributes(attrs, attrsArray)
            columnWidth = array.getDimensionPixelSize(0, -1)
            array.recycle()
        }
        manager = GridLayoutManager(getContext(), 1)
        layoutManager = manager
    }

    override fun onMeasure(widthSpec: Int, heightSpec: Int) {
        super.onMeasure(widthSpec, heightSpec)
        if (columnWidth > 0) {
            val spanCount = Math.max(1, measuredWidth / columnWidth)
            manager?.spanCount = spanCount
        }
    }
}

================================================
FILE: app/src/main/java/com/garage/aastream/views/FingerTapDetector.kt
================================================
package com.garage.aastream.views

import android.content.Context
import android.util.ArrayMap
import android.view.MotionEvent
import android.view.MotionEvent.*
import android.view.ViewConfiguration
import com.garage.aastream.handlers.PreferenceHandler
import com.garage.aastream.interfaces.OnMenuTapCallback
import com.garage.aastream.utils.Const

/**
 * Created by Endy Rubbin on 27.05.2019 13:34.
 * For project: AAStream
 */
class FingerTapDetector(
    context: Context,
    preferenceHandler: PreferenceHandler,
    callback: OnMenuTapCallback
) {

    private var callback: OnMenuTapCallback? = null
    private var eventMap: ArrayMap<Int, PointerCoords> = ArrayMap()
    private var touchSlopSquare: Int = 0
    private var previousTime: Long = 0
    private var tapCount = 0
    private val openMethod = preferenceHandler.getInt(PreferenceHandler.KEY_OPEN_MENU_METHOD, MenuOpenMethod.TWO_FINGER_TAP.value)
    private val minTapCount = when (openMethod) {
        MenuOpenMethod.DOUBLE_TAP.value -> 2
        MenuOpenMethod.TRIPLE_TAP.value -> 3
        else -> 0
    }

    init {
        this.callback = callback
        val configuration = ViewConfiguration.get(context)
        val touchSlop = configuration.scaledTouchSlop
        touchSlopSquare = touchSlop * touchSlop
    }

    fun removeCallback() {
        this.callback = null
    }

    fun onTouchEvent(event: MotionEvent): Boolean {
        val action = event.actionMasked
        for (i in 0 until event.pointerCount) {
            val id = event.getPointerId(i)
            val coords = PointerCoords()

            when (action) {
                ACTION_DOWN, ACTION_POINTER_DOWN -> {
                    event.getPointerCoords(i, coords)
                    eventMap[id] = coords
                }
                ACTION_MOVE -> if (eventMap.containsKey(id)) {
                    event.getPointerCoords(i, coords)
                    eventMap[id]?.let {
                        val dist = getSquaredDistance(it, coords)
                        if (dist > touchSlopSquare) {
                            eventMap.remove(id)
                        }
                    }

                }
                ACTION_UP -> {
                    if (openMethod == MenuOpenMethod.TWO_FINGER_TAP.value && eventMap.size == 2) {
                        callback?.onTapForMenu()
                    }
                    eventMap.clear()
                }
                ACTION_CANCEL -> eventMap.remove(id)
            }
        }

        if (event.action == ACTION_UP && minTapCount > 0) {
            val currentTime = System.currentTimeMillis()
            if (currentTime - previousTime <= Const.CLICK_INTERVAL) {
                tapCount++
            } else {
                tapCount = 0
            }
            previousTime = currentTime
            if (tapCount >= minTapCount) {
                tapCount = 0
                callback?.onTapForMenu()
            }
        }
        return false
    }

    /**
     * Calculate distance between taps
     */
    private fun getSquaredDistance(p1: PointerCoords, p2: PointerCoords): Double {
        val dx = (p1.x - p2.x).toDouble()
        val dy = (p1.y - p2.y).toDouble()
        return dx * dx + dy * dy
    }

    companion object {
        enum class MenuOpenMethod(val value: Int) {
            TWO_FINGER_TAP(0),
            DOUBLE_TAP(1),
            TRIPLE_TAP(2)
        }
    }
}

================================================
FILE: app/src/main/java/com/garage/aastream/views/MarginDecoration.kt
================================================
package com.garage.aastream.views

import android.content.Context
import android.graphics.Rect
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import com.garage.aastream.R

/**
 * Created by Endy Rubbin on 23.05.2019 14:49.
 * For project: AAStream
 */
class MarginDecoration(context: Context) : RecyclerView.ItemDecoration() {

    private var margin = context.resources.getDimensionPixelSize(R.dimen.item_margin)

    override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
        outRect.set(0, margin, margin, margin)
    }
}

================================================
FILE: app/src/main/res/drawable-anydpi/bg.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
    android:src="@drawable/bg_tile"
    android:tileMode="repeat" />

================================================
FILE: app/src/main/res/drawable-anydpi/bg_input.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <corners android:radius="5dp" />
    <stroke android:color="@color/color_input_border" android:width="2dp" />
    <solid android:color="@color/color_input_background" />
</shape>

================================================
FILE: app/src/main/res/drawable-anydpi/ic_apps.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="24dp"
        android:height="24dp"
        android:viewportWidth="24.0"
        android:viewportHeight="24.0">
    <path
        android:fillColor="#FFF"
        android:pathData="M4,8h4L8,4L4,4v4zM10,20h4v-4h-4v4zM4,20h4v-4L4,16v4zM4,14h4v-4L4,10v4zM10,14h4v-4h-4v4zM16,
        4v4h4L20,4h-4zM10,8h4L14,4h-4v4zM16,14h4v-4h-4v4zM16,20h4v-4h-4v4z"/>
</vector>


================================================
FILE: app/src/main/res/drawable-anydpi/ic_back.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="24dp"
        android:height="24dp"
        android:viewportWidth="24.0"
        android:viewportHeight="24.0">
    <path
        android:fillColor="#FFF"
        android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
</vector>


================================================
FILE: app/src/main/res/drawable-anydpi/ic_bookmark.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="24dp"
        android:height="24dp"
        android:viewportWidth="24.0"
        android:viewportHeight="24.0">
    <path
        android:fillColor="#FFC107"
        android:pathData="M17,3H7c-1.1,0 -1.99,0.9 -1.99,2L5,21l7,-3 7,3V5c0,-1.1 -0.9,-2 -2,-2z"/>
</vector>


================================================
FILE: app/src/main/res/drawable-anydpi/ic_bookmark_border.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="24dp"
        android:height="24dp"
        android:viewportWidth="24.0"
        android:viewportHeight="24.0">
    <path
        android:fillColor="#FFF"
        android:pathData="M17,3L7,3c-1.1,0 -1.99,0.9 -1.99,2L5,21l7,-3 7,3L19,5c0,-1.1 -0.9,-2 -2,-2zM17,18l-5,-2.18L7,
        18L7,5h10v13z"/>
</vector>


================================================
FILE: app/src/main/res/drawable-anydpi/ic_check.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="24dp"
        android:height="24dp"
        android:viewportWidth="24.0"
        android:viewportHeight="24.0">
    <path
        android:fillColor="#FF000000"
        android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
</vector>


================================================
FILE: app/src/main/res/drawable-anydpi/ic_close.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="24dp"
        android:height="24dp"
        android:viewportWidth="24.0"
        android:viewportHeight="24.0">
    <path
        android:fillColor="#FFF"
        android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59
        13.41,12z"/>
</vector>


================================================
FILE: app/src/main/res/drawable-anydpi/ic_terminal.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="24dp"
        android:height="24dp"
        android:viewportWidth="24.0"
        android:viewportHeight="24.0">
    <path
        android:fillColor="#FFF"
        android:pathData="M13,7h-2v2h2L13,7zM13,11h-2v6h2v-6zM17,1.01L7,1c-1.1,0 -2,0.9 -2,2v18c0,1.1 0.9,2 2,2h10c1.1,
        0 2,-0.9 2,-2L19,3c0,-1.1 -0.9,-1.99 -2,-1.99zM17,19L7,19L7,5h10v14z"/>
</vector>


================================================
FILE: app/src/main/res/layout/activity_car.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".activities.CarMainActivity">

    <!-- Used for mirroring device screen -->
    <SurfaceView
            android:id="@+id/car_surface_view"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"/>

    <!-- Menu holder -->
    <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent" >

        <!-- App list -->
        <com.garage.aastream.views.AutoFitRecyclerView
                android:id="@+id/car_app_grid"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:columnWidth="@dimen/item_size"
                android:background="@color/color_overlay_background"
                android:visibility="gone"
                app:spanCount="1"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toEndOf="@id/car_menu_holder"
                app:layout_constraintEnd_toEndOf="parent"
                tools:visibility="visible"
                tools:listitem="@layout/row_app_item">

        </com.garage.aastream.views.AutoFitRecyclerView>

        <ProgressBar
                android:id="@+id/car_app_grid_loader"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:visibility="gone"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toEndOf="@id/car_menu_holder"
                app:layout_constraintEnd_toEndOf="parent"/>

        <TextView
                android:id="@+id/car_app_favorite_empty"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:text="@string/txt_favorite_list_empty"
                style="@style/FavoriteTextStyle"
                android:visibility="gone"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toEndOf="@id/car_menu_holder"
                app:layout_constraintEnd_toEndOf="parent"/>

        <!-- Debug view -->
        <androidx.constraintlayout.widget.ConstraintLayout
                android:layout_width="0dp"
                android:layout_height="0dp"
                android:visibility="gone"
                android:id="@+id/view_car_terminal"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toEndOf="@id/car_menu_holder"
                app:layout_constraintEnd_toEndOf="parent">

            <include layout="@layout/view_car_terminal"/>

        </androidx.constraintlayout.widget.ConstraintLayout>

        <!-- Menu view -->
        <ScrollView
                android:id="@+id/car_menu_holder"
                android:layout_width="wrap_content"
                android:layout_height="0dp"
                android:background="@color/color_menu_background"
                android:alpha="0"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                tools:alpha="1">

            <androidx.constraintlayout.widget.ConstraintLayout
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:padding="8dp">

                <ImageView
                        style="@style/IconStyle"
                        android:id="@+id/car_menu_close"
                        android:src="@drawable/ic_close"
                        android:background="?android:attr/selectableItemBackground"
                        app:layout_constraintEnd_toEndOf="parent"
                        app:layout_constraintTop_toTopOf="parent"
                        app:layout_constraintStart_toStartOf="parent"
                        app:layout_constraintBottom_toTopOf="@id/car_menu_back"
                        tools:ignore="ContentDescription"/>

                <ImageView
                        style="@style/IconStyle"
                        android:id="@+id/car_menu_back"
                        android:src="@drawable/ic_back"
                        android:background="?android:attr/selectableItemBackground"
                        app:layout_constraintEnd_toEndOf="parent"
                        app:layout_constraintTop_toBottomOf="@id/car_menu_close"
                        app:layout_constraintStart_toStartOf="parent"
                        app:layout_constraintBottom_toTopOf="@id/car_menu_app_list"
                        tools:ignore="ContentDescription"/>

                <ImageView
                        style="@style/IconStyle"
                        android:id="@+id/car_menu_app_list"
                        android:src="@drawable/ic_apps"
                        android:background="?android:attr/selectableItemBackground"
                        app:layout_constraintEnd_toEndOf="parent"
                        app:layout_constraintTop_toBottomOf="@id/car_menu_back"
                        app:layout_constraintBottom_toTopOf="@id/car_menu_favorites"
                        app:layout_constraintStart_toStartOf="parent"
                        tools:ignore="ContentDescription"/>

                <ImageView
                        style="@style/IconStyle"
                        android:id="@+id/car_menu_favorites"
                        android:src="@drawable/ic_bookmark_border"
                        android:background="?android:attr/selectableItemBackground"
                        app:layout_constraintEnd_toEndOf="parent"
                        app:layout_constraintTop_toBottomOf="@id/car_menu_app_list"
                        app:layout_constraintBottom_toTopOf="@id/car_menu_terminal"
                        app:layout_constraintStart_toStartOf="parent"
                        tools:ignore="ContentDescription"/>

                <ImageView
                        style="@style/IconStyle"
                        android:id="@+id/car_menu_terminal"
                        android:src="@drawable/ic_terminal"
                        android:background="?android:attr/selectableItemBackground"
                        android:visibility="gone"
                        app:layout_constraintEnd_toEndOf="parent"
                        app:layout_constraintTop_toBottomOf="@id/car_menu_favorites"
                        app:layout_constraintBottom_toBottomOf="parent"
                        app:layout_constraintStart_toStartOf="parent"
                        tools:ignore="ContentDescription"
                        tools:visibility="visible"/>
            </androidx.constraintlayout.widget.ConstraintLayout>
        </ScrollView>
    </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

================================================
FILE: app/src/main/res/layout/activity_request_result.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:background="@color/color_overlay_background"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

</androidx.constraintlayout.widget.ConstraintLayout>

================================================
FILE: app/src/main/res/layout/activity_settings.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/settings_root"
        android:background="@drawable/bg"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".activities.SettingsActivity">

    <LinearLayout
            android:orientation="vertical"
            android:paddingBottom="16dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

        <!-- Unlock app control -->
        <include layout="@layout/view_settings_unlock"
                 android:id="@+id/view_settings_unlock" />

        <View style="@style/DividerStyle"/>

        <!-- Brightness control -->
        <include layout="@layout/view_settings_brightness"
                 android:id="@+id/view_settings_brightness" />

        <View style="@style/DividerStyle"/>

        <!-- Rotation control -->
        <include layout="@layout/view_settings_rotation"
                 android:id="@+id/view_settings_rotation" />

        <View style="@style/DividerStyle"/>

        <!-- Resize control -->
        <include layout="@layout/view_settings_resize"
                 android:id="@+id/view_settings_resize" />

        <View style="@style/DividerStyle"/>

        <!-- Immersive control -->
        <include layout="@layout/view_settings_immersive"
                 android:id="@+id/view_settings_immersive" />

        <View style="@style/DividerStyle"/>

        <!-- Audio control -->
        <include layout="@layout/view_settings_audio"
                 android:id="@+id/view_settings_audio" />

        <View style="@style/DividerStyle"/>

        <!-- Sidebar control -->
        <include layout="@layout/view_settings_sidebar"
                 android:id="@+id/view_settings_sidebar" />

        <View style="@style/DividerStyle"/>

        <!-- Debug car app control -->
        <include layout="@layout/view_settings_debug"
                 android:visibility="gone"
                 android:id="@+id/view_settings_debug"
                 tools:visibility="visible"/>

        <!-- About -->
        <include layout="@layout/view_settings_about"
                 android:id="@+id/view_settings_about" />

    </LinearLayout>
</ScrollView>

================================================
FILE: app/src/main/res/layout/row_app_item.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="@dimen/item_size"
        android:layout_height="@dimen/item_size"
        android:alpha="0"
        android:background="?android:attr/selectableItemBackground"
        tools:alpha="1">

    <ImageView
            android:id="@+id/item_app_icon"
            style="@style/AppIconStyle"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toTopOf="@id/item_app_name"
            app:layout_constraintTop_toTopOf="parent"
            tools:src="@android:drawable/sym_def_app_icon"
            tools:ignore="ContentDescription"/>

    <RelativeLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:paddingTop="2dp"
            android:paddingEnd="8dp"
            android:paddingStart="16dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent">

        <ImageView
                android:id="@+id/item_app_favorite"
                style="@style/BookmarkStyle"
                android:src="@drawable/ic_bookmark"
                android:layout_alignParentEnd="true"
                tools:ignore="ContentDescription"
                tools:visibility="visible"/>
    </RelativeLayout>

    <TextView
            android:id="@+id/item_app_name"
            style="@style/AppTextStyle"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            tools:text="@string/app_name"
            app:layout_constraintTop_toBottomOf="@id/item_app_icon"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

================================================
FILE: app/src/main/res/layout/view_car_terminal.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/color_overlay_background"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto">

    <!-- Console output -->
    <ScrollView
            android:id="@+id/terminal_scroller"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toTopOf="@id/terminal_footer">

        <TextView
                android:id="@+id/terminal_console"
                tools:text="Console log\nline1\nline2"
                android:padding="16dp"
                android:textSize="12sp"
                android:textColor="@color/color_terminal"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />
    </ScrollView>

    <androidx.constraintlayout.widget.ConstraintLayout
            android:id="@+id/terminal_footer"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="#333"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent">

        <View
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:background="@color/color_divider"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintBottom_toTopOf="@id/terminal_input_holder"/>

        <androidx.constraintlayout.widget.ConstraintLayout
                android:id="@+id/terminal_input_holder"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:paddingTop="8dp"
                android:paddingBottom="8dp"
                android:visibility="gone"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                tools:visibility="visible">

            <EditText
                    android:padding="8dp"
                    android:textSize="16sp"
                    android:maxLines="6"
                    android:layout_marginStart="16dp"
                    android:layout_marginEnd="8dp"
                    android:fontFamily="sans-serif"
                    android:hint="@string/txt_enter_a_command"
                    android:textColorHint="@color/color_terminal_hint"
                    android:textColor="@color/color_terminal_text"
                    android:id="@+id/terminal_input"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:inputType="textCapSentences|textMultiLine"
                    android:imeOptions="actionDone"
                    android:background="@drawable/bg_input"
                    app:layout_constraintTop_toTopOf="parent"
                    app:layout_constraintBottom_toBottomOf="parent"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintEnd_toStartOf="@+id/terminal_input_button"
                    app:layout_constraintHorizontal_chainStyle="spread_inside"
                    tools:ignore="Autofill" />

            <ImageView
                    android:id="@+id/terminal_input_button"
                    android:layout_width="24dp"
                    android:layout_height="24dp"
                    android:layout_marginEnd="8dp"
                    android:src="@drawable/ic_check"
                    android:tint="@color/color_terminal_icon"
                    android:foreground="?android:attr/selectableItemBackground"
                    tools:ignore="ContentDescription,UnusedAttribute"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintBottom_toBottomOf="parent"
                    app:layout_constraintTop_toTopOf="parent"/>
        </androidx.constraintlayout.widget.ConstraintLayout>
    </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

================================================
FILE: app/src/main/res/layout/view_settings_about.xml
================================================
<?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"
        android:padding="16dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

    <LinearLayout
            android:id="@+id/settings_about_holder"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:layout_marginEnd="16dp"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent">

        <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/txt_about_title"
                style="@style/TitleTextStyle"/>

        <TextView
                android:id="@+id/settings_about"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/txt_about"
                android:drawableStart="@mipmap/ic_launcher_round"
                android:drawablePadding="8dp"
                style="@style/AboutTextStyle"/>

        <TextView
                android:id="@+id/settings_about_usage"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:autoLink="web"
                android:text="@string/txt_about_use"
                style="@style/AboutTextStyle"/>

        <TextView
                android:id="@+id/settings_about_version"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/txt_version"
                style="@style/AboutTextStyle"/>

        <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:text="@string/txt_endy_rubbin"
                style="@style/AboutTextStyle"/>
    </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

================================================
FILE: app/src/main/res/layout/view_settings_audio.xml
================================================
<?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"
        android:padding="16dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

    <LinearLayout
            android:id="@+id/settings_audio_holder"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintEnd_toStartOf="@id/settings_audio_switch"
            app:layout_constraintStart_toStartOf="parent">

        <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/txt_force_audio_title"
                style="@style/TitleTextStyle"/>

        <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/txt_force_audio_subtitle"
                style="@style/SubtitleTextStyle"/>

    </LinearLayout>

    <androidx.appcompat.widget.SwitchCompat
            android:id="@+id/settings_audio_switch"
            style="@style/SwitchStyle"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@id/settings_audio_holder"
            app:layout_constraintBottom_toBottomOf="@id/settings_audio_holder"
            app:layout_constraintTop_toTopOf="@id/settings_audio_holder"/>

</androidx.constraintlayout.widget.ConstraintLayout>

================================================
FILE: app/src/main/res/layout/view_settings_brightness.xml
================================================
<?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"
        android:padding="16dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

    <LinearLayout
            android:id="@+id/settings_brightness_holder"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintEnd_toStartOf="@id/settings_brightness_switch"
            app:layout_constraintStart_toStartOf="parent">

        <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/txt_overwrite_screen_brightness_title"
                style="@style/TitleTextStyle"/>

        <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/txt_overwrite_screen_brightness_subtitle"
                style="@style/SubtitleTextStyle"/>

    </LinearLayout>

    <androidx.appcompat.widget.SwitchCompat
            android:id="@+id/settings_brightness_switch"
            style="@style/SwitchStyle"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@id/settings_brightness_holder"
            app:layout_constraintBottom_toBottomOf="@id/settings_brightness_holder"
            app:layout_constraintTop_toTopOf="@id/settings_brightness_holder"/>

    <androidx.appcompat.widget.AppCompatSeekBar
            android:id="@+id/settings_brightness_seek_bar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="-8dp"
            android:layout_marginEnd="-8dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintTop_toBottomOf="@id/settings_brightness_holder" />

</androidx.constraintlayout.widget.ConstraintLayout>

================================================
FILE: app/src/main/res/layout/view_settings_debug.xml
================================================
<?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"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

    <LinearLayout
            android:id="@+id/settings_debug_holder"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:paddingStart="16dp"
            android:paddingEnd="16dp"
            android:paddingTop="16dp"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintEnd_toStartOf="@id/settings_debug_switch"
            app:layout_constraintBottom_toTopOf="@id/settings_debug_switch"
            app:layout_constraintStart_toStartOf="parent">

        <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/txt_disable_developer_mode_title"
                style="@style/TitleTextStyle"/>

        <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/txt_disable_developer_mode_subtitle"
                style="@style/SubtitleTextStyle"/>
    </LinearLayout>

    <androidx.appcompat.widget.SwitchCompat
            android:id="@+id/settings_debug_switch"
            android:layout_marginEnd="16dp"
            style="@style/SwitchStyle"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@id/settings_debug_holder"
            app:layout_constraintBottom_toBottomOf="@id/settings_debug_holder"
            app:layout_constraintTop_toTopOf="@id/settings_debug_holder"/>

    <View style="@style/DividerStyle"
          android:id="@+id/settings_debug_divider"
          app:layout_constraintTop_toBottomOf="@id/settings_debug_holder"
          app:layout_constraintBottom_toTopOf="@+id/settings_debug_activity_holder"
          app:layout_constraintEnd_toEndOf="parent"
          app:layout_constraintStart_toStartOf="parent"/>

    <LinearLayout
            android:id="@+id/settings_debug_activity_holder"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:paddingTop="16dp"
            android:background="?android:attr/selectableItemBackground"
            app:layout_constraintTop_toBottomOf="@id/settings_debug_holder"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent">

        <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginStart="16dp"
                android:layout_marginEnd="16dp"
                android:text="@string/txt_debug_car_activity_title"
                style="@style/TitleTextStyle"/>

        <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginStart="16dp"
                android:layout_marginEnd="16dp"
                android:text="@string/txt_debug_car_activity_subtitle"
                style="@style/SubtitleTextStyle"/>

        <View style="@style/DividerStyle"/>
    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

================================================
FILE: app/src/main/res/layout/view_settings_immersive.xml
================================================
<?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"
        android:padding="16dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

    <LinearLayout
            android:id="@+id/settings_immersive_holder"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintEnd_toStartOf="@id/settings_immersive_switch"
            app:layout_constraintStart_toStartOf="parent">

        <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/txt_force_immersive_title"
                style="@style/TitleTextStyle"/>

        <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/txt_force_immersive_subtitle"
                style="@style/SubtitleTextStyle"/>

    </LinearLayout>

    <androidx.appcompat.widget.SwitchCompat
            android:id="@+id/settings_immersive_switch"
            style="@style/SwitchStyle"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@id/settings_immersive_holder"
            app:layout_constraintBottom_toBottomOf="@id/settings_immersive_holder"
            app:layout_constraintTop_toTopOf="@id/settings_immersive_holder"/>

</androidx.constraintlayout.widget.ConstraintLayout>

================================================
FILE: app/src/main/res/layout/view_settings_resize.xml
================================================
<?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"
        android:padding="16dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

    <LinearLayout
            android:id="@+id/settings_resize_holder"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintEnd_toStartOf="@id/settings_resize_switch"
            app:layout_constraintStart_toStartOf="parent">

        <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/txt_force_resize_title"
                style="@style/TitleTextStyle"/>

        <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/txt_force_resize_subtitle"
                style="@style/SubtitleTextStyle"/>

    </LinearLayout>

    <androidx.appcompat.widget.SwitchCompat
            android:id="@+id/settings_resize_switch"
            style="@style/SwitchStyle"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@id/settings_resize_holder"
            app:layout_constraintBottom_toBottomOf="@id/settings_resize_holder"
            app:layout_constraintTop_toTopOf="@id/settings_resize_holder"/>

</androidx.constraintlayout.widget.ConstraintLayout>

================================================
FILE: app/src/main/res/layout/view_settings_rotation.xml
================================================
<?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"
        android:padding="16dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

    <LinearLayout
            android:id="@+id/settings_rotation_holder"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintEnd_toStartOf="@id/settings_rotation_switch"
            app:layout_constraintStart_toStartOf="parent">

        <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/txt_force_screen_rotation_title"
                style="@style/TitleTextStyle"/>

        <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/txt_force_screen_rotation_subtitle"
                style="@style/SubtitleTextStyle"/>

    </LinearLayout>

    <androidx.appcompat.widget.SwitchCompat
            android:id="@+id/settings_rotation_switch"
            style="@style/SwitchStyle"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@id/settings_rotation_holder"
            app:layout_constraintBottom_toBottomOf="@id/settings_rotation_holder"
            app:layout_constraintTop_toTopOf="@id/settings_rotation_holder"/>

    <androidx.appcompat.widget.AppCompatSpinner
            android:id="@+id/settings_rotation_dropdown"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            style="@style/SpinnerStyle"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintTop_toBottomOf="@id/settings_rotation_holder"/>

</androidx.constraintlayout.widget.ConstraintLayout>

================================================
FILE: app/src/main/res/layout/view_settings_sidebar.xml
================================================
<?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"
        android:padding="16dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

    <LinearLayout
            android:id="@+id/settings_sidebar_holder"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toTopOf="@id/settings_sidebar_dropdown_title"
            app:layout_constraintEnd_toStartOf="@id/settings_sidebar_switch"
            app:layout_constraintStart_toStartOf="parent">

        <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/txt_enable_sidebar_title"
                style="@style/TitleTextStyle"/>

        <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/txt_enable_sidebar_subtitle"
                style="@style/SubtitleTextStyle"/>

    </LinearLayout>

    <androidx.appcompat.widget.SwitchCompat
            android:id="@+id/settings_sidebar_switch"
            style="@style/SwitchStyle"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@id/settings_sidebar_holder"
            app:layout_constraintBottom_toBottomOf="@id/settings_sidebar_holder"
            app:layout_constraintTop_toTopOf="@id/settings_sidebar_holder"/>

    <TextView
            android:id="@+id/settings_sidebar_dropdown_title"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:text="@string/txt_choose_startup_screen"
            style="@style/TitleTextStyle"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toTopOf="@id/settings_sidebar_dropdown"
            app:layout_constraintTop_toBottomOf="@id/settings_sidebar_holder"/>

    <androidx.appcompat.widget.AppCompatSpinner
            android:id="@+id/settings_sidebar_dropdown"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            style="@style/SpinnerStyle"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintBottom_toTopOf="@id/settings_sidebar_dropdown_menu_title"
            app:layout_constraintTop_toBottomOf="@id/settings_sidebar_dropdown_title"/>

    <TextView
            android:id="@+id/settings_sidebar_dropdown_menu_title"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:text="@string/txt_choose_open_sidebar_method"
            style="@style/TitleTextStyle"
            android:layout_marginTop="16dp"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toTopOf="@id/settings_sidebar_dropdown_menu"
            app:layout_constraintTop_toBottomOf="@id/settings_sidebar_dropdown"/>

    <androidx.appcompat.widget.AppCompatSpinner
            android:id="@+id/settings_sidebar_dropdown_menu"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            style="@style/SpinnerStyle"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintTop_toBottomOf="@id/settings_sidebar_dropdown_menu_title"/>

</androidx.constraintlayout.widget.ConstraintLayout>

================================================
FILE: app/src/main/res/layout/view_settings_unlock.xml
================================================
<?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:padding="16dp"
        android:background="?android:attr/selectableItemBackground"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

    <LinearLayout
            android:id="@+id/settings_unlock_holder"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintEnd_toStartOf="@id/settings_unlock_state_holder"
            app:layout_constraintStart_toStartOf="parent">

        <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/txt_unlock_for_android_auto_title"
                style="@style/TitleTextStyle"/>

        <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/txt_unlock_for_android_auto_subtitle"
                style="@style/SubtitleTextStyle"/>
    </LinearLayout>

    <androidx.constraintlayout.widget.ConstraintLayout
            android:id="@+id/settings_unlock_state_holder"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@id/settings_unlock_holder"
            app:layout_constraintBottom_toBottomOf="@id/settings_unlock_holder"
            app:layout_constraintTop_toTopOf="@id/settings_unlock_holder">

        <ProgressBar
                android:id="@+id/settings_unlock_state_spinner"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:visibility="gone"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintBottom_toBottomOf="parent"
                tools:visibility="visible"/>

        <ImageView
                android:id="@+id/settings_unlock_state_icon"
                android:layout_width="48dp"
                android:layout_height="48dp"
                android:src="@drawable/ic_check"
                android:tint="@color/color_unlocked"
                android:visibility="gone"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintBottom_toBottomOf="parent"
                tools:visibility="visible"
                tools:ignore="ContentDescription"/>
    </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

================================================
FILE: app/src/main/res/values/colors.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="color_primary">#607D8B</color>
    <color name="color_primary_dark">#455A64</color>
    <color name="color_accent">#795548</color>
    <color name="color_title">#333</color>
    <color name="color_subtitle">#aaa</color>
    <color name="color_terminal">#fff</color>
    <color name="color_terminal_icon">#dbdbdb</color>
    <color name="color_terminal_text">#333</color>
    <color name="color_terminal_hint">#aaa</color>
    <color name="color_favorite">#fff</color>
    <color name="color_divider">#333</color>
    <color name="color
Download .txt
gitextract_rg_4i2rc/

├── README.md
├── apk_releases/
│   ├── aa-stream-v1.0.6.apk
│   └── aa-stream-v1.1.0.27.apk
├── app/
│   ├── build.gradle
│   ├── libs/
│   │   └── aauto.aar
│   └── src/
│       └── main/
│           ├── AndroidManifest.xml
│           ├── assets/
│           │   └── libs/
│           │       ├── arm64-v8a/
│           │       │   ├── minitouch
│           │       │   └── minitouch-nopie
│           │       ├── armeabi/
│           │       │   ├── minitouch
│           │       │   └── minitouch-nopie
│           │       ├── armeabi-v7a/
│           │       │   ├── minitouch
│           │       │   └── minitouch-nopie
│           │       ├── mips/
│           │       │   ├── minitouch
│           │       │   └── minitouch-nopie
│           │       ├── mips64/
│           │       │   ├── minitouch
│           │       │   └── minitouch-nopie
│           │       ├── x86/
│           │       │   ├── minitouch
│           │       │   └── minitouch-nopie
│           │       └── x86_64/
│           │           ├── minitouch
│           │           └── minitouch-nopie
│           ├── java/
│           │   └── com/
│           │       └── garage/
│           │           └── aastream/
│           │               ├── App.kt
│           │               ├── activities/
│           │               │   ├── CarDebugActivity.kt
│           │               │   ├── CarMainActivity.kt
│           │               │   ├── KillerActivity.kt
│           │               │   ├── ResultRequestActivity.kt
│           │               │   ├── SettingsActivity.kt
│           │               │   └── controllers/
│           │               │       ├── CarActivityController.kt
│           │               │       └── TerminalController.kt
│           │               ├── adapters/
│           │               │   └── AppListAdapter.kt
│           │               ├── handlers/
│           │               │   ├── AppHandler.kt
│           │               │   ├── AudioHandler.kt
│           │               │   ├── BrightnessHandler.kt
│           │               │   ├── DisplayHandler.kt
│           │               │   ├── NotificationHandler.kt
│           │               │   ├── PreferenceHandler.kt
│           │               │   └── RotationHandler.kt
│           │               ├── injection/
│           │               │   ├── GlideModule.kt
│           │               │   ├── InjectionComponent.kt
│           │               │   └── InjectionModule.kt
│           │               ├── interfaces/
│           │               │   ├── OnAppClickedCallback.kt
│           │               │   ├── OnAppListLoadedCallback.kt
│           │               │   ├── OnLogCallback.kt
│           │               │   ├── OnMenuTapCallback.kt
│           │               │   ├── OnMinitouchCallback.kt
│           │               │   ├── OnPatchStatusCallback.kt
│           │               │   ├── OnRotationChangedCallback.kt
│           │               │   └── OnScreenLockCallback.kt
│           │               ├── minitouch/
│           │               │   ├── MiniTouchHandler.kt
│           │               │   ├── MiniTouchSocket.kt
│           │               │   └── MinitouchDaemon.kt
│           │               ├── models/
│           │               │   ├── AppItem.kt
│           │               │   └── AppItemWrapper.kt
│           │               ├── receivers/
│           │               │   ├── ScreenLockReceiver.kt
│           │               │   └── UsbStateReceiver.kt
│           │               ├── services/
│           │               │   └── CarService.kt
│           │               ├── shell/
│           │               │   └── ShellExecutor.kt
│           │               ├── utils/
│           │               │   ├── Const.kt
│           │               │   ├── DevLog.kt
│           │               │   └── PhenotypePatcher.kt
│           │               └── views/
│           │                   ├── AutoFitRecyclerView.kt
│           │                   ├── FingerTapDetector.kt
│           │                   └── MarginDecoration.kt
│           └── res/
│               ├── drawable-anydpi/
│               │   ├── bg.xml
│               │   ├── bg_input.xml
│               │   ├── ic_apps.xml
│               │   ├── ic_back.xml
│               │   ├── ic_bookmark.xml
│               │   ├── ic_bookmark_border.xml
│               │   ├── ic_check.xml
│               │   ├── ic_close.xml
│               │   └── ic_terminal.xml
│               ├── layout/
│               │   ├── activity_car.xml
│               │   ├── activity_request_result.xml
│               │   ├── activity_settings.xml
│               │   ├── row_app_item.xml
│               │   ├── view_car_terminal.xml
│               │   ├── view_settings_about.xml
│               │   ├── view_settings_audio.xml
│               │   ├── view_settings_brightness.xml
│               │   ├── view_settings_debug.xml
│               │   ├── view_settings_immersive.xml
│               │   ├── view_settings_resize.xml
│               │   ├── view_settings_rotation.xml
│               │   ├── view_settings_sidebar.xml
│               │   └── view_settings_unlock.xml
│               ├── raw/
│               │   └── sqlite3
│               ├── values/
│               │   ├── colors.xml
│               │   ├── dimens.xml
│               │   ├── ids.xml
│               │   ├── strings.xml
│               │   └── styles.xml
│               └── xml/
│                   └── automotive_app_desc.xml
├── build.gradle
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
└── settings.gradle
Condensed preview — 97 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (220K chars).
[
  {
    "path": "README.md",
    "chars": 4186,
    "preview": "# AA Stream\n![App Icon](img/logo.png \"App Icon\") \n## About\n**AA Stream** is an unofficial and unsupported device screen "
  },
  {
    "path": "app/build.gradle",
    "chars": 2099,
    "preview": "apply plugin: 'com.android.application'\napply plugin: 'kotlin-android'\napply plugin: 'kotlin-kapt'\napply plugin: 'kotlin"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "chars": 3304,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n          xm"
  },
  {
    "path": "app/src/main/java/com/garage/aastream/App.kt",
    "chars": 1189,
    "preview": "package com.garage.aastream\n\nimport android.app.Application\nimport android.content.res.Configuration\nimport com.garage.a"
  },
  {
    "path": "app/src/main/java/com/garage/aastream/activities/CarDebugActivity.kt",
    "chars": 1490,
    "preview": "package com.garage.aastream.activities\n\nimport android.content.res.Configuration\nimport android.os.Bundle\nimport android"
  },
  {
    "path": "app/src/main/java/com/garage/aastream/activities/CarMainActivity.kt",
    "chars": 1914,
    "preview": "package com.garage.aastream.activities\n\nimport android.content.res.Configuration\nimport android.os.Bundle\nimport com.gar"
  },
  {
    "path": "app/src/main/java/com/garage/aastream/activities/KillerActivity.kt",
    "chars": 1336,
    "preview": "package com.garage.aastream.activities\n\nimport android.os.Bundle\nimport androidx.appcompat.app.AppCompatActivity\nimport "
  },
  {
    "path": "app/src/main/java/com/garage/aastream/activities/ResultRequestActivity.kt",
    "chars": 2137,
    "preview": "package com.garage.aastream.activities\n\nimport android.content.Context\nimport android.content.Intent\nimport android.os.B"
  },
  {
    "path": "app/src/main/java/com/garage/aastream/activities/SettingsActivity.kt",
    "chars": 12218,
    "preview": "package com.garage.aastream.activities\n\nimport android.annotation.TargetApi\nimport android.content.Intent\nimport android"
  },
  {
    "path": "app/src/main/java/com/garage/aastream/activities/controllers/CarActivityController.kt",
    "chars": 24707,
    "preview": "package com.garage.aastream.activities.controllers\n\nimport android.animation.Animator\nimport android.animation.AnimatorL"
  },
  {
    "path": "app/src/main/java/com/garage/aastream/activities/controllers/TerminalController.kt",
    "chars": 1441,
    "preview": "package com.garage.aastream.activities.controllers\n\nimport android.os.Handler\nimport android.os.Looper\nimport android.vi"
  },
  {
    "path": "app/src/main/java/com/garage/aastream/adapters/AppListAdapter.kt",
    "chars": 6153,
    "preview": "package com.garage.aastream.adapters\n\nimport android.content.Context\nimport android.graphics.drawable.Drawable\nimport an"
  },
  {
    "path": "app/src/main/java/com/garage/aastream/handlers/AppHandler.kt",
    "chars": 2186,
    "preview": "package com.garage.aastream.handlers\n\nimport android.content.Context\nimport android.content.pm.ApplicationInfo\nimport an"
  },
  {
    "path": "app/src/main/java/com/garage/aastream/handlers/AudioHandler.kt",
    "chars": 2122,
    "preview": "package com.garage.aastream.handlers\n\nimport android.content.Context\nimport android.media.AudioManager.AUDIOFOCUS_GAIN\ni"
  },
  {
    "path": "app/src/main/java/com/garage/aastream/handlers/BrightnessHandler.kt",
    "chars": 2178,
    "preview": "package com.garage.aastream.handlers\n\nimport android.content.Context\nimport android.os.Build\nimport android.provider.Set"
  },
  {
    "path": "app/src/main/java/com/garage/aastream/handlers/DisplayHandler.kt",
    "chars": 1256,
    "preview": "package com.garage.aastream.handlers\n\nimport android.content.Context\nimport com.garage.aastream.shell.ShellExecutor\nimpo"
  },
  {
    "path": "app/src/main/java/com/garage/aastream/handlers/NotificationHandler.kt",
    "chars": 3433,
    "preview": "package com.garage.aastream.handlers\n\nimport android.app.Notification\nimport android.app.NotificationChannel\nimport andr"
  },
  {
    "path": "app/src/main/java/com/garage/aastream/handlers/PreferenceHandler.kt",
    "chars": 2536,
    "preview": "package com.garage.aastream.handlers\n\nimport android.app.Application\nimport android.content.Context\nimport com.garage.aa"
  },
  {
    "path": "app/src/main/java/com/garage/aastream/handlers/RotationHandler.kt",
    "chars": 2166,
    "preview": "package com.garage.aastream.handlers\n\nimport android.content.Context\nimport android.os.Build\nimport android.provider.Set"
  },
  {
    "path": "app/src/main/java/com/garage/aastream/injection/GlideModule.kt",
    "chars": 266,
    "preview": "package com.garage.aastream.injection\n\nimport com.bumptech.glide.annotation.GlideModule\nimport com.bumptech.glide.module"
  },
  {
    "path": "app/src/main/java/com/garage/aastream/injection/InjectionComponent.kt",
    "chars": 928,
    "preview": "package com.garage.aastream.injection\n\nimport com.garage.aastream.App\nimport com.garage.aastream.activities.CarDebugActi"
  },
  {
    "path": "app/src/main/java/com/garage/aastream/injection/InjectionModule.kt",
    "chars": 2264,
    "preview": "package com.garage.aastream.injection\n\nimport android.app.Application\nimport com.garage.aastream.activities.controllers."
  },
  {
    "path": "app/src/main/java/com/garage/aastream/interfaces/OnAppClickedCallback.kt",
    "chars": 451,
    "preview": "package com.garage.aastream.interfaces\n\nimport com.garage.aastream.models.AppItem\n\n/**\n * Created by Endy Rubbin on 23.0"
  },
  {
    "path": "app/src/main/java/com/garage/aastream/interfaces/OnAppListLoadedCallback.kt",
    "chars": 420,
    "preview": "package com.garage.aastream.interfaces\n\nimport com.garage.aastream.models.AppItem\n\n/**\n * Created by Endy Rubbin on 23.0"
  },
  {
    "path": "app/src/main/java/com/garage/aastream/interfaces/OnLogCallback.kt",
    "chars": 250,
    "preview": "package com.garage.aastream.interfaces\n\n/**\n * Created by Endy Rubbin on 28.05.2019 13:35.\n * For project: AAStream\n */\n"
  },
  {
    "path": "app/src/main/java/com/garage/aastream/interfaces/OnMenuTapCallback.kt",
    "chars": 239,
    "preview": "package com.garage.aastream.interfaces\n\n/**\n * Created by Endy Rubbin on 27.05.2019 13:35.\n * For project: AAStream\n */\n"
  },
  {
    "path": "app/src/main/java/com/garage/aastream/interfaces/OnMinitouchCallback.kt",
    "chars": 322,
    "preview": "package com.garage.aastream.interfaces\n\n/**\n * Created by Endy Rubbin on 10.06.2019 14:20.\n * For project: AAStream\n */\n"
  },
  {
    "path": "app/src/main/java/com/garage/aastream/interfaces/OnPatchStatusCallback.kt",
    "chars": 313,
    "preview": "package com.garage.aastream.interfaces\n\n/**\n * Created by Endy Rubbin on 27.05.2019 10:33.\n * For project: AAStream\n */\n"
  },
  {
    "path": "app/src/main/java/com/garage/aastream/interfaces/OnRotationChangedCallback.kt",
    "chars": 251,
    "preview": "package com.garage.aastream.interfaces\n\n/**\n * Created by Endy Rubbin on 03.06.2019 16:38.\n * For project: AAStream\n */\n"
  },
  {
    "path": "app/src/main/java/com/garage/aastream/interfaces/OnScreenLockCallback.kt",
    "chars": 457,
    "preview": "package com.garage.aastream.interfaces\n\n/**\n * Created by Endy Rubbin on 22.05.2019 13:06.\n * For project: AAStream\n */\n"
  },
  {
    "path": "app/src/main/java/com/garage/aastream/minitouch/MiniTouchHandler.kt",
    "chars": 9134,
    "preview": "package com.garage.aastream.minitouch\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport andr"
  },
  {
    "path": "app/src/main/java/com/garage/aastream/minitouch/MiniTouchSocket.kt",
    "chars": 6281,
    "preview": "package com.garage.aastream.minitouch\n\nimport android.graphics.Point\nimport android.net.LocalSocket\nimport android.net.L"
  },
  {
    "path": "app/src/main/java/com/garage/aastream/minitouch/MinitouchDaemon.kt",
    "chars": 569,
    "preview": "package com.garage.aastream.minitouch\n\nimport android.os.AsyncTask\nimport com.garage.aastream.interfaces.OnMinitouchCall"
  },
  {
    "path": "app/src/main/java/com/garage/aastream/models/AppItem.kt",
    "chars": 421,
    "preview": "package com.garage.aastream.models\n\nimport android.graphics.drawable.Drawable\n\n/**\n * Created by Endy Rubbin on 23.05.20"
  },
  {
    "path": "app/src/main/java/com/garage/aastream/models/AppItemWrapper.kt",
    "chars": 171,
    "preview": "package com.garage.aastream.models\n\n/**\n * Created by Endy Rubbin on 23.05.2019 18:59.\n * For project: AAStream\n */\ndata"
  },
  {
    "path": "app/src/main/java/com/garage/aastream/receivers/ScreenLockReceiver.kt",
    "chars": 845,
    "preview": "package com.garage.aastream.receivers\n\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport an"
  },
  {
    "path": "app/src/main/java/com/garage/aastream/receivers/UsbStateReceiver.kt",
    "chars": 938,
    "preview": "package com.garage.aastream.receivers\n\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport an"
  },
  {
    "path": "app/src/main/java/com/garage/aastream/services/CarService.kt",
    "chars": 544,
    "preview": "package com.garage.aastream.services\n\nimport com.google.android.apps.auto.sdk.CarActivity\nimport com.google.android.apps"
  },
  {
    "path": "app/src/main/java/com/garage/aastream/shell/ShellExecutor.kt",
    "chars": 488,
    "preview": "package com.garage.aastream.shell\n\nimport com.garage.aastream.utils.DevLog\nimport eu.chainfire.libsuperuser.Shell\n\n/**\n "
  },
  {
    "path": "app/src/main/java/com/garage/aastream/utils/Const.kt",
    "chars": 544,
    "preview": "package com.garage.aastream.utils\n\n/**\n * Created by Endy Rubbin on 22.05.2019 14:36.\n * For project: AAStream\n */\nobjec"
  },
  {
    "path": "app/src/main/java/com/garage/aastream/utils/DevLog.kt",
    "chars": 2686,
    "preview": "package com.garage.aastream.utils\nimport android.util.Log\nimport com.garage.aastream.interfaces.OnLogCallback\n\nimport ja"
  },
  {
    "path": "app/src/main/java/com/garage/aastream/utils/PhenotypePatcher.kt",
    "chars": 32210,
    "preview": "package com.garage.aastream.utils\n\nimport android.content.Context\nimport com.garage.aastream.R\nimport com.garage.aastrea"
  },
  {
    "path": "app/src/main/java/com/garage/aastream/views/AutoFitRecyclerView.kt",
    "chars": 1455,
    "preview": "package com.garage.aastream.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport androidx.recyc"
  },
  {
    "path": "app/src/main/java/com/garage/aastream/views/FingerTapDetector.kt",
    "chars": 3422,
    "preview": "package com.garage.aastream.views\n\nimport android.content.Context\nimport android.util.ArrayMap\nimport android.view.Motio"
  },
  {
    "path": "app/src/main/java/com/garage/aastream/views/MarginDecoration.kt",
    "chars": 606,
    "preview": "package com.garage.aastream.views\n\nimport android.content.Context\nimport android.graphics.Rect\nimport android.view.View\n"
  },
  {
    "path": "app/src/main/res/drawable-anydpi/bg.xml",
    "chars": 174,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<bitmap xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:sr"
  },
  {
    "path": "app/src/main/res/drawable-anydpi/bg_input.xml",
    "chars": 288,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <corners a"
  },
  {
    "path": "app/src/main/res/drawable-anydpi/ic_apps.xml",
    "chars": 490,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        androi"
  },
  {
    "path": "app/src/main/res/drawable-anydpi/ic_back.xml",
    "chars": 347,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\""
  },
  {
    "path": "app/src/main/res/drawable-anydpi/ic_bookmark.xml",
    "chars": 397,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        androi"
  },
  {
    "path": "app/src/main/res/drawable-anydpi/ic_bookmark_border.xml",
    "chars": 439,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        androi"
  },
  {
    "path": "app/src/main/res/drawable-anydpi/ic_check.xml",
    "chars": 379,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        androi"
  },
  {
    "path": "app/src/main/res/drawable-anydpi/ic_close.xml",
    "chars": 433,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        androi"
  },
  {
    "path": "app/src/main/res/drawable-anydpi/ic_terminal.xml",
    "chars": 494,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        androi"
  },
  {
    "path": "app/src/main/res/layout/activity_car.xml",
    "chars": 7753,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n        xmlns:android=\"http://"
  },
  {
    "path": "app/src/main/res/layout/activity_request_result.xml",
    "chars": 361,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n        xmlns:android=\"http://"
  },
  {
    "path": "app/src/main/res/layout/activity_settings.xml",
    "chars": 2382,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ScrollView\n        xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  "
  },
  {
    "path": "app/src/main/res/layout/row_app_item.xml",
    "chars": 2182,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n        xmlns:android=\"http://"
  },
  {
    "path": "app/src/main/res/layout/view_car_terminal.xml",
    "chars": 4692,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n        xmlns:android=\"http://"
  },
  {
    "path": "app/src/main/res/layout/view_settings_about.xml",
    "chars": 2359,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n        xmlns:android=\"http://"
  },
  {
    "path": "app/src/main/res/layout/view_settings_audio.xml",
    "chars": 1693,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n        xmlns:android=\"http://"
  },
  {
    "path": "app/src/main/res/layout/view_settings_brightness.xml",
    "chars": 2306,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n        xmlns:android=\"http://"
  },
  {
    "path": "app/src/main/res/layout/view_settings_debug.xml",
    "chars": 3594,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n        xmlns:android=\"http://"
  },
  {
    "path": "app/src/main/res/layout/view_settings_immersive.xml",
    "chars": 1725,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n        xmlns:android=\"http://"
  },
  {
    "path": "app/src/main/res/layout/view_settings_resize.xml",
    "chars": 1701,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n        xmlns:android=\"http://"
  },
  {
    "path": "app/src/main/res/layout/view_settings_rotation.xml",
    "chars": 2227,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n        xmlns:android=\"http://"
  },
  {
    "path": "app/src/main/res/layout/view_settings_sidebar.xml",
    "chars": 3983,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n        xmlns:android=\"http://"
  },
  {
    "path": "app/src/main/res/layout/view_settings_unlock.xml",
    "chars": 3152,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n        xmlns:android=\"http://"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "chars": 913,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"color_primary\">#607D8B</color>\n    <color name=\"colo"
  },
  {
    "path": "app/src/main/res/values/dimens.xml",
    "chars": 146,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <dimen name=\"item_margin\">8dp</dimen>\n    <dimen name=\"item_size\""
  },
  {
    "path": "app/src/main/res/values/ids.xml",
    "chars": 640,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <item type=\"id\" name=\"icon\" />\n    <item type=\"id\" name=\"title\" /"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "chars": 4477,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"app_name\">AA Stream</string>\n\n    <string-array nam"
  },
  {
    "path": "app/src/main/res/values/styles.xml",
    "chars": 3955,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light.DarkActionBa"
  },
  {
    "path": "app/src/main/res/xml/automotive_app_desc.xml",
    "chars": 289,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<automotiveApp xmlns:tools=\"http://schemas.android.com/tools\">\n    <uses name=\"se"
  },
  {
    "path": "build.gradle",
    "chars": 684,
    "preview": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n    e"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "chars": 233,
    "preview": "#Wed May 22 10:28:41 EEST 2019\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER"
  },
  {
    "path": "gradlew",
    "chars": 5296,
    "preview": "#!/usr/bin/env sh\n\n##############################################################################\n##\n##  Gradle start up"
  },
  {
    "path": "settings.gradle",
    "chars": 15,
    "preview": "include ':app'\n"
  }
]

// ... and 19 more files (download for full content)

About this extraction

This page contains the full source code of the endyrubbin/AAStream GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 97 files (199.9 KB), approximately 47.4k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!