| /* |
| * Copyright (C) 2018 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file |
| * except in compliance with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software distributed under the |
| * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the specific language governing |
| * permissions and limitations under the License. |
| */ |
| |
| package com.android.settings.search; |
| |
| import static android.app.slice.Slice.HINT_LARGE; |
| import static android.app.slice.Slice.HINT_TITLE; |
| import static android.app.slice.SliceItem.FORMAT_TEXT; |
| import static com.android.settings.search.DeviceIndexFeatureProvider.createDeepLink; |
| |
| import android.app.job.JobParameters; |
| import android.app.job.JobService; |
| import android.content.ContentResolver; |
| import android.content.Intent; |
| import android.net.Uri; |
| import android.net.Uri.Builder; |
| import android.provider.SettingsSlicesContract; |
| import android.util.Log; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.settings.overlay.FeatureFactory; |
| import com.android.settings.slices.SettingsSliceProvider; |
| import com.android.settings.slices.SliceDeepLinkSpringBoard; |
| |
| import java.util.Collection; |
| import java.util.concurrent.CountDownLatch; |
| |
| import androidx.slice.Slice; |
| import androidx.slice.SliceItem; |
| import androidx.slice.SliceViewManager; |
| import androidx.slice.SliceViewManager.SliceCallback; |
| import androidx.slice.SliceMetadata; |
| import androidx.slice.core.SliceQuery; |
| import androidx.slice.widget.ListContent; |
| |
| public class DeviceIndexUpdateJobService extends JobService { |
| |
| private static final String TAG = "DeviceIndexUpdate"; |
| private static final boolean DEBUG = false; |
| @VisibleForTesting |
| protected boolean mRunningJob; |
| |
| @Override |
| public boolean onStartJob(JobParameters params) { |
| if (DEBUG) Log.d(TAG, "onStartJob"); |
| if (!mRunningJob) { |
| mRunningJob = true; |
| Thread thread = new Thread(() -> updateIndex(params)); |
| thread.setPriority(Thread.MIN_PRIORITY); |
| thread.start(); |
| } |
| return true; |
| } |
| |
| @Override |
| public boolean onStopJob(JobParameters params) { |
| if (DEBUG) Log.d(TAG, "onStopJob " + mRunningJob); |
| if (mRunningJob) { |
| mRunningJob = false; |
| return true; |
| } |
| return false; |
| } |
| |
| @VisibleForTesting |
| protected void updateIndex(JobParameters params) { |
| if (DEBUG) { |
| Log.d(TAG, "Starting index"); |
| } |
| final DeviceIndexFeatureProvider indexProvider = FeatureFactory.getFactory(this) |
| .getDeviceIndexFeatureProvider(); |
| final SliceViewManager manager = getSliceViewManager(); |
| final Uri baseUri = new Builder() |
| .scheme(ContentResolver.SCHEME_CONTENT) |
| .authority(SettingsSliceProvider.SLICE_AUTHORITY) |
| .build(); |
| final Uri platformBaseUri = new Builder() |
| .scheme(ContentResolver.SCHEME_CONTENT) |
| .authority(SettingsSlicesContract.AUTHORITY) |
| .build(); |
| final Collection<Uri> slices = manager.getSliceDescendants(baseUri); |
| slices.addAll(manager.getSliceDescendants(platformBaseUri)); |
| |
| if (DEBUG) { |
| Log.d(TAG, "Indexing " + slices.size() + " slices"); |
| } |
| |
| indexProvider.clearIndex(this /* context */); |
| |
| for (Uri slice : slices) { |
| if (!mRunningJob) { |
| return; |
| } |
| Slice loadedSlice = bindSliceSynchronous(manager, slice); |
| // TODO: Get Title APIs on SliceMetadata and use that. |
| SliceMetadata metaData = getMetadata(loadedSlice); |
| CharSequence title = findTitle(loadedSlice, metaData); |
| if (title != null) { |
| if (DEBUG) { |
| Log.d(TAG, "Indexing: " + slice + " " + title + " " + loadedSlice); |
| } |
| indexProvider.index(this, title, slice, createDeepLink( |
| new Intent(SliceDeepLinkSpringBoard.ACTION_VIEW_SLICE) |
| .setPackage(getPackageName()) |
| .putExtra(SliceDeepLinkSpringBoard.EXTRA_SLICE, slice.toString()) |
| .toUri(Intent.URI_ANDROID_APP_SCHEME)), |
| metaData.getSliceKeywords()); |
| } |
| } |
| if (DEBUG) { |
| Log.d(TAG, "Done indexing"); |
| } |
| jobFinished(params, false); |
| } |
| |
| protected SliceViewManager getSliceViewManager() { |
| return SliceViewManager.getInstance(this); |
| } |
| |
| protected SliceMetadata getMetadata(Slice loadedSlice) { |
| return SliceMetadata.from(this, loadedSlice); |
| } |
| |
| protected CharSequence findTitle(Slice loadedSlice, SliceMetadata metaData) { |
| ListContent content = new ListContent(null, loadedSlice); |
| SliceItem headerItem = content.getHeaderItem(); |
| if (headerItem == null) { |
| if (content.getRowItems().size() != 0) { |
| headerItem = content.getRowItems().get(0); |
| } else { |
| return null; |
| } |
| } |
| // Look for a title, then large text, then any text at all. |
| SliceItem title = SliceQuery.find(headerItem, FORMAT_TEXT, HINT_TITLE, null); |
| if (title != null) { |
| return title.getText(); |
| } |
| title = SliceQuery.find(headerItem, FORMAT_TEXT, HINT_LARGE, null); |
| if (title != null) { |
| return title.getText(); |
| } |
| title = SliceQuery.find(headerItem, FORMAT_TEXT); |
| if (title != null) { |
| return title.getText(); |
| } |
| return null; |
| } |
| |
| protected Slice bindSliceSynchronous(SliceViewManager manager, Uri slice) { |
| final Slice[] returnSlice = new Slice[1]; |
| CountDownLatch latch = new CountDownLatch(1); |
| SliceCallback callback = new SliceCallback() { |
| @Override |
| public void onSliceUpdated(Slice s) { |
| try { |
| SliceMetadata m = SliceMetadata.from(DeviceIndexUpdateJobService.this, s); |
| if (m.getLoadingState() == SliceMetadata.LOADED_ALL) { |
| returnSlice[0] = s; |
| latch.countDown(); |
| manager.unregisterSliceCallback(slice, this); |
| } |
| } catch (Exception e) { |
| Log.w(TAG, slice + " cannot be indexed", e); |
| returnSlice[0] = s; |
| } |
| } |
| }; |
| // Register a callback until we get a loaded slice. |
| manager.registerSliceCallback(slice, callback); |
| // Trigger the first bind in case no loading is needed. |
| callback.onSliceUpdated(manager.bindSlice(slice)); |
| try { |
| latch.await(); |
| } catch (InterruptedException e) { |
| } |
| return returnSlice[0]; |
| } |
| } |