1 /* 2 * Copyright (C) 2019 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 18 package com.android.tv.twopanelsettings.slices; 19 20 import android.content.Context; 21 import android.content.Intent; 22 import android.net.Uri; 23 import android.os.AsyncTask; 24 import android.util.Log; 25 26 import androidx.annotation.MainThread; 27 import androidx.annotation.NonNull; 28 import androidx.annotation.RequiresApi; 29 import androidx.lifecycle.LiveData; 30 import androidx.lifecycle.MutableLiveData; 31 import androidx.slice.Slice; 32 import androidx.slice.SliceViewManager; 33 34 import java.util.concurrent.atomic.AtomicBoolean; 35 36 /** 37 * In TvSettings, if a preference item with corresponding slice first get focused, and regain focus 38 * later, we always receive a cache of the slice data first and then we get the real data from 39 * SliceProvider. If the cache and the real data is not consistent, user would clearly see the 40 * process of the cache data transforming to the real one. e.g, toggle state switching from false 41 * to true. 42 * 43 * This is because when a Lifecycle starts again, LiveData natively notify us about the change, 44 * which will trigger onChanged() in SliceFragment. 45 * 46 * To avoid this issue, we should ignore the lifecycle event, use SingleLiveEvent so we only get 47 * notified when the data is actually changed. 48 */ 49 @RequiresApi(19) 50 public final class PreferenceSliceLiveData { 51 private static final String TAG = "SliceLiveData"; 52 53 /** 54 * Produces a {@link LiveData} that tracks a Slice for a given Uri. To use 55 * this method your app must have the permission to the slice Uri. 56 */ fromUri(@onNull Context context, @NonNull Uri uri)57 static @NonNull SliceLiveDataImpl fromUri(@NonNull Context context, @NonNull Uri uri) { 58 return new SliceLiveDataImpl(context.getApplicationContext(), uri); 59 } 60 61 /** 62 * LiveData for slice used by TvSettings. 63 */ 64 static class SliceLiveDataImpl extends MutableLiveData<Slice> { 65 final Intent mIntent; 66 final SliceViewManager mSliceViewManager; 67 Uri mUri; 68 final AtomicBoolean mUpdatePending = new AtomicBoolean(false); SliceLiveDataImpl(Context context, Uri uri)69 SliceLiveDataImpl(Context context, Uri uri) { 70 super(); 71 mSliceViewManager = SliceViewManager.getInstance(context); 72 mUri = uri; 73 mIntent = null; 74 // TODO: Check if uri points at a Slice? 75 } 76 77 @Override onActive()78 protected void onActive() { 79 AsyncTask.execute(mUpdateSlice); 80 if (mUri != null) { 81 mSliceViewManager.registerSliceCallback(mUri, mSliceCallback); 82 } 83 } 84 85 @Override onInactive()86 protected void onInactive() { 87 if (mUri != null) { 88 mSliceViewManager.unregisterSliceCallback(mUri, mSliceCallback); 89 } 90 } 91 92 @Override 93 @MainThread setValue(Slice slice)94 public void setValue(Slice slice) { 95 mUpdatePending.set(true); 96 super.setValue(slice); 97 } 98 99 private final Runnable mUpdateSlice = new Runnable() { 100 @Override 101 public void run() { 102 try { 103 Slice s = mUri != null ? mSliceViewManager.bindSlice(mUri) 104 : mSliceViewManager.bindSlice(mIntent); 105 if (mUri == null && s != null) { 106 mUri = s.getUri(); 107 mSliceViewManager.registerSliceCallback(mUri, mSliceCallback); 108 } 109 postValue(s); 110 } catch (Exception e) { 111 Log.e(TAG, "Error binding slice", e); 112 postValue(null); 113 } 114 } 115 }; 116 117 final SliceViewManager.SliceCallback mSliceCallback = 118 new SliceViewManager.SliceCallback() { 119 @Override 120 public void onSliceUpdated(@NonNull Slice s) { 121 postValue(s); 122 } 123 }; 124 } 125 PreferenceSliceLiveData()126 private PreferenceSliceLiveData() { 127 } 128 } 129