Skip to content

Commit 39a87fa

Browse files
authored
Move SharedPreferences usage to background thread (#4480)
* Migrate executors in fad/in-app-feedback * Add an import and format * Move SharedPreferences usage to background thread * Address feedback * Fix onSuccessUpdateTask
1 parent 8a08968 commit 39a87fa

File tree

8 files changed

+267
-147
lines changed

8 files changed

+267
-147
lines changed

firebase-appdistribution/src/main/java/com/google/firebase/appdistribution/impl/FirebaseAppDistributionImpl.java

Lines changed: 62 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,6 @@ class FirebaseAppDistributionImpl implements FirebaseAppDistribution {
116116

117117
@Override
118118
@NonNull
119-
// TODO(b/261014422): Use an explicit executor in continuations.
120-
@SuppressLint("TaskMainThread")
121119
public UpdateTask updateIfNewReleaseAvailable() {
122120
return updateIfNewReleaseAvailableTaskCache.getOrCreateTask(
123121
() -> {
@@ -126,9 +124,7 @@ public UpdateTask updateIfNewReleaseAvailable() {
126124
remakeUpdateConfirmationDialog = false;
127125
dialogHostActivity = null;
128126

129-
lifecycleNotifier
130-
.applyToForegroundActivityTask(this::showSignInConfirmationDialog)
131-
.onSuccessTask(lightweightExecutor, unused -> signInTester())
127+
signInWithConfirmationIfNeeded()
132128
.onSuccessTask(lightweightExecutor, unused -> checkForNewRelease())
133129
.continueWithTask(
134130
lightweightExecutor,
@@ -177,11 +173,23 @@ public UpdateTask updateIfNewReleaseAvailable() {
177173
}
178174

179175
@NonNull
180-
private Task<Void> showSignInConfirmationDialog(Activity hostActivity) {
181-
if (isTesterSignedIn()) {
182-
return Tasks.forResult(null);
183-
}
176+
private Task<Void> signInWithConfirmationIfNeeded() {
177+
return signInStorage
178+
.getSignInStatus()
179+
.onSuccessTask(
180+
lightweightExecutor,
181+
signedIn -> {
182+
if (signedIn) {
183+
return Tasks.forResult(null);
184+
}
185+
return lifecycleNotifier
186+
.applyToForegroundActivityTask(this::showSignInConfirmationDialog)
187+
.onSuccessTask(lightweightExecutor, unused -> signInTester());
188+
});
189+
}
184190

191+
@NonNull
192+
private Task<Void> showSignInConfirmationDialog(Activity hostActivity) {
185193
if (showSignInDialogTask == null || showSignInDialogTask.getTask().isComplete()) {
186194
showSignInDialogTask = new TaskCompletionSource<>();
187195
}
@@ -225,7 +233,7 @@ private Task<Void> showSignInConfirmationDialog(Activity hostActivity) {
225233

226234
@Override
227235
public boolean isTesterSignedIn() {
228-
return signInStorage.getSignInStatus();
236+
return signInStorage.getSignInStatusBlocking();
229237
}
230238

231239
@Override
@@ -242,19 +250,12 @@ public void signOutTester() {
242250
}
243251

244252
@Override
245-
@NonNull
246-
// TODO(b/261014422): Use an explicit executor in continuations.
247-
@SuppressLint("TaskMainThread")
248253
public synchronized Task<AppDistributionRelease> checkForNewRelease() {
249-
if (!isTesterSignedIn()) {
250-
return Tasks.forException(
251-
new FirebaseAppDistributionException("Tester is not signed in", AUTHENTICATION_FAILURE));
252-
}
253-
254254
return checkForNewReleaseTaskCache.getOrCreateTask(
255255
() ->
256-
newReleaseFetcher
257-
.checkForNewRelease()
256+
assertTesterIsSignedIn()
257+
.onSuccessTask(
258+
lightweightExecutor, unused -> newReleaseFetcher.checkForNewRelease())
258259
.onSuccessTask(lightweightExecutor, release -> cachedNewRelease.set(release))
259260
.onSuccessTask(
260261
lightweightExecutor,
@@ -274,6 +275,21 @@ public synchronized Task<AppDistributionRelease> checkForNewRelease() {
274275
}));
275276
}
276277

278+
private Task<Void> assertTesterIsSignedIn() {
279+
return signInStorage
280+
.getSignInStatus()
281+
.onSuccessTask(
282+
lightweightExecutor,
283+
signedIn -> {
284+
if (!signedIn) {
285+
return Tasks.forException(
286+
new FirebaseAppDistributionException(
287+
"Tester is not signed in", AUTHENTICATION_FAILURE));
288+
}
289+
return Tasks.forResult(null);
290+
});
291+
}
292+
277293
@Override
278294
@NonNull
279295
public UpdateTask updateApp() {
@@ -285,36 +301,34 @@ public UpdateTask updateApp() {
285301
* basic configuration and false for advanced configuration.
286302
*/
287303
private UpdateTask updateApp(boolean showDownloadInNotificationManager) {
288-
if (!isTesterSignedIn()) {
289-
UpdateTaskImpl updateTask = new UpdateTaskImpl();
290-
updateTask.setException(
291-
new FirebaseAppDistributionException("Tester is not signed in", AUTHENTICATION_FAILURE));
292-
return updateTask;
293-
}
294304
return TaskUtils.onSuccessUpdateTask(
295-
cachedNewRelease.get(),
305+
assertTesterIsSignedIn(),
296306
lightweightExecutor,
297-
release -> {
298-
if (release == null) {
299-
LogWrapper.v(TAG, "New release not found.");
300-
return getErrorUpdateTask(
301-
new FirebaseAppDistributionException(
302-
ErrorMessages.RELEASE_NOT_FOUND_ERROR, UPDATE_NOT_AVAILABLE));
303-
}
304-
if (release.getDownloadUrl() == null) {
305-
LogWrapper.v(TAG, "Download failed to execute.");
306-
return getErrorUpdateTask(
307-
new FirebaseAppDistributionException(
308-
ErrorMessages.DOWNLOAD_URL_NOT_FOUND,
309-
FirebaseAppDistributionException.Status.DOWNLOAD_FAILURE));
310-
}
311-
312-
if (release.getBinaryType() == BinaryType.AAB) {
313-
return aabUpdater.updateAab(release);
314-
} else {
315-
return apkUpdater.updateApk(release, showDownloadInNotificationManager);
316-
}
317-
});
307+
unused ->
308+
TaskUtils.onSuccessUpdateTask(
309+
cachedNewRelease.get(),
310+
lightweightExecutor,
311+
release -> {
312+
if (release == null) {
313+
LogWrapper.v(TAG, "New release not found.");
314+
return getErrorUpdateTask(
315+
new FirebaseAppDistributionException(
316+
ErrorMessages.RELEASE_NOT_FOUND_ERROR, UPDATE_NOT_AVAILABLE));
317+
}
318+
if (release.getDownloadUrl() == null) {
319+
LogWrapper.v(TAG, "Download failed to execute.");
320+
return getErrorUpdateTask(
321+
new FirebaseAppDistributionException(
322+
ErrorMessages.DOWNLOAD_URL_NOT_FOUND,
323+
FirebaseAppDistributionException.Status.DOWNLOAD_FAILURE));
324+
}
325+
326+
if (release.getBinaryType() == BinaryType.AAB) {
327+
return aabUpdater.updateAab(release);
328+
} else {
329+
return apkUpdater.updateApk(release, showDownloadInNotificationManager);
330+
}
331+
}));
318332
}
319333

320334
@Override

firebase-appdistribution/src/main/java/com/google/firebase/appdistribution/impl/FirebaseAppDistributionRegistrar.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,14 +110,15 @@ private FirebaseAppDistribution buildFirebaseAppDistribution(
110110
firebaseInstallationsApiProvider,
111111
new TesterApiHttpClient(firebaseApp),
112112
blockingExecutor);
113-
SignInStorage signInStorage = new SignInStorage(context);
113+
SignInStorage signInStorage = new SignInStorage(context, backgroundExecutor);
114114
FirebaseAppDistributionLifecycleNotifier lifecycleNotifier =
115115
FirebaseAppDistributionLifecycleNotifier.getInstance();
116116
ReleaseIdentifier releaseIdentifier = new ReleaseIdentifier(firebaseApp, testerApiClient);
117117
FirebaseAppDistribution appDistribution =
118118
new FirebaseAppDistributionImpl(
119119
firebaseApp,
120-
new TesterSignInManager(firebaseApp, firebaseInstallationsApiProvider, signInStorage),
120+
new TesterSignInManager(
121+
firebaseApp, firebaseInstallationsApiProvider, signInStorage, blockingExecutor),
121122
new NewReleaseFetcher(
122123
firebaseApp.getApplicationContext(), testerApiClient, releaseIdentifier),
123124
new ApkUpdater(firebaseApp, new ApkInstaller(), blockingExecutor),

firebase-appdistribution/src/main/java/com/google/firebase/appdistribution/impl/SignInStorage.java

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,32 +16,56 @@
1616

1717
import android.content.Context;
1818
import android.content.SharedPreferences;
19-
import com.google.firebase.components.Lazy;
19+
import com.google.android.gms.tasks.Task;
20+
import com.google.android.gms.tasks.TaskCompletionSource;
21+
import com.google.android.gms.tasks.Tasks;
22+
import com.google.firebase.annotations.concurrent.Background;
23+
import java.util.concurrent.Executor;
2024

2125
/** Class that handles storage for App Distribution SignIn persistence. */
2226
class SignInStorage {
2327

2428
private static final String SIGNIN_PREFERENCES_NAME = "FirebaseAppDistributionSignInStorage";
2529
private static final String SIGNIN_TAG = "firebase_app_distribution_signin";
2630

27-
private final Lazy<SharedPreferences> signInSharedPreferences;
28-
29-
SignInStorage(Context applicationContext) {
30-
this.signInSharedPreferences =
31-
new Lazy(
32-
() ->
33-
// TODO(lkellogg): This constructs a SharedPreferences object, which touches disk
34-
// and therefore should be run on a @Background executor. This could ideally be
35-
// done by a new shared component.
36-
applicationContext.getSharedPreferences(
37-
SIGNIN_PREFERENCES_NAME, Context.MODE_PRIVATE));
31+
private final Context applicationContext;
32+
@Background private final Executor backgroundExecutor;
33+
34+
SignInStorage(Context applicationContext, @Background Executor backgroundExecutor) {
35+
this.applicationContext = applicationContext;
36+
this.backgroundExecutor = backgroundExecutor;
37+
}
38+
39+
Task<Void> setSignInStatus(boolean testerSignedIn) {
40+
return getSharedPreferences()
41+
.onSuccessTask(
42+
backgroundExecutor,
43+
sharedPreferences -> {
44+
sharedPreferences.edit().putBoolean(SIGNIN_TAG, testerSignedIn).apply();
45+
return null;
46+
});
47+
}
48+
49+
Task<Boolean> getSignInStatus() {
50+
return getSharedPreferences()
51+
.onSuccessTask(
52+
backgroundExecutor,
53+
sharedPreferences -> Tasks.forResult(sharedPreferences.getBoolean(SIGNIN_TAG, false)));
54+
}
55+
56+
boolean getSignInStatusBlocking() {
57+
return getSharedPreferencesBlocking().getBoolean(SIGNIN_TAG, false);
3858
}
3959

40-
void setSignInStatus(boolean testerSignedIn) {
41-
this.signInSharedPreferences.get().edit().putBoolean(SIGNIN_TAG, testerSignedIn).apply();
60+
private Task<SharedPreferences> getSharedPreferences() {
61+
TaskCompletionSource<SharedPreferences> taskCompletionSource = new TaskCompletionSource<>();
62+
backgroundExecutor.execute(
63+
() -> taskCompletionSource.setResult(getSharedPreferencesBlocking()));
64+
return taskCompletionSource.getTask();
4265
}
4366

44-
boolean getSignInStatus() {
45-
return signInSharedPreferences.get().getBoolean(SIGNIN_TAG, false);
67+
private SharedPreferences getSharedPreferencesBlocking() {
68+
// This may construct a new SharedPreferences object, which requires storage I/O
69+
return applicationContext.getSharedPreferences(SIGNIN_PREFERENCES_NAME, Context.MODE_PRIVATE);
4670
}
4771
}

firebase-appdistribution/src/main/java/com/google/firebase/appdistribution/impl/TaskUtils.java

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -132,14 +132,15 @@ static <T> UpdateTask onSuccessUpdateTask(
132132
Task<T> task, Executor executor, UpdateTaskContinuation<T> continuation) {
133133
UpdateTaskImpl updateTask = new UpdateTaskImpl();
134134
task.addOnSuccessListener(
135-
executor,
136-
result -> {
137-
try {
138-
updateTask.shadow(continuation.then(result));
139-
} catch (Throwable t) {
140-
updateTask.setException(FirebaseAppDistributionExceptions.wrap(t));
141-
}
142-
});
135+
executor,
136+
result -> {
137+
try {
138+
updateTask.shadow(continuation.then(result));
139+
} catch (Throwable t) {
140+
updateTask.setException(FirebaseAppDistributionExceptions.wrap(t));
141+
}
142+
})
143+
.addOnFailureListener(executor, updateTask::setException);
143144
return updateTask;
144145
}
145146

firebase-appdistribution/src/main/java/com/google/firebase/appdistribution/impl/TesterSignInManager.java

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,14 @@
3333
import com.google.android.gms.tasks.TaskCompletionSource;
3434
import com.google.android.gms.tasks.Tasks;
3535
import com.google.firebase.FirebaseApp;
36+
import com.google.firebase.annotations.concurrent.Blocking;
37+
import com.google.firebase.annotations.concurrent.Lightweight;
3638
import com.google.firebase.appdistribution.FirebaseAppDistributionException;
3739
import com.google.firebase.appdistribution.FirebaseAppDistributionException.Status;
3840
import com.google.firebase.inject.Provider;
3941
import com.google.firebase.installations.FirebaseInstallationsApi;
4042
import java.util.List;
43+
import java.util.concurrent.Executor;
4144

4245
/** Class that handles signing in the tester. */
4346
class TesterSignInManager {
@@ -51,6 +54,9 @@ class TesterSignInManager {
5154
private final SignInStorage signInStorage;
5255
private final FirebaseAppDistributionLifecycleNotifier lifecycleNotifier;
5356

57+
// TODO: remove synchronized block usage in this class so this does not have to be blocking
58+
@Blocking private final Executor blockingExecutor;
59+
5460
private final Object signInTaskLock = new Object();
5561

5662
@GuardedBy("signInTaskLock")
@@ -62,24 +68,28 @@ class TesterSignInManager {
6268
TesterSignInManager(
6369
@NonNull FirebaseApp firebaseApp,
6470
@NonNull Provider<FirebaseInstallationsApi> firebaseInstallationsApiProvider,
65-
@NonNull final SignInStorage signInStorage) {
71+
@NonNull final SignInStorage signInStorage,
72+
@Blocking Executor blockingExecutor) {
6673
this(
6774
firebaseApp,
6875
firebaseInstallationsApiProvider,
6976
signInStorage,
70-
FirebaseAppDistributionLifecycleNotifier.getInstance());
77+
FirebaseAppDistributionLifecycleNotifier.getInstance(),
78+
blockingExecutor);
7179
}
7280

7381
@VisibleForTesting
7482
TesterSignInManager(
7583
@NonNull FirebaseApp firebaseApp,
7684
@NonNull Provider<FirebaseInstallationsApi> firebaseInstallationsApiProvider,
7785
@NonNull final SignInStorage signInStorage,
78-
@NonNull FirebaseAppDistributionLifecycleNotifier lifecycleNotifier) {
86+
@NonNull FirebaseAppDistributionLifecycleNotifier lifecycleNotifier,
87+
@Lightweight Executor blockingExecutor) {
7988
this.firebaseApp = firebaseApp;
8089
this.firebaseInstallationsApiProvider = firebaseInstallationsApiProvider;
8190
this.signInStorage = signInStorage;
8291
this.lifecycleNotifier = lifecycleNotifier;
92+
this.blockingExecutor = blockingExecutor;
8393

8494
lifecycleNotifier.addOnActivityCreatedListener(this::onActivityCreated);
8595
lifecycleNotifier.addOnActivityResumedListener(this::onActivityResumed);
@@ -119,11 +129,22 @@ void onActivityResumed(Activity activity) {
119129
@SuppressLint("TaskMainThread")
120130
@NonNull
121131
public Task<Void> signInTester() {
122-
if (signInStorage.getSignInStatus()) {
123-
LogWrapper.v(TAG, "Tester is already signed in.");
124-
return Tasks.forResult(null);
125-
}
132+
return signInStorage
133+
.getSignInStatus()
134+
.onSuccessTask(
135+
blockingExecutor,
136+
signedIn -> {
137+
if (signedIn) {
138+
LogWrapper.v(TAG, "Tester is already signed in.");
139+
return Tasks.forResult(null);
140+
}
141+
return doSignInTester();
142+
});
143+
}
126144

145+
// TODO(b/261014422): Use an explicit executor in continuations.
146+
@SuppressLint("TaskMainThread")
147+
private Task<Void> doSignInTester() {
127148
synchronized (signInTaskLock) {
128149
if (signInTaskCompletionSource != null
129150
&& !signInTaskCompletionSource.getTask().isComplete()) {
@@ -138,10 +159,15 @@ public Task<Void> signInTester() {
138159
.get()
139160
.getId()
140161
.addOnFailureListener(
162+
blockingExecutor,
141163
handleTaskFailure(ErrorMessages.AUTHENTICATION_ERROR, Status.AUTHENTICATION_FAILURE))
142-
.onSuccessTask(this::getForegroundActivityAndOpenSignInFlow)
143-
// Catch any unexpected failures to be safe.
144-
.addOnFailureListener(handleTaskFailure(ErrorMessages.UNKNOWN_ERROR, Status.UNKNOWN));
164+
.addOnSuccessListener(
165+
blockingExecutor,
166+
fid ->
167+
getForegroundActivityAndOpenSignInFlow(fid)
168+
.addOnFailureListener(
169+
blockingExecutor,
170+
handleTaskFailure(ErrorMessages.UNKNOWN_ERROR, Status.UNKNOWN)));
145171

146172
return signInTaskCompletionSource.getTask();
147173
}

0 commit comments

Comments
 (0)