Home | History | Annotate | Download | only in demos
      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 
     17 package com.example.androidx.slice.demos;
     18 
     19 import static androidx.slice.core.SliceHints.INFINITY;
     20 
     21 import static com.example.androidx.slice.demos.SampleSliceProvider.URI_PATHS;
     22 import static com.example.androidx.slice.demos.SampleSliceProvider.getUri;
     23 
     24 import android.content.ContentResolver;
     25 import android.content.Intent;
     26 import android.content.pm.ActivityInfo;
     27 import android.content.pm.PackageInfo;
     28 import android.content.pm.PackageManager;
     29 import android.database.Cursor;
     30 import android.database.MatrixCursor;
     31 import android.net.Uri;
     32 import android.os.Bundle;
     33 import android.provider.BaseColumns;
     34 import android.util.ArrayMap;
     35 import android.util.Log;
     36 import android.view.Menu;
     37 import android.view.MenuItem;
     38 import android.view.SubMenu;
     39 import android.view.View;
     40 import android.view.ViewGroup;
     41 import android.widget.CursorAdapter;
     42 import android.widget.SearchView;
     43 import android.widget.SimpleCursorAdapter;
     44 import android.widget.Toast;
     45 
     46 import androidx.annotation.NonNull;
     47 import androidx.appcompat.app.AppCompatActivity;
     48 import androidx.appcompat.widget.Toolbar;
     49 import androidx.lifecycle.LiveData;
     50 import androidx.slice.Slice;
     51 import androidx.slice.SliceItem;
     52 import androidx.slice.SliceMetadata;
     53 import androidx.slice.widget.EventInfo;
     54 import androidx.slice.widget.SliceLiveData;
     55 import androidx.slice.widget.SliceView;
     56 
     57 import java.util.ArrayList;
     58 import java.util.Collections;
     59 import java.util.List;
     60 
     61 /**
     62  * Example use of SliceView. Uses a search bar to select/auto-complete a slice uri which is
     63  * then displayed in the selected mode with SliceView.
     64  */
     65 public class SliceBrowser extends AppCompatActivity implements SliceView.OnSliceActionListener {
     66 
     67     private static final String TAG = "SlicePresenter";
     68 
     69     private static final String SLICE_METADATA_KEY = "android.metadata.SLICE_URI";
     70     private static final boolean TEST_INTENT = false;
     71     private static final boolean TEST_THEMES = true;
     72     private static final boolean SCROLLING_ENABLED = true;
     73 
     74     private ArrayList<Uri> mSliceUris = new ArrayList<Uri>();
     75     private int mSelectedMode;
     76     private ViewGroup mContainer;
     77     private SearchView mSearchView;
     78     private SimpleCursorAdapter mAdapter;
     79     private SubMenu mTypeMenu;
     80     private LiveData<Slice> mSliceLiveData;
     81 
     82     @Override
     83     public void onCreate(Bundle savedInstanceState) {
     84         super.onCreate(savedInstanceState);
     85         setContentView(R.layout.activity_layout);
     86 
     87         Toolbar toolbar = findViewById(R.id.search_toolbar);
     88         setSupportActionBar(toolbar);
     89 
     90         // Shows the slice
     91         mContainer = findViewById(R.id.slice_preview);
     92         mSearchView = findViewById(R.id.search_view);
     93 
     94         final String[] from = new String[]{"uri"};
     95         final int[] to = new int[]{android.R.id.text1};
     96         mAdapter = new SimpleCursorAdapter(this, R.layout.simple_list_item_1,
     97                 null, from, to, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
     98         mSearchView.setSuggestionsAdapter(mAdapter);
     99         mSearchView.setIconifiedByDefault(false);
    100         mSearchView.setOnSuggestionListener(new SearchView.OnSuggestionListener() {
    101             @Override
    102             public boolean onSuggestionClick(int position) {
    103                 mSearchView.setQuery(((Cursor) mAdapter.getItem(position)).getString(1), true);
    104                 return true;
    105             }
    106 
    107             @Override
    108             public boolean onSuggestionSelect(int position) {
    109                 mSearchView.setQuery(((Cursor) mAdapter.getItem(position)).getString(1), true);
    110                 return true;
    111             }
    112         });
    113         mSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
    114             @Override
    115             public boolean onQueryTextSubmit(String s) {
    116                 addSlice(Uri.parse(s));
    117                 mSearchView.clearFocus();
    118                 return false;
    119             }
    120 
    121             @Override
    122             public boolean onQueryTextChange(String s) {
    123                 populateAdapter(s);
    124                 return false;
    125             }
    126         });
    127 
    128         mSelectedMode = (savedInstanceState != null)
    129                 ? savedInstanceState.getInt("SELECTED_MODE", SliceView.MODE_LARGE)
    130                 : SliceView.MODE_LARGE;
    131         if (savedInstanceState != null) {
    132             mSearchView.setQuery(savedInstanceState.getString("SELECTED_QUERY"), true);
    133         }
    134 
    135         // TODO: Listen for changes.
    136         updateAvailableSlices();
    137         if (TEST_INTENT) {
    138             addSlice(new Intent("androidx.intent.SLICE_ACTION").setPackage(getPackageName()));
    139         }
    140     }
    141 
    142     @Override
    143     public boolean onCreateOptionsMenu(Menu menu) {
    144         mTypeMenu = menu.addSubMenu("Type");
    145         mTypeMenu.setIcon(R.drawable.ic_large);
    146         mTypeMenu.getItem().setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
    147         mTypeMenu.add("Shortcut");
    148         mTypeMenu.add("Small");
    149         mTypeMenu.add("Large");
    150         super.onCreateOptionsMenu(menu);
    151         return true;
    152     }
    153 
    154     @Override
    155     public boolean onOptionsItemSelected(MenuItem item) {
    156         switch (item.getTitle().toString()) {
    157             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) {
    178         super.onSaveInstanceState(outState);
    179         outState.putInt("SELECTED_MODE", mSelectedMode);
    180         outState.putString("SELECTED_QUERY", mSearchView.getQuery().toString());
    181     }
    182 
    183     private void updateAvailableSlices() {
    184         mSliceUris.clear();
    185         List<PackageInfo> packageInfos = getPackageManager()
    186                 .getInstalledPackages(PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA);
    187         for (PackageInfo pi : packageInfos) {
    188             ActivityInfo[] activityInfos = pi.activities;
    189             if (activityInfos != null) {
    190                 for (ActivityInfo ai : activityInfos) {
    191                     if (ai.metaData != null) {
    192                         String sliceUri = ai.metaData.getString(SLICE_METADATA_KEY);
    193                         if (sliceUri != null) {
    194                             mSliceUris.add(Uri.parse(sliceUri));
    195                         }
    196                     }
    197                 }
    198             }
    199         }
    200         for (int i = 0; i < URI_PATHS.length; i++) {
    201             mSliceUris.add(getUri(URI_PATHS[i], getApplicationContext()));
    202         }
    203         populateAdapter(String.valueOf(mSearchView.getQuery()));
    204     }
    205 
    206     private void addSlice(Intent intent) {
    207         SliceView v = createSliceView();
    208         v.setTag(intent);
    209         mContainer.removeAllViews();
    210         mContainer.addView(v);
    211         mSliceLiveData = SliceLiveData.fromIntent(this, intent);
    212         v.setMode(mSelectedMode);
    213         mSliceLiveData.observe(this, v);
    214     }
    215 
    216     private void addSlice(Uri uri) {
    217         if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
    218             SliceView v = createSliceView();
    219             v.setTag(uri);
    220             mContainer.removeAllViews();
    221             mContainer.addView(v);
    222             mSliceLiveData = SliceLiveData.fromUri(this, uri);
    223             v.setMode(mSelectedMode);
    224             mSliceLiveData.observe(this, slice -> {
    225                 v.setSlice(slice);
    226                 SliceMetadata metadata = SliceMetadata.from(this, slice);
    227                 long expiry = metadata.getExpiry();
    228                 if (expiry != INFINITY) {
    229                     // Shows the updated text after the TTL expires.
    230                     v.postDelayed(() -> v.setSlice(slice),
    231                             expiry - System.currentTimeMillis() + 15);
    232                 }
    233             });
    234             mSliceLiveData.observe(this, slice -> Log.d(TAG, "Slice: " + slice));
    235         } else {
    236             Log.w(TAG, "Invalid uri, skipping slice: " + uri);
    237         }
    238     }
    239 
    240     private void updateSliceModes() {
    241         final int count = mContainer.getChildCount();
    242         for (int i = 0; i < count; i++) {
    243             ((SliceView) mContainer.getChildAt(i)).setMode(mSelectedMode);
    244         }
    245     }
    246 
    247     private void populateAdapter(String query) {
    248         final MatrixCursor c = new MatrixCursor(new String[]{BaseColumns._ID, "uri"});
    249         ArrayMap<String, Integer> ranking = new ArrayMap<>();
    250         ArrayList<String> suggestions = new ArrayList();
    251         for (Uri uri : mSliceUris) {
    252 
    253             String uriString = uri.toString();
    254             if (uriString.contains(query)) {
    255                 ranking.put(uriString, uriString.indexOf(query));
    256                 suggestions.add(uriString);
    257             }
    258         }
    259         Collections.sort(suggestions, (o1, o2) ->
    260                 Integer.compare(ranking.get(o1), ranking.get(o2)));
    261         for (int i = 0; i < suggestions.size(); i++) {
    262             c.addRow(new Object[]{i, suggestions.get(i)});
    263         }
    264         mAdapter.changeCursor(c);
    265     }
    266 
    267     @Override
    268     public void onSliceAction(@NonNull EventInfo info, @NonNull SliceItem item) {
    269         Log.w(TAG, "onSliceAction, info: " + info);
    270         Log.w(TAG, "onSliceAction, sliceItem: \n" + item);
    271     }
    272 
    273     private SliceView createSliceView() {
    274         SliceView v = TEST_THEMES
    275                 ? new SliceView(this)
    276                 : new SliceView(getApplicationContext());
    277         v.setOnSliceActionListener(this);
    278         v.setOnClickListener(new View.OnClickListener() {
    279             @Override
    280             public void onClick(View v) {
    281                 Toast.makeText(getApplicationContext(),
    282                         "Custom listener clicked", Toast.LENGTH_SHORT).show();
    283             }
    284         });
    285         if (mSliceLiveData != null) {
    286             mSliceLiveData.removeObservers(this);
    287         }
    288         v.setScrollable(SCROLLING_ENABLED);
    289         v.setOnLongClickListener(new View.OnLongClickListener() {
    290             @Override
    291             public boolean onLongClick(View v) {
    292                 Toast.makeText(getApplicationContext(), "LONGPRESS !!", Toast.LENGTH_SHORT).show();
    293                 return true;
    294             }
    295         });
    296         return v;
    297     }
    298 }
    299