SliceBrowser.java revision c6620e77a6e224f1e31e87e10fcb97c8e224500b
1/* 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 19import static com.example.androidx.slice.demos.SampleSliceProvider.URI_PATHS; 20import static com.example.androidx.slice.demos.SampleSliceProvider.getUri; 21 22import android.arch.lifecycle.LiveData; 23import android.content.ContentResolver; 24import android.content.Intent; 25import android.content.pm.ActivityInfo; 26import android.content.pm.ApplicationInfo; 27import 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; 34import android.support.annotation.RequiresApi; 35import android.support.v7.app.AppCompatActivity; 36import android.support.v7.widget.Toolbar; 37import android.util.ArrayMap; 38import android.util.Log; 39import android.view.Menu; 40import android.view.MenuItem; 41import android.view.SubMenu; 42import android.view.ViewGroup; 43import android.widget.CursorAdapter; 44import android.widget.SearchView; 45import android.widget.SimpleCursorAdapter; 46 47import java.util.ArrayList; 48import java.util.Comparator; 49import java.util.List; 50 51import androidx.app.slice.Slice; 52import androidx.app.slice.widget.SliceLiveData; 53import androidx.app.slice.widget.SliceView; 54 55/** 56 * Example use of SliceView. Uses a search bar to select/auto-complete a slice uri which is 57 * then displayed in the selected mode with SliceView. 58 */ 59@RequiresApi(api = 28) 60public class SliceBrowser extends AppCompatActivity { 61 62 private static final String TAG = "SlicePresenter"; 63 64 private static final String SLICE_METADATA_KEY = "android.metadata.SLICE_URI"; 65 private static final boolean TEST_INTENT = false; 66 67 private ArrayList<Uri> mSliceUris = new ArrayList<Uri>(); 68 private int mSelectedMode; 69 private ViewGroup mContainer; 70 private SearchView mSearchView; 71 private SimpleCursorAdapter mAdapter; 72 private SubMenu mTypeMenu; 73 private LiveData<Slice> mSliceLiveData; 74 75 @Override 76 public void onCreate(Bundle savedInstanceState) { 77 super.onCreate(savedInstanceState); 78 setContentView(R.layout.activity_layout); 79 80 Toolbar toolbar = findViewById(R.id.search_toolbar); 81 setSupportActionBar(toolbar); 82 83 // Shows the slice 84 mContainer = findViewById(R.id.slice_preview); 85 mSearchView = findViewById(R.id.search_view); 86 87 final String[] from = new String[]{"uri"}; 88 final int[] to = new int[]{android.R.id.text1}; 89 mAdapter = new SimpleCursorAdapter(this, android.R.layout.simple_list_item_1, 90 null, from, to, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER); 91 mSearchView.setSuggestionsAdapter(mAdapter); 92 mSearchView.setIconifiedByDefault(false); 93 mSearchView.setOnSuggestionListener(new SearchView.OnSuggestionListener() { 94 @Override 95 public boolean onSuggestionClick(int position) { 96 mSearchView.setQuery(((Cursor) mAdapter.getItem(position)).getString(1), true); 97 return true; 98 } 99 100 @Override 101 public boolean onSuggestionSelect(int position) { 102 mSearchView.setQuery(((Cursor) mAdapter.getItem(position)).getString(1), true); 103 return true; 104 } 105 }); 106 mSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { 107 @Override 108 public boolean onQueryTextSubmit(String s) { 109 addSlice(Uri.parse(s)); 110 mSearchView.clearFocus(); 111 return false; 112 } 113 114 @Override 115 public boolean onQueryTextChange(String s) { 116 populateAdapter(s); 117 return false; 118 } 119 }); 120 121 mSelectedMode = (savedInstanceState != null) 122 ? savedInstanceState.getInt("SELECTED_MODE", SliceView.MODE_SHORTCUT) 123 : SliceView.MODE_SHORTCUT; 124 if (savedInstanceState != null) { 125 mSearchView.setQuery(savedInstanceState.getString("SELECTED_QUERY"), true); 126 } 127 128 // TODO: Listen for changes. 129 updateAvailableSlices(); 130 if (TEST_INTENT) { 131 addSlice(new Intent("androidx.intent.SLICE_ACTION").setPackage(getPackageName())); 132 } 133 } 134 135 @Override 136 public boolean onCreateOptionsMenu(Menu menu) { 137 mTypeMenu = menu.addSubMenu("Type"); 138 mTypeMenu.setIcon(R.drawable.ic_shortcut); 139 mTypeMenu.getItem().setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); 140 mTypeMenu.add("Shortcut"); 141 mTypeMenu.add("Small"); 142 mTypeMenu.add("Large"); 143 menu.add("Auth"); 144 super.onCreateOptionsMenu(menu); 145 return true; 146 } 147 148 @Override 149 public boolean onOptionsItemSelected(MenuItem item) { 150 switch (item.getTitle().toString()) { 151 case "Auth": 152 authAllSlices(); 153 return true; 154 case "Shortcut": 155 mTypeMenu.setIcon(R.drawable.ic_shortcut); 156 mSelectedMode = SliceView.MODE_SHORTCUT; 157 updateSliceModes(); 158 return true; 159 case "Small": 160 mTypeMenu.setIcon(R.drawable.ic_small); 161 mSelectedMode = SliceView.MODE_SMALL; 162 updateSliceModes(); 163 return true; 164 case "Large": 165 mTypeMenu.setIcon(R.drawable.ic_large); 166 mSelectedMode = SliceView.MODE_LARGE; 167 updateSliceModes(); 168 return true; 169 } 170 return super.onOptionsItemSelected(item); 171 } 172 173 @Override 174 protected void onSaveInstanceState(Bundle outState) { 175 super.onSaveInstanceState(outState); 176 outState.putInt("SELECTED_MODE", mSelectedMode); 177 outState.putString("SELECTED_QUERY", mSearchView.getQuery().toString()); 178 } 179 180 private void authAllSlices() { 181 List<ApplicationInfo> packages = getPackageManager().getInstalledApplications(0); 182 packages.forEach(info -> { 183 for (int i = 0; i < URI_PATHS.length; i++) { 184 grantUriPermission(info.packageName, getUri(URI_PATHS[i], getApplicationContext()), 185 Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION 186 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); 187 } 188 }); 189 } 190 191 private void updateAvailableSlices() { 192 mSliceUris.clear(); 193 List<PackageInfo> packageInfos = getPackageManager() 194 .getInstalledPackages(PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA); 195 for (PackageInfo pi : packageInfos) { 196 ActivityInfo[] activityInfos = pi.activities; 197 if (activityInfos != null) { 198 for (ActivityInfo ai : activityInfos) { 199 if (ai.metaData != null) { 200 String sliceUri = ai.metaData.getString(SLICE_METADATA_KEY); 201 if (sliceUri != null) { 202 mSliceUris.add(Uri.parse(sliceUri)); 203 } 204 } 205 } 206 } 207 } 208 for (int i = 0; i < URI_PATHS.length; i++) { 209 mSliceUris.add(getUri(URI_PATHS[i], getApplicationContext())); 210 } 211 populateAdapter(String.valueOf(mSearchView.getQuery())); 212 } 213 214 private void addSlice(Intent intent) { 215 SliceView v = new SliceView(getApplicationContext()); 216 v.setTag(intent); 217 if (mSliceLiveData != null) { 218 mSliceLiveData.removeObservers(this); 219 } 220 mContainer.removeAllViews(); 221 mContainer.addView(v); 222 mSliceLiveData = SliceLiveData.fromIntent(this, intent); 223 v.setMode(mSelectedMode); 224 mSliceLiveData.observe(this, v); 225 } 226 227 private void addSlice(Uri uri) { 228 if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) { 229 SliceView v = new SliceView(getApplicationContext()); 230 v.setTag(uri); 231 if (mSliceLiveData != null) { 232 mSliceLiveData.removeObservers(this); 233 } 234 mContainer.removeAllViews(); 235 mContainer.addView(v); 236 mSliceLiveData = SliceLiveData.fromUri(this, uri); 237 v.setMode(mSelectedMode); 238 mSliceLiveData.observe(this, v); 239 } else { 240 Log.w(TAG, "Invalid uri, skipping slice: " + uri); 241 } 242 } 243 244 private void updateSliceModes() { 245 final int count = mContainer.getChildCount(); 246 for (int i = 0; i < count; i++) { 247 ((SliceView) mContainer.getChildAt(i)).setMode(mSelectedMode); 248 } 249 } 250 251 private void populateAdapter(String query) { 252 final MatrixCursor c = new MatrixCursor(new String[]{BaseColumns._ID, "uri"}); 253 ArrayMap<String, Integer> ranking = new ArrayMap<>(); 254 ArrayList<String> suggestions = new ArrayList(); 255 mSliceUris.forEach(uri -> { 256 String uriString = uri.toString(); 257 if (uriString.contains(query)) { 258 ranking.put(uriString, uriString.indexOf(query)); 259 suggestions.add(uriString); 260 } 261 }); 262 suggestions.sort(new Comparator<String>() { 263 @Override 264 public int compare(String o1, String o2) { 265 return Integer.compare(ranking.get(o1), ranking.get(o2)); 266 } 267 }); 268 for (int i = 0; i < suggestions.size(); i++) { 269 c.addRow(new Object[]{i, suggestions.get(i)}); 270 } 271 mAdapter.changeCursor(c); 272 } 273} 274