Skip to content

Commit ed1b4ef

Browse files
authored
Firebase Data Collection:- Add tri states to the data collection api (#1424)
* add tri state to common data collection flag * address comments * fix storage * Fix tests * fix tests * add direct boot tests * add tests * format fix * use config 19 * Fix tests * fix tests * Fix tests * remove crash tests * remove crash
1 parent 035329d commit ed1b4ef

File tree

11 files changed

+359
-33
lines changed

11 files changed

+359
-33
lines changed

firebase-common/data-collection-tests/data-collection-tests.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,5 @@ dependencies {
5050
testImplementation 'junit:junit:4.12'
5151
testImplementation "com.google.truth:truth:$googleTruthVersion"
5252
testImplementation 'org.mockito:mockito-core:2.25.0'
53+
implementation 'androidx.core:core:1.2.0'
5354
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// Copyright 2020 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;
16+
17+
import static com.google.common.truth.Truth.assertThat;
18+
import static com.google.firebase.DataCollectionTestUtil.getSharedPreferences;
19+
import static com.google.firebase.DataCollectionTestUtil.setSharedPreferencesTo;
20+
import static com.google.firebase.DataCollectionTestUtil.withApp;
21+
22+
import android.content.Context;
23+
import android.content.SharedPreferences;
24+
import androidx.core.content.ContextCompat;
25+
import androidx.test.ext.junit.runners.AndroidJUnit4;
26+
import com.google.firebase.internal.DataCollectionConfigStorage;
27+
import org.junit.Test;
28+
import org.junit.runner.RunWith;
29+
import org.robolectric.annotation.Config;
30+
31+
@RunWith(AndroidJUnit4.class)
32+
@Config(sdk = 25)
33+
public class DataCollectionPostNDefaultDisabledTest {
34+
35+
@Test
36+
public void isDataCollectionDefaultEnabled_whenMetadataFalse_shouldReturnFalse() {
37+
withApp(app -> assertThat(app.isDataCollectionDefaultEnabled()).isFalse());
38+
}
39+
40+
@Test
41+
public void isDataCollectionDefaultEnabled_whenMetadataFalseAndPrefsFalse_shouldReturnFalse() {
42+
setSharedPreferencesTo(false);
43+
withApp(app -> assertThat(app.isDataCollectionDefaultEnabled()).isFalse());
44+
}
45+
46+
@Test
47+
public void isDataCollectionDefaultEnabled_whenMetadataFalseAndPrefsTrue_shouldReturnTrue() {
48+
withApp(
49+
app -> {
50+
Context directBootContext =
51+
ContextCompat.createDeviceProtectedStorageContext(app.getApplicationContext());
52+
setSharedPreferencesTo(directBootContext, true);
53+
assertThat(app.isDataCollectionDefaultEnabled()).isTrue();
54+
});
55+
}
56+
57+
@Test
58+
public void isDataCollectionDefaultEnabled_whenMetadataFalseAndPrefsNull_shouldReturnFalse() {
59+
setSharedPreferencesTo(null);
60+
withApp(app -> assertThat(app.isDataCollectionDefaultEnabled()).isFalse());
61+
}
62+
63+
@Test
64+
public void setDataCollectionDefaultEnabledTrue_shouldUpdateSharedPrefs() {
65+
withApp(
66+
app -> {
67+
Context directBootContext =
68+
ContextCompat.createDeviceProtectedStorageContext(app.getApplicationContext());
69+
app.setDataCollectionDefaultEnabled(true);
70+
SharedPreferences prefs = getSharedPreferences(directBootContext);
71+
assertThat(prefs.contains(DataCollectionConfigStorage.DATA_COLLECTION_DEFAULT_ENABLED))
72+
.isTrue();
73+
assertThat(
74+
prefs.getBoolean(
75+
DataCollectionConfigStorage.DATA_COLLECTION_DEFAULT_ENABLED, false))
76+
.isTrue();
77+
assertThat(app.isDataCollectionDefaultEnabled()).isTrue();
78+
app.setDataCollectionDefaultEnabled(false);
79+
assertThat(
80+
prefs.getBoolean(
81+
DataCollectionConfigStorage.DATA_COLLECTION_DEFAULT_ENABLED, false))
82+
.isFalse();
83+
assertThat(app.isDataCollectionDefaultEnabled()).isFalse();
84+
app.setDataCollectionDefaultEnabled(null);
85+
assertThat(prefs.contains(DataCollectionConfigStorage.DATA_COLLECTION_DEFAULT_ENABLED))
86+
.isFalse();
87+
// Fallback on manifest value
88+
assertThat(app.isDataCollectionDefaultEnabled()).isFalse();
89+
});
90+
}
91+
92+
@Test
93+
public void setDataCollectionDefaultEnabledTrue_shouldEmitEvents() {
94+
withApp(
95+
app -> {
96+
DataCollectionDefaultChangeRegistrar.ChangeListener changeListener =
97+
app.get(DataCollectionDefaultChangeRegistrar.ChangeListener.class);
98+
assertThat(changeListener.changes).isEmpty();
99+
100+
app.setDataCollectionDefaultEnabled(false);
101+
assertThat(changeListener.changes).isEmpty();
102+
103+
app.setDataCollectionDefaultEnabled(true);
104+
assertThat(changeListener.changes).containsExactly(true);
105+
106+
app.setDataCollectionDefaultEnabled(false);
107+
assertThat(changeListener.changes).containsExactly(true, false).inOrder();
108+
109+
app.setDataCollectionDefaultEnabled(null);
110+
assertThat(changeListener.changes).containsExactly(true, false).inOrder();
111+
112+
app.setDataCollectionDefaultEnabled(true);
113+
assertThat(changeListener.changes).containsExactly(true, false, true).inOrder();
114+
115+
app.setDataCollectionDefaultEnabled(null);
116+
assertThat(changeListener.changes).containsExactly(true, false, true, false).inOrder();
117+
});
118+
}
119+
}
Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@
2424
import com.google.firebase.internal.DataCollectionConfigStorage;
2525
import org.junit.Test;
2626
import org.junit.runner.RunWith;
27+
import org.robolectric.annotation.Config;
2728

2829
@RunWith(AndroidJUnit4.class)
29-
public class DataCollectionDefaultDisabledTest {
30+
@Config(sdk = 19)
31+
public class DataCollectionPreNDefaultDisabledTest {
3032

3133
@Test
3234
public void isDataCollectionDefaultEnabled_whenMetadataFalse_shouldReturnFalse() {
@@ -45,6 +47,12 @@ public void isDataCollectionDefaultEnabled_whenMetadataFalseAndPrefsTrue_shouldR
4547
withApp(app -> assertThat(app.isDataCollectionDefaultEnabled()).isTrue());
4648
}
4749

50+
@Test
51+
public void isDataCollectionDefaultEnabled_whenMetadataFalseAndPrefsNull_shouldReturnFalse() {
52+
setSharedPreferencesTo(null);
53+
withApp(app -> assertThat(app.isDataCollectionDefaultEnabled()).isFalse());
54+
}
55+
4856
@Test
4957
public void setDataCollectionDefaultEnabledTrue_shouldUpdateSharedPrefs() {
5058
withApp(
@@ -58,6 +66,17 @@ public void setDataCollectionDefaultEnabledTrue_shouldUpdateSharedPrefs() {
5866
DataCollectionConfigStorage.DATA_COLLECTION_DEFAULT_ENABLED, false))
5967
.isTrue();
6068
assertThat(app.isDataCollectionDefaultEnabled()).isTrue();
69+
app.setDataCollectionDefaultEnabled(false);
70+
assertThat(
71+
prefs.getBoolean(
72+
DataCollectionConfigStorage.DATA_COLLECTION_DEFAULT_ENABLED, false))
73+
.isFalse();
74+
assertThat(app.isDataCollectionDefaultEnabled()).isFalse();
75+
app.setDataCollectionDefaultEnabled(null);
76+
assertThat(prefs.contains(DataCollectionConfigStorage.DATA_COLLECTION_DEFAULT_ENABLED))
77+
.isFalse();
78+
// Fallback on manifest value
79+
assertThat(app.isDataCollectionDefaultEnabled()).isFalse();
6180
});
6281
}
6382

@@ -77,6 +96,15 @@ public void setDataCollectionDefaultEnabledTrue_shouldEmitEvents() {
7796

7897
app.setDataCollectionDefaultEnabled(false);
7998
assertThat(changeListener.changes).containsExactly(true, false).inOrder();
99+
100+
app.setDataCollectionDefaultEnabled(null);
101+
assertThat(changeListener.changes).containsExactly(true, false).inOrder();
102+
103+
app.setDataCollectionDefaultEnabled(true);
104+
assertThat(changeListener.changes).containsExactly(true, false, true).inOrder();
105+
106+
app.setDataCollectionDefaultEnabled(null);
107+
assertThat(changeListener.changes).containsExactly(true, false, true, false).inOrder();
80108
});
81109
}
82110
}

firebase-common/data-collection-tests/src/test/java/com/google/firebase/DataCollectionTestUtil.java

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,44 @@ static void withApp(String name, Consumer<FirebaseApp> callable) {
4545
}
4646
}
4747

48+
static SharedPreferences getSharedPreferences(Context context) {
49+
return context.getSharedPreferences(
50+
FIREBASE_APP_PREFS + FirebaseApp.getPersistenceKey(APP_NAME, OPTIONS),
51+
Context.MODE_PRIVATE);
52+
}
53+
4854
static SharedPreferences getSharedPreferences() {
4955
return ApplicationProvider.getApplicationContext()
5056
.getSharedPreferences(
5157
FIREBASE_APP_PREFS + FirebaseApp.getPersistenceKey(APP_NAME, OPTIONS),
5258
Context.MODE_PRIVATE);
5359
}
5460

55-
static void setSharedPreferencesTo(boolean enabled) {
56-
getSharedPreferences()
57-
.edit()
58-
.putBoolean(DataCollectionConfigStorage.DATA_COLLECTION_DEFAULT_ENABLED, enabled)
59-
.commit();
61+
static void setSharedPreferencesTo(Context context, Boolean enabled) {
62+
if (enabled != null) {
63+
getSharedPreferences(context)
64+
.edit()
65+
.putBoolean(DataCollectionConfigStorage.DATA_COLLECTION_DEFAULT_ENABLED, enabled)
66+
.commit();
67+
} else {
68+
getSharedPreferences(context)
69+
.edit()
70+
.remove(DataCollectionConfigStorage.DATA_COLLECTION_DEFAULT_ENABLED)
71+
.apply();
72+
}
73+
}
74+
75+
static void setSharedPreferencesTo(Boolean enabled) {
76+
if (enabled != null) {
77+
getSharedPreferences()
78+
.edit()
79+
.putBoolean(DataCollectionConfigStorage.DATA_COLLECTION_DEFAULT_ENABLED, enabled)
80+
.commit();
81+
} else {
82+
getSharedPreferences()
83+
.edit()
84+
.remove(DataCollectionConfigStorage.DATA_COLLECTION_DEFAULT_ENABLED)
85+
.apply();
86+
}
6087
}
6188
}

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
import static com.google.android.gms.common.util.Base64Utils.decodeUrlSafeNoPadding;
1818
import static com.google.common.truth.Truth.assertThat;
1919
import static com.google.firebase.common.testutil.Assert.assertThrows;
20+
import static org.junit.Assert.assertFalse;
21+
import static org.junit.Assert.assertTrue;
2022
import static org.junit.Assert.fail;
2123
import static org.mockito.Mockito.mock;
2224
import static org.mockito.Mockito.never;
@@ -391,6 +393,30 @@ public void testDirectBoot_shouldInitializeEagerComponentsOnDeviceUnlock() {
391393
assertThat(sdkVerifier.isAnalyticsInitialized()).isTrue();
392394
}
393395

396+
@Test
397+
public void testDirectBoot_shouldPreserveDataCollectionAfterUnlock() {
398+
Context mockContext = createForwardingMockContext();
399+
400+
isUserUnlocked.set(false);
401+
FirebaseApp firebaseApp = FirebaseApp.initializeApp(mockContext);
402+
assert (firebaseApp != null);
403+
firebaseApp.setDataCollectionDefaultEnabled(false);
404+
assertFalse(firebaseApp.isDataCollectionDefaultEnabled());
405+
// User unlocks the device.
406+
isUserUnlocked.set(true);
407+
Intent userUnlockBroadcast = new Intent(Intent.ACTION_USER_UNLOCKED);
408+
localBroadcastManager.sendBroadcastSync(userUnlockBroadcast);
409+
410+
assertFalse(firebaseApp.isDataCollectionDefaultEnabled());
411+
firebaseApp.setDataCollectionDefaultEnabled(true);
412+
assertTrue(firebaseApp.isDataCollectionDefaultEnabled());
413+
firebaseApp.setDataCollectionDefaultEnabled(false);
414+
assertFalse(firebaseApp.isDataCollectionDefaultEnabled());
415+
// Because default is true.
416+
firebaseApp.setDataCollectionDefaultEnabled(null);
417+
assertTrue(firebaseApp.isDataCollectionDefaultEnabled());
418+
}
419+
394420
/** Returns mock context that forwards calls to targetContext and localBroadcastManager. */
395421
private Context createForwardingMockContext() {
396422
final UserManager spyUserManager = spy(targetContext.getSystemService(UserManager.class));

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ public boolean isDataCollectionDefaultEnabled() {
384384
* @hide
385385
*/
386386
@KeepForSdk
387-
public void setDataCollectionDefaultEnabled(boolean enabled) {
387+
public void setDataCollectionDefaultEnabled(Boolean enabled) {
388388
checkNotDeleted();
389389
dataCollectionConfigStorage.get().setEnabled(enabled);
390390
}

firebase-common/src/main/java/com/google/firebase/internal/DataCollectionConfigStorage.java

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import com.google.firebase.DataCollectionDefaultChange;
2525
import com.google.firebase.events.Event;
2626
import com.google.firebase.events.Publisher;
27-
import java.util.concurrent.atomic.AtomicBoolean;
2827

2928
/** Encapsulates data collection configuration. */
3029
public class DataCollectionConfigStorage {
@@ -34,52 +33,59 @@ public class DataCollectionConfigStorage {
3433
public static final String DATA_COLLECTION_DEFAULT_ENABLED =
3534
"firebase_data_collection_default_enabled";
3635

37-
private final Context applicationContext;
36+
private final Context deviceProtectedContext;
3837
private final SharedPreferences sharedPreferences;
3938
private final Publisher publisher;
40-
private final AtomicBoolean dataCollectionDefaultEnabled;
39+
private boolean dataCollectionDefaultEnabled;
4140

4241
public DataCollectionConfigStorage(
4342
Context applicationContext, String persistenceKey, Publisher publisher) {
44-
this.applicationContext = directBootSafe(applicationContext);
43+
this.deviceProtectedContext = directBootSafe(applicationContext);
4544
this.sharedPreferences =
46-
applicationContext.getSharedPreferences(
45+
deviceProtectedContext.getSharedPreferences(
4746
FIREBASE_APP_PREFS + persistenceKey, Context.MODE_PRIVATE);
4847
this.publisher = publisher;
49-
this.dataCollectionDefaultEnabled = new AtomicBoolean(readAutoDataCollectionEnabled());
48+
this.dataCollectionDefaultEnabled = readAutoDataCollectionEnabled();
5049
}
5150

5251
private static Context directBootSafe(Context applicationContext) {
53-
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N
54-
|| ContextCompat.isDeviceProtectedStorage(applicationContext)) {
52+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
5553
return applicationContext;
5654
}
5755
return ContextCompat.createDeviceProtectedStorageContext(applicationContext);
5856
}
5957

60-
public boolean isEnabled() {
61-
return dataCollectionDefaultEnabled.get();
58+
public synchronized boolean isEnabled() {
59+
return dataCollectionDefaultEnabled;
6260
}
6361

64-
public void setEnabled(boolean enabled) {
65-
if (dataCollectionDefaultEnabled.compareAndSet(!enabled, enabled)) {
66-
sharedPreferences.edit().putBoolean(DATA_COLLECTION_DEFAULT_ENABLED, enabled).apply();
67-
62+
private synchronized void updateDataCollectionDefaultEnabled(boolean enabled) {
63+
if (dataCollectionDefaultEnabled != enabled) {
64+
dataCollectionDefaultEnabled = enabled;
6865
publisher.publish(
6966
new Event<>(DataCollectionDefaultChange.class, new DataCollectionDefaultChange(enabled)));
7067
}
7168
}
7269

73-
private boolean readAutoDataCollectionEnabled() {
74-
if (sharedPreferences.contains(DATA_COLLECTION_DEFAULT_ENABLED)) {
75-
return sharedPreferences.getBoolean(DATA_COLLECTION_DEFAULT_ENABLED, true);
70+
public synchronized void setEnabled(Boolean enabled) {
71+
if (enabled == null) {
72+
sharedPreferences.edit().remove(DATA_COLLECTION_DEFAULT_ENABLED).apply();
73+
updateDataCollectionDefaultEnabled(readManifestDataCollectionEnabled());
74+
75+
} else {
76+
boolean apiSetting = Boolean.TRUE.equals(enabled);
77+
sharedPreferences.edit().putBoolean(DATA_COLLECTION_DEFAULT_ENABLED, apiSetting).apply();
78+
updateDataCollectionDefaultEnabled(apiSetting);
7679
}
80+
}
81+
82+
private boolean readManifestDataCollectionEnabled() {
7783
try {
78-
PackageManager packageManager = applicationContext.getPackageManager();
84+
PackageManager packageManager = deviceProtectedContext.getPackageManager();
7985
if (packageManager != null) {
8086
ApplicationInfo applicationInfo =
8187
packageManager.getApplicationInfo(
82-
applicationContext.getPackageName(), PackageManager.GET_META_DATA);
88+
deviceProtectedContext.getPackageName(), PackageManager.GET_META_DATA);
8389
if (applicationInfo != null
8490
&& applicationInfo.metaData != null
8591
&& applicationInfo.metaData.containsKey(DATA_COLLECTION_DEFAULT_ENABLED)) {
@@ -91,4 +97,11 @@ private boolean readAutoDataCollectionEnabled() {
9197
}
9298
return true;
9399
}
100+
101+
private boolean readAutoDataCollectionEnabled() {
102+
if (sharedPreferences.contains(DATA_COLLECTION_DEFAULT_ENABLED)) {
103+
return sharedPreferences.getBoolean(DATA_COLLECTION_DEFAULT_ENABLED, true);
104+
}
105+
return readManifestDataCollectionEnabled();
106+
}
94107
}

0 commit comments

Comments
 (0)