Skip to content

Commit dcf82a5

Browse files
authored
Platform logging main branch (#3347)
* Collect heartbeat in android (#3120) * add heart beat * update * update according to comments * update * update * update comments * update * gjf * address comments * Synchronize old and new heartbeats (#3177) * synchronize heartbeats * update * gJF * update * update * make synchornized * update * update * Set fis as an unloader (#3222) * update * add gmp app id * update * gJF * update * update fix * update * Use gzip (#3316) * update * gJF * delete default heartbeat info
1 parent f25efea commit dcf82a5

File tree

16 files changed

+752
-345
lines changed

16 files changed

+752
-345
lines changed

firebase-common/firebase-common.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ dependencies {
8484
testImplementation "com.google.truth:truth:$googleTruthVersion"
8585
testImplementation 'org.mockito:mockito-core:2.25.0'
8686
testImplementation 'androidx.test:core:1.2.0'
87+
testImplementation 'org.json:json:20210307'
8788

8889
annotationProcessor 'com.google.auto.value:auto-value:1.6.5'
8990

firebase-common/src/androidTest/java/com/google/firebase/FirebaseAppTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,8 @@ public void testInvokeAfterDeleteThrows() throws Exception {
272272
int modifiers = method.getModifiers();
273273
if (Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers)) {
274274
try {
275-
if (!allowedToCallAfterDelete.contains(method.getName())) {
275+
if (!allowedToCallAfterDelete.contains(method.getName())
276+
&& !(method.getName().contains("$"))) {
276277
invokePublicInstanceMethodWithDefaultValues(firebaseApp, method);
277278
fail("Method expected to throw, but didn't " + method.getName());
278279
}

firebase-common/src/main/java/com/google/firebase/FirebaseApp.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import com.google.firebase.components.ComponentRuntime;
4848
import com.google.firebase.components.Lazy;
4949
import com.google.firebase.events.Publisher;
50+
import com.google.firebase.heartbeatinfo.DefaultHeartBeatController;
5051
import com.google.firebase.inject.Provider;
5152
import com.google.firebase.internal.DataCollectionConfigStorage;
5253
import java.nio.charset.Charset;
@@ -114,7 +115,7 @@ public class FirebaseApp {
114115
private final AtomicBoolean automaticResourceManagementEnabled = new AtomicBoolean(false);
115116
private final AtomicBoolean deleted = new AtomicBoolean();
116117
private final Lazy<DataCollectionConfigStorage> dataCollectionConfigStorage;
117-
118+
private final Provider<DefaultHeartBeatController> defaultHeartBeatController;
118119
private final List<BackgroundStateChangeListener> backgroundStateChangeListeners =
119120
new CopyOnWriteArrayList<>();
120121
private final List<FirebaseAppLifecycleListener> lifecycleListeners =
@@ -200,6 +201,7 @@ public static FirebaseApp getInstance(@NonNull String name) {
200201
synchronized (LOCK) {
201202
FirebaseApp firebaseApp = INSTANCES.get(normalize(name));
202203
if (firebaseApp != null) {
204+
firebaseApp.defaultHeartBeatController.get().registerHeartBeat();
203205
return firebaseApp;
204206
}
205207

@@ -433,6 +435,14 @@ protected FirebaseApp(Context applicationContext, String name, FirebaseOptions o
433435
applicationContext,
434436
getPersistenceKey(),
435437
componentRuntime.get(Publisher.class)));
438+
defaultHeartBeatController = componentRuntime.getProvider(DefaultHeartBeatController.class);
439+
440+
addBackgroundStateChangeListener(
441+
background -> {
442+
if (!background) {
443+
defaultHeartBeatController.get().registerHeartBeat();
444+
}
445+
});
436446
}
437447

438448
private void checkNotDeleted() {
@@ -582,6 +592,7 @@ private void initializeAllApis() {
582592
} else {
583593
Log.i(LOG_TAG, "Device unlocked: initializing all Firebase APIs for app " + getName());
584594
componentRuntime.initializeEagerComponents(isDefaultApp());
595+
defaultHeartBeatController.get().registerHeartBeat();
585596
}
586597
}
587598

firebase-common/src/main/java/com/google/firebase/FirebaseCommonRegistrar.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import android.os.Build;
2020
import com.google.firebase.components.Component;
2121
import com.google.firebase.components.ComponentRegistrar;
22-
import com.google.firebase.heartbeatinfo.DefaultHeartBeatInfo;
22+
import com.google.firebase.heartbeatinfo.DefaultHeartBeatController;
2323
import com.google.firebase.platforminfo.DefaultUserAgentPublisher;
2424
import com.google.firebase.platforminfo.KotlinDetector;
2525
import com.google.firebase.platforminfo.LibraryVersionComponent;
@@ -43,7 +43,7 @@ public class FirebaseCommonRegistrar implements ComponentRegistrar {
4343
public List<Component<?>> getComponents() {
4444
List<Component<?>> result = new ArrayList<>();
4545
result.add(DefaultUserAgentPublisher.component());
46-
result.add(DefaultHeartBeatInfo.component());
46+
result.add(DefaultHeartBeatController.component());
4747
result.add(
4848
LibraryVersionComponent.create(FIREBASE_ANDROID, String.valueOf(Build.VERSION.SDK_INT)));
4949
result.add(LibraryVersionComponent.create(FIREBASE_COMMON, BuildConfig.VERSION_NAME));
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
// Copyright 2021 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.heartbeatinfo;
16+
17+
import android.content.Context;
18+
import android.util.Base64;
19+
import androidx.annotation.NonNull;
20+
import androidx.annotation.VisibleForTesting;
21+
import androidx.core.os.UserManagerCompat;
22+
import com.google.android.gms.tasks.Task;
23+
import com.google.android.gms.tasks.Tasks;
24+
import com.google.firebase.FirebaseApp;
25+
import com.google.firebase.components.Component;
26+
import com.google.firebase.components.Dependency;
27+
import com.google.firebase.inject.Provider;
28+
import com.google.firebase.platforminfo.UserAgentPublisher;
29+
import java.io.ByteArrayOutputStream;
30+
import java.util.List;
31+
import java.util.Set;
32+
import java.util.concurrent.Executor;
33+
import java.util.concurrent.LinkedBlockingQueue;
34+
import java.util.concurrent.ThreadFactory;
35+
import java.util.concurrent.ThreadPoolExecutor;
36+
import java.util.concurrent.TimeUnit;
37+
import java.util.zip.GZIPOutputStream;
38+
import org.json.JSONArray;
39+
import org.json.JSONObject;
40+
41+
/** Provides a function to store heartbeats and another function to retrieve stored heartbeats. */
42+
public class DefaultHeartBeatController implements HeartBeatController, HeartBeatInfo {
43+
44+
private final Provider<HeartBeatInfoStorage> storageProvider;
45+
46+
private final Context applicationContext;
47+
48+
private final Provider<UserAgentPublisher> userAgentProvider;
49+
50+
private final Set<HeartBeatConsumer> consumers;
51+
52+
private final Executor backgroundExecutor;
53+
54+
private static final ThreadFactory THREAD_FACTORY =
55+
r -> new Thread(r, "heartbeat-information-executor");
56+
57+
public Task<Void> registerHeartBeat() {
58+
if (consumers.size() <= 0) {
59+
return Tasks.forResult(null);
60+
}
61+
boolean inDirectBoot = !UserManagerCompat.isUserUnlocked(applicationContext);
62+
if (inDirectBoot) {
63+
return Tasks.forResult(null);
64+
}
65+
66+
return Tasks.call(
67+
backgroundExecutor,
68+
() -> {
69+
synchronized (DefaultHeartBeatController.this) {
70+
this.storageProvider
71+
.get()
72+
.storeHeartBeat(
73+
System.currentTimeMillis(), this.userAgentProvider.get().getUserAgent());
74+
}
75+
76+
return null;
77+
});
78+
}
79+
80+
@Override
81+
public Task<String> getHeartBeatsHeader() {
82+
boolean inDirectBoot = !UserManagerCompat.isUserUnlocked(applicationContext);
83+
if (inDirectBoot) {
84+
return Tasks.forResult("");
85+
}
86+
return Tasks.call(
87+
backgroundExecutor,
88+
() -> {
89+
synchronized (DefaultHeartBeatController.this) {
90+
HeartBeatInfoStorage storage = this.storageProvider.get();
91+
List<HeartBeatResult> allHeartBeats = storage.getAllHeartBeats();
92+
storage.deleteAllHeartBeats();
93+
JSONArray array = new JSONArray();
94+
for (int i = 0; i < allHeartBeats.size(); i++) {
95+
HeartBeatResult result = allHeartBeats.get(i);
96+
JSONObject obj = new JSONObject();
97+
obj.put("agent", result.getUserAgent());
98+
obj.put("date", result.getUsedDates());
99+
array.put(obj);
100+
}
101+
JSONObject output = new JSONObject();
102+
output.put("heartbeats", array);
103+
output.put("version", "2");
104+
ByteArrayOutputStream out = new ByteArrayOutputStream();
105+
GZIPOutputStream gzip = new GZIPOutputStream(out);
106+
gzip.write(output.toString().getBytes());
107+
gzip.close();
108+
return Base64.encodeToString(out.toString("UTF-8").getBytes(), Base64.URL_SAFE);
109+
}
110+
});
111+
}
112+
113+
private DefaultHeartBeatController(
114+
Context context,
115+
String persistenceKey,
116+
Set<HeartBeatConsumer> consumers,
117+
Provider<UserAgentPublisher> userAgentProvider) {
118+
this(
119+
() -> new HeartBeatInfoStorage(context, persistenceKey),
120+
consumers,
121+
new ThreadPoolExecutor(
122+
0, 1, 30, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), THREAD_FACTORY),
123+
userAgentProvider,
124+
context);
125+
}
126+
127+
@VisibleForTesting
128+
DefaultHeartBeatController(
129+
Provider<HeartBeatInfoStorage> testStorage,
130+
Set<HeartBeatConsumer> consumers,
131+
Executor executor,
132+
Provider<UserAgentPublisher> userAgentProvider,
133+
Context context) {
134+
storageProvider = testStorage;
135+
this.consumers = consumers;
136+
this.backgroundExecutor = executor;
137+
this.userAgentProvider = userAgentProvider;
138+
this.applicationContext = context;
139+
}
140+
141+
public static @NonNull Component<DefaultHeartBeatController> component() {
142+
return Component.builder(
143+
DefaultHeartBeatController.class, HeartBeatController.class, HeartBeatInfo.class)
144+
.add(Dependency.required(Context.class))
145+
.add(Dependency.required(FirebaseApp.class))
146+
.add(Dependency.setOf(HeartBeatConsumer.class))
147+
.add(Dependency.requiredProvider(UserAgentPublisher.class))
148+
.factory(
149+
c ->
150+
new DefaultHeartBeatController(
151+
c.get(Context.class),
152+
c.get(FirebaseApp.class).getPersistenceKey(),
153+
c.setOf(HeartBeatConsumer.class),
154+
c.getProvider(UserAgentPublisher.class)))
155+
.build();
156+
}
157+
158+
@Override
159+
@NonNull
160+
public synchronized HeartBeat getHeartBeatCode(@NonNull String heartBeatTag) {
161+
long presentTime = System.currentTimeMillis();
162+
HeartBeatInfoStorage storage = storageProvider.get();
163+
boolean shouldSendGlobalHB = storage.shouldSendGlobalHeartBeat(presentTime);
164+
if (shouldSendGlobalHB) {
165+
storage.postHeartBeatCleanUp();
166+
return HeartBeat.GLOBAL;
167+
} else {
168+
return HeartBeat.NONE;
169+
}
170+
}
171+
}

firebase-common/src/main/java/com/google/firebase/heartbeatinfo/DefaultHeartBeatInfo.java

Lines changed: 0 additions & 87 deletions
This file was deleted.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright 2021 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.heartbeatinfo;
16+
17+
import com.google.android.gms.tasks.Task;
18+
19+
/**
20+
* Class provides information about heartbeats.
21+
*
22+
* <p>This exposes a function which returns a base-64 encoded string based on the stored heartbeats.
23+
*/
24+
public interface HeartBeatController {
25+
Task<String> getHeartBeatsHeader();
26+
}

0 commit comments

Comments
 (0)