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