1 /* 2 * Copyright 2022 Google LLC 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.google.android.libraries.mobiledatadownload.populator; 17 18 import android.content.Context; 19 import android.content.SharedPreferences; 20 import com.google.android.libraries.mobiledatadownload.internal.util.SharedPreferencesUtil; 21 import com.google.android.libraries.mobiledatadownload.tracing.PropagatedFutures; 22 import com.google.common.base.Optional; 23 import com.google.common.base.Supplier; 24 import com.google.common.base.Suppliers; 25 import com.google.common.util.concurrent.ListenableFuture; 26 import com.google.mobiledatadownload.populator.MetadataProto.ManifestFileBookkeeping; 27 import java.io.IOException; 28 import java.util.concurrent.Executor; 29 30 /** ManifestFileMetadataStore based on SharedPreferences. */ 31 public final class SharedPreferencesManifestFileMetadata implements ManifestFileMetadataStore { 32 33 private static final String SHARED_PREFS_NAME = "ManifestFileMetadata"; 34 35 private final Object lock = new Object(); 36 37 private final Supplier<SharedPreferences> sharedPrefs; 38 private final Executor backgroundExecutor; 39 40 /** 41 * @param sharedPrefs may be called multiple times, so memoization is recommended 42 */ create( Supplier<SharedPreferences> sharedPrefs, Executor backgroundExecutor)43 public static SharedPreferencesManifestFileMetadata create( 44 Supplier<SharedPreferences> sharedPrefs, Executor backgroundExecutor) { 45 return new SharedPreferencesManifestFileMetadata(sharedPrefs, backgroundExecutor); 46 } 47 createFromContext( Context context, Optional<String> instanceIdOptional, Executor backgroundExecutor)48 public static SharedPreferencesManifestFileMetadata createFromContext( 49 Context context, Optional<String> instanceIdOptional, Executor backgroundExecutor) { 50 // Avoid calling getSharedPreferences on the main thread. 51 Supplier<SharedPreferences> sharedPrefs = 52 Suppliers.memoize( 53 () -> 54 SharedPreferencesUtil.getSharedPreferences( 55 context, SHARED_PREFS_NAME, instanceIdOptional)); 56 return new SharedPreferencesManifestFileMetadata(sharedPrefs, backgroundExecutor); 57 } 58 SharedPreferencesManifestFileMetadata( Supplier<SharedPreferences> sharedPrefs, Executor backgroundExecutor)59 private SharedPreferencesManifestFileMetadata( 60 Supplier<SharedPreferences> sharedPrefs, Executor backgroundExecutor) { 61 this.sharedPrefs = sharedPrefs; 62 this.backgroundExecutor = backgroundExecutor; 63 } 64 65 @Override read(String manifestId)66 public ListenableFuture<Optional<ManifestFileBookkeeping>> read(String manifestId) { 67 return PropagatedFutures.submit( 68 () -> { 69 synchronized (lock) { 70 ManifestFileBookkeeping proto = 71 SharedPreferencesUtil.readProto( 72 sharedPrefs.get(), manifestId, ManifestFileBookkeeping.parser()); 73 return Optional.fromNullable(proto); 74 } 75 }, 76 backgroundExecutor); 77 } 78 79 @Override 80 public ListenableFuture<Void> upsert(String manifestId, ManifestFileBookkeeping value) { 81 return PropagatedFutures.submit( 82 () -> { 83 synchronized (lock) { 84 SharedPreferences.Editor editor = sharedPrefs.get().edit(); 85 SharedPreferencesUtil.writeProto(editor, manifestId, value); 86 if (!editor.commit()) { 87 throw new IOException("Failed to commit"); 88 } 89 return null; // for Callable 90 } 91 }, 92 backgroundExecutor); 93 } 94 } 95