├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── pspdfkit │ │ └── labs │ │ └── quickdemo │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ └── setup-guide.html │ ├── kotlin │ │ └── com │ │ │ └── pspdfkit │ │ │ └── labs │ │ │ └── quickdemo │ │ │ ├── DemoMode.kt │ │ │ ├── Utils.kt │ │ │ ├── activity │ │ │ ├── ConfigurationActivity.kt │ │ │ └── SetupGuideActivity.kt │ │ │ └── service │ │ │ └── DemoModeTileService.kt │ └── res │ │ ├── drawable │ │ ├── ic_configuration_activity.xml │ │ ├── ic_demo_mode.xml │ │ └── ic_launcher.xml │ │ ├── layout │ │ └── activity_setup_guide.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ ├── values │ │ ├── array.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ └── demo_mode_preferences.xml │ └── test │ └── java │ └── com │ └── pspdfkit │ └── labs │ └── quickdemo │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle ├── setup-guide.md ├── setup.sh └── showcase.gif /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # Intellij 36 | *.iml 37 | .idea 38 | 39 | # Keystore files 40 | *.jks 41 | 42 | # External native build folder generated in Android Studio 2.2 and later 43 | .externalNativeBuild 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016-2019 PSPDFKit Labs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QuickDemo 2 | 3 | _QuickDemo_ is a Nougat 7.0 [quick settings tile](https://developer.android.com/about/versions/nougat/android-7.0.html#tile_api) for fast access to the Marshmallow 6.0 [System UI demo mode](https://android.googlesource.com/platform/frameworks/base/+/android-6.0.0_r1/packages/SystemUI/docs/demo_mode.md). The app also provides a configuration activity for customizing available demo mode settings. 4 | 5 | Release blog post: https://pspdfkit.com/blog/2016/clean-statusbar-with-systemui-and-quickdemo/ 6 | 7 | ![QuickDemo in action](showcase.gif) 8 | 9 | ## Requirements 10 | 11 | * Android SDK (API 25) 12 | * Android Studio 2.2+ 13 | * `adb` (for installing the app and granting the required permissions) 14 | * Android emulator or devices running Marshmallow (API 23+) 15 | 16 | ## Building 17 | 18 | To build and run the app, you can open the project with Android Studio and press `Run`. Alternatively you can install the app from the command line. 19 | 20 | ```bash 21 | git clone git@github.com:PSPDFKit-labs/QuickDemo.git 22 | cd QuickDemo/ 23 | ./gradlew installDebug 24 | ``` 25 | 26 | You can also run the [`setup.sh`](https://github.com/PSPDFKit-labs/QuickDemo/blob/master/setup.sh) script, wich will check for ANDROID_HOME, clone the project, and use Gradle to install and setup the tool. The script will also remove files of the project after installation. 27 | 28 | ## Setup 29 | 30 | ### With Gradle 31 | 32 | If you cloned the project, you can run `setupDemoMode` gradle task to do the setup. 33 | 34 | This can be done either by finding and selecting `setupDemoMode` in `Gradle` window in Android Studio, or by running the following: 35 | 36 | ```bash 37 | ./gradlew setupDemoMode 38 | ``` 39 | 40 | ### Manually via adb 41 | 42 | 1. When launching the app for the first time you need to grant the `android.permission.DUMP` permission, which is required to control the System UI demo mode. You need to do this using `adb`. 43 | 44 | ```bash 45 | adb shell pm grant com.pspdfkit.labs.quickdemo android.permission.DUMP 46 | ``` 47 | 48 | 2. Since the System UI tuner (and its demo mode) is an experimental Android feature, you need to activate it globally. 49 | 50 | ```bash 51 | adb shell settings put global sysui_demo_allowed 1 52 | ``` 53 | 54 | ## Usage 55 | 56 | 1. The app comes with a quick settings tile which you can use to quickly toggle the demo mode. 57 | 1. Completely open the status bar drawer, expanding all quick setting tiles. 58 | 2. Press the edit button on top of the drawer, to show the quick setting tiles picker. 59 | 3. Drag the QuickDemo tile to your desired position. 60 | 4. Exit edit mode, and tap the tile. 61 | 62 | 2. You can launch QuickDemo activity to configure all displayed icons of the demo mode. 63 | 1. You can find the activity in your app launcher. 64 | 65 | ## Feedback and contribution 66 | 67 | Since this project is open source, feel free to use it, give feedback, or contribute in any way you find suitable. 68 | 69 | ## About 70 | 71 | 72 | 73 | 74 | 75 | This project is maintained and funded by [PSPDFKit](https://pspdfkit.com/). 76 | 77 | See [our other open source projects](https://github.com/PSPDFKit-labs), read [our blog](https://pspdfkit.com/blog/) or say hello on Twitter ([@PSPDFKit](https://twitter.com/pspdfkit)). 78 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016-2019 PSPDFKit GmbH. All rights reserved. 3 | * 4 | * THIS SOURCE CODE AND ANY ACCOMPANYING DOCUMENTATION ARE PROTECTED BY INTERNATIONAL COPYRIGHT LAW 5 | * AND MAY NOT BE RESOLD OR REDISTRIBUTED. USAGE IS BOUND TO THE PSPDFKIT LICENSE AGREEMENT. 6 | * UNAUTHORIZED REPRODUCTION OR DISTRIBUTION IS SUBJECT TO CIVIL AND CRIMINAL PENALTIES. 7 | * This notice may not be removed from this file 8 | */ 9 | 10 | apply plugin: 'com.android.application' 11 | apply plugin: 'kotlin-android' 12 | apply plugin: 'android-command' 13 | 14 | android { 15 | compileSdkVersion 28 16 | defaultConfig { 17 | applicationId "com.pspdfkit.labs.quickdemo" 18 | minSdkVersion 23 19 | targetSdkVersion 28 20 | versionCode 1 21 | versionName "1.0" 22 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 23 | } 24 | buildTypes { 25 | release { 26 | minifyEnabled false 27 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 28 | } 29 | } 30 | sourceSets { 31 | main.java.srcDirs += 'src/main/kotlin' 32 | } 33 | } 34 | 35 | dependencies { 36 | androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', { 37 | exclude group: 'com.android.support', module: 'support-annotations' 38 | }) 39 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 40 | implementation 'androidx.appcompat:appcompat:1.0.2' 41 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' 42 | 43 | implementation 'io.reactivex.rxjava2:rxjava:2.2.4' 44 | implementation 'io.reactivex.rxjava2:rxandroid:2.1.0' 45 | 46 | testImplementation 'junit:junit:4.12' 47 | } 48 | 49 | task setupDemoMode(type: com.novoda.gradle.command.AdbTask) { 50 | description 'Demo Mode initial setup' 51 | doLast { 52 | runCommand(['shell', 'settings', 'put', 'global', 'sysui_demo_allowed', '1']) 53 | runCommand(['shell', 'pm', 'grant', 'com.pspdfkit.labs.quickdemo', 'android.permission.DUMP']) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/david/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/pspdfkit/labs/quickdemo/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016-2019 PSPDFKit GmbH. All rights reserved. 3 | * 4 | * THIS SOURCE CODE AND ANY ACCOMPANYING DOCUMENTATION ARE PROTECTED BY INTERNATIONAL COPYRIGHT LAW 5 | * AND MAY NOT BE RESOLD OR REDISTRIBUTED. USAGE IS BOUND TO THE PSPDFKIT LICENSE AGREEMENT. 6 | * UNAUTHORIZED REPRODUCTION OR DISTRIBUTION IS SUBJECT TO CIVIL AND CRIMINAL PENALTIES. 7 | * This notice may not be removed from this file 8 | */ 9 | 10 | package com.pspdfkit.labs.quickdemo 11 | 12 | import androidx.test.InstrumentationRegistry 13 | import androidx.test.runner.AndroidJUnit4 14 | import org.junit.Assert.assertEquals 15 | import org.junit.Test 16 | import org.junit.runner.RunWith 17 | 18 | /** 19 | * Instrumentation test, which will execute on an Android device. 20 | 21 | * @see [Testing documentation](http://d.android.com/tools/testing) 22 | */ 23 | @RunWith(AndroidJUnit4::class) 24 | class ExampleInstrumentedTest { 25 | @Test 26 | @Throws(Exception::class) 27 | fun useAppContext() { 28 | // Context of the app under test. 29 | val appContext = InstrumentationRegistry.getTargetContext() 30 | 31 | assertEquals("com.pspdfkit.demoqstile", appContext.packageName) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 14 | 15 | 16 | 19 | 20 | 21 | 22 | 23 | 29 | 34 | 35 | 36 | 37 | 38 | 39 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /app/src/main/assets/setup-guide.html: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | setup-guide 18 | 19 | 20 | 623 | 624 | 763 | 764 | 765 | 766 | 767 | 768 | 769 |

QuickDemo is a tool for Android developers. If you don't know what this application is for, do not use it!

770 | 771 |

Setup

772 | 773 |

To enable QuickDemo you need to do two things.

774 | 775 |
    776 |
  1. Enable the System UI demo mode on your phone.
  2. 777 |
  3. Grant the android.permission.DUMP to the QuickDemo app. This permissions is protected and can't be requested at runtime.
  4. 778 |
779 | 780 |

This can be achieved by running the following gradle task.

781 | 782 |
./gradlew setupDemoMode
783 | 784 |

If you don't have the project. Both steps can be performed manually from command line via adb.

785 | 786 |
# Ensure demo mode is activated.
787 | adb shell settings put global sysui_demo_allowed 1
788 | 
789 | # Grant required permissions.
790 | adb shell pm grant com.pspdfkit.labs.quickdemo android.permission.DUMP
791 | 792 |

Please note: The permissions is granted until you revoke it or uninstall the app.

793 | 794 | 797 | 798 | 799 | 800 | 801 | 802 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/pspdfkit/labs/quickdemo/DemoMode.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016-2019 PSPDFKit GmbH. All rights reserved. 3 | * 4 | * THIS SOURCE CODE AND ANY ACCOMPANYING DOCUMENTATION ARE PROTECTED BY INTERNATIONAL COPYRIGHT LAW 5 | * AND MAY NOT BE RESOLD OR REDISTRIBUTED. USAGE IS BOUND TO THE PSPDFKIT LICENSE AGREEMENT. 6 | * UNAUTHORIZED REPRODUCTION OR DISTRIBUTION IS SUBJECT TO CIVIL AND CRIMINAL PENALTIES. 7 | * This notice may not be removed from this file 8 | */ 9 | 10 | package com.pspdfkit.labs.quickdemo 11 | 12 | import android.content.Context 13 | import android.content.Intent 14 | import android.content.SharedPreferences 15 | import android.content.pm.PackageManager 16 | import android.preference.PreferenceManager 17 | import android.provider.Settings 18 | import androidx.core.content.ContextCompat 19 | import kotlin.properties.ReadWriteProperty 20 | import kotlin.reflect.KProperty 21 | 22 | class DemoMode private constructor(context: Context) { 23 | private val applicationContext: Context = context.applicationContext 24 | private val sharedPreferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) 25 | private val demoModeProperties = mutableSetOf>() 26 | 27 | companion object { 28 | private var singleton: DemoMode? = null 29 | 30 | /** Returns the demo mode singleton */ 31 | fun get(context: Context): DemoMode { 32 | var demoMode = singleton 33 | if (demoMode == null) { 34 | demoMode = DemoMode(context) 35 | singleton = demoMode 36 | } 37 | 38 | return demoMode 39 | } 40 | 41 | const val DEFAULT_MOBILE_DATATYPE = "hidden" 42 | const val DEFAULT_MOBILE_LEVEL = 4 43 | const val DEFAULT_WIFI_LEVEL = 4 44 | const val DEFAULT_NETWORK_NUM_OF_SIMS = 1 45 | const val DEFAULT_STATUS_BAR_MODE = "transparent" 46 | const val DEFAULT_BATTERY_LEVEL = 100 47 | const val DEFAULT_STATUS_HIDDEN = "hidden" 48 | } 49 | 50 | /** Returns `true` if the required permissions to send out demo mode broadcasts are available, or `false` if they need to be requested first. */ 51 | val requiredPermissionsGranted: Boolean 52 | get() = ContextCompat.checkSelfPermission(applicationContext, "android.permission.DUMP") == PackageManager.PERMISSION_GRANTED 53 | 54 | /** Returns `true` if the demo mode has been activated in system UI. **/ 55 | val demoModeAllowed: Boolean 56 | get() = Settings.Global.getInt(applicationContext.contentResolver, "sysui_demo_allowed", 0) == 1 57 | 58 | /** 59 | * Enables or disables the demo mode. If this is set to `false`, no other demo mode settings will have effect. 60 | */ 61 | var enabled: Boolean 62 | get() = sharedPreferences.getBoolean("demoEnabled", false) 63 | set(value) { 64 | sharedPreferences.edit().putBoolean("demoEnabled", value).commit() 65 | 66 | if (value) { 67 | sendDemoCommand("enter") 68 | for (property in demoModeProperties) { 69 | property.issueDemoModeCommand() 70 | } 71 | } else { 72 | sendDemoCommand("exit") 73 | } 74 | 75 | } 76 | 77 | /** 78 | * If set to `true`, all notifications get hidden. 79 | */ 80 | var hideNotifications by booleanPreference("hideNotifications", true) { value -> 81 | if (enabled) sendDemoCommand("notifications", "visible" to !value) 82 | } 83 | 84 | /** 85 | * Sets the time in the format of `hhmm`. 86 | */ 87 | var time by stringPreference("time", "0700") { value -> 88 | if (enabled) sendDemoCommand("clock", "hhmm" to value) 89 | } 90 | 91 | /** 92 | * Can be set to any of `opaque`, `translucent`, `semi-transparent`, `transparent`, or `warning`. 93 | */ 94 | var statusBarMode by stringPreference("statusBarMode", DEFAULT_STATUS_BAR_MODE) { value -> 95 | if (enabled) sendDemoCommand("bars", "mode" to value) 96 | } 97 | 98 | /** 99 | * Configure the network display (Wi-Fi, mobile, airplane, etc.). 100 | */ 101 | val network = Network() 102 | 103 | inner class Network { 104 | /** 105 | * Enables or disables airplane mode. Disabled by default. 106 | */ 107 | var showAirplane by booleanPreference("networkShowAirplane", false) { value -> 108 | if (enabled) sendDemoCommand("network", "airplane" to if (value) "show" else "hide") 109 | } 110 | 111 | /** 112 | * Enables or disables full network connectivity mode. Enabled by default. 113 | */ 114 | var fullConnectivity by booleanPreference("networkFullConnectivity", true) { value -> 115 | if (enabled) sendDemoCommand("network", "fully" to value) 116 | } 117 | 118 | /** 119 | * Shows or hides the Wi-Fi icon. Hidden by default. 120 | */ 121 | var showWifi by booleanPreference("networkShowWifi", false) { value -> 122 | if (enabled) sendDemoCommand("network", "wifi" to if (value) "show" else "hide") 123 | } 124 | 125 | /** 126 | * Sets the Wi-Fi connection level. Can be an integer from `0` (no reception) to `4` (full reception). Defaults to `4`. 127 | */ 128 | var wifiLevel by intPreference("networkWifiLevel", DEFAULT_WIFI_LEVEL) { value -> 129 | if (enabled && showWifi) sendDemoCommand("network", "wifi" to "show", "level" to value) 130 | } 131 | 132 | /** 133 | * Shows or hides the mobile network icon. Shown by default. 134 | */ 135 | var mobileShown by booleanPreference("networkMobileShown", true) { value -> 136 | if (enabled) sendDemoCommand("network", "mobile" to if (value) "show" else "hide") 137 | } 138 | 139 | /** 140 | * Sets the mobile network level. Can be an integer from `0` (no reception) to `4` (full reception). Defaults to `4`. 141 | */ 142 | var mobileLevel by intPreference("networkMobileLevel", DEFAULT_MOBILE_LEVEL) { value -> 143 | if (enabled && mobileShown) sendDemoCommand("network", "mobile" to "show", "level" to value) 144 | } 145 | 146 | /** 147 | * Sets the mobile network connection type. Can be any of `1x`, `3g`, `4g`, `e`, `g`, `h`, `lte`, `roam`, or any other value to hide. Defaults to `lte`. 148 | */ 149 | var mobileDatatype by stringPreference("networkMobileDatatype", DEFAULT_MOBILE_DATATYPE) { value -> 150 | if (enabled && mobileShown) sendDemoCommand("network", "mobile" to "show", "datatype" to value) 151 | } 152 | 153 | /** 154 | * Sets mobile signal icon to carrier network change UX when disconnected. Disabled by default. 155 | */ 156 | var showCarrierNetworkChange by booleanPreference("networkCarrierNetworkChange", false) { value -> 157 | if (enabled) sendDemoCommand("network", "carriernetworkchange" to if (value) "show" else "hide") 158 | } 159 | 160 | /** 161 | * Sets the number of used SIMs. Can be an integer from `1` to `8`. Defaults to `1`. 162 | */ 163 | var numberOfSims by intPreference("networkNumberOfSims", DEFAULT_NETWORK_NUM_OF_SIMS) { value -> 164 | if (enabled) sendDemoCommand("network", "sims" to value) 165 | } 166 | 167 | /** 168 | * Show or hide the "no SIM" icon. Hidden by default. 169 | */ 170 | var showNoSim by booleanPreference("networkShowNoSim", false) { value -> 171 | if (enabled) sendDemoCommand("network", "nosim" to if (value) "show" else "hide") 172 | } 173 | } 174 | 175 | /** 176 | * Configure the status icons (alarm, volume, bluetooth, etc.) 177 | */ 178 | val status = Status() 179 | 180 | inner class Status { 181 | /** 182 | * Sets the volume icon. Can be any of `silent`, `vibrate`, or any other value to hide the icon. Hidden by default. 183 | */ 184 | var volume by stringPreference("statusVolume", DEFAULT_STATUS_HIDDEN) { value -> 185 | if (enabled) sendDemoCommand("status", "volume" to value) 186 | } 187 | 188 | /** 189 | * Sets the bluetooth icon. Can be any of `connected`, `disconnected`, `hidden`. Hidden by default. 190 | */ 191 | var bluetooth by stringPreference("statusBluetooth", DEFAULT_STATUS_HIDDEN, { value -> 192 | if (enabled) sendDemoCommand("status", "bluetooth" to value) 193 | }) 194 | 195 | /** 196 | * Shows or hides the icon in the location slot. Hidden by default. 197 | */ 198 | var showLocation by booleanPreference("statusLocation", false) { value -> 199 | if (enabled) sendDemoCommand("status", "location" to if (value) "show" else "hide") 200 | } 201 | 202 | /** 203 | * Shows or hides the alarm clock icon. Hidden by default. 204 | */ 205 | var showAlarm by booleanPreference("statusAlarm", false) { value -> 206 | if (enabled) sendDemoCommand("status", "alarm" to if (value) "show" else "hide") 207 | } 208 | 209 | /** 210 | * Shows or hides the sync icon. Hidden by default. 211 | */ 212 | var showSync by booleanPreference("statusSync", false) { value -> 213 | if (enabled) sendDemoCommand("status", "sync" to if (value) "show" else "hide") 214 | } 215 | 216 | /** 217 | * Shows or hides the TTY icon. Hidden by default. 218 | */ 219 | var showTty by booleanPreference("statusTty", false) { value -> 220 | if (enabled) sendDemoCommand("status", "tty" to if (value) "show" else "hide") 221 | } 222 | 223 | /** 224 | * Shows or hides the CDMA ERI icon. Hidden by default. 225 | */ 226 | var showEri by booleanPreference("statusEri", false) { value -> 227 | if (enabled) sendDemoCommand("status", "eri" to if (value) "show" else "hide") 228 | } 229 | 230 | /** 231 | * Shows or hides the mute icon. Hidden by default. 232 | */ 233 | var showMute by booleanPreference("statusMute", false) { value -> 234 | if (enabled) sendDemoCommand("status", "mute" to if (value) "show" else "hide") 235 | } 236 | 237 | /** 238 | * Shows or hides the speakerphone icon. Hidden by default. 239 | */ 240 | var showSpeakerphone by booleanPreference("statusSpeakerphone", false) { value -> 241 | if (enabled) sendDemoCommand("status", "speakerphone" to if (value) "show" else "hide") 242 | } 243 | } 244 | 245 | /** 246 | * Configure the battery display. 247 | */ 248 | val battery = Battery() 249 | 250 | inner class Battery() { 251 | /** 252 | * Sets the shown battery level. Can be an integer from `0` (depleted) to `100` (fully charged). Defaults to `100`. 253 | */ 254 | var level by intPreference("batteryLevel", DEFAULT_BATTERY_LEVEL) { value -> 255 | if (enabled) sendDemoCommand("battery", "level" to value) 256 | } 257 | 258 | /** 259 | * Enables or disables the battery being shown as being plugged in and charging. Defaults to `false`. 260 | */ 261 | var plugged by booleanPreference("batteryPlugged", false) { value -> 262 | if (enabled) sendDemoCommand("battery", "plugged" to value) 263 | } 264 | 265 | /** 266 | * Enables or disables the device's power save mode. Defaults to `false`. 267 | */ 268 | var powersave by booleanPreference("batteryPowersave", default = false) { value -> 269 | if (enabled) sendDemoCommand("battery", "powersave" to value) 270 | } 271 | } 272 | 273 | private fun sendDemoCommand(command: String, vararg extras: Pair) { 274 | val intent = Intent("com.android.systemui.demo").apply { 275 | putExtra("command", command) 276 | for ((key, value) in extras) { 277 | putExtra(key, value.toString()) 278 | } 279 | } 280 | 281 | applicationContext.sendBroadcast(intent) 282 | } 283 | 284 | abstract class DemoModeProperty : ReadWriteProperty, SharedPreferences.OnSharedPreferenceChangeListener { 285 | abstract val key: String 286 | abstract fun issueDemoModeCommand() 287 | abstract fun getStoredValue(): T 288 | override fun getValue(thisRef: Any, property: KProperty<*>) = getStoredValue() 289 | override fun onSharedPreferenceChanged(p0: SharedPreferences?, changedKey: String?) { 290 | if (key == changedKey) issueDemoModeCommand() 291 | } 292 | } 293 | 294 | fun booleanPreference(key: String, default: Boolean, onSendCommand: (Boolean) -> Unit) = object : DemoModeProperty() { 295 | override val key = key 296 | override fun issueDemoModeCommand() = onSendCommand(getStoredValue()) 297 | override fun getStoredValue() = sharedPreferences.getBoolean(key, default) 298 | override fun setValue(thisRef: Any, property: KProperty<*>, value: Boolean) { 299 | sharedPreferences.edit().putBoolean(key, value).apply() 300 | } 301 | }.apply { 302 | sharedPreferences.registerOnSharedPreferenceChangeListener(this) 303 | demoModeProperties.add(this) 304 | } 305 | 306 | fun intPreference(key: String, default: Int, onSendCommand: (Int) -> Unit) = object : DemoModeProperty() { 307 | override val key = key 308 | override fun issueDemoModeCommand() = onSendCommand(getStoredValue()) 309 | override fun getStoredValue() = sharedPreferences.getString(key, default.toString()).toInt() 310 | override fun setValue(thisRef: Any, property: KProperty<*>, value: Int) { 311 | sharedPreferences.edit().putString(key, value.toString()).apply() 312 | } 313 | }.apply { 314 | sharedPreferences.registerOnSharedPreferenceChangeListener(this) 315 | demoModeProperties.add(this) 316 | } 317 | 318 | fun stringPreference(key: String, default: String, onSendCommand: (String) -> Unit) = object : DemoModeProperty() { 319 | override val key = key 320 | override fun issueDemoModeCommand() = onSendCommand(getStoredValue()) 321 | override fun getStoredValue() = sharedPreferences.getString(key, default) 322 | override fun setValue(thisRef: Any, property: KProperty<*>, value: String) { 323 | sharedPreferences.edit().putString(key, value).apply() 324 | } 325 | }.apply { 326 | sharedPreferences.registerOnSharedPreferenceChangeListener(this) 327 | demoModeProperties.add(this) 328 | } 329 | } 330 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/pspdfkit/labs/quickdemo/Utils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016-2019 PSPDFKit GmbH. All rights reserved. 3 | * 4 | * THIS SOURCE CODE AND ANY ACCOMPANYING DOCUMENTATION ARE PROTECTED BY INTERNATIONAL COPYRIGHT LAW 5 | * AND MAY NOT BE RESOLD OR REDISTRIBUTED. USAGE IS BOUND TO THE PSPDFKIT LICENSE AGREEMENT. 6 | * UNAUTHORIZED REPRODUCTION OR DISTRIBUTION IS SUBJECT TO CIVIL AND CRIMINAL PENALTIES. 7 | * This notice may not be removed from this file 8 | */ 9 | 10 | package com.pspdfkit.labs.quickdemo 11 | 12 | import android.content.Context 13 | import android.widget.Toast 14 | 15 | /** Shows a normal toast message. */ 16 | fun Context.toast(message: CharSequence, length: Int = Toast.LENGTH_SHORT) 17 | = Toast.makeText(this, message, length).show() -------------------------------------------------------------------------------- /app/src/main/kotlin/com/pspdfkit/labs/quickdemo/activity/ConfigurationActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016-2019 PSPDFKit GmbH. All rights reserved. 3 | * 4 | * THIS SOURCE CODE AND ANY ACCOMPANYING DOCUMENTATION ARE PROTECTED BY INTERNATIONAL COPYRIGHT LAW 5 | * AND MAY NOT BE RESOLD OR REDISTRIBUTED. USAGE IS BOUND TO THE PSPDFKIT LICENSE AGREEMENT. 6 | * UNAUTHORIZED REPRODUCTION OR DISTRIBUTION IS SUBJECT TO CIVIL AND CRIMINAL PENALTIES. 7 | * This notice may not be removed from this file 8 | */ 9 | 10 | package com.pspdfkit.labs.quickdemo.activity 11 | 12 | 13 | import android.content.BroadcastReceiver 14 | import android.content.Context 15 | import android.content.Intent 16 | import android.content.IntentFilter 17 | import android.content.SharedPreferences 18 | import android.os.Bundle 19 | import android.preference.PreferenceFragment 20 | import android.preference.SwitchPreference 21 | import androidx.annotation.ArrayRes 22 | import androidx.appcompat.app.AppCompatActivity 23 | import com.pspdfkit.labs.quickdemo.DemoMode 24 | import com.pspdfkit.labs.quickdemo.R 25 | 26 | class ConfigurationActivity : AppCompatActivity() { 27 | 28 | override fun onCreate(savedInstanceState: Bundle?) { 29 | super.onCreate(savedInstanceState) 30 | supportActionBar?.setIcon(R.drawable.ic_configuration_activity) 31 | fragmentManager.beginTransaction().replace(android.R.id.content, DemoModePreferences()).commit() 32 | } 33 | 34 | override fun onStart() { 35 | super.onStart() 36 | 37 | // Whenever this activity comes to the foreground, we check if the required permissions have been granted. 38 | // If the permissions are missing, launch the setup guide. 39 | val demoMode = DemoMode.get(this) 40 | if (!demoMode.requiredPermissionsGranted || !demoMode.demoModeAllowed) { 41 | SetupGuideActivity.launch(this) 42 | // We have to finish this activity, otherwise users that do not complete the setup guide would end up in a endless loop. 43 | finish() 44 | } 45 | } 46 | 47 | class DemoModePreferences : PreferenceFragment(), SharedPreferences.OnSharedPreferenceChangeListener { 48 | private lateinit var demoMode: DemoMode 49 | private var updateFromReceiver: Boolean = false 50 | 51 | private val demoModeChangeReceiver = object : BroadcastReceiver() { 52 | override fun onReceive(p0: Context?, p1: Intent?) { 53 | updateFromReceiver = true 54 | updateDemoModeSwitch() 55 | updateFromReceiver = false 56 | } 57 | } 58 | 59 | override fun onCreate(savedInstanceState: Bundle?) { 60 | super.onCreate(savedInstanceState) 61 | demoMode = DemoMode.get(context) 62 | addPreferencesFromResource(R.xml.demo_mode_preferences) 63 | preferenceManager.sharedPreferences.registerOnSharedPreferenceChangeListener(this) 64 | updateSummaries() 65 | } 66 | 67 | fun updateSummaries() { 68 | setSummaryFromArrayValue("batteryLevel", R.array.batteryLevelEscaped, R.array.batteryLevelValues, DemoMode.DEFAULT_BATTERY_LEVEL.toString()) 69 | setSummaryFromArrayValue("statusBarMode", R.array.statusBarModes, R.array.statusBarModeValues, DemoMode.DEFAULT_STATUS_BAR_MODE) 70 | setSummaryFromArrayValue("networkMobileDatatype", R.array.networkMobileDatatypes, R.array.networkMobileDatatypeValues, DemoMode.DEFAULT_MOBILE_DATATYPE) 71 | setSummaryFromArrayValue("networkMobileLevel", R.array.networkReceptionLevels, R.array.networkReceptionLevelValues, DemoMode.DEFAULT_MOBILE_LEVEL.toString()) 72 | setSummaryFromArrayValue("networkNumberOfSims", R.array.networkNumOfSims, R.array.networkNumOfSims, DemoMode.DEFAULT_NETWORK_NUM_OF_SIMS.toString()) 73 | setSummaryFromArrayValue("networkWifiLevel", R.array.networkReceptionLevels, R.array.networkReceptionLevelValues, DemoMode.DEFAULT_WIFI_LEVEL.toString()) 74 | setSummaryFromArrayValue("statusVolume", R.array.statusVolume, R.array.statusVolumeValues, DemoMode.DEFAULT_STATUS_HIDDEN) 75 | setSummaryFromArrayValue("statusBluetooth", R.array.statusBluetooth, R.array.statusBluetoothValues, DemoMode.DEFAULT_STATUS_HIDDEN) 76 | } 77 | 78 | fun setSummaryFromArrayValue(key: String, @ArrayRes labelArrayRes: Int, @ArrayRes valueArrayRes: Int, default: String) { 79 | val preference = findPreference(key) 80 | val value = preferenceManager.sharedPreferences.getString(key, default) 81 | val i = resources.getStringArray(valueArrayRes).indexOf(value) 82 | if (i >= 0) preference.summary = resources.getStringArray(labelArrayRes)[i] 83 | } 84 | 85 | override fun onStart() { 86 | super.onStart() 87 | context.registerReceiver(demoModeChangeReceiver, IntentFilter("com.android.systemui.demo")) 88 | updateDemoModeSwitch() 89 | } 90 | 91 | override fun onStop() { 92 | super.onStop() 93 | context.unregisterReceiver(demoModeChangeReceiver) 94 | } 95 | 96 | private fun updateDemoModeSwitch() { 97 | val enabled = findPreference("enable_demo") as SwitchPreference 98 | enabled.isChecked = demoMode.enabled 99 | } 100 | 101 | override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) { 102 | when (key) { 103 | "enable_demo" -> { 104 | if (!updateFromReceiver) demoMode.enabled = sharedPreferences.getBoolean(key, false) 105 | } 106 | "batteryLevel", "statusBarMode", "networkMobileDatatype", "networkMobileLevel", 107 | "networkNumberOfSims", "networkWifiLevel", "statusVolume", "statusBluetooth" -> { 108 | updateSummaries() 109 | } 110 | } 111 | } 112 | } 113 | 114 | companion object { 115 | fun launch(context: Context) { 116 | val intent = Intent(context, ConfigurationActivity::class.java) 117 | context.startActivity(intent) 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/pspdfkit/labs/quickdemo/activity/SetupGuideActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016-2019 PSPDFKit GmbH. All rights reserved. 3 | * 4 | * THIS SOURCE CODE AND ANY ACCOMPANYING DOCUMENTATION ARE PROTECTED BY INTERNATIONAL COPYRIGHT LAW 5 | * AND MAY NOT BE RESOLD OR REDISTRIBUTED. USAGE IS BOUND TO THE PSPDFKIT LICENSE AGREEMENT. 6 | * UNAUTHORIZED REPRODUCTION OR DISTRIBUTION IS SUBJECT TO CIVIL AND CRIMINAL PENALTIES. 7 | * This notice may not be removed from this file 8 | */ 9 | 10 | package com.pspdfkit.labs.quickdemo.activity 11 | 12 | import android.content.Context 13 | import android.content.Intent 14 | import android.os.Bundle 15 | import androidx.appcompat.app.AppCompatActivity 16 | import android.webkit.WebView 17 | import android.widget.Toast 18 | import com.pspdfkit.labs.quickdemo.DemoMode 19 | import com.pspdfkit.labs.quickdemo.R 20 | import com.pspdfkit.labs.quickdemo.toast 21 | import io.reactivex.Observable 22 | import io.reactivex.android.schedulers.AndroidSchedulers 23 | import io.reactivex.disposables.Disposable 24 | import java.util.concurrent.TimeUnit 25 | 26 | /** 27 | * An interactive introduction guide for first-time users. 28 | */ 29 | class SetupGuideActivity : AppCompatActivity() { 30 | 31 | var permissionChecking: Disposable? = null 32 | 33 | override fun onCreate(savedInstanceState: Bundle?) { 34 | super.onCreate(savedInstanceState) 35 | val demoMode = DemoMode.get(this) 36 | 37 | // If the app has already been granted the required permissions, there's no need to setup. 38 | if (demoMode.requiredPermissionsGranted && demoMode.demoModeAllowed) finish() 39 | 40 | // Setup content is just informative. We're using a webview to show pre-formatted instructions. 41 | setContentView(R.layout.activity_setup_guide) 42 | val webView: WebView = findViewById(R.id.webview) 43 | webView.loadUrl("file:///android_asset/setup-guide.html") 44 | 45 | // We periodically check if the permission has been granted. Once this happened, we notify the user and finish the activity. 46 | permissionChecking = Observable.interval(1, TimeUnit.SECONDS) 47 | .filter { demoMode.requiredPermissionsGranted && demoMode.demoModeAllowed } 48 | .observeOn(AndroidSchedulers.mainThread()) 49 | .doOnNext { 50 | toast("QuickDemo has been successfully set up! Ready for activating demo mode.", Toast.LENGTH_LONG) 51 | ConfigurationActivity.launch(this) 52 | finish() 53 | } 54 | .subscribe() 55 | } 56 | 57 | override fun onDestroy() { 58 | super.onDestroy() 59 | 60 | // Stop permission checking when destroying the activity (preventing leaks). 61 | permissionChecking?.dispose() 62 | permissionChecking = null 63 | } 64 | 65 | companion object { 66 | fun launch(context: Context) { 67 | context.startActivity(intent(context)) 68 | } 69 | 70 | fun intent(context: Context) = Intent(context, SetupGuideActivity::class.java) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/pspdfkit/labs/quickdemo/service/DemoModeTileService.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016-2019 PSPDFKit GmbH. All rights reserved. 3 | * 4 | * THIS SOURCE CODE AND ANY ACCOMPANYING DOCUMENTATION ARE PROTECTED BY INTERNATIONAL COPYRIGHT LAW 5 | * AND MAY NOT BE RESOLD OR REDISTRIBUTED. USAGE IS BOUND TO THE PSPDFKIT LICENSE AGREEMENT. 6 | * UNAUTHORIZED REPRODUCTION OR DISTRIBUTION IS SUBJECT TO CIVIL AND CRIMINAL PENALTIES. 7 | * This notice may not be removed from this file 8 | */ 9 | 10 | package com.pspdfkit.labs.quickdemo.service 11 | 12 | import android.content.Intent 13 | import android.graphics.drawable.Icon 14 | import android.service.quicksettings.Tile 15 | import android.service.quicksettings.TileService 16 | import com.pspdfkit.labs.quickdemo.DemoMode 17 | import com.pspdfkit.labs.quickdemo.R 18 | import com.pspdfkit.labs.quickdemo.activity.SetupGuideActivity 19 | 20 | class DemoModeTileService : TileService() { 21 | 22 | private lateinit var demoMode: DemoMode 23 | 24 | override fun onCreate() { 25 | super.onCreate() 26 | demoMode = DemoMode.get(this) 27 | } 28 | 29 | override fun onStartListening() { 30 | qsTile.icon = Icon.createWithResource(this, R.drawable.ic_demo_mode) 31 | qsTile.label = "Demo mode" 32 | updateIcon() 33 | } 34 | 35 | override fun onClick() { 36 | if (!demoMode.requiredPermissionsGranted || !demoMode.demoModeAllowed) { 37 | startActivityAndCollapse(SetupGuideActivity.intent(this)) 38 | return 39 | } 40 | 41 | demoMode.enabled = !demoMode.enabled 42 | updateIcon() 43 | 44 | val it = Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS) 45 | sendBroadcast(it) 46 | } 47 | 48 | private fun updateIcon() { 49 | qsTile.state = if (demoMode.enabled) Tile.STATE_ACTIVE else Tile.STATE_INACTIVE 50 | qsTile.updateTile() 51 | } 52 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_configuration_activity.xml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_demo_mode.xml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_setup_guide.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 64dp 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/values/array.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | Transparent 14 | Opaque 15 | Translucent 16 | Semi-Transparent 17 | Warning 18 | 19 | 20 | 21 | transparent 22 | opaque 23 | translucent 24 | semi-transparent 25 | warning 26 | 27 | 28 | 29 | 1x 30 | 3G 31 | 4G 32 | Edge 33 | GPRS 34 | HSPA 35 | LTE 36 | Roaming 37 | Hidden 38 | 39 | 40 | 41 | 1x 42 | 3g 43 | 4g 44 | e 45 | g 46 | h 47 | lte 48 | roam 49 | hidden 50 | 51 | 52 | 53 | 100% (fully charged) 54 | 85% 55 | 70% 56 | 50% 57 | 30% 58 | 15% 59 | 0% (depleted) 60 | 61 | 62 | 63 | 100%% (fully charged) 64 | 85%% 65 | 70%% 66 | 50%% 67 | 30%% 68 | 15%% 69 | 0%% (depleted) 70 | 71 | 72 | 73 | 100 74 | 85 75 | 70 76 | 50 77 | 30 78 | 15 79 | 0 80 | 81 | 82 | 83 | 4 (full reception) 84 | 3 85 | 2 86 | 1 87 | 0 (no reception) 88 | 89 | 90 | 91 | 4 92 | 3 93 | 2 94 | 1 95 | 0 96 | 97 | 98 | 99 | 1 100 | 2 101 | 3 102 | 4 103 | 5 104 | 6 105 | 7 106 | 8 107 | 108 | 109 | 110 | Hidden 111 | Silent 112 | Vibrate 113 | 114 | 115 | 116 | hidden 117 | silent 118 | vibrate 119 | 120 | 121 | 122 | Hidden 123 | Connected 124 | Disconnected 125 | 126 | 127 | 128 | hidden 129 | connected 130 | disconnected 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | #0282BE 13 | #015580 14 | #4FC3F7 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 16dp 12 | 16dp 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | QuickDemo 12 | Demo mode 13 | QuickDemo Settings 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/xml/demo_mode_preferences.xml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 12 | 13 | 17 | 18 | 19 | 26 | 27 | 28 | 29 | 33 | 34 | 35 | 36 | 42 | 43 | 47 | 48 | 52 | 53 | 54 | 55 | 56 | 60 | android:singleLine="true"/> 61 | 62 | 63 | 64 | 65 | 69 | 70 | 74 | 75 | 81 | 82 | 89 | 90 | 94 | 95 | 99 | 100 | 104 | 105 | 111 | 112 | 113 | 114 | 118 | 119 | 125 | 126 | 127 | 128 | 134 | 135 | 141 | 142 | 146 | 147 | 151 | 152 | 156 | 157 | 161 | 162 | 166 | 167 | 171 | 172 | 173 | 174 | 175 | -------------------------------------------------------------------------------- /app/src/test/java/com/pspdfkit/labs/quickdemo/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016-2019 PSPDFKit GmbH. All rights reserved. 3 | * 4 | * THIS SOURCE CODE AND ANY ACCOMPANYING DOCUMENTATION ARE PROTECTED BY INTERNATIONAL COPYRIGHT LAW 5 | * AND MAY NOT BE RESOLD OR REDISTRIBUTED. USAGE IS BOUND TO THE PSPDFKIT LICENSE AGREEMENT. 6 | * UNAUTHORIZED REPRODUCTION OR DISTRIBUTION IS SUBJECT TO CIVIL AND CRIMINAL PENALTIES. 7 | * This notice may not be removed from this file 8 | */ 9 | 10 | package com.pspdfkit.labs.quickdemo 11 | 12 | import org.junit.Assert.assertEquals 13 | import org.junit.Test 14 | 15 | /** 16 | * Example local unit test, which will execute on the development machine (host). 17 | 18 | * @see [Testing documentation](http://d.android.com/tools/testing) 19 | */ 20 | class ExampleUnitTest { 21 | @Test 22 | @Throws(Exception::class) 23 | fun addition_isCorrect() { 24 | assertEquals(4, (2 + 2).toLong()) 25 | } 26 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016-2019 PSPDFKit GmbH. All rights reserved. 3 | * 4 | * THIS SOURCE CODE AND ANY ACCOMPANYING DOCUMENTATION ARE PROTECTED BY INTERNATIONAL COPYRIGHT LAW 5 | * AND MAY NOT BE RESOLD OR REDISTRIBUTED. USAGE IS BOUND TO THE PSPDFKIT LICENSE AGREEMENT. 6 | * UNAUTHORIZED REPRODUCTION OR DISTRIBUTION IS SUBJECT TO CIVIL AND CRIMINAL PENALTIES. 7 | * This notice may not be removed from this file 8 | */ 9 | 10 | buildscript { 11 | ext.kotlin_version = '1.3.31' 12 | 13 | repositories { 14 | jcenter() 15 | google() 16 | } 17 | 18 | dependencies { 19 | classpath 'com.android.tools.build:gradle:3.4.1' 20 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 21 | classpath 'com.novoda:gradle-android-command-plugin:1.6.2' 22 | } 23 | } 24 | 25 | allprojects { 26 | repositories { 27 | jcenter() 28 | google() 29 | } 30 | } 31 | 32 | task clean(type: Delete) { 33 | delete rootProject.buildDir 34 | } 35 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2016-2019 PSPDFKit GmbH. All rights reserved. 3 | # 4 | # THIS SOURCE CODE AND ANY ACCOMPANYING DOCUMENTATION ARE PROTECTED BY INTERNATIONAL COPYRIGHT LAW 5 | # AND MAY NOT BE RESOLD OR REDISTRIBUTED. USAGE IS BOUND TO THE PSPDFKIT LICENSE AGREEMENT. 6 | # UNAUTHORIZED REPRODUCTION OR DISTRIBUTION IS SUBJECT TO CIVIL AND CRIMINAL PENALTIES. 7 | # This notice may not be removed from this file 8 | # 9 | 10 | org.gradle.jvmargs=-Xmx1536m 11 | kotlin.incremental=true 12 | android.useAndroidX=true 13 | android.enableJetifier=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PSPDFKit-labs/QuickDemo/8fda952e4019f3b6437d1605be86165764dd88ce/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | # 2 | # gradle-wrapper.properties 3 | # 4 | # PSPDFKit 5 | # 6 | # Copyright (c) 2016 PSPDFKit GmbH. All rights reserved. 7 | # 8 | # THIS SOURCE CODE AND ANY ACCOMPANYING DOCUMENTATION ARE PROTECTED BY INTERNATIONAL COPYRIGHT LAW 9 | # AND MAY NOT BE RESOLD OR REDISTRIBUTED. USAGE IS BOUND TO THE PSPDFKIT LICENSE AGREEMENT. 10 | # UNAUTHORIZED REPRODUCTION OR DISTRIBUTION IS SUBJECT TO CIVIL AND CRIMINAL PENALTIES. 11 | # This notice may not be removed from this file 12 | # 13 | 14 | #Mon Oct 16 10:43:14 EDT 2017 15 | distributionBase=GRADLE_USER_HOME 16 | distributionPath=wrapper/dists 17 | zipStoreBase=GRADLE_USER_HOME 18 | zipStorePath=wrapper/dists 19 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip 20 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016-2019 PSPDFKit GmbH. All rights reserved. 3 | * 4 | * THIS SOURCE CODE AND ANY ACCOMPANYING DOCUMENTATION ARE PROTECTED BY INTERNATIONAL COPYRIGHT LAW 5 | * AND MAY NOT BE RESOLD OR REDISTRIBUTED. USAGE IS BOUND TO THE PSPDFKIT LICENSE AGREEMENT. 6 | * UNAUTHORIZED REPRODUCTION OR DISTRIBUTION IS SUBJECT TO CIVIL AND CRIMINAL PENALTIES. 7 | * This notice may not be removed from this file 8 | */ 9 | 10 | include ':app' 11 | -------------------------------------------------------------------------------- /setup-guide.md: -------------------------------------------------------------------------------- 1 | ***QuickDemo is a tool for Android developers. If you don't know what this application is for, do not use it!*** 2 | 3 | ## Setup 4 | 5 | To enable _QuickDemo_ you need to do two things. 6 | 7 | 1. Enable the System UI demo mode on your phone. 8 | 2. Grant the `android.permission.DUMP` to the QuickDemo app. This permissions is protected and can't be requested at runtime. 9 | 10 | This can be achieved by running the following `gradle` task. 11 | 12 | ``` 13 | ./gradlew setupDemoMode 14 | ``` 15 | 16 | If you don't have the project. Both steps can be performed manually from command line via `adb`. 17 | 18 | ``` 19 | # Ensure demo mode is activated. 20 | adb shell settings put global sysui_demo_allowed 1 21 | 22 | # Grant required permissions. 23 | adb shell pm grant com.pspdfkit.labs.quickdemo android.permission.DUMP 24 | ``` 25 | 26 | **Please note:** The permissions is granted until you revoke it or uninstall the app. 27 | -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [[ $1 ]] || [[ ${ANDROID_HOME} ]]; 4 | then 5 | cd ~ 6 | git clone git@github.com:PSPDFKit-labs/QuickDemo.git 7 | cd QuickDemo/ 8 | if [[ $1 ]]; then echo "sdk.dir="$1 >>local.properties 9 | fi 10 | ./gradlew installDebug 11 | ./gradlew setupDemoMode 12 | cd ~ 13 | yes | rm -r QuickDemo 14 | else 15 | echo "Missing ANDROID_HOME" 16 | echo "Usage: ./quick_demo.sh ANDROID_HOME" 17 | echo "or set an ANROID_HOME environment variable" 18 | fi 19 | -------------------------------------------------------------------------------- /showcase.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PSPDFKit-labs/QuickDemo/8fda952e4019f3b6437d1605be86165764dd88ce/showcase.gif --------------------------------------------------------------------------------