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