@capgo/capacitor-android-kiosk 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +340 -0
- package/android/build.gradle +59 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/ee/forgr/plugin/android_kiosk/CapacitorAndroidKioskPlugin.java +283 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/docs.json +316 -0
- package/dist/esm/definitions.d.ts +158 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +17 -0
- package/dist/esm/web.js +27 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +41 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +44 -0
- package/dist/plugin.js.map +1 -0
- package/package.json +77 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Martin Donadieu
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
# @capgo/capacitor-android-kiosk
|
|
2
|
+
|
|
3
|
+
Android Kiosk Mode plugin for Capacitor - Lock device into kiosk mode with launcher functionality
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @capgo/capacitor-android-kiosk
|
|
9
|
+
npx cap sync
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Platform Support
|
|
13
|
+
|
|
14
|
+
This plugin is **Android-only**. For iOS kiosk mode functionality, please use the device's built-in [Guided Access](https://support.apple.com/en-us/HT202612) feature.
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
- **Kiosk Mode**: Hide system UI and enter immersive fullscreen mode
|
|
19
|
+
- **Launcher Integration**: Set your app as the device launcher/home app
|
|
20
|
+
- **Hardware Key Control**: Block or allow specific hardware buttons
|
|
21
|
+
- **Status Detection**: Check if kiosk mode is active or if app is set as launcher
|
|
22
|
+
- **Android 6.0+**: Supports Android API 23 through Android 15 (API 35)
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
### Basic Kiosk Mode
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { CapacitorAndroidKiosk } from '@capgo/capacitor-android-kiosk';
|
|
30
|
+
|
|
31
|
+
// Enter kiosk mode
|
|
32
|
+
await CapacitorAndroidKiosk.enterKioskMode();
|
|
33
|
+
|
|
34
|
+
// Exit kiosk mode
|
|
35
|
+
await CapacitorAndroidKiosk.exitKioskMode();
|
|
36
|
+
|
|
37
|
+
// Check if in kiosk mode
|
|
38
|
+
const { isInKioskMode } = await CapacitorAndroidKiosk.isInKioskMode();
|
|
39
|
+
console.log('Kiosk mode active:', isInKioskMode);
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Launcher Functionality
|
|
43
|
+
|
|
44
|
+
For full kiosk mode functionality, you need to set your app as the device launcher:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
// Open home screen settings for user to select your app as launcher
|
|
48
|
+
await CapacitorAndroidKiosk.setAsLauncher();
|
|
49
|
+
|
|
50
|
+
// Check if app is set as launcher
|
|
51
|
+
const { isLauncher } = await CapacitorAndroidKiosk.isSetAsLauncher();
|
|
52
|
+
console.log('App is launcher:', isLauncher);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Hardware Key Control
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
// Allow only volume keys
|
|
59
|
+
await CapacitorAndroidKiosk.setAllowedKeys({
|
|
60
|
+
volumeUp: true,
|
|
61
|
+
volumeDown: true,
|
|
62
|
+
back: false,
|
|
63
|
+
home: false,
|
|
64
|
+
recent: false
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Block all keys (default)
|
|
68
|
+
await CapacitorAndroidKiosk.setAllowedKeys({});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Complete Example
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
async function setupKioskMode() {
|
|
75
|
+
try {
|
|
76
|
+
// Check if already set as launcher
|
|
77
|
+
const { isLauncher } = await CapacitorAndroidKiosk.isSetAsLauncher();
|
|
78
|
+
|
|
79
|
+
if (!isLauncher) {
|
|
80
|
+
// Prompt user to set as launcher
|
|
81
|
+
await CapacitorAndroidKiosk.setAsLauncher();
|
|
82
|
+
alert('Please select this app as your Home app');
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Configure allowed keys
|
|
87
|
+
await CapacitorAndroidKiosk.setAllowedKeys({
|
|
88
|
+
volumeUp: true,
|
|
89
|
+
volumeDown: true,
|
|
90
|
+
back: false,
|
|
91
|
+
home: false,
|
|
92
|
+
recent: false,
|
|
93
|
+
power: false
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Enter kiosk mode
|
|
97
|
+
await CapacitorAndroidKiosk.enterKioskMode();
|
|
98
|
+
console.log('Kiosk mode activated');
|
|
99
|
+
|
|
100
|
+
} catch (error) {
|
|
101
|
+
console.error('Failed to setup kiosk mode:', error);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## API
|
|
107
|
+
|
|
108
|
+
<docgen-index>
|
|
109
|
+
|
|
110
|
+
* [`isInKioskMode()`](#isinkioskmode)
|
|
111
|
+
* [`isSetAsLauncher()`](#issetaslauncher)
|
|
112
|
+
* [`enterKioskMode()`](#enterkioskmode)
|
|
113
|
+
* [`exitKioskMode()`](#exitkioskmode)
|
|
114
|
+
* [`setAsLauncher()`](#setaslauncher)
|
|
115
|
+
* [`setAllowedKeys(...)`](#setallowedkeys)
|
|
116
|
+
* [`getPluginVersion()`](#getpluginversion)
|
|
117
|
+
* [Interfaces](#interfaces)
|
|
118
|
+
|
|
119
|
+
</docgen-index>
|
|
120
|
+
|
|
121
|
+
<docgen-api>
|
|
122
|
+
<!--Update the source file JSDoc comments and rerun docgen to update the docs below-->
|
|
123
|
+
|
|
124
|
+
Capacitor Android Kiosk Plugin for controlling kiosk mode and launcher functionality.
|
|
125
|
+
This plugin is Android-only. For iOS kiosk mode, use the device's Guided Access feature.
|
|
126
|
+
|
|
127
|
+
### isInKioskMode()
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
isInKioskMode() => Promise<{ isInKioskMode: boolean; }>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Checks if the app is currently running in kiosk mode.
|
|
134
|
+
|
|
135
|
+
**Returns:** <code>Promise<{ isInKioskMode: boolean; }></code>
|
|
136
|
+
|
|
137
|
+
**Since:** 1.0.0
|
|
138
|
+
|
|
139
|
+
--------------------
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
### isSetAsLauncher()
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
isSetAsLauncher() => Promise<{ isLauncher: boolean; }>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Checks if the app is set as the device launcher (home app).
|
|
149
|
+
|
|
150
|
+
**Returns:** <code>Promise<{ isLauncher: boolean; }></code>
|
|
151
|
+
|
|
152
|
+
**Since:** 1.0.0
|
|
153
|
+
|
|
154
|
+
--------------------
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
### enterKioskMode()
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
enterKioskMode() => Promise<void>
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Enters kiosk mode, hiding system UI and blocking hardware buttons.
|
|
164
|
+
The app must be set as the device launcher for this to work effectively.
|
|
165
|
+
|
|
166
|
+
**Since:** 1.0.0
|
|
167
|
+
|
|
168
|
+
--------------------
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
### exitKioskMode()
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
exitKioskMode() => Promise<void>
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Exits kiosk mode, restoring normal system UI and hardware button functionality.
|
|
178
|
+
|
|
179
|
+
**Since:** 1.0.0
|
|
180
|
+
|
|
181
|
+
--------------------
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
### setAsLauncher()
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
setAsLauncher() => Promise<void>
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Opens the device's home screen settings to allow user to set this app as the launcher.
|
|
191
|
+
This is required for full kiosk mode functionality.
|
|
192
|
+
|
|
193
|
+
**Since:** 1.0.0
|
|
194
|
+
|
|
195
|
+
--------------------
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
### setAllowedKeys(...)
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
setAllowedKeys(options: AllowedKeysOptions) => Promise<void>
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Sets which hardware keys are allowed to function in kiosk mode.
|
|
205
|
+
By default, all hardware keys are blocked in kiosk mode.
|
|
206
|
+
|
|
207
|
+
| Param | Type | Description |
|
|
208
|
+
| ------------- | ----------------------------------------------------------------- | ------------------------------ |
|
|
209
|
+
| **`options`** | <code><a href="#allowedkeysoptions">AllowedKeysOptions</a></code> | Configuration for allowed keys |
|
|
210
|
+
|
|
211
|
+
**Since:** 1.0.0
|
|
212
|
+
|
|
213
|
+
--------------------
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
### getPluginVersion()
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
getPluginVersion() => Promise<{ version: string; }>
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Get the native Capacitor plugin version.
|
|
223
|
+
|
|
224
|
+
**Returns:** <code>Promise<{ version: string; }></code>
|
|
225
|
+
|
|
226
|
+
**Since:** 1.0.0
|
|
227
|
+
|
|
228
|
+
--------------------
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
### Interfaces
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
#### AllowedKeysOptions
|
|
235
|
+
|
|
236
|
+
Configuration options for allowed hardware keys in kiosk mode.
|
|
237
|
+
|
|
238
|
+
| Prop | Type | Description | Default |
|
|
239
|
+
| ---------------- | -------------------- | -------------------------------- | ------------------ |
|
|
240
|
+
| **`volumeUp`** | <code>boolean</code> | Allow volume up button | <code>false</code> |
|
|
241
|
+
| **`volumeDown`** | <code>boolean</code> | Allow volume down button | <code>false</code> |
|
|
242
|
+
| **`back`** | <code>boolean</code> | Allow back button | <code>false</code> |
|
|
243
|
+
| **`home`** | <code>boolean</code> | Allow home button | <code>false</code> |
|
|
244
|
+
| **`recent`** | <code>boolean</code> | Allow recent apps button | <code>false</code> |
|
|
245
|
+
| **`power`** | <code>boolean</code> | Allow power button | <code>false</code> |
|
|
246
|
+
| **`camera`** | <code>boolean</code> | Allow camera button (if present) | <code>false</code> |
|
|
247
|
+
| **`menu`** | <code>boolean</code> | Allow menu button (if present) | <code>false</code> |
|
|
248
|
+
|
|
249
|
+
</docgen-api>
|
|
250
|
+
|
|
251
|
+
## Android Configuration
|
|
252
|
+
|
|
253
|
+
### 1. MainActivity Setup
|
|
254
|
+
|
|
255
|
+
To enable full hardware key blocking, you need to override `dispatchKeyEvent` in your `MainActivity.java`:
|
|
256
|
+
|
|
257
|
+
```java
|
|
258
|
+
import android.view.KeyEvent;
|
|
259
|
+
import ee.forgr.plugin.android_kiosk.CapacitorAndroidKioskPlugin;
|
|
260
|
+
|
|
261
|
+
public class MainActivity extends BridgeActivity {
|
|
262
|
+
@Override
|
|
263
|
+
public boolean dispatchKeyEvent(KeyEvent event) {
|
|
264
|
+
// Get the kiosk plugin
|
|
265
|
+
CapacitorAndroidKioskPlugin kioskPlugin = (CapacitorAndroidKioskPlugin)
|
|
266
|
+
this.getBridge().getPlugin("CapacitorAndroidKiosk").getInstance();
|
|
267
|
+
|
|
268
|
+
if (kioskPlugin != null && kioskPlugin.shouldBlockKey(event.getKeyCode())) {
|
|
269
|
+
return true; // Block the key
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return super.dispatchKeyEvent(event);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
@Override
|
|
276
|
+
public void onBackPressed() {
|
|
277
|
+
// Don't call super.onBackPressed() to disable back button
|
|
278
|
+
// Or call the plugin's handleOnBackPressed
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### 2. AndroidManifest.xml
|
|
284
|
+
|
|
285
|
+
Add launcher intent filter to make your app selectable as a launcher:
|
|
286
|
+
|
|
287
|
+
```xml
|
|
288
|
+
<activity
|
|
289
|
+
android:name=".MainActivity"
|
|
290
|
+
...>
|
|
291
|
+
|
|
292
|
+
<!-- Existing intent filter -->
|
|
293
|
+
<intent-filter>
|
|
294
|
+
<action android:name="android.intent.action.MAIN" />
|
|
295
|
+
<category android:name="android.intent.category.LAUNCHER" />
|
|
296
|
+
</intent-filter>
|
|
297
|
+
|
|
298
|
+
<!-- Add this to make app selectable as launcher -->
|
|
299
|
+
<intent-filter>
|
|
300
|
+
<action android:name="android.intent.action.MAIN" />
|
|
301
|
+
<category android:name="android.intent.category.HOME" />
|
|
302
|
+
<category android:name="android.intent.category.DEFAULT" />
|
|
303
|
+
</intent-filter>
|
|
304
|
+
</activity>
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## Important Notes
|
|
308
|
+
|
|
309
|
+
1. **Launcher Requirement**: For full kiosk mode functionality (blocking home button, preventing task switching), your app must be set as the device launcher.
|
|
310
|
+
|
|
311
|
+
2. **Testing**: When testing, you can exit kiosk mode programmatically or by setting another app as the launcher.
|
|
312
|
+
|
|
313
|
+
3. **Android Versions**: The plugin uses modern Android APIs for Android 11+ and falls back to older methods for compatibility with Android 6.0+.
|
|
314
|
+
|
|
315
|
+
4. **Security**: This plugin is designed for legitimate kiosk applications. Ensure you provide users with a way to exit kiosk mode.
|
|
316
|
+
|
|
317
|
+
5. **Battery**: Kiosk mode keeps the screen on. Consider implementing your own screen timeout or brightness management.
|
|
318
|
+
|
|
319
|
+
## iOS Alternative
|
|
320
|
+
|
|
321
|
+
For iOS devices, use the built-in [Guided Access](https://support.apple.com/en-us/HT202612) feature:
|
|
322
|
+
|
|
323
|
+
1. Go to Settings > Accessibility > Guided Access
|
|
324
|
+
2. Turn on Guided Access
|
|
325
|
+
3. Set a passcode
|
|
326
|
+
4. Open your app
|
|
327
|
+
5. Triple-click the side button
|
|
328
|
+
6. Adjust settings and start Guided Access
|
|
329
|
+
|
|
330
|
+
## Contributing
|
|
331
|
+
|
|
332
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md)
|
|
333
|
+
|
|
334
|
+
## License
|
|
335
|
+
|
|
336
|
+
MIT
|
|
337
|
+
|
|
338
|
+
## Author
|
|
339
|
+
|
|
340
|
+
Martin Donadieu <martin@capgo.app>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
ext {
|
|
2
|
+
junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
|
|
3
|
+
androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.2.1'
|
|
4
|
+
androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1'
|
|
5
|
+
androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.0'
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
buildscript {
|
|
9
|
+
repositories {
|
|
10
|
+
mavenCentral()
|
|
11
|
+
google()
|
|
12
|
+
}
|
|
13
|
+
dependencies {
|
|
14
|
+
classpath 'com.android.tools.build:gradle:8.7.2'
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
apply plugin: 'com.android.library'
|
|
19
|
+
|
|
20
|
+
android {
|
|
21
|
+
namespace "ee.forgr.plugin.android_kiosk"
|
|
22
|
+
compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35
|
|
23
|
+
defaultConfig {
|
|
24
|
+
minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23
|
|
25
|
+
targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35
|
|
26
|
+
versionCode 1
|
|
27
|
+
versionName "1.0"
|
|
28
|
+
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
29
|
+
}
|
|
30
|
+
buildTypes {
|
|
31
|
+
release {
|
|
32
|
+
minifyEnabled false
|
|
33
|
+
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
lintOptions {
|
|
37
|
+
abortOnError false
|
|
38
|
+
}
|
|
39
|
+
compileOptions {
|
|
40
|
+
sourceCompatibility JavaVersion.VERSION_21
|
|
41
|
+
targetCompatibility JavaVersion.VERSION_21
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
repositories {
|
|
46
|
+
google()
|
|
47
|
+
jcenter()
|
|
48
|
+
mavenCentral()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
dependencies {
|
|
53
|
+
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
|
54
|
+
implementation project(':capacitor-android')
|
|
55
|
+
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
|
56
|
+
testImplementation "junit:junit:$junitVersion"
|
|
57
|
+
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
|
58
|
+
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
|
59
|
+
}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
package ee.forgr.plugin.android_kiosk;
|
|
2
|
+
|
|
3
|
+
import android.app.Activity;
|
|
4
|
+
import android.app.ActivityManager;
|
|
5
|
+
import android.content.ComponentName;
|
|
6
|
+
import android.content.Context;
|
|
7
|
+
import android.content.Intent;
|
|
8
|
+
import android.content.pm.PackageManager;
|
|
9
|
+
import android.os.Build;
|
|
10
|
+
import android.view.KeyEvent;
|
|
11
|
+
import android.view.View;
|
|
12
|
+
import android.view.WindowManager;
|
|
13
|
+
import com.getcapacitor.JSObject;
|
|
14
|
+
import com.getcapacitor.Plugin;
|
|
15
|
+
import com.getcapacitor.PluginCall;
|
|
16
|
+
import com.getcapacitor.PluginMethod;
|
|
17
|
+
import com.getcapacitor.annotation.CapacitorPlugin;
|
|
18
|
+
import java.util.HashSet;
|
|
19
|
+
import java.util.Set;
|
|
20
|
+
|
|
21
|
+
@CapacitorPlugin(name = "CapacitorAndroidKiosk")
|
|
22
|
+
public class CapacitorAndroidKioskPlugin extends Plugin {
|
|
23
|
+
|
|
24
|
+
private final String pluginVersion = "1.0.0";
|
|
25
|
+
private boolean isInKioskMode = false;
|
|
26
|
+
private final Set<Integer> allowedKeys = new HashSet<>();
|
|
27
|
+
|
|
28
|
+
@Override
|
|
29
|
+
public void load() {
|
|
30
|
+
// Initialize with no allowed keys by default
|
|
31
|
+
allowedKeys.clear();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@PluginMethod
|
|
35
|
+
public void isInKioskMode(PluginCall call) {
|
|
36
|
+
JSObject ret = new JSObject();
|
|
37
|
+
ret.put("isInKioskMode", isInKioskMode);
|
|
38
|
+
call.resolve(ret);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@PluginMethod
|
|
42
|
+
public void isSetAsLauncher(PluginCall call) {
|
|
43
|
+
JSObject ret = new JSObject();
|
|
44
|
+
boolean isLauncher = checkIfLauncher();
|
|
45
|
+
ret.put("isLauncher", isLauncher);
|
|
46
|
+
call.resolve(ret);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@PluginMethod
|
|
50
|
+
public void enterKioskMode(PluginCall call) {
|
|
51
|
+
try {
|
|
52
|
+
Activity activity = getActivity();
|
|
53
|
+
if (activity == null) {
|
|
54
|
+
call.reject("Activity not available");
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
activity.runOnUiThread(() -> {
|
|
59
|
+
try {
|
|
60
|
+
View decorView = activity.getWindow().getDecorView();
|
|
61
|
+
|
|
62
|
+
// Hide system UI for different Android versions
|
|
63
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
64
|
+
// Android 11+ (API 30+)
|
|
65
|
+
activity.getWindow().setDecorFitsSystemWindows(false);
|
|
66
|
+
android.view.WindowInsetsController controller = decorView.getWindowInsetsController();
|
|
67
|
+
if (controller != null) {
|
|
68
|
+
controller.hide(android.view.WindowInsets.Type.statusBars() | android.view.WindowInsets.Type.navigationBars());
|
|
69
|
+
controller.setSystemBarsBehavior(android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
// Android 10 and below
|
|
73
|
+
int uiOptions =
|
|
74
|
+
View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
|
|
75
|
+
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
|
|
76
|
+
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
|
|
77
|
+
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
|
|
78
|
+
View.SYSTEM_UI_FLAG_FULLSCREEN |
|
|
79
|
+
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
|
|
80
|
+
decorView.setSystemUiVisibility(uiOptions);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Keep screen on
|
|
84
|
+
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
|
85
|
+
|
|
86
|
+
// Prevent status bar from being pulled down
|
|
87
|
+
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
|
88
|
+
|
|
89
|
+
isInKioskMode = true;
|
|
90
|
+
call.resolve();
|
|
91
|
+
} catch (Exception e) {
|
|
92
|
+
call.reject("Failed to enter kiosk mode", e);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
} catch (Exception e) {
|
|
96
|
+
call.reject("Failed to enter kiosk mode", e);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@PluginMethod
|
|
101
|
+
public void exitKioskMode(PluginCall call) {
|
|
102
|
+
try {
|
|
103
|
+
Activity activity = getActivity();
|
|
104
|
+
if (activity == null) {
|
|
105
|
+
call.reject("Activity not available");
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
activity.runOnUiThread(() -> {
|
|
110
|
+
try {
|
|
111
|
+
View decorView = activity.getWindow().getDecorView();
|
|
112
|
+
|
|
113
|
+
// Restore system UI for different Android versions
|
|
114
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
115
|
+
// Android 11+ (API 30+)
|
|
116
|
+
activity.getWindow().setDecorFitsSystemWindows(true);
|
|
117
|
+
android.view.WindowInsetsController controller = decorView.getWindowInsetsController();
|
|
118
|
+
if (controller != null) {
|
|
119
|
+
controller.show(android.view.WindowInsets.Type.statusBars() | android.view.WindowInsets.Type.navigationBars());
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
// Android 10 and below
|
|
123
|
+
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Clear screen on flag
|
|
127
|
+
activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
|
128
|
+
|
|
129
|
+
// Clear fullscreen flag
|
|
130
|
+
activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
|
131
|
+
|
|
132
|
+
isInKioskMode = false;
|
|
133
|
+
call.resolve();
|
|
134
|
+
} catch (Exception e) {
|
|
135
|
+
call.reject("Failed to exit kiosk mode", e);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
} catch (Exception e) {
|
|
139
|
+
call.reject("Failed to exit kiosk mode", e);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
@PluginMethod
|
|
144
|
+
public void setAsLauncher(PluginCall call) {
|
|
145
|
+
try {
|
|
146
|
+
Context context = getContext();
|
|
147
|
+
if (context == null) {
|
|
148
|
+
call.reject("Context not available");
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Enable launcher intent filter
|
|
153
|
+
ComponentName componentName = new ComponentName(context, getLauncherActivity());
|
|
154
|
+
PackageManager packageManager = context.getPackageManager();
|
|
155
|
+
packageManager.setComponentEnabledSetting(
|
|
156
|
+
componentName,
|
|
157
|
+
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
|
158
|
+
PackageManager.DONT_KILL_APP
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
// Open home screen settings
|
|
162
|
+
Intent intent = new Intent(android.provider.Settings.ACTION_HOME_SETTINGS);
|
|
163
|
+
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
164
|
+
context.startActivity(intent);
|
|
165
|
+
|
|
166
|
+
call.resolve();
|
|
167
|
+
} catch (Exception e) {
|
|
168
|
+
call.reject("Failed to set as launcher", e);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
@PluginMethod
|
|
173
|
+
public void setAllowedKeys(PluginCall call) {
|
|
174
|
+
allowedKeys.clear();
|
|
175
|
+
|
|
176
|
+
// Parse allowed keys from options
|
|
177
|
+
if (call.getBoolean("volumeUp", false)) {
|
|
178
|
+
allowedKeys.add(KeyEvent.KEYCODE_VOLUME_UP);
|
|
179
|
+
}
|
|
180
|
+
if (call.getBoolean("volumeDown", false)) {
|
|
181
|
+
allowedKeys.add(KeyEvent.KEYCODE_VOLUME_DOWN);
|
|
182
|
+
}
|
|
183
|
+
if (call.getBoolean("back", false)) {
|
|
184
|
+
allowedKeys.add(KeyEvent.KEYCODE_BACK);
|
|
185
|
+
}
|
|
186
|
+
if (call.getBoolean("home", false)) {
|
|
187
|
+
allowedKeys.add(KeyEvent.KEYCODE_HOME);
|
|
188
|
+
}
|
|
189
|
+
if (call.getBoolean("recent", false)) {
|
|
190
|
+
allowedKeys.add(KeyEvent.KEYCODE_APP_SWITCH);
|
|
191
|
+
}
|
|
192
|
+
if (call.getBoolean("power", false)) {
|
|
193
|
+
allowedKeys.add(KeyEvent.KEYCODE_POWER);
|
|
194
|
+
}
|
|
195
|
+
if (call.getBoolean("camera", false)) {
|
|
196
|
+
allowedKeys.add(KeyEvent.KEYCODE_CAMERA);
|
|
197
|
+
}
|
|
198
|
+
if (call.getBoolean("menu", false)) {
|
|
199
|
+
allowedKeys.add(KeyEvent.KEYCODE_MENU);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
call.resolve();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
@PluginMethod
|
|
206
|
+
public void getPluginVersion(PluginCall call) {
|
|
207
|
+
try {
|
|
208
|
+
JSObject ret = new JSObject();
|
|
209
|
+
ret.put("version", pluginVersion);
|
|
210
|
+
call.resolve(ret);
|
|
211
|
+
} catch (Exception e) {
|
|
212
|
+
call.reject("Could not get plugin version", e);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Handle key events to block hardware buttons when in kiosk mode
|
|
218
|
+
* This method should be called from the main activity's dispatchKeyEvent
|
|
219
|
+
*/
|
|
220
|
+
@Override
|
|
221
|
+
protected void handleOnPause() {
|
|
222
|
+
super.handleOnPause();
|
|
223
|
+
if (isInKioskMode) {
|
|
224
|
+
Activity activity = getActivity();
|
|
225
|
+
if (activity != null) {
|
|
226
|
+
// Bring app back to foreground if in kiosk mode
|
|
227
|
+
ActivityManager activityManager = (ActivityManager) activity.getSystemService(Context.ACTIVITY_SERVICE);
|
|
228
|
+
if (activityManager != null) {
|
|
229
|
+
activityManager.moveTaskToFront(activity.getTaskId(), 0);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Check if this app is set as the default launcher
|
|
237
|
+
*/
|
|
238
|
+
private boolean checkIfLauncher() {
|
|
239
|
+
try {
|
|
240
|
+
Context context = getContext();
|
|
241
|
+
if (context == null) {
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
Intent intent = new Intent(Intent.ACTION_MAIN);
|
|
246
|
+
intent.addCategory(Intent.CATEGORY_HOME);
|
|
247
|
+
|
|
248
|
+
PackageManager packageManager = context.getPackageManager();
|
|
249
|
+
ComponentName componentName = intent.resolveActivity(packageManager);
|
|
250
|
+
|
|
251
|
+
if (componentName == null) {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return componentName.getPackageName().equals(context.getPackageName());
|
|
256
|
+
} catch (Exception e) {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Get the launcher activity class
|
|
263
|
+
* This should be the main activity of the Capacitor app
|
|
264
|
+
*/
|
|
265
|
+
private Class<?> getLauncherActivity() {
|
|
266
|
+
Activity activity = getActivity();
|
|
267
|
+
if (activity != null) {
|
|
268
|
+
return activity.getClass();
|
|
269
|
+
}
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Check if a key event should be blocked
|
|
275
|
+
* Call this from your MainActivity's dispatchKeyEvent method
|
|
276
|
+
*/
|
|
277
|
+
public boolean shouldBlockKey(int keyCode) {
|
|
278
|
+
if (!isInKioskMode) {
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
return !allowedKeys.contains(keyCode);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
File without changes
|