@capgo/capacitor-android-usagestatsmanager 8.1.2 → 8.2.1
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/README.md +159 -4
- package/android/src/main/java/ee/forgr/capacitor_android_usagestatsmanager/CapacitorUsageStatsManagerPlugin.java +209 -4
- package/dist/docs.json +308 -2
- package/dist/esm/definitions.d.ts +197 -4
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +21 -1
- package/dist/esm/web.js +24 -0
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +24 -0
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +24 -0
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapacitorUsageStatsManagerPlugin/CapacitorUsageStatsManagerPlugin.swift +26 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,6 +15,7 @@ The only plugin exposing Android's **UsageStatsManager API** to Capacitor - this
|
|
|
15
15
|
|
|
16
16
|
- **App usage tracking** - Monitor which apps users open and for how long
|
|
17
17
|
- **Screen time analytics** - Build parental controls and digital wellbeing features
|
|
18
|
+
- **Exact-window events** - Query raw lifecycle events when you need foreground time clipped to a specific range
|
|
18
19
|
- **Package information** - Query all installed apps on the device
|
|
19
20
|
- **Time-based queries** - Get usage stats for any time range
|
|
20
21
|
|
|
@@ -74,6 +75,8 @@ npx cap sync
|
|
|
74
75
|
<docgen-index>
|
|
75
76
|
|
|
76
77
|
* [`queryAndAggregateUsageStats(...)`](#queryandaggregateusagestats)
|
|
78
|
+
* [`queryUsageStats(...)`](#queryusagestats)
|
|
79
|
+
* [`queryEvents(...)`](#queryevents)
|
|
77
80
|
* [`isUsageStatsPermissionGranted()`](#isusagestatspermissiongranted)
|
|
78
81
|
* [`openUsageStatsSettings()`](#openusagestatssettings)
|
|
79
82
|
* [`queryAllPackages(...)`](#queryallpackages)
|
|
@@ -96,6 +99,12 @@ queryAndAggregateUsageStats(options: UsageStatsOptions) => Promise<Record<string
|
|
|
96
99
|
|
|
97
100
|
Queries and aggregates usage stats for the given time range.
|
|
98
101
|
|
|
102
|
+
Android reads pre-aggregated daily/weekly/monthly/yearly buckets and sums
|
|
103
|
+
every bucket that intersects `[beginTime, endTime)`, without clipping to it.
|
|
104
|
+
`totalTimeInForeground` can therefore include usage from outside the window.
|
|
105
|
+
Use `queryUsageStats` for the unmerged per-interval buckets, or `queryEvents`
|
|
106
|
+
for timestamped lifecycle events in the requested range.
|
|
107
|
+
|
|
99
108
|
| Param | Type | Description |
|
|
100
109
|
| ------------- | --------------------------------------------------------------- | -------------------------------------- |
|
|
101
110
|
| **`options`** | <code><a href="#usagestatsoptions">UsageStatsOptions</a></code> | - The time range options for the query |
|
|
@@ -107,6 +116,86 @@ Queries and aggregates usage stats for the given time range.
|
|
|
107
116
|
--------------------
|
|
108
117
|
|
|
109
118
|
|
|
119
|
+
### queryUsageStats(...)
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
queryUsageStats(options: QueryUsageStatsOptions) => Promise<QueryUsageStatsResult>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Queries usage stats for the given interval type and time range.
|
|
126
|
+
|
|
127
|
+
Wraps Android `UsageStatsManager.queryUsageStats`. Unlike
|
|
128
|
+
`queryAndAggregateUsageStats`, this does not merge buckets: the result is
|
|
129
|
+
one <a href="#usagestats">`UsageStats`</a> object per package per overlapping interval. Android may
|
|
130
|
+
expand `[beginTime, endTime)` to the nearest whole interval period, so
|
|
131
|
+
`totalTimeInForeground` can include usage from outside the window.
|
|
132
|
+
|
|
133
|
+
On Android R (API 30) and above, the OS returns no data while the user is
|
|
134
|
+
locked; this plugin then resolves `{ stats: [] }`, which must not be treated
|
|
135
|
+
as zero foreground usage.
|
|
136
|
+
|
|
137
|
+
This uses the same `PACKAGE_USAGE_STATS` permission as
|
|
138
|
+
`queryAndAggregateUsageStats`.
|
|
139
|
+
|
|
140
|
+
| Param | Type | Description |
|
|
141
|
+
| ------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------ |
|
|
142
|
+
| **`options`** | <code><a href="#queryusagestatsoptions">QueryUsageStatsOptions</a></code> | - The interval type, time range, and optional package filter |
|
|
143
|
+
|
|
144
|
+
**Returns:** <code>Promise<<a href="#queryusagestatsresult">QueryUsageStatsResult</a>></code>
|
|
145
|
+
|
|
146
|
+
**Since:** 8.1.3
|
|
147
|
+
|
|
148
|
+
--------------------
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
### queryEvents(...)
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
queryEvents(options: QueryEventsOptions) => Promise<QueryEventsResult>
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Queries the raw usage event log for the given time range.
|
|
158
|
+
|
|
159
|
+
Returns lifecycle events whose timestamps fall in `[beginTime, endTime)`.
|
|
160
|
+
Android does not emit a synthetic event for "already in foreground at
|
|
161
|
+
beginTime" or "still in foreground at endTime". To measure duration across
|
|
162
|
+
those boundaries, pass an earlier `beginTime` as lookback and clip locally:
|
|
163
|
+
treat an unmatched pause as starting at the window start, and an unmatched
|
|
164
|
+
resume as ending at the window end.
|
|
165
|
+
|
|
166
|
+
Android retains events for only a few days. Older ranges may return an
|
|
167
|
+
incomplete or empty list; that is not the same as zero foreground usage.
|
|
168
|
+
On Android R (API 30) and above, the OS also returns no events while the
|
|
169
|
+
user is locked; this plugin then resolves `{ events: [] }`, which likewise
|
|
170
|
+
must not be treated as zero foreground usage.
|
|
171
|
+
|
|
172
|
+
Callers can sum resumed-to-paused intervals from the returned events.
|
|
173
|
+
Do not treat `DEVICE_SHUTDOWN` as a pause: it is a reset marker, not an
|
|
174
|
+
interval close. This uses the same `PACKAGE_USAGE_STATS` permission as
|
|
175
|
+
`queryAndAggregateUsageStats`.
|
|
176
|
+
|
|
177
|
+
Only lifecycle events are returned, to keep the bridge payload small:
|
|
178
|
+
- `1` — ACTIVITY_RESUMED / MOVE_TO_FOREGROUND
|
|
179
|
+
- `2` — ACTIVITY_PAUSED / MOVE_TO_BACKGROUND
|
|
180
|
+
- `23` — ACTIVITY_STOPPED
|
|
181
|
+
- `26` — DEVICE_SHUTDOWN (device-wide reset marker; still returned when
|
|
182
|
+
`packageName` is set. Android typically reports package `"android"`.
|
|
183
|
+
`packageName` is omitted if the OS does not attach one. The timestamp
|
|
184
|
+
is the last <a href="#usagestats">UsageStats</a> persist before shutdown, not the actual
|
|
185
|
+
power-off. Open resume events without a matching pause between this
|
|
186
|
+
marker and the next boot have unknown duration and must be ignored.)
|
|
187
|
+
|
|
188
|
+
| Param | Type | Description |
|
|
189
|
+
| ------------- | ----------------------------------------------------------------- | -------------------------------------------- |
|
|
190
|
+
| **`options`** | <code><a href="#queryeventsoptions">QueryEventsOptions</a></code> | - The time range and optional package filter |
|
|
191
|
+
|
|
192
|
+
**Returns:** <code>Promise<<a href="#queryeventsresult">QueryEventsResult</a>></code>
|
|
193
|
+
|
|
194
|
+
**Since:** 8.1.3
|
|
195
|
+
|
|
196
|
+
--------------------
|
|
197
|
+
|
|
198
|
+
|
|
110
199
|
### isUsageStatsPermissionGranted()
|
|
111
200
|
|
|
112
201
|
```typescript
|
|
@@ -196,10 +285,76 @@ Usage statistics for an Android app.
|
|
|
196
285
|
|
|
197
286
|
Options for querying usage statistics.
|
|
198
287
|
|
|
199
|
-
| Prop
|
|
200
|
-
|
|
|
201
|
-
| **`beginTime`**
|
|
202
|
-
| **`endTime`**
|
|
288
|
+
| Prop | Type | Description | Since |
|
|
289
|
+
| ----------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- |
|
|
290
|
+
| **`beginTime`** | <code>number</code> | The inclusive beginning of the range of stats to include in the results. Defined in terms of "Unix time" | |
|
|
291
|
+
| **`endTime`** | <code>number</code> | The exclusive end of the range of stats to include in the results. Defined in terms of "Unix time" | |
|
|
292
|
+
| **`packageName`** | <code>string</code> | Optional package name. When set, only stats for this package are returned. Omit to return stats for every package (previous behavior). An empty string is rejected. | 8.1.3 |
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
#### QueryUsageStatsResult
|
|
296
|
+
|
|
297
|
+
Result of a `queryUsageStats` call.
|
|
298
|
+
|
|
299
|
+
| Prop | Type | Description |
|
|
300
|
+
| ----------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
301
|
+
| **`stats`** | <code>UsageStats[]</code> | Usage stats buckets in the requested range, ordered as returned by the OS. The same package can appear more than once when multiple intervals overlap. |
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
#### QueryUsageStatsOptions
|
|
305
|
+
|
|
306
|
+
Options for querying per-interval usage statistics.
|
|
307
|
+
|
|
308
|
+
| Prop | Type | Description |
|
|
309
|
+
| ------------------ | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
310
|
+
| **`intervalType`** | <code>number</code> | Interval type from `android.app.usage.UsageStatsManager`: - `0` — INTERVAL_DAILY - `1` — INTERVAL_WEEKLY - `2` — INTERVAL_MONTHLY - `3` — INTERVAL_YEARLY - `4` — INTERVAL_BEST |
|
|
311
|
+
| **`beginTime`** | <code>number</code> | The inclusive beginning of the range of stats to include in the results. Defined in terms of "Unix time" |
|
|
312
|
+
| **`endTime`** | <code>number</code> | The exclusive end of the range of stats to include in the results. Defined in terms of "Unix time" |
|
|
313
|
+
| **`packageName`** | <code>string</code> | Optional package name. When set, only stats for this package are returned. An empty string is rejected. |
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
#### QueryEventsResult
|
|
317
|
+
|
|
318
|
+
Result of a `queryEvents` call.
|
|
319
|
+
|
|
320
|
+
| Prop | Type | Description |
|
|
321
|
+
| ------------ | ------------------------- | ----------------------------------------------------------------------------- |
|
|
322
|
+
| **`events`** | <code>UsageEvent[]</code> | Lifecycle usage events in the requested range, ordered as returned by the OS. |
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
#### UsageEvent
|
|
326
|
+
|
|
327
|
+
Represents a single usage event.
|
|
328
|
+
|
|
329
|
+
`queryEvents` currently populates `className`, `timeStamp`, and `eventType`.
|
|
330
|
+
`packageName` is set when Android attaches one (DEVICE_SHUTDOWN usually uses
|
|
331
|
+
`"android"`) and omitted otherwise. Other fields remain optional for
|
|
332
|
+
compatibility.
|
|
333
|
+
|
|
334
|
+
| Prop | Type | Description |
|
|
335
|
+
| --------------------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
336
|
+
| **`packageName`** | <code>string</code> | Package name of the app. Omitted when Android does not attach a package. DEVICE_SHUTDOWN typically uses `"android"`. |
|
|
337
|
+
| **`className`** | <code>string</code> | Class name (might be null) |
|
|
338
|
+
| **`timeStamp`** | <code>number</code> | Timestamp in milliseconds since epoch |
|
|
339
|
+
| **`eventType`** | <code>number</code> | Event type constant from `android.app.usage.UsageEvents.Event`. `queryEvents` returns lifecycle types only: - `1` — ACTIVITY_RESUMED / MOVE_TO_FOREGROUND - `2` — ACTIVITY_PAUSED / MOVE_TO_BACKGROUND - `23` — ACTIVITY_STOPPED - `26` — DEVICE_SHUTDOWN (reset marker, not an interval close) |
|
|
340
|
+
| **`configuration`** | <code>any</code> | Configuration object (requires API 28+) |
|
|
341
|
+
| **`shortcutId`** | <code>string</code> | Shortcut ID (requires API 28+) |
|
|
342
|
+
| **`standbyBucket`** | <code>number</code> | App standby bucket (requires API 28+) |
|
|
343
|
+
| **`notificationChannelId`** | <code>string</code> | Notification channel ID (requires API 29+) |
|
|
344
|
+
| **`instanceId`** | <code>number</code> | Instance ID (requires API 30+) |
|
|
345
|
+
| **`taskRootPackageName`** | <code>string</code> | Task root package name (requires API 31+) |
|
|
346
|
+
| **`taskRootClassName`** | <code>string</code> | Task root class name (requires API 31+) |
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
#### QueryEventsOptions
|
|
350
|
+
|
|
351
|
+
Options for querying the raw usage event log.
|
|
352
|
+
|
|
353
|
+
| Prop | Type | Description |
|
|
354
|
+
| ----------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
355
|
+
| **`beginTime`** | <code>number</code> | The inclusive beginning of the range of events to include in the results. Defined in terms of "Unix time" |
|
|
356
|
+
| **`endTime`** | <code>number</code> | The exclusive end of the range of events to include in the results. Defined in terms of "Unix time" |
|
|
357
|
+
| **`packageName`** | <code>string</code> | Optional package name. When set, only events for this package are returned, plus device-wide `DEVICE_SHUTDOWN` events. An empty string is rejected. Keeps the Capacitor bridge payload small when you care about one app. |
|
|
203
358
|
|
|
204
359
|
|
|
205
360
|
#### UsageStatsPermissionResult
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
package ee.forgr.capacitor_android_usagestatsmanager;
|
|
2
2
|
|
|
3
3
|
import android.app.AppOpsManager;
|
|
4
|
+
import android.app.usage.UsageEvents;
|
|
4
5
|
import android.app.usage.UsageStats;
|
|
5
6
|
import android.app.usage.UsageStatsManager;
|
|
6
7
|
import android.content.Context;
|
|
@@ -17,6 +18,7 @@ import android.provider.Settings;
|
|
|
17
18
|
import android.util.Base64;
|
|
18
19
|
import android.util.Log;
|
|
19
20
|
import androidx.annotation.NonNull;
|
|
21
|
+
import com.getcapacitor.JSArray;
|
|
20
22
|
import com.getcapacitor.JSObject;
|
|
21
23
|
import com.getcapacitor.Plugin;
|
|
22
24
|
import com.getcapacitor.PluginCall;
|
|
@@ -31,8 +33,15 @@ import java.util.Map;
|
|
|
31
33
|
@CapacitorPlugin(name = "CapacitorUsageStatsManager")
|
|
32
34
|
public class CapacitorUsageStatsManagerPlugin extends Plugin {
|
|
33
35
|
|
|
34
|
-
private final String pluginVersion = "8.1
|
|
36
|
+
private final String pluginVersion = "8.2.1";
|
|
35
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Queries and aggregates usage stats for the given time range.
|
|
40
|
+
* Optional {@code packageName} limits the result to one package; an empty
|
|
41
|
+
* string is rejected. Omit the field to return every package.
|
|
42
|
+
*
|
|
43
|
+
* @param call Capacitor plugin call with beginTime, endTime, and optional packageName
|
|
44
|
+
*/
|
|
36
45
|
@PluginMethod
|
|
37
46
|
public void queryAndAggregateUsageStats(final PluginCall call) {
|
|
38
47
|
// I cannot use the primitive long here because it would create an NPE
|
|
@@ -54,13 +63,23 @@ public class CapacitorUsageStatsManagerPlugin extends Plugin {
|
|
|
54
63
|
return;
|
|
55
64
|
}
|
|
56
65
|
|
|
66
|
+
// optional filter, avoids pushing every app's stats through the bridge
|
|
67
|
+
final String packageFilter = call.getString("packageName");
|
|
68
|
+
if (packageFilter != null && packageFilter.isEmpty()) {
|
|
69
|
+
call.reject("packageName is empty");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
57
73
|
try {
|
|
58
74
|
final UsageStatsManager usageStatsManager = (UsageStatsManager) this.getContext().getSystemService(Context.USAGE_STATS_SERVICE);
|
|
59
75
|
final Map<String, UsageStats> response = usageStatsManager.queryAndAggregateUsageStats(beginTime, endTime);
|
|
60
76
|
final JSObject finalResponse = new JSObject();
|
|
61
77
|
for (final Map.Entry<String, UsageStats> stat : response.entrySet()) {
|
|
62
78
|
final String key = stat.getKey();
|
|
63
|
-
|
|
79
|
+
if (packageFilter != null && !packageFilter.equals(key)) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
final JSObject encodedValue = getJsObject(stat.getValue());
|
|
64
83
|
finalResponse.put(key, encodedValue);
|
|
65
84
|
}
|
|
66
85
|
call.resolve(finalResponse);
|
|
@@ -73,6 +92,193 @@ public class CapacitorUsageStatsManagerPlugin extends Plugin {
|
|
|
73
92
|
}
|
|
74
93
|
}
|
|
75
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Queries usage stats for the given interval type and time range.
|
|
97
|
+
* Optional {@code packageName} limits the result to one package; an empty
|
|
98
|
+
* string is rejected. Resolves {@code stats: []} when Android returns null
|
|
99
|
+
* because the user is locked.
|
|
100
|
+
*
|
|
101
|
+
* @param call Capacitor plugin call with intervalType, beginTime, endTime,
|
|
102
|
+
* and optional packageName
|
|
103
|
+
*/
|
|
104
|
+
@PluginMethod
|
|
105
|
+
public void queryUsageStats(final PluginCall call) {
|
|
106
|
+
final Integer intervalType = call.getInt("intervalType");
|
|
107
|
+
if (intervalType == null) {
|
|
108
|
+
call.reject("intervalType is null");
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (
|
|
112
|
+
intervalType != UsageStatsManager.INTERVAL_DAILY &&
|
|
113
|
+
intervalType != UsageStatsManager.INTERVAL_WEEKLY &&
|
|
114
|
+
intervalType != UsageStatsManager.INTERVAL_MONTHLY &&
|
|
115
|
+
intervalType != UsageStatsManager.INTERVAL_YEARLY &&
|
|
116
|
+
intervalType != UsageStatsManager.INTERVAL_BEST
|
|
117
|
+
) {
|
|
118
|
+
call.reject("intervalType is invalid");
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
final Long beginTime = call.getLong("beginTime");
|
|
123
|
+
if (beginTime == null) {
|
|
124
|
+
call.reject("beginTime is null");
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
final Long endTime = call.getLong("endTime");
|
|
129
|
+
if (endTime == null) {
|
|
130
|
+
call.reject("endTime is null");
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!this.checkUsageStatsPermission(this.getContext())) {
|
|
135
|
+
call.reject("Not allowed to query usage stats");
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
final String packageFilter = call.getString("packageName");
|
|
140
|
+
if (packageFilter != null && packageFilter.isEmpty()) {
|
|
141
|
+
call.reject("packageName is empty");
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
final UsageStatsManager usageStatsManager = (UsageStatsManager) this.getContext().getSystemService(Context.USAGE_STATS_SERVICE);
|
|
147
|
+
final List<UsageStats> usageStats = usageStatsManager.queryUsageStats(intervalType, beginTime, endTime);
|
|
148
|
+
if (usageStats == null) {
|
|
149
|
+
// Android R+: queryUsageStats returns null when the user is locked.
|
|
150
|
+
final JSObject ret = new JSObject();
|
|
151
|
+
ret.put("stats", new JSArray());
|
|
152
|
+
call.resolve(ret);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
final JSArray result = new JSArray();
|
|
157
|
+
for (final UsageStats stat : usageStats) {
|
|
158
|
+
if (stat == null) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
final String packageName = stat.getPackageName();
|
|
162
|
+
if (packageName == null) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (packageFilter != null && !packageFilter.equals(packageName)) {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
result.put(getJsObject(stat));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
final JSObject ret = new JSObject();
|
|
172
|
+
ret.put("stats", result);
|
|
173
|
+
call.resolve(ret);
|
|
174
|
+
} catch (Throwable t) {
|
|
175
|
+
Log.e("CapacitorUsageStatsManager", "Error during queryUsageStats", t);
|
|
176
|
+
StringWriter sw = new StringWriter();
|
|
177
|
+
PrintWriter pw = new PrintWriter(sw);
|
|
178
|
+
t.printStackTrace(pw);
|
|
179
|
+
call.reject("Error during queryUsageStats: " + sw);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Queries the raw usage event log for {@code beginTime}..{@code endTime}.
|
|
185
|
+
* Returns lifecycle events only. Optional {@code packageName} filters the
|
|
186
|
+
* result; an empty string is rejected. {@code DEVICE_SHUTDOWN} is always
|
|
187
|
+
* included because it is a device-wide reset marker. Resolves {@code events: []} when
|
|
188
|
+
* Android returns null because the user is locked.
|
|
189
|
+
*
|
|
190
|
+
* @param call Capacitor plugin call with beginTime, endTime, and optional packageName
|
|
191
|
+
*/
|
|
192
|
+
@PluginMethod
|
|
193
|
+
public void queryEvents(final PluginCall call) {
|
|
194
|
+
final Long beginTime = call.getLong("beginTime");
|
|
195
|
+
if (beginTime == null) {
|
|
196
|
+
call.reject("beginTime is null");
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
final Long endTime = call.getLong("endTime");
|
|
201
|
+
if (endTime == null) {
|
|
202
|
+
call.reject("endTime is null");
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (!this.checkUsageStatsPermission(this.getContext())) {
|
|
207
|
+
call.reject("Not allowed to query usage stats");
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// optional filter, avoids pushing every app's events through the bridge
|
|
212
|
+
final String packageFilter = call.getString("packageName");
|
|
213
|
+
if (packageFilter != null && packageFilter.isEmpty()) {
|
|
214
|
+
call.reject("packageName is empty");
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
try {
|
|
219
|
+
final UsageStatsManager usageStatsManager = (UsageStatsManager) this.getContext().getSystemService(Context.USAGE_STATS_SERVICE);
|
|
220
|
+
final UsageEvents events = usageStatsManager.queryEvents(beginTime, endTime);
|
|
221
|
+
if (events == null) {
|
|
222
|
+
// Android R+: queryEvents returns null when the user is locked.
|
|
223
|
+
final JSObject ret = new JSObject();
|
|
224
|
+
ret.put("events", new JSArray());
|
|
225
|
+
call.resolve(ret);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
final UsageEvents.Event event = new UsageEvents.Event();
|
|
229
|
+
final JSArray result = new JSArray();
|
|
230
|
+
|
|
231
|
+
while (events.hasNextEvent()) {
|
|
232
|
+
events.getNextEvent(event);
|
|
233
|
+
|
|
234
|
+
final int type = event.getEventType();
|
|
235
|
+
// ACTIVITY_RESUMED/PAUSED share values with pre-API-29 MOVE_TO_FOREGROUND/BACKGROUND (1/2).
|
|
236
|
+
if (
|
|
237
|
+
type != UsageEvents.Event.ACTIVITY_RESUMED &&
|
|
238
|
+
type != UsageEvents.Event.ACTIVITY_PAUSED &&
|
|
239
|
+
type != UsageEvents.Event.ACTIVITY_STOPPED &&
|
|
240
|
+
type != UsageEvents.Event.DEVICE_SHUTDOWN
|
|
241
|
+
) {
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
final String eventPackage = event.getPackageName();
|
|
246
|
+
// DEVICE_SHUTDOWN is a device-wide reset marker. Android usually sets
|
|
247
|
+
// package "android"; keep it even when filtering to one app.
|
|
248
|
+
if (type != UsageEvents.Event.DEVICE_SHUTDOWN) {
|
|
249
|
+
if (eventPackage == null) {
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
if (packageFilter != null && !packageFilter.equals(eventPackage)) {
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
final JSObject e = new JSObject();
|
|
258
|
+
if (eventPackage != null) {
|
|
259
|
+
e.put("packageName", eventPackage);
|
|
260
|
+
}
|
|
261
|
+
final String className = event.getClassName();
|
|
262
|
+
if (className != null) {
|
|
263
|
+
e.put("className", className);
|
|
264
|
+
}
|
|
265
|
+
e.put("timeStamp", event.getTimeStamp());
|
|
266
|
+
e.put("eventType", type);
|
|
267
|
+
result.put(e);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
final JSObject ret = new JSObject();
|
|
271
|
+
ret.put("events", result);
|
|
272
|
+
call.resolve(ret);
|
|
273
|
+
} catch (Throwable t) {
|
|
274
|
+
Log.e("CapacitorUsageStatsManager", "Error during queryEvents", t);
|
|
275
|
+
StringWriter sw = new StringWriter();
|
|
276
|
+
PrintWriter pw = new PrintWriter(sw);
|
|
277
|
+
t.printStackTrace(pw);
|
|
278
|
+
call.reject("Error during queryEvents: " + sw);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
76
282
|
@PluginMethod
|
|
77
283
|
public void isUsageStatsPermissionGranted(final PluginCall call) {
|
|
78
284
|
try {
|
|
@@ -181,8 +387,7 @@ public class CapacitorUsageStatsManagerPlugin extends Plugin {
|
|
|
181
387
|
}
|
|
182
388
|
|
|
183
389
|
@NonNull
|
|
184
|
-
private static JSObject getJsObject(
|
|
185
|
-
final UsageStats value = stat.getValue();
|
|
390
|
+
private static JSObject getJsObject(final UsageStats value) {
|
|
186
391
|
final JSObject encodedValue = new JSObject();
|
|
187
392
|
|
|
188
393
|
encodedValue.put("firstTimeStamp", value.getFirstTimeStamp());
|