Skip to content

Commit e2c9609

Browse files
authored
Merge pull request #74 from DeployGate/feat/capture_prototype
Send foreground status to the host application and handle extensible Logcat requests
2 parents 1ca666c + e906a15 commit e2c9609

22 files changed

+514
-149
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,14 @@ Unless required by applicable law or agreed to in writing, software distributed
125125
- Add new tests for your changes
126126
- Make sure all tests are passed
127127

128+
```bash
129+
# help: build and install artifacts into your local maven repo
130+
./gradlew clean \
131+
sdk:verifyBytecodeVersionRelease sdkMock:verifyBytecodeVersionRelease \
132+
sdk:publishReleasePublicationToMavenLocal sdkMock:publishReleasePublicationToMavenLocal \
133+
--stacktrace
134+
```
135+
128136
### sdk
129137

130138
- Consider if we should use external libraries carefully

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Top-level build file where you can add configuration options common to all sub-projects/modules.
22
ext {
3-
releaseVersion = '4.5.0'
3+
releaseVersion = '4.6.0-alpha03'
44
}
55

66
buildscript {

jitpack.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
jdk:
2+
- openjdk17
3+
before_install:
4+
- sdk install java 17.0.8-tem
5+
- sdk use java 17.0.8-tem

sdk.build.gradle

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,34 @@ android {
1111
targetSdkVersion 33
1212
versionCode 1
1313
versionName project.version
14-
manifestPlaceholders = [sdkVersion:"4"]
14+
15+
// A map of name to isSupporting
16+
def features = [
17+
UPDATE_MESSAGE_OF_BUILD: true,
18+
SERIALIZED_EXCEPTION: true,
19+
LOGCAT_BUNDLE: true,
20+
STREAMED_LOGCAT: true,
21+
DEVICE_CAPTURE: true,
22+
]
23+
24+
if (!(features instanceof LinkedHashMap)) {
25+
throw new IllegalAccessException("The key order may not be kept")
26+
}
27+
28+
int flags = 0
29+
30+
features.keySet().eachWithIndex { String key, int i ->
31+
buildConfigField("int", key, "1 << $i")
32+
33+
if (features[key]) {
34+
flags |= 1 << i
35+
}
36+
}
37+
38+
manifestPlaceholders += [
39+
featureFlags: flags,
40+
sdkVersion: "4"
41+
]
1542
}
1643

1744
buildTypes {
@@ -37,6 +64,10 @@ android {
3764
jvmArgs "-Xmx1g"
3865
}
3966
}
67+
68+
buildFeatures {
69+
buildConfig = true
70+
}
4071
}
4172

4273
dependencies {

sdk/src/main/AndroidManifest.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@
1313
android:name="com.deploygate.sdk.version"
1414
android:value="${sdkVersion}"
1515
/>
16+
17+
<meta-data
18+
android:name="com.deploygate.sdk.feature_flags"
19+
android:value="${featureFlags}"
20+
/>
1621
</application>
1722
<queries>
1823
<package android:name="com.deploygate" />

sdk/src/main/java/com/deploygate/sdk/Compatibility.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
package com.deploygate.sdk;
22

3+
/**
4+
* A metadata content to represent feature compatibilities of the client app.
5+
*
6+
* NOTE: Do not remove any enum entry even if it's completely removed from the client app.
7+
*/
38
enum Compatibility {
4-
UPDATE_MESSAGE_OF_BUILD(1),
5-
SERIALIZED_EXCEPTION(1 << 1),
6-
LOGCAT_BUNDLE(1 << 2),
7-
STREAMED_LOGCAT(1 << 3);
9+
UPDATE_MESSAGE_OF_BUILD(BuildConfig.UPDATE_MESSAGE_OF_BUILD),
10+
SERIALIZED_EXCEPTION(BuildConfig.SERIALIZED_EXCEPTION),
11+
LOGCAT_BUNDLE(BuildConfig.LOGCAT_BUNDLE),
12+
STREAMED_LOGCAT(BuildConfig.STREAMED_LOGCAT),
13+
14+
DEVICE_CAPTURE(BuildConfig.DEVICE_CAPTURE);
815

916
final int bitMask;
1017

sdk/src/main/java/com/deploygate/sdk/DeployGate.java

Lines changed: 91 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,18 @@
1616
import android.util.Log;
1717

1818
import com.deploygate.sdk.internal.Logger;
19+
import com.deploygate.sdk.internal.VisibilityLifecycleCallbacks;
1920
import com.deploygate.service.DeployGateEvent;
2021
import com.deploygate.service.IDeployGateSdkService;
2122
import com.deploygate.service.IDeployGateSdkServiceCallback;
2223

24+
import java.util.HashMap;
2325
import java.util.HashSet;
26+
import java.util.Iterator;
2427
import java.util.LinkedList;
28+
import java.util.Map;
2529
import java.util.concurrent.CountDownLatch;
30+
import java.util.concurrent.TimeUnit;
2631

2732
/**
2833
* This is DeployGate SDK library. Import this library to the application
@@ -42,6 +47,7 @@ public class DeployGate {
4247

4348
private static final String ACTION_DEPLOYGATE_STARTED = "com.deploygate.action.ServiceStarted";
4449
private static final String DEPLOYGATE_PACKAGE = "com.deploygate";
50+
private static final Object sPendingEventLock = new Object();
4551

4652
private static DeployGate sInstance;
4753

@@ -52,6 +58,7 @@ public class DeployGate {
5258
private final ILogcatInstructionSerializer mLogcatInstructionSerializer;
5359
private final CustomLogInstructionSerializer mCustomLogInstructionSerializer;
5460
private final HashSet<DeployGateCallback> mCallbacks;
61+
private final HashMap<String, Bundle> mPendingEvents;
5562
private final String mExpectedAuthor;
5663
private String mAuthor;
5764

@@ -85,12 +92,22 @@ public void onEvent(
8592
return;
8693
}
8794

95+
// ensure non-null
96+
extras = extras != null ? extras : new Bundle();
97+
8898
if (DeployGateEvent.ACTION_INIT.equals(action)) {
8999
onInitialized(extras.getBoolean(DeployGateEvent.EXTRA_IS_MANAGED, false), extras.getBoolean(DeployGateEvent.EXTRA_IS_AUTHORIZED, false), extras.getString(DeployGateEvent.EXTRA_LOGIN_USERNAME), extras.getString(DeployGateEvent.EXTRA_DISTRIBUTION_USER_NAME), extras.getBoolean(DeployGateEvent.EXTRA_IS_STOP_REQUESTED, false), extras.getString(DeployGateEvent.EXTRA_AUTHOR), extras.getInt(DeployGateEvent.EXTRA_CURRENT_REVISION, 0), extras.getString(DeployGateEvent.EXTRA_CURRENT_DISTRIBUTION_ID), extras.getString(DeployGateEvent.EXTRA_CURRENT_DISTRIBUTION_TITLE));
90100
} else if (DeployGateEvent.ACTION_UPDATE_AVAILABLE.equals(action)) {
91101
onUpdateArrived(extras.getInt(DeployGateEvent.EXTRA_SERIAL), extras.getString(DeployGateEvent.EXTRA_VERSION_NAME), extras.getInt(DeployGateEvent.EXTRA_VERSION_CODE), extras.getString(DeployGateEvent.EXTRA_SERIAL_MESSAGE));
92102
} else if (DeployGateEvent.ACTION_ONESHOT_LOGCAT.equals(action)) {
93-
onOneshotLogcat();
103+
String captureId = null;
104+
105+
if (mDeployGateClient.isSupported(Compatibility.DEVICE_CAPTURE)) {
106+
// still nullable
107+
captureId = extras.getString(DeployGateEvent.EXTRA_CAPTURE_ID);
108+
}
109+
110+
onOneshotLogcat(captureId);
94111
} else if (DeployGateEvent.ACTION_ENABLE_LOGCAT.equals(action)) {
95112
if (mDeployGateClient.isSupported(Compatibility.STREAMED_LOGCAT)) {
96113
String sessionKey = extras.getString(DeployGateEvent.EXTRA_LOGCAT_STREAM_SESSION_KEY);
@@ -110,6 +127,8 @@ public void onEvent(
110127
} else {
111128
Logger.w("streamed logcat is not supported");
112129
}
130+
} else {
131+
Logger.w("%s is not supported by this sdk version", action);
113132
}
114133
}
115134

@@ -153,6 +172,9 @@ public void run() {
153172
mLogcatInstructionSerializer.connect(mRemoteService);
154173

155174
mInitializedLatch.countDown();
175+
176+
// to release a lock as soon as possible.
177+
flushPendingEvents();
156178
}
157179

158180
private void onUpdateArrived(
@@ -178,12 +200,38 @@ public void run() {
178200
}
179201
};
180202

203+
@SuppressWarnings("FieldCanBeLocal")
204+
private final VisibilityLifecycleCallbacks.OnVisibilityChangeListener mOnVisibilityChangeListener = new VisibilityLifecycleCallbacks.OnVisibilityChangeListener() {
205+
206+
@Override
207+
public void onForeground(
208+
long elapsedRealtime,
209+
TimeUnit timeUnit
210+
) {
211+
Bundle extras = new Bundle();
212+
extras.putLong(DeployGateEvent.EXTRA_VISIBILITY_EVENT_ELAPSED_REAL_TIME_IN_NANOS, timeUnit.toNanos(elapsedRealtime));
213+
extras.putInt(DeployGateEvent.EXTRA_VISIBILITY_EVENT_TYPE, DeployGateEvent.VisibilityType.FOREGROUND);
214+
invokeAction(DeployGateEvent.ACTION_VISIBILITY_EVENT, extras, true);
215+
}
216+
217+
@Override
218+
public void onBackground(
219+
long elapsedRealtime,
220+
TimeUnit timeUnit
221+
) {
222+
Bundle extras = new Bundle();
223+
extras.putLong(DeployGateEvent.EXTRA_VISIBILITY_EVENT_ELAPSED_REAL_TIME_IN_NANOS, timeUnit.toNanos(elapsedRealtime));
224+
extras.putInt(DeployGateEvent.EXTRA_VISIBILITY_EVENT_TYPE, DeployGateEvent.VisibilityType.BACKGROUND);
225+
invokeAction(DeployGateEvent.ACTION_VISIBILITY_EVENT, extras, true);
226+
}
227+
};
228+
181229
private void requestOneshotLogcat() {
182-
onOneshotLogcat();
230+
onOneshotLogcat(null);
183231
}
184232

185-
private void onOneshotLogcat() {
186-
mLogcatInstructionSerializer.requestOneshotLogcat();
233+
private void onOneshotLogcat(String captureId) {
234+
mLogcatInstructionSerializer.requestOneshotLogcat(captureId);
187235
}
188236

189237
private void onEnableStreamedLogcat(String streamSessionKey) {
@@ -229,7 +277,8 @@ private DeployGate(
229277
mHandler = new Handler();
230278
mLogcatInstructionSerializer = mHostApp.canUseLogcat ? new LogcatInstructionSerializer(mHostApp.packageName) : ILogcatInstructionSerializer.NULL_INSTANCE;
231279
mCustomLogInstructionSerializer = new CustomLogInstructionSerializer(mHostApp.packageName, customLogConfiguration);
232-
mCallbacks = new HashSet<DeployGateCallback>();
280+
mCallbacks = new HashSet<>();
281+
mPendingEvents = new HashMap<>();
233282
mExpectedAuthor = author;
234283

235284
prepareBroadcastReceiver();
@@ -239,6 +288,7 @@ private DeployGate(
239288
}
240289

241290
mInitializedLatch = new CountDownLatch(1);
291+
((Application) applicationContext).registerActivityLifecycleCallbacks(new VisibilityLifecycleCallbacks(mOnVisibilityChangeListener));
242292
initService(true);
243293
}
244294

@@ -318,11 +368,30 @@ private void requestServiceInit(final boolean isBoot) {
318368
}
319369
}
320370

371+
/**
372+
* Send an event to the client application
373+
*
374+
* @param action
375+
* to be sent
376+
* @param extras
377+
* to be sent
378+
* @param allowPending
379+
* Allow queueing events to send them after a service connection is established (since 4.6.0)
380+
*/
321381
private void invokeAction(
322382
String action,
323-
Bundle extras
383+
Bundle extras,
384+
boolean allowPending
324385
) {
386+
extras = extras != null ? extras : new Bundle();
387+
325388
if (mRemoteService == null) {
389+
if (allowPending) {
390+
synchronized (sPendingEventLock) {
391+
mPendingEvents.put(action, extras);
392+
}
393+
}
394+
326395
return;
327396
}
328397
try {
@@ -332,6 +401,19 @@ private void invokeAction(
332401
}
333402
}
334403

404+
private void flushPendingEvents() {
405+
// cannot re-enqueue events for now
406+
synchronized (sPendingEventLock) {
407+
Iterator<Map.Entry<String, Bundle>> iterator = mPendingEvents.entrySet().iterator();
408+
409+
while (iterator.hasNext()) {
410+
Map.Entry<String, Bundle> entry = iterator.next();
411+
invokeAction(entry.getKey(), entry.getValue(), false);
412+
iterator.remove();
413+
}
414+
}
415+
}
416+
335417
/**
336418
* Clear the initiated DeployGate instance.
337419
* <p>
@@ -1191,7 +1273,7 @@ public static void installUpdate() {
11911273
return;
11921274
}
11931275

1194-
sInstance.invokeAction(DeployGateEvent.ACTION_INSTALL_UPDATE, null);
1276+
sInstance.invokeAction(DeployGateEvent.ACTION_INSTALL_UPDATE, null, false);
11951277
}
11961278

11971279
/**
@@ -1207,7 +1289,7 @@ public static void openComments() {
12071289
return;
12081290
}
12091291

1210-
sInstance.invokeAction(DeployGateEvent.ACTION_OPEN_COMMENTS, null);
1292+
sInstance.invokeAction(DeployGateEvent.ACTION_OPEN_COMMENTS, null, false);
12111293
}
12121294

12131295
/**
@@ -1240,7 +1322,7 @@ public static void composeComment(String defaultComment) {
12401322

12411323
Bundle extras = new Bundle();
12421324
extras.putString(DeployGateEvent.EXTRA_COMMENT, defaultComment);
1243-
sInstance.invokeAction(DeployGateEvent.ACTION_COMPOSE_COMMENT, extras);
1325+
sInstance.invokeAction(DeployGateEvent.ACTION_COMPOSE_COMMENT, extras, false);
12441326
}
12451327

12461328
/**

sdk/src/main/java/com/deploygate/sdk/ILogcatInstructionSerializer.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,11 @@ interface ILogcatInstructionSerializer {
2020

2121
/**
2222
* Create and enqueue a request to start sending oneshot logcat
23+
*
24+
* @param captureId
25+
* this is nullable. Set to non-null if this logcat is for a capture.
2326
*/
24-
boolean requestOneshotLogcat();
27+
boolean requestOneshotLogcat(String captureId);
2528

2629
/**
2730
* Create and enqueue a request to start sending streamed logcat
@@ -56,8 +59,8 @@ public void disconnect() {
5659
}
5760

5861
@Override
59-
public boolean requestOneshotLogcat() {
60-
Logger.d("Logcat (no-op): requestOneshotLogcat");
62+
public boolean requestOneshotLogcat(String captureId) {
63+
Logger.d("Logcat (no-op): requestOneshotLogcat(%s)", captureId != null ? captureId : "null");
6164
return false;
6265
}
6366

0 commit comments

Comments
 (0)