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.Intent;
20 import android.os.Bundle;
21 import android.util.Log;
22 import android.view.Menu;
23 import android.view.MenuItem;
24 import android.view.View;
25 import android.widget.ArrayAdapter;
26 import android.widget.ListView;
27 import android.widget.TextView;
28 import android.widget.Toast;
29 
30 import androidx.annotation.WorkerThread;
31 import androidx.appcompat.app.AppCompatActivity;
32 import androidx.appsearch.app.AppSearchEnvironmentFactory;
33 import androidx.appsearch.debugview.samples.model.Note;
34 import androidx.appsearch.debugview.view.AppSearchDebugActivity;
35 import androidx.core.content.ContextCompat;
36 
37 import com.google.common.util.concurrent.FutureCallback;
38 import com.google.common.util.concurrent.Futures;
39 import com.google.common.util.concurrent.ListenableFuture;
40 import com.google.common.util.concurrent.ListeningExecutorService;
41 import com.google.common.util.concurrent.MoreExecutors;
42 import com.google.common.util.concurrent.SettableFuture;
43 import com.google.gson.Gson;
44 import com.google.gson.JsonArray;
45 import com.google.gson.JsonObject;
46 
47 import org.jspecify.annotations.NonNull;
48 import org.jspecify.annotations.Nullable;
49 
50 import java.io.IOException;
51 import java.io.InputStreamReader;
52 import java.util.ArrayList;
53 import java.util.List;
54 
55 /**
56  * Default Activity for AppSearch Debug View Sample App
57  *
58  * <p>This activity reads sample data, converts it into {@link Note} objects, and then indexes
59  * them into AppSearch.
60  *
61  * <p>Each sample note's text is added to the list view for display.
62  */
63 public class NotesActivity extends AppCompatActivity {
64     private static final String DB_NAME = "notesDb";
65     private static final String SAMPLE_NOTES_FILENAME = "sample_notes.json";
66     private static final String TAG = "NotesActivity";
67 
68     private final SettableFuture<NotesAppSearchManager> mNotesAppSearchManagerFuture =
69             SettableFuture.create();
70     private ArrayAdapter<Note> mNotesAdapter;
71     private ListView mListView;
72     private TextView mLoadingView;
73     private ListeningExecutorService mBackgroundExecutor;
74     private List<Note> mSampleNotes;
75 
76     @Override
onCreate(@ullable Bundle savedInstanceState)77     protected void onCreate(@Nullable Bundle savedInstanceState) {
78         super.onCreate(savedInstanceState);
79         setContentView(R.layout.activity_notes);
80 
81         mListView = findViewById(R.id.list_view);
82         mLoadingView = findViewById(R.id.text_view);
83 
84         mBackgroundExecutor = MoreExecutors.listeningDecorator(AppSearchEnvironmentFactory
85                 .getEnvironmentInstance().createCachedThreadPoolExecutor());
86 
87         mNotesAppSearchManagerFuture.setFuture(NotesAppSearchManager.createNotesAppSearchManager(
88                 getApplicationContext(), mBackgroundExecutor));
89         ListenableFuture<List<Note>> sampleNotesFuture =
90                 mBackgroundExecutor.submit(() -> loadSampleNotes());
91 
92         ListenableFuture<Void> insertNotesFuture =
93                 Futures.whenAllSucceed(mNotesAppSearchManagerFuture, sampleNotesFuture).call(
94                         () -> {
95                             mSampleNotes = Futures.getDone(sampleNotesFuture);
96                             Futures.getDone(mNotesAppSearchManagerFuture).insertNotes(
97                                     mSampleNotes).get();
98                             return null;
99                         }, mBackgroundExecutor);
100 
101         Futures.addCallback(insertNotesFuture,
102                 new FutureCallback<Void>() {
103                     @Override
104                     public void onSuccess(Void result) {
105                         displayNotes();
106                     }
107 
108                     @Override
109                     public void onFailure(@NonNull Throwable t) {
110                         Toast.makeText(NotesActivity.this, "Failed to insert notes "
111                                 + "into AppSearch.", Toast.LENGTH_LONG).show();
112                         Log.e(TAG, "Failed to insert notes into AppSearch.", t);
113                     }
114                 }, ContextCompat.getMainExecutor(this));
115     }
116 
117     @Override
onCreateOptionsMenu(@onNull Menu menu)118     public boolean onCreateOptionsMenu(@NonNull Menu menu) {
119         getMenuInflater().inflate(R.menu.debug_menu, menu);
120 
121         return true;
122     }
123 
124     @Override
onOptionsItemSelected(@onNull MenuItem item)125     public boolean onOptionsItemSelected(@NonNull MenuItem item) {
126         if (item.getItemId() == R.id.app_search_debug) {
127             Intent intent = new Intent(this, AppSearchDebugActivity.class);
128             intent.putExtra(AppSearchDebugActivity.DB_INTENT_KEY, DB_NAME);
129             intent.putExtra(AppSearchDebugActivity.STORAGE_TYPE_INTENT_KEY,
130                     AppSearchDebugActivity.STORAGE_TYPE_LOCAL);
131             startActivity(intent);
132             return true;
133         }
134 
135         return false;
136     }
137 
138     @Override
onStop()139     protected void onStop() {
140         Futures.whenAllSucceed(mNotesAppSearchManagerFuture).call(() -> {
141             Futures.getDone(mNotesAppSearchManagerFuture).close();
142             return null;
143         }, mBackgroundExecutor);
144 
145         super.onStop();
146     }
147 
148     @WorkerThread
loadSampleNotes()149     private List<Note> loadSampleNotes() {
150         List<Note> sampleNotes = new ArrayList<>();
151         Gson gson = new Gson();
152         try (InputStreamReader r = new InputStreamReader(
153                 getAssets().open(SAMPLE_NOTES_FILENAME))) {
154             JsonObject samplesJson = gson.fromJson(r, JsonObject.class);
155             JsonArray sampleJsonArr = samplesJson.getAsJsonArray("data");
156             for (int i = 0; i < sampleJsonArr.size(); ++i) {
157                 JsonObject noteJson = sampleJsonArr.get(i).getAsJsonObject();
158                 sampleNotes.add(new Note.Builder().setId(noteJson.get("id").getAsString())
159                         .setNamespace(noteJson.get("namespace").getAsString())
160                         .setText(noteJson.get("noteText").getAsString())
161                         .build()
162                 );
163             }
164         } catch (IOException e) {
165             Toast.makeText(NotesActivity.this, "Failed to load sample notes ",
166                     Toast.LENGTH_LONG).show();
167             Log.e(TAG, "Sample notes IO failed: ", e);
168         }
169         return sampleNotes;
170     }
171 
displayNotes()172     private void displayNotes() {
173         mNotesAdapter = new ArrayAdapter<>(this,
174                 android.R.layout.simple_list_item_1, mSampleNotes);
175         mListView.setAdapter(mNotesAdapter);
176 
177         mLoadingView.setVisibility(View.GONE);
178         mListView.setVisibility(View.VISIBLE);
179     }
180 }
181