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