1 /*
2  * Copyright (C) 2023 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.android.settings.biometrics.activeunlock;
18 
19 import android.content.ContentProviderClient;
20 import android.content.ContentResolver;
21 import android.content.Context;
22 import android.database.ContentObserver;
23 import android.net.Uri;
24 import android.os.Bundle;
25 import android.os.Handler;
26 import android.os.Looper;
27 import android.os.RemoteException;
28 import android.text.TextUtils;
29 import android.util.Log;
30 
31 import androidx.annotation.Nullable;
32 
33 import com.android.settingslib.utils.ThreadUtils;
34 
35 /** Listens to updates from the content provider and fetches the latest value. */
36 public class ActiveUnlockContentListener {
37 
38     /** Callback interface for updates to values from the ContentProvider. */
39     public interface OnContentChangedListener {
40         /**
41          * Called when the content observer has updated.
42          *
43          * @param newValue the new value retrieved from the ContentProvider.
44          **/
onContentChanged(@ullable String newValue)45         void onContentChanged(@Nullable String newValue);
46     }
47 
48     private static final String CONTENT_PROVIDER_PATH = "getSummary";
49 
50     private final Context mContext;
51     private final OnContentChangedListener mContentChangedListener;
52     @Nullable private final Uri mUri;
53     private final String mLogTag;
54     private final String mMethodName;
55     private final String mContentKey;
56     @Nullable private String mContent;
57     private boolean mSubscribed = false;
58     private ContentObserver mContentObserver =
59             new ContentObserver(new Handler(Looper.getMainLooper())) {
60                 @Override
61                 public void onChange(boolean selfChange) {
62                     getContentFromUri();
63                 }
64             };
65 
ActiveUnlockContentListener( Context context, OnContentChangedListener listener, String logTag, String methodName, String contentKey)66     ActiveUnlockContentListener(
67             Context context,
68             OnContentChangedListener listener,
69             String logTag,
70             String methodName,
71             String contentKey) {
72         mContext = context;
73         mContentChangedListener = listener;
74         mLogTag = logTag;
75         mMethodName = methodName;
76         mContentKey = contentKey;
77         String authority = new ActiveUnlockStatusUtils(mContext).getAuthority();
78         if (authority != null) {
79             mUri = new Uri.Builder()
80                     .scheme(ContentResolver.SCHEME_CONTENT)
81                     .authority(authority)
82                     .appendPath(CONTENT_PROVIDER_PATH)
83                     .build();
84         } else {
85             mUri = null;
86         }
87 
88     }
89 
90     /** Returns true if start listening for updates from the ContentProvider, false otherwise. */
subscribe()91     public synchronized boolean subscribe() {
92         if (mSubscribed || mUri == null) {
93             return false;
94         }
95         mSubscribed = true;
96         mContext.getContentResolver().registerContentObserver(
97                 mUri, true /* notifyForDescendants */, mContentObserver);
98         ThreadUtils.postOnBackgroundThread(
99                 () -> {
100                     getContentFromUri();
101                 });
102         return true;
103     }
104 
105     /** Returns true if stops listening for updates from the ContentProvider, false otherewise. */
unsubscribe()106     public synchronized boolean unsubscribe() {
107         if (!mSubscribed || mUri == null) {
108             return false;
109         }
110         mSubscribed = false;
111         mContext.getContentResolver().unregisterContentObserver(mContentObserver);
112         return true;
113     }
114 
115     /** Retrieves the most recently fetched value from the ContentProvider. */
116     @Nullable
getContent()117     public String getContent() {
118         return mContent;
119     }
120 
getContentFromUri()121     private void getContentFromUri() {
122         if (mUri == null) {
123             Log.e(mLogTag, "Uri null when trying to fetch content");
124             return;
125         }
126         ContentResolver contentResolver = mContext.getContentResolver();
127         ContentProviderClient client = contentResolver.acquireContentProviderClient(mUri);
128         Bundle bundle;
129         try {
130             bundle = client.call(mMethodName, null /* arg */, null /* extras */);
131         } catch (RemoteException e) {
132             Log.e(mLogTag, "Failed to call contentProvider", e);
133             return;
134         } finally {
135             client.close();
136         }
137         if (bundle == null) {
138             Log.e(mLogTag, "Null bundle returned from contentProvider");
139             return;
140         }
141         String newValue = bundle.getString(mContentKey);
142         if (!TextUtils.equals(mContent, newValue)) {
143             mContent = newValue;
144             mContentChangedListener.onContentChanged(mContent);
145         }
146     }
147 }
148