• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5  * except in compliance with the License. You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the
10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11  * KIND, either express or implied. See the License for the specific language governing
12  * permissions and limitations under the License.
13  */
14 
15 package com.android.settings.search;
16 
17 import static android.app.slice.Slice.HINT_LARGE;
18 import static android.app.slice.Slice.HINT_TITLE;
19 import static android.app.slice.SliceItem.FORMAT_TEXT;
20 import static com.android.settings.search.DeviceIndexFeatureProvider.createDeepLink;
21 
22 import android.app.job.JobParameters;
23 import android.app.job.JobService;
24 import android.content.ContentResolver;
25 import android.content.Intent;
26 import android.net.Uri;
27 import android.net.Uri.Builder;
28 import android.provider.SettingsSlicesContract;
29 import android.util.Log;
30 
31 import com.android.internal.annotations.VisibleForTesting;
32 import com.android.settings.overlay.FeatureFactory;
33 import com.android.settings.slices.SettingsSliceProvider;
34 import com.android.settings.slices.SliceDeepLinkSpringBoard;
35 
36 import java.util.Collection;
37 import java.util.concurrent.CountDownLatch;
38 
39 import androidx.slice.Slice;
40 import androidx.slice.SliceItem;
41 import androidx.slice.SliceViewManager;
42 import androidx.slice.SliceViewManager.SliceCallback;
43 import androidx.slice.SliceMetadata;
44 import androidx.slice.core.SliceQuery;
45 import androidx.slice.widget.ListContent;
46 
47 public class DeviceIndexUpdateJobService extends JobService {
48 
49     private static final String TAG = "DeviceIndexUpdate";
50     private static final boolean DEBUG = false;
51     @VisibleForTesting
52     protected boolean mRunningJob;
53 
54     @Override
onStartJob(JobParameters params)55     public boolean onStartJob(JobParameters params) {
56         if (DEBUG) Log.d(TAG, "onStartJob");
57         if (!mRunningJob) {
58             mRunningJob = true;
59             Thread thread = new Thread(() -> updateIndex(params));
60             thread.setPriority(Thread.MIN_PRIORITY);
61             thread.start();
62         }
63         return true;
64     }
65 
66     @Override
onStopJob(JobParameters params)67     public boolean onStopJob(JobParameters params) {
68         if (DEBUG) Log.d(TAG, "onStopJob " + mRunningJob);
69         if (mRunningJob) {
70             mRunningJob = false;
71             return true;
72         }
73         return false;
74     }
75 
76     @VisibleForTesting
updateIndex(JobParameters params)77     protected void updateIndex(JobParameters params) {
78         if (DEBUG) {
79             Log.d(TAG, "Starting index");
80         }
81         final DeviceIndexFeatureProvider indexProvider = FeatureFactory.getFactory(this)
82                 .getDeviceIndexFeatureProvider();
83         final SliceViewManager manager = getSliceViewManager();
84         final Uri baseUri = new Builder()
85                 .scheme(ContentResolver.SCHEME_CONTENT)
86                 .authority(SettingsSliceProvider.SLICE_AUTHORITY)
87                 .build();
88         final Uri platformBaseUri = new Builder()
89                 .scheme(ContentResolver.SCHEME_CONTENT)
90                 .authority(SettingsSlicesContract.AUTHORITY)
91                 .build();
92         final Collection<Uri> slices = manager.getSliceDescendants(baseUri);
93         slices.addAll(manager.getSliceDescendants(platformBaseUri));
94 
95         if (DEBUG) {
96             Log.d(TAG, "Indexing " + slices.size() + " slices");
97         }
98 
99         indexProvider.clearIndex(this /* context */);
100 
101         for (Uri slice : slices) {
102             if (!mRunningJob) {
103                 return;
104             }
105             Slice loadedSlice = bindSliceSynchronous(manager, slice);
106             // TODO: Get Title APIs on SliceMetadata and use that.
107             SliceMetadata metaData = getMetadata(loadedSlice);
108             CharSequence title = findTitle(loadedSlice, metaData);
109             if (title != null) {
110                 if (DEBUG) {
111                     Log.d(TAG, "Indexing: " + slice + " " + title + " " + loadedSlice);
112                 }
113                 indexProvider.index(this, title, slice, createDeepLink(
114                         new Intent(SliceDeepLinkSpringBoard.ACTION_VIEW_SLICE)
115                                 .setPackage(getPackageName())
116                                 .putExtra(SliceDeepLinkSpringBoard.EXTRA_SLICE, slice.toString())
117                                 .toUri(Intent.URI_ANDROID_APP_SCHEME)),
118                         metaData.getSliceKeywords());
119             }
120         }
121         if (DEBUG) {
122             Log.d(TAG, "Done indexing");
123         }
124         jobFinished(params, false);
125     }
126 
getSliceViewManager()127     protected SliceViewManager getSliceViewManager() {
128         return SliceViewManager.getInstance(this);
129     }
130 
getMetadata(Slice loadedSlice)131     protected SliceMetadata getMetadata(Slice loadedSlice) {
132         return SliceMetadata.from(this, loadedSlice);
133     }
134 
findTitle(Slice loadedSlice, SliceMetadata metaData)135     protected CharSequence findTitle(Slice loadedSlice, SliceMetadata metaData) {
136         ListContent content = new ListContent(null, loadedSlice);
137         SliceItem headerItem = content.getHeaderItem();
138         if (headerItem == null) {
139             if (content.getRowItems().size() != 0) {
140                 headerItem = content.getRowItems().get(0);
141             } else {
142                 return null;
143             }
144         }
145         // Look for a title, then large text, then any text at all.
146         SliceItem title = SliceQuery.find(headerItem, FORMAT_TEXT, HINT_TITLE, null);
147         if (title != null) {
148             return title.getText();
149         }
150         title = SliceQuery.find(headerItem, FORMAT_TEXT, HINT_LARGE, null);
151         if (title != null) {
152             return title.getText();
153         }
154         title = SliceQuery.find(headerItem, FORMAT_TEXT);
155         if (title != null) {
156             return title.getText();
157         }
158         return null;
159     }
160 
bindSliceSynchronous(SliceViewManager manager, Uri slice)161     protected Slice bindSliceSynchronous(SliceViewManager manager, Uri slice) {
162         final Slice[] returnSlice = new Slice[1];
163         CountDownLatch latch = new CountDownLatch(1);
164         SliceCallback callback = new SliceCallback() {
165             @Override
166             public void onSliceUpdated(Slice s) {
167                 try {
168                     SliceMetadata m = SliceMetadata.from(DeviceIndexUpdateJobService.this, s);
169                     if (m.getLoadingState() == SliceMetadata.LOADED_ALL) {
170                         returnSlice[0] = s;
171                         latch.countDown();
172                         manager.unregisterSliceCallback(slice, this);
173                     }
174                 } catch (Exception e) {
175                     Log.w(TAG, slice + " cannot be indexed", e);
176                     returnSlice[0] = s;
177                 }
178             }
179         };
180         // Register a callback until we get a loaded slice.
181         manager.registerSliceCallback(slice, callback);
182         // Trigger the first bind in case no loading is needed.
183         callback.onSliceUpdated(manager.bindSlice(slice));
184         try {
185             latch.await();
186         } catch (InterruptedException e) {
187         }
188         return returnSlice[0];
189     }
190 }
191