1 /* 2 * Copyright 2021 The Android Open Source Project 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 17 package androidx.appsearch.debugview.samples; 18 19 import android.content.Context; 20 21 import androidx.appsearch.app.AppSearchBatchResult; 22 import androidx.appsearch.app.AppSearchSession; 23 import androidx.appsearch.app.PutDocumentsRequest; 24 import androidx.appsearch.app.SetSchemaRequest; 25 import androidx.appsearch.app.SetSchemaResponse; 26 import androidx.appsearch.debugview.samples.model.Note; 27 import androidx.appsearch.exceptions.AppSearchException; 28 import androidx.appsearch.localstorage.LocalStorage; 29 30 import com.google.common.util.concurrent.Futures; 31 import com.google.common.util.concurrent.ListenableFuture; 32 import com.google.common.util.concurrent.SettableFuture; 33 34 import org.jspecify.annotations.NonNull; 35 36 import java.io.Closeable; 37 import java.util.List; 38 import java.util.concurrent.Executor; 39 40 /** 41 * Manages interactions with AppSearch. 42 */ 43 public class NotesAppSearchManager implements Closeable { 44 private static final String DB_NAME = "notesDb"; 45 private static final boolean FORCE_OVERRIDE = true; 46 47 private final Context mContext; 48 private final Executor mExecutor; 49 private final SettableFuture<AppSearchSession> mAppSearchSessionFuture = 50 SettableFuture.create(); 51 NotesAppSearchManager(@onNull Context context, @NonNull Executor executor)52 private NotesAppSearchManager(@NonNull Context context, @NonNull Executor executor) { 53 mContext = context; 54 mExecutor = executor; 55 } 56 57 /** 58 * Factory for creating a {@link NotesAppSearchManager} instance. 59 * 60 * <p>This creates and initializes an {@link AppSearchSession}. It also resets existing 61 * {@link Note} objects from the index and re-adds the {@link Note} document class to the 62 * AppSearch schema. 63 * 64 * @param executor to run AppSearch operations on. 65 */ createNotesAppSearchManager( @onNull Context context, @NonNull Executor executor)66 public static @NonNull ListenableFuture<NotesAppSearchManager> createNotesAppSearchManager( 67 @NonNull Context context, @NonNull Executor executor) { 68 NotesAppSearchManager notesAppSearchManager = new NotesAppSearchManager(context, executor); 69 return Futures.transform(notesAppSearchManager.initialize(), 70 unused -> notesAppSearchManager, executor); 71 } 72 73 /** 74 * Closes the AppSearch session. 75 */ 76 @Override close()77 public void close() { 78 Futures.whenAllSucceed(mAppSearchSessionFuture).call(() -> { 79 Futures.getDone(mAppSearchSessionFuture).close(); 80 return null; 81 }, mExecutor); 82 } 83 84 /** 85 * Inserts {@link Note} documents into the AppSearch database. 86 * 87 * @param notes list of notes to index in AppSearch. 88 */ insertNotes( @onNull List<Note> notes)89 public @NonNull ListenableFuture<AppSearchBatchResult<String, Void>> insertNotes( 90 @NonNull List<Note> notes) { 91 try { 92 PutDocumentsRequest request = new PutDocumentsRequest.Builder().addDocuments(notes) 93 .build(); 94 return Futures.transformAsync(mAppSearchSessionFuture, 95 session -> session.putAsync(request), mExecutor); 96 } catch (Exception e) { 97 return Futures.immediateFailedFuture(e); 98 } 99 } 100 initialize()101 private @NonNull ListenableFuture<Void> initialize() { 102 return Futures.transformAsync(createLocalSession(), session -> { 103 mAppSearchSessionFuture.set(session); 104 return Futures.transformAsync(resetDocuments(), 105 unusedResetResult -> Futures.transform(setSchema(), 106 unusedSetSchemaResult -> null, 107 mExecutor), 108 mExecutor); 109 }, mExecutor); 110 } 111 createLocalSession()112 private ListenableFuture<AppSearchSession> createLocalSession() { 113 return LocalStorage.createSearchSessionAsync( 114 new LocalStorage.SearchContext.Builder(mContext, DB_NAME) 115 .build() 116 ); 117 } 118 resetDocuments()119 private ListenableFuture<SetSchemaResponse> resetDocuments() { 120 SetSchemaRequest request = 121 new SetSchemaRequest.Builder().setForceOverride(FORCE_OVERRIDE).build(); 122 return Futures.transformAsync(mAppSearchSessionFuture, 123 session -> session.setSchemaAsync(request), 124 mExecutor); 125 } 126 setSchema()127 private ListenableFuture<SetSchemaResponse> setSchema() { 128 try { 129 SetSchemaRequest request = new SetSchemaRequest.Builder().addDocumentClasses(Note.class) 130 .build(); 131 return Futures.transformAsync(mAppSearchSessionFuture, 132 session -> session.setSchemaAsync(request), mExecutor); 133 } catch (AppSearchException e) { 134 return Futures.immediateFailedFuture(e); 135 } 136 } 137 } 138