Skip to content

Commit dc413f9

Browse files
author
Rachel Prince
committed
Merge remote-tracking branch 'origin' into fad-add-support-for-apk-hash
2 parents 37c369f + 9815e45 commit dc413f9

File tree

18 files changed

+603
-102
lines changed

18 files changed

+603
-102
lines changed

firebase-crashlytics-ndk/src/androidTest/java/com/google/firebase/crashlytics/ndk/FirebaseCrashlyticsNdkTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public class FirebaseCrashlyticsNdkTest extends TestCase {
2929
protected void setUp() throws Exception {
3030
super.setUp();
3131
mockController = mock(CrashpadController.class);
32-
nativeComponent = new FirebaseCrashlyticsNdk(mockController);
32+
nativeComponent = new FirebaseCrashlyticsNdk(mockController, true);
3333
}
3434

3535
@Override

firebase-crashlytics-ndk/src/main/java/com/google/firebase/crashlytics/ndk/CrashlyticsNdkRegistrar.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.google.firebase.components.ComponentRegistrar;
2121
import com.google.firebase.components.Dependency;
2222
import com.google.firebase.crashlytics.internal.CrashlyticsNativeComponent;
23+
import com.google.firebase.crashlytics.internal.unity.ResourceUnityVersionProvider;
2324
import com.google.firebase.platforminfo.LibraryVersionComponent;
2425
import java.util.Arrays;
2526
import java.util.List;
@@ -38,6 +39,10 @@ public List<Component<?>> getComponents() {
3839

3940
private CrashlyticsNativeComponent buildCrashlyticsNdk(ComponentContainer container) {
4041
Context context = container.get(Context.class);
41-
return FirebaseCrashlyticsNdk.create(context);
42+
// The signal handler is installed immediately for non-Unity apps. For Unity apps, it will
43+
// be installed when the Firebase Unity SDK explicitly calls installSignalHandler().
44+
boolean installHandlerDuringPrepSession =
45+
(ResourceUnityVersionProvider.resolveUnityEditorVersion(context) == null);
46+
return FirebaseCrashlyticsNdk.create(context, installHandlerDuringPrepSession);
4247
}
4348
}

firebase-crashlytics-ndk/src/main/java/com/google/firebase/crashlytics/ndk/FirebaseCrashlyticsNdk.java

Lines changed: 72 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,36 +28,62 @@ class FirebaseCrashlyticsNdk implements CrashlyticsNativeComponent {
2828
/** Relative sub-path to use for storing files. */
2929
private static final String FILES_PATH = ".com.google.firebase.crashlytics-ndk";
3030

31-
static FirebaseCrashlyticsNdk create(@NonNull Context context) {
31+
private static FirebaseCrashlyticsNdk instance;
32+
33+
static FirebaseCrashlyticsNdk create(
34+
@NonNull Context context, boolean installHandlerDuringPrepareSession) {
3235
final File rootDir = new File(context.getFilesDir(), FILES_PATH);
3336

3437
final CrashpadController controller =
3538
new CrashpadController(
3639
context, new JniNativeApi(context), new NdkCrashFilesManager(rootDir));
37-
return new FirebaseCrashlyticsNdk(controller);
40+
41+
instance = new FirebaseCrashlyticsNdk(controller, installHandlerDuringPrepareSession);
42+
return instance;
3843
}
3944

4045
private final CrashpadController controller;
4146

42-
FirebaseCrashlyticsNdk(@NonNull CrashpadController controller) {
47+
private interface SignalHandlerInstaller {
48+
void installHandler();
49+
}
50+
51+
private boolean installHandlerDuringPrepareSession;
52+
private SignalHandlerInstaller signalHandlerInstaller;
53+
54+
FirebaseCrashlyticsNdk(
55+
@NonNull CrashpadController controller, boolean installHandlerDuringPrepareSession) {
4356
this.controller = controller;
57+
this.installHandlerDuringPrepareSession = installHandlerDuringPrepareSession;
4458
}
4559

4660
@Override
4761
public boolean hasCrashDataForSession(@NonNull String sessionId) {
4862
return controller.hasCrashDataForSession(sessionId);
4963
}
5064

65+
/**
66+
* Prepares the session to be opened. If installSignalHandlerDuringPrepareSession was false at the
67+
* constructor, the signal handler will not be fully installed until {@link
68+
* FirebaseCrashlyticsNdk#installSignalHandler()} is called.
69+
*/
5170
@Override
52-
public void openSession(
71+
public synchronized void prepareNativeSession(
5372
@NonNull String sessionId,
5473
@NonNull String generator,
5574
long startedAtSeconds,
5675
@NonNull StaticSessionData sessionData) {
5776

58-
Logger.getLogger().d("Opening native session: " + sessionId);
59-
if (!controller.initialize(sessionId, generator, startedAtSeconds, sessionData)) {
60-
Logger.getLogger().w("Failed to initialize Crashlytics NDK for session " + sessionId);
77+
signalHandlerInstaller =
78+
() -> {
79+
Logger.getLogger().d("Initializing native session: " + sessionId);
80+
if (!controller.initialize(sessionId, generator, startedAtSeconds, sessionData)) {
81+
Logger.getLogger().w("Failed to initialize Crashlytics NDK for session " + sessionId);
82+
}
83+
};
84+
85+
if (installHandlerDuringPrepareSession) {
86+
signalHandlerInstaller.installHandler();
6187
}
6288
}
6389

@@ -77,4 +103,43 @@ public NativeSessionFileProvider getSessionFileProvider(@NonNull String sessionI
77103
// equivalent objects.
78104
return new SessionFilesProvider(controller.getFilesForSession(sessionId));
79105
}
106+
107+
/**
108+
* Installs the native signal handler, if the session has already been prepared. Otherwise,
109+
* calling this method will result in the native signal handler being installed as soon as the
110+
* session is prepared. Used by Firebase Crashlytics for Unity.
111+
*/
112+
public synchronized void installSignalHandler() {
113+
// If the handler is already initialized, execute it immediately.
114+
// Otherwise, set installHandlerDuringPrepareSession=true so it will be installed as soon as it
115+
// is available.
116+
if (signalHandlerInstaller != null) {
117+
signalHandlerInstaller.installHandler();
118+
return;
119+
}
120+
if (installHandlerDuringPrepareSession) {
121+
// If installHandlerDuringPrepareSession is already true, we can no-op. The signal handler
122+
// was likely already installed (during prep). This method probably should not have been
123+
// called, so log a warning.
124+
Logger.getLogger().w("Native signal handler already installed; skipping re-install.");
125+
} else {
126+
Logger.getLogger()
127+
.d(
128+
"Deferring signal handler installation until the FirebaseCrashlyticsNdk session has been prepared");
129+
installHandlerDuringPrepareSession = true;
130+
}
131+
}
132+
133+
/**
134+
* Gets the singleton {@link FirebaseCrashlyticsNdk} instance. Used by Firebase Unity.
135+
*
136+
* @throws NullPointerException if create() has not already been called.
137+
*/
138+
@NonNull
139+
public static FirebaseCrashlyticsNdk getInstance() {
140+
if (instance == null) {
141+
throw new NullPointerException("FirebaseCrashlyticsNdk component is not present.");
142+
}
143+
return instance;
144+
}
80145
}

firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/internal/CrashlyticsNativeComponentDeferredProxyTest.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,10 @@ public void whenAvailable(
6363
0, "model", 1, 1000, 2000, false, 0, "manufacturer", "modelClass");
6464
StaticSessionData sessionData = StaticSessionData.create(appData, osData, deviceData);
6565

66-
proxy.openSession(TEST_SESSION_ID, TEST_GENERATOR, TEST_START_TIME, sessionData);
66+
proxy.prepareNativeSession(TEST_SESSION_ID, TEST_GENERATOR, TEST_START_TIME, sessionData);
6767
Mockito.verify(component, Mockito.times(1))
68-
.openSession(eq(TEST_SESSION_ID), eq(TEST_GENERATOR), eq(TEST_START_TIME), eq(sessionData));
68+
.prepareNativeSession(
69+
eq(TEST_SESSION_ID), eq(TEST_GENERATOR), eq(TEST_START_TIME), eq(sessionData));
6970

7071
proxy.finalizeSession(TEST_SESSION_ID);
7172
Mockito.verify(component, Mockito.times(1)).finalizeSession(eq(TEST_SESSION_ID));

firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/CrashlyticsNativeComponent.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@ public interface CrashlyticsNativeComponent {
2121

2222
boolean hasCrashDataForSession(@NonNull String sessionId);
2323

24-
void openSession(
24+
/**
25+
* Prepares the native session for opening. Whether or not Crashlytics is fully initialized to
26+
* handle native symbols is implementation dependent.
27+
*/
28+
void prepareNativeSession(
2529
@NonNull String sessionId,
2630
@NonNull String generator,
2731
long startedAtSeconds,

firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/CrashlyticsNativeComponentDeferredProxy.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public boolean hasCrashDataForSession(@NonNull String sessionId) {
4747
}
4848

4949
@Override
50-
public void openSession(
50+
public void prepareNativeSession(
5151
@NonNull String sessionId,
5252
@NonNull String generator,
5353
long startedAtSeconds,
@@ -57,7 +57,9 @@ public void openSession(
5757

5858
this.deferredNativeComponent.whenAvailable(
5959
nativeComponent -> {
60-
nativeComponent.get().openSession(sessionId, generator, startedAtSeconds, sessionData);
60+
nativeComponent
61+
.get()
62+
.prepareNativeSession(sessionId, generator, startedAtSeconds, sessionData);
6163
});
6264
}
6365

firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/CommonUtils.java

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,6 @@ public class CommonUtils {
6767
"com.google.firebase.crashlytics.mapping_file_id";
6868
static final String LEGACY_MAPPING_FILE_ID_RESOURCE_NAME = "com.crashlytics.android.build_id";
6969

70-
private static final String UNITY_EDITOR_VERSION =
71-
"com.google.firebase.crashlytics.unity_version";
72-
7370
private static final long UNCALCULATED_TOTAL_RAM = -1;
7471
static final int BYTES_IN_A_GIGABYTE = 1073741824;
7572
static final int BYTES_IN_A_MEGABYTE = 1048576;
@@ -601,20 +598,6 @@ public static String getMappingFileId(Context context) {
601598
return mappingFileId;
602599
}
603600

604-
/**
605-
* @return the Unity Editor version resolved from String resources, or <code>null</code> if the
606-
* value was not present.
607-
*/
608-
public static String resolveUnityEditorVersion(Context context) {
609-
String version = null;
610-
final int id = CommonUtils.getResourcesIdentifier(context, UNITY_EDITOR_VERSION, "string");
611-
if (id != 0) {
612-
version = context.getResources().getString(id);
613-
Logger.getLogger().v("Unity Editor version is: " + version);
614-
}
615-
return version;
616-
}
617-
618601
public static void closeQuietly(Closeable closeable) {
619602
if (closeable != null) {
620603
try {

firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/CrashlyticsController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -577,7 +577,7 @@ private void doOpenSession() {
577577
StaticSessionData.OsData osData = createOsData(getContext());
578578
StaticSessionData.DeviceData deviceData = createDeviceData(getContext());
579579

580-
nativeComponent.openSession(
580+
nativeComponent.prepareNativeSession(
581581
sessionIdentifier,
582582
generator,
583583
startedAtSeconds,

firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/unity/ResourceUnityVersionProvider.java

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,28 +15,43 @@
1515
package com.google.firebase.crashlytics.internal.unity;
1616

1717
import android.content.Context;
18+
import com.google.firebase.crashlytics.internal.Logger;
1819
import com.google.firebase.crashlytics.internal.common.CommonUtils;
1920

2021
public class ResourceUnityVersionProvider implements UnityVersionProvider {
2122

23+
private static final String UNITY_EDITOR_VERSION =
24+
"com.google.firebase.crashlytics.unity_version";
25+
26+
private static boolean isUnityVersionSet = false;
27+
private static String unityVersion = null;
28+
2229
private final Context context;
2330

24-
private boolean hasRead = false;
25-
private String unityVersion;
31+
/**
32+
* @return the Unity Editor version resolved from String resources, or <code>null</code> if the
33+
* value was not present. This method can be invoked directly; access via the instance method
34+
* from UnityVersionProvider is provided to support mocking while testing.
35+
*/
36+
public static synchronized String resolveUnityEditorVersion(Context context) {
37+
if (isUnityVersionSet) {
38+
return unityVersion;
39+
}
40+
final int id = CommonUtils.getResourcesIdentifier(context, UNITY_EDITOR_VERSION, "string");
41+
if (id != 0) {
42+
unityVersion = context.getResources().getString(id);
43+
isUnityVersionSet = true;
44+
Logger.getLogger().v("Unity Editor version is: " + unityVersion);
45+
}
46+
return unityVersion;
47+
}
2648

2749
public ResourceUnityVersionProvider(Context context) {
2850
this.context = context;
2951
}
3052

3153
@Override
3254
public String getUnityVersion() {
33-
if (!hasRead) {
34-
unityVersion = CommonUtils.resolveUnityEditorVersion(context);
35-
hasRead = true;
36-
}
37-
if (unityVersion != null) {
38-
return unityVersion;
39-
}
40-
return null;
55+
return resolveUnityEditorVersion(this.context);
4156
}
4257
}

firebase-firestore/src/main/java/com/google/firebase/firestore/local/LocalDocumentsView.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import com.google.firebase.firestore.model.MutableDocument;
2626
import com.google.firebase.firestore.model.ResourcePath;
2727
import com.google.firebase.firestore.model.SnapshotVersion;
28+
import com.google.firebase.firestore.model.mutation.FieldMask;
2829
import com.google.firebase.firestore.model.mutation.Mutation;
2930
import com.google.firebase.firestore.model.mutation.MutationBatch;
3031
import com.google.firebase.firestore.model.mutation.PatchMutation;
@@ -209,7 +210,9 @@ private ImmutableSortedMap<DocumentKey, Document> getDocumentsMatchingCollection
209210
document = MutableDocument.newInvalidDocument(key);
210211
remoteDocuments = remoteDocuments.insert(key, document);
211212
}
212-
mutation.applyToLocalView(document, batch.getLocalWriteTime());
213+
// TODO(Overlay): Here we should be reading overlay mutation and apply that instead.
214+
mutation.applyToLocalView(
215+
document, FieldMask.fromSet(new HashSet<>()), batch.getLocalWriteTime());
213216
if (!document.isFoundDocument()) {
214217
remoteDocuments = remoteDocuments.remove(key);
215218
}

firebase-firestore/src/main/java/com/google/firebase/firestore/model/ObjectValue.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ public int hashCode() {
261261
@Override
262262
@NonNull
263263
public String toString() {
264-
return "ObjectValue{" + "internalValue=" + buildProto() + '}';
264+
return "ObjectValue{" + "internalValue=" + Values.canonicalId(buildProto()) + '}';
265265
}
266266

267267
@NonNull

firebase-firestore/src/main/java/com/google/firebase/firestore/model/mutation/DeleteMutation.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import static com.google.firebase.firestore.util.Assert.hardAssert;
1818

19+
import androidx.annotation.Nullable;
1920
import com.google.firebase.Timestamp;
2021
import com.google.firebase.firestore.model.DocumentKey;
2122
import com.google.firebase.firestore.model.MutableDocument;
@@ -67,11 +68,15 @@ public void applyToRemoteDocument(MutableDocument document, MutationResult mutat
6768
}
6869

6970
@Override
70-
public void applyToLocalView(MutableDocument document, Timestamp localWriteTime) {
71+
public @Nullable FieldMask applyToLocalView(
72+
MutableDocument document, @Nullable FieldMask previousMask, Timestamp localWriteTime) {
7173
verifyKeyMatches(document);
7274

7375
if (getPrecondition().isValidFor(document)) {
7476
document.convertToNoDocument(document.getVersion()).setHasLocalMutations();
77+
return null;
7578
}
79+
80+
return previousMask;
7681
}
7782
}

firebase-firestore/src/main/java/com/google/firebase/firestore/model/mutation/Mutation.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import static com.google.firebase.firestore.util.Assert.hardAssert;
1818

19+
import androidx.annotation.Nullable;
1920
import com.google.firebase.Timestamp;
2021
import com.google.firebase.firestore.model.Document;
2122
import com.google.firebase.firestore.model.DocumentKey;
@@ -109,10 +110,13 @@ public abstract void applyToRemoteDocument(
109110
* modified.
110111
*
111112
* @param document The document to mutate.
113+
* @param previousMask The fields that have been updated before applying this mutation.
112114
* @param localWriteTime A timestamp indicating the local write time of the batch this mutation is
113115
* a part of.
116+
* @return A {@code FieldMask} representing the fields that are changed by applying this mutation.
114117
*/
115-
public abstract void applyToLocalView(MutableDocument document, Timestamp localWriteTime);
118+
public abstract @Nullable FieldMask applyToLocalView(
119+
MutableDocument document, @Nullable FieldMask previousMask, Timestamp localWriteTime);
116120

117121
/** Helper for derived classes to implement .equals(). */
118122
boolean hasSameKeyAndPrecondition(Mutation other) {
@@ -169,7 +173,7 @@ protected Map<FieldPath, Value> serverTransformResults(
169173
* result of applying a transform) for use when applying a transform locally.
170174
*
171175
* @param localWriteTime The local time of the mutation (used to generate ServerTimestampValues).
172-
* @param mutableDocument The current state of the document after applying all previous mutations.
176+
* @param mutableDocument The document to apply transforms on.
173177
* @return A map of fields to transform results.
174178
*/
175179
protected Map<FieldPath, Value> localTransformResults(

firebase-firestore/src/main/java/com/google/firebase/firestore/model/mutation/MutationBatch.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,22 +99,25 @@ public void applyToRemoteDocument(MutableDocument document, MutationBatchResult
9999

100100
/** Computes the local view of a document given all the mutations in this batch. */
101101
public void applyToLocalView(MutableDocument document) {
102+
FieldMask mutatedFields = FieldMask.fromSet(new HashSet<>());
102103
// First, apply the base state. This allows us to apply non-idempotent transform against a
103104
// consistent set of values.
104105
for (int i = 0; i < baseMutations.size(); i++) {
105106
Mutation mutation = baseMutations.get(i);
106107
if (mutation.getKey().equals(document.getKey())) {
107-
mutation.applyToLocalView(document, localWriteTime);
108+
mutatedFields = mutation.applyToLocalView(document, mutatedFields, localWriteTime);
108109
}
109110
}
110111

111112
// Second, apply all user-provided mutations.
112113
for (int i = 0; i < mutations.size(); i++) {
113114
Mutation mutation = mutations.get(i);
114115
if (mutation.getKey().equals(document.getKey())) {
115-
mutation.applyToLocalView(document, localWriteTime);
116+
mutatedFields = mutation.applyToLocalView(document, mutatedFields, localWriteTime);
116117
}
117118
}
119+
120+
// TODO(Overlay): Calculate overlay mutation here.
118121
}
119122

120123
/** Computes the local view for all provided documents given the mutations in this batch. */

0 commit comments

Comments
 (0)