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.view;
18 
19 import android.os.Bundle;
20 import android.util.Log;
21 import android.widget.Toast;
22 
23 import androidx.annotation.IntDef;
24 import androidx.annotation.RestrictTo;
25 import androidx.appsearch.app.AppSearchEnvironmentFactory;
26 import androidx.appsearch.debugview.DebugAppSearchManager;
27 import androidx.appsearch.debugview.R;
28 import androidx.appsearch.exceptions.AppSearchException;
29 import androidx.fragment.app.FragmentActivity;
30 
31 import com.google.common.util.concurrent.Futures;
32 import com.google.common.util.concurrent.ListenableFuture;
33 import com.google.common.util.concurrent.ListeningExecutorService;
34 import com.google.common.util.concurrent.MoreExecutors;
35 
36 import org.jspecify.annotations.NonNull;
37 import org.jspecify.annotations.Nullable;
38 
39 import java.lang.annotation.Retention;
40 import java.lang.annotation.RetentionPolicy;
41 
42 /**
43  * Debug Activity for AppSearch.
44  *
45  * <p>This activity provides a view of all the documents that have been put into an application's
46  * AppSearch database. The database is specified by creating an {@link android.content.Intent}
47  * with extras specifying the database name and the AppSearch storage type.
48  *
49  * <p>To launch this activity, declare it in the application's manifest:
50  * <pre>
51  *     <activity android:name="androidx.appsearch.debugview.view.AppSearchDebugActivity" />
52  * </pre>
53  *
54  * <p>Next, create an {@link android.content.Intent} from the activity that will launch the debug
55  * activity. Add the database name as an extra with key: {@link #DB_INTENT_KEY} and the storage
56  * type, which can be either {@link #STORAGE_TYPE_LOCAL} or {@link #STORAGE_TYPE_PLATFORM} with
57  * key: {@link #STORAGE_TYPE_INTENT_KEY}.
58  *
59  * <p>Example of launching the debug activity for local storage:
60  * <pre>
61  *     Intent intent = new Intent(this, AppSearchDebugActivity.class);
62  *     intent.putExtra(AppSearchDebugActivity.DB_INTENT_KEY, DB_NAME);
63  *     intent.putExtra(AppSearchDebugActivity.STORAGE_TYPE_INTENT_KEY,
64  *             AppSearchDebugActivity.STORAGE_TYPE_LOCAL);
65  *     startActivity(intent);
66  * </pre>
67  *
68  * @exportToFramework:hide
69  */
70 @RestrictTo(RestrictTo.Scope.LIBRARY)
71 public class AppSearchDebugActivity extends FragmentActivity {
72     private static final String TAG = "AppSearchDebugActivity";
73     public static final String TARGET_PACKAGE_NAME_INTENT_KEY = "targetPackageName";
74     public static final String DB_INTENT_KEY = "databaseName";
75     public static final String STORAGE_TYPE_INTENT_KEY = "storageType";
76     public static final String SEARCH_TYPE_INTENT_KEY = "searchType";
77 
78     private String mDbName;
79     private ListenableFuture<DebugAppSearchManager> mDebugAppSearchManager;
80     private ListeningExecutorService mBackgroundExecutor;
81 
82     @IntDef(value = {
83             STORAGE_TYPE_LOCAL,
84             STORAGE_TYPE_PLATFORM,
85     })
86     @Retention(RetentionPolicy.SOURCE)
87     public @interface StorageType {
88     }
89 
90     public static final int STORAGE_TYPE_LOCAL = 0;
91     public static final int STORAGE_TYPE_PLATFORM = 1;
92 
93     @IntDef(value = {
94             SEARCH_TYPE_LOCAL,
95             SEARCH_TYPE_GLOBAL,
96     })
97     @Retention(RetentionPolicy.SOURCE)
98     public @interface SearchType {
99     }
100 
101     public static final int SEARCH_TYPE_LOCAL = 0;
102     public static final int SEARCH_TYPE_GLOBAL = 1;
103 
104     @Override
onCreate(@ullable Bundle savedInstanceState)105     protected void onCreate(@Nullable Bundle savedInstanceState) {
106         super.onCreate(savedInstanceState);
107         setContentView(R.layout.activity_appsearchdebug);
108 
109         mBackgroundExecutor = MoreExecutors.listeningDecorator(AppSearchEnvironmentFactory
110                 .getEnvironmentInstance().createCachedThreadPoolExecutor());
111         mDbName = getIntent().getExtras().getString(DB_INTENT_KEY);
112         String targetPackageName =
113                 getIntent().getExtras().getString(TARGET_PACKAGE_NAME_INTENT_KEY);
114         @StorageType int storageType =
115                 getIntent().getExtras().getInt(STORAGE_TYPE_INTENT_KEY);
116         @SearchType int searchType =
117                 getIntent().getExtras().getInt(SEARCH_TYPE_INTENT_KEY);
118         try {
119             mDebugAppSearchManager = DebugAppSearchManager.createAsync(
120                     getApplicationContext(), mBackgroundExecutor, mDbName, targetPackageName,
121                     storageType, searchType);
122         } catch (AppSearchException e) {
123             Toast.makeText(getApplicationContext(),
124                     "Failed to initialize AppSearch: " + e.getMessage(),
125                     Toast.LENGTH_LONG).show();
126             Log.e(TAG, "Failed to initialize AppSearch.", e);
127         }
128 
129         MenuFragment menuFragment = new MenuFragment();
130         getSupportFragmentManager()
131                 .beginTransaction()
132                 .replace(R.id.fragment_container, menuFragment)
133                 .commit();
134     }
135 
136     @Override
onStop()137     protected void onStop() {
138         Futures.whenAllSucceed(mDebugAppSearchManager).call(() -> {
139             Futures.getDone(mDebugAppSearchManager).close();
140             return null;
141         }, mBackgroundExecutor);
142 
143         super.onStop();
144     }
145 
146     /**
147      * Gets the {@link DebugAppSearchManager} instance created by the activity.
148      */
getDebugAppSearchManager()149     public @NonNull ListenableFuture<DebugAppSearchManager> getDebugAppSearchManager() {
150         return mDebugAppSearchManager;
151     }
152 
153     /**
154      * Gets the {@link ListeningExecutorService} instance created by the activity.
155      */
getBackgroundExecutor()156     public @NonNull ListeningExecutorService getBackgroundExecutor() {
157         return mBackgroundExecutor;
158     }
159 }
160