blob: 60956aeea2c60671ece34993a09543cb49ef7de3 [file] [log] [blame]
Jason Monk8a452e92017-10-31 19:21:47 -04001/*
2 * Copyright 2017 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
17package com.example.androidx.slice.demos;
18
Mady Mellorc1334182017-11-10 15:50:35 -080019import static com.example.androidx.slice.demos.SampleSliceProvider.URI_PATHS;
20import static com.example.androidx.slice.demos.SampleSliceProvider.getUri;
21
Jason Monk698ad8c2017-11-10 10:31:19 -050022import android.arch.lifecycle.LiveData;
Jason Monk8a452e92017-10-31 19:21:47 -040023import android.content.ContentResolver;
Jason Monked974952017-11-27 13:48:04 -050024import android.content.Intent;
Jason Monk8a452e92017-10-31 19:21:47 -040025import android.content.pm.ActivityInfo;
Mady Mellorc6620e72017-11-28 13:29:57 -080026import android.content.pm.ApplicationInfo;
Jason Monk8a452e92017-10-31 19:21:47 -040027import android.content.pm.PackageInfo;
28import android.content.pm.PackageManager;
29import android.database.Cursor;
30import android.database.MatrixCursor;
31import android.net.Uri;
32import android.os.Bundle;
33import android.provider.BaseColumns;
Mady Mellor238b9b62018-01-09 16:15:40 -080034import android.support.annotation.NonNull;
Jason Monk8a452e92017-10-31 19:21:47 -040035import android.support.annotation.RequiresApi;
Jason Monk698ad8c2017-11-10 10:31:19 -050036import android.support.v7.app.AppCompatActivity;
37import android.support.v7.widget.Toolbar;
Jason Monk8a452e92017-10-31 19:21:47 -040038import android.util.ArrayMap;
39import android.util.Log;
40import android.view.Menu;
41import android.view.MenuItem;
42import android.view.SubMenu;
43import android.view.ViewGroup;
44import android.widget.CursorAdapter;
45import android.widget.SearchView;
46import android.widget.SimpleCursorAdapter;
Jason Monk8a452e92017-10-31 19:21:47 -040047
48import java.util.ArrayList;
49import java.util.Comparator;
50import java.util.List;
51
Jason Monkdcb5e2f2017-11-15 20:19:43 -050052import androidx.app.slice.Slice;
Mady Mellor238b9b62018-01-09 16:15:40 -080053import androidx.app.slice.SliceItem;
54import androidx.app.slice.widget.EventInfo;
Jason Monk698ad8c2017-11-10 10:31:19 -050055import androidx.app.slice.widget.SliceLiveData;
Jason Monk8a452e92017-10-31 19:21:47 -040056import androidx.app.slice.widget.SliceView;
57
58/**
59 * Example use of SliceView. Uses a search bar to select/auto-complete a slice uri which is
60 * then displayed in the selected mode with SliceView.
61 */
62@RequiresApi(api = 28)
Mady Mellor238b9b62018-01-09 16:15:40 -080063public class SliceBrowser extends AppCompatActivity implements SliceView.SliceObserver {
Jason Monk8a452e92017-10-31 19:21:47 -040064
65 private static final String TAG = "SlicePresenter";
66
67 private static final String SLICE_METADATA_KEY = "android.metadata.SLICE_URI";
Mady Mellorc1334182017-11-10 15:50:35 -080068 private static final boolean TEST_INTENT = false;
Jason Monk8a452e92017-10-31 19:21:47 -040069
70 private ArrayList<Uri> mSliceUris = new ArrayList<Uri>();
71 private int mSelectedMode;
72 private ViewGroup mContainer;
73 private SearchView mSearchView;
74 private SimpleCursorAdapter mAdapter;
75 private SubMenu mTypeMenu;
Jason Monk698ad8c2017-11-10 10:31:19 -050076 private LiveData<Slice> mSliceLiveData;
Jason Monk8a452e92017-10-31 19:21:47 -040077
78 @Override
79 public void onCreate(Bundle savedInstanceState) {
80 super.onCreate(savedInstanceState);
81 setContentView(R.layout.activity_layout);
82
83 Toolbar toolbar = findViewById(R.id.search_toolbar);
Jason Monk698ad8c2017-11-10 10:31:19 -050084 setSupportActionBar(toolbar);
Jason Monk8a452e92017-10-31 19:21:47 -040085
86 // Shows the slice
87 mContainer = findViewById(R.id.slice_preview);
88 mSearchView = findViewById(R.id.search_view);
89
90 final String[] from = new String[]{"uri"};
91 final int[] to = new int[]{android.R.id.text1};
92 mAdapter = new SimpleCursorAdapter(this, android.R.layout.simple_list_item_1,
93 null, from, to, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
94 mSearchView.setSuggestionsAdapter(mAdapter);
95 mSearchView.setIconifiedByDefault(false);
96 mSearchView.setOnSuggestionListener(new SearchView.OnSuggestionListener() {
97 @Override
98 public boolean onSuggestionClick(int position) {
99 mSearchView.setQuery(((Cursor) mAdapter.getItem(position)).getString(1), true);
100 return true;
101 }
102
103 @Override
104 public boolean onSuggestionSelect(int position) {
105 mSearchView.setQuery(((Cursor) mAdapter.getItem(position)).getString(1), true);
106 return true;
107 }
108 });
109 mSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
110 @Override
111 public boolean onQueryTextSubmit(String s) {
112 addSlice(Uri.parse(s));
113 mSearchView.clearFocus();
114 return false;
115 }
116
117 @Override
118 public boolean onQueryTextChange(String s) {
119 populateAdapter(s);
120 return false;
121 }
122 });
123
124 mSelectedMode = (savedInstanceState != null)
125 ? savedInstanceState.getInt("SELECTED_MODE", SliceView.MODE_SHORTCUT)
126 : SliceView.MODE_SHORTCUT;
127 if (savedInstanceState != null) {
128 mSearchView.setQuery(savedInstanceState.getString("SELECTED_QUERY"), true);
129 }
130
131 // TODO: Listen for changes.
132 updateAvailableSlices();
Jason Monked974952017-11-27 13:48:04 -0500133 if (TEST_INTENT) {
134 addSlice(new Intent("androidx.intent.SLICE_ACTION").setPackage(getPackageName()));
135 }
Jason Monk8a452e92017-10-31 19:21:47 -0400136 }
137
138 @Override
139 public boolean onCreateOptionsMenu(Menu menu) {
140 mTypeMenu = menu.addSubMenu("Type");
141 mTypeMenu.setIcon(R.drawable.ic_shortcut);
142 mTypeMenu.getItem().setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
143 mTypeMenu.add("Shortcut");
144 mTypeMenu.add("Small");
145 mTypeMenu.add("Large");
Mady Mellorc6620e72017-11-28 13:29:57 -0800146 menu.add("Auth");
Jason Monk8a452e92017-10-31 19:21:47 -0400147 super.onCreateOptionsMenu(menu);
148 return true;
149 }
150
151 @Override
152 public boolean onOptionsItemSelected(MenuItem item) {
153 switch (item.getTitle().toString()) {
Mady Mellorc6620e72017-11-28 13:29:57 -0800154 case "Auth":
155 authAllSlices();
156 return true;
Jason Monk8a452e92017-10-31 19:21:47 -0400157 case "Shortcut":
158 mTypeMenu.setIcon(R.drawable.ic_shortcut);
159 mSelectedMode = SliceView.MODE_SHORTCUT;
160 updateSliceModes();
161 return true;
162 case "Small":
163 mTypeMenu.setIcon(R.drawable.ic_small);
164 mSelectedMode = SliceView.MODE_SMALL;
165 updateSliceModes();
166 return true;
167 case "Large":
168 mTypeMenu.setIcon(R.drawable.ic_large);
169 mSelectedMode = SliceView.MODE_LARGE;
170 updateSliceModes();
171 return true;
172 }
173 return super.onOptionsItemSelected(item);
174 }
175
176 @Override
177 protected void onSaveInstanceState(Bundle outState) {
Jason Monk698ad8c2017-11-10 10:31:19 -0500178 super.onSaveInstanceState(outState);
Jason Monk8a452e92017-10-31 19:21:47 -0400179 outState.putInt("SELECTED_MODE", mSelectedMode);
180 outState.putString("SELECTED_QUERY", mSearchView.getQuery().toString());
181 }
182
Mady Mellorc6620e72017-11-28 13:29:57 -0800183 private void authAllSlices() {
184 List<ApplicationInfo> packages = getPackageManager().getInstalledApplications(0);
185 packages.forEach(info -> {
186 for (int i = 0; i < URI_PATHS.length; i++) {
187 grantUriPermission(info.packageName, getUri(URI_PATHS[i], getApplicationContext()),
188 Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
189 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
190 }
191 });
192 }
193
Jason Monk8a452e92017-10-31 19:21:47 -0400194 private void updateAvailableSlices() {
195 mSliceUris.clear();
196 List<PackageInfo> packageInfos = getPackageManager()
197 .getInstalledPackages(PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA);
198 for (PackageInfo pi : packageInfos) {
199 ActivityInfo[] activityInfos = pi.activities;
200 if (activityInfos != null) {
201 for (ActivityInfo ai : activityInfos) {
202 if (ai.metaData != null) {
203 String sliceUri = ai.metaData.getString(SLICE_METADATA_KEY);
204 if (sliceUri != null) {
205 mSliceUris.add(Uri.parse(sliceUri));
206 }
207 }
208 }
209 }
210 }
Mady Mellorc1334182017-11-10 15:50:35 -0800211 for (int i = 0; i < URI_PATHS.length; i++) {
212 mSliceUris.add(getUri(URI_PATHS[i], getApplicationContext()));
213 }
Jason Monk8a452e92017-10-31 19:21:47 -0400214 populateAdapter(String.valueOf(mSearchView.getQuery()));
215 }
216
Jason Monked974952017-11-27 13:48:04 -0500217 private void addSlice(Intent intent) {
218 SliceView v = new SliceView(getApplicationContext());
Mady Mellor238b9b62018-01-09 16:15:40 -0800219 v.setSliceObserver(this);
Jason Monked974952017-11-27 13:48:04 -0500220 v.setTag(intent);
221 if (mSliceLiveData != null) {
222 mSliceLiveData.removeObservers(this);
223 }
224 mContainer.removeAllViews();
225 mContainer.addView(v);
226 mSliceLiveData = SliceLiveData.fromIntent(this, intent);
227 v.setMode(mSelectedMode);
228 mSliceLiveData.observe(this, v);
229 }
230
Jason Monk8a452e92017-10-31 19:21:47 -0400231 private void addSlice(Uri uri) {
232 if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
233 SliceView v = new SliceView(getApplicationContext());
Mady Mellor238b9b62018-01-09 16:15:40 -0800234 v.setSliceObserver(this);
Jason Monk8a452e92017-10-31 19:21:47 -0400235 v.setTag(uri);
Jason Monk698ad8c2017-11-10 10:31:19 -0500236 if (mSliceLiveData != null) {
237 mSliceLiveData.removeObservers(this);
238 }
Jason Monk8a452e92017-10-31 19:21:47 -0400239 mContainer.removeAllViews();
240 mContainer.addView(v);
Jason Monk698ad8c2017-11-10 10:31:19 -0500241 mSliceLiveData = SliceLiveData.fromUri(this, uri);
Jason Monk8a452e92017-10-31 19:21:47 -0400242 v.setMode(mSelectedMode);
Jason Monk698ad8c2017-11-10 10:31:19 -0500243 mSliceLiveData.observe(this, v);
Jason Monk8a452e92017-10-31 19:21:47 -0400244 } else {
245 Log.w(TAG, "Invalid uri, skipping slice: " + uri);
246 }
247 }
248
249 private void updateSliceModes() {
250 final int count = mContainer.getChildCount();
251 for (int i = 0; i < count; i++) {
252 ((SliceView) mContainer.getChildAt(i)).setMode(mSelectedMode);
253 }
254 }
255
256 private void populateAdapter(String query) {
257 final MatrixCursor c = new MatrixCursor(new String[]{BaseColumns._ID, "uri"});
258 ArrayMap<String, Integer> ranking = new ArrayMap<>();
259 ArrayList<String> suggestions = new ArrayList();
260 mSliceUris.forEach(uri -> {
261 String uriString = uri.toString();
262 if (uriString.contains(query)) {
263 ranking.put(uriString, uriString.indexOf(query));
264 suggestions.add(uriString);
265 }
266 });
Jason Monk2a7d0fc2017-11-15 10:10:24 -0500267 suggestions.sort(new Comparator<String>() {
268 @Override
269 public int compare(String o1, String o2) {
270 return Integer.compare(ranking.get(o1), ranking.get(o2));
271 }
272 });
Jason Monk8a452e92017-10-31 19:21:47 -0400273 for (int i = 0; i < suggestions.size(); i++) {
274 c.addRow(new Object[]{i, suggestions.get(i)});
275 }
276 mAdapter.changeCursor(c);
277 }
Mady Mellor238b9b62018-01-09 16:15:40 -0800278
279 @Override
280 public void onSliceAction(@NonNull EventInfo info, @NonNull SliceItem item) {
281 Log.w(TAG, "onSliceAction, info: " + info);
282 Log.w(TAG, "onSliceAction, sliceItem: \n" + item);
283 }
Jason Monk8a452e92017-10-31 19:21:47 -0400284}