1 /*
2  * Copyright (C) 2021 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 androidx.window.common;
18 
19 import android.annotation.NonNull;
20 import android.content.ContentResolver;
21 import android.content.Context;
22 import android.database.ContentObserver;
23 import android.net.Uri;
24 import android.os.Handler;
25 import android.os.Looper;
26 import android.provider.Settings;
27 import android.text.TextUtils;
28 
29 import androidx.window.util.BaseDataProducer;
30 
31 import com.android.internal.R;
32 
33 import java.util.Optional;
34 import java.util.function.Consumer;
35 
36 /**
37  * Implementation of {@link androidx.window.util.DataProducer} that produces a
38  * {@link String} that can be parsed to a {@link CommonFoldingFeature}.
39  * {@link RawFoldingFeatureProducer} searches for the value in two places. The first check is in
40  * settings where the {@link String} property is saved with the key
41  * {@link RawFoldingFeatureProducer#DISPLAY_FEATURES}. If this value is null or empty then the
42  * value in {@link android.content.res.Resources} is used. If both are empty then
43  * {@link RawFoldingFeatureProducer#getData} returns an empty object.
44  * {@link RawFoldingFeatureProducer} listens to changes in the setting so that it can override
45  * the system {@link CommonFoldingFeature} data.
46  */
47 public final class RawFoldingFeatureProducer extends BaseDataProducer<String> {
48     private static final String DISPLAY_FEATURES = "display_features";
49 
50     private final Uri mDisplayFeaturesUri =
51             Settings.Global.getUriFor(DISPLAY_FEATURES);
52 
53     private final ContentResolver mResolver;
54     private final ContentObserver mObserver;
55     private final String mResourceFeature;
56     private boolean mRegisteredObservers;
57 
RawFoldingFeatureProducer(@onNull Context context)58     public RawFoldingFeatureProducer(@NonNull Context context) {
59         mResolver = context.getContentResolver();
60         mObserver = new SettingsObserver();
61         mResourceFeature = context.getResources().getString(R.string.config_display_features);
62     }
63 
64     @Override
65     @NonNull
getData(Consumer<String> dataConsumer)66     public void getData(Consumer<String> dataConsumer) {
67         String displayFeaturesString = getFeatureString();
68         if (displayFeaturesString == null) {
69             dataConsumer.accept("");
70         } else {
71             dataConsumer.accept(displayFeaturesString);
72         }
73     }
74 
75     /**
76      * Returns the {@link String} representation for a {@link CommonFoldingFeature} from settings if
77      * present and falls back to the resource value if empty or {@code null}.
78      */
getFeatureString()79     private String getFeatureString() {
80         String settingsFeature = Settings.Global.getString(mResolver, DISPLAY_FEATURES);
81         if (TextUtils.isEmpty(settingsFeature)) {
82             return mResourceFeature;
83         }
84         return settingsFeature;
85     }
86 
87     @Override
onListenersChanged()88     protected void onListenersChanged() {
89         if (hasListeners()) {
90             registerObserversIfNeeded();
91         } else {
92             unregisterObserversIfNeeded();
93         }
94     }
95 
96     @NonNull
97     @Override
getCurrentData()98     public Optional<String> getCurrentData() {
99         return Optional.of(getFeatureString());
100     }
101 
102     /**
103      * Registers settings observers, if needed. When settings observers are registered for this
104      * producer callbacks for changes in data will be triggered.
105      */
registerObserversIfNeeded()106     private void registerObserversIfNeeded() {
107         if (mRegisteredObservers) {
108             return;
109         }
110         mRegisteredObservers = true;
111         mResolver.registerContentObserver(mDisplayFeaturesUri, false /* notifyForDescendants */,
112                 mObserver /* ContentObserver */);
113     }
114 
115     /**
116      * Unregisters settings observers, if needed. When settings observers are unregistered for this
117      * producer callbacks for changes in data will not be triggered.
118      */
unregisterObserversIfNeeded()119     private void unregisterObserversIfNeeded() {
120         if (!mRegisteredObservers) {
121             return;
122         }
123         mRegisteredObservers = false;
124         mResolver.unregisterContentObserver(mObserver);
125     }
126 
127     private final class SettingsObserver extends ContentObserver {
SettingsObserver()128         SettingsObserver() {
129             super(new Handler(Looper.getMainLooper()));
130         }
131 
132         @Override
onChange(boolean selfChange, Uri uri)133         public void onChange(boolean selfChange, Uri uri) {
134             if (mDisplayFeaturesUri.equals(uri)) {
135                 notifyDataChanged(getFeatureString());
136             }
137         }
138     }
139 }