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