1 /* 2 * Copyright (C) 2024 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.car.carlauncher.datasources.restricted 18 19 import android.car.settings.CarSettings 20 import android.content.ContentResolver 21 import android.content.pm.PackageManager 22 import android.content.pm.PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS 23 import android.content.pm.ResolveInfo 24 import android.database.ContentObserver 25 import android.os.Handler 26 import android.os.Looper 27 import android.provider.Settings 28 import android.util.Log 29 import com.android.car.carlauncher.datasources.restricted.RestrictedAppsUtils.getLauncherActivitiesForRestrictedApps 30 import kotlinx.coroutines.CoroutineDispatcher 31 import kotlinx.coroutines.channels.awaitClose 32 import kotlinx.coroutines.flow.Flow 33 import kotlinx.coroutines.flow.callbackFlow 34 import kotlinx.coroutines.flow.conflate 35 import kotlinx.coroutines.flow.flowOn 36 37 /** 38 * DataSource exposes a flow which tracks list of disabled apps. 39 */ 40 interface DisabledAppsDataSource { getDisabledAppsnull41 fun getDisabledApps(): Flow<List<ResolveInfo>> 42 } 43 44 /** 45 * Impl of [DisabledAppsDataSource], to surface all DisabledApps apis. 46 * 47 * All the operations in this class are non blocking. 48 */ 49 class DisabledAppsDataSourceImpl( 50 private val contentResolver: ContentResolver, 51 private val packageManager: PackageManager, 52 private val bgDispatcher: CoroutineDispatcher 53 ) : DisabledAppsDataSource { 54 55 /** 56 * Gets a Flow producer which gets the current list of disabled apps installed for this user 57 * and found at [Settings.Secure.getString] 58 * for Key [CarSettings.Secure.KEY_PACKAGES_DISABLED_ON_RESOURCE_OVERUSE]. 59 * 60 * The Flow also pushes new list if there is any updates in the list of disabled apps. 61 */ 62 override fun getDisabledApps(): Flow<List<ResolveInfo>> { 63 return callbackFlow { 64 trySend( 65 getLauncherActivitiesForRestrictedApps( 66 packageManager, 67 contentResolver, 68 CarSettings.Secure.KEY_PACKAGES_DISABLED_ON_RESOURCE_OVERUSE, 69 PACKAGES_DISABLED_ON_RESOURCE_OVERUSE_SEPARATOR, 70 MATCH_DISABLED_UNTIL_USED_COMPONENTS 71 ) 72 ) 73 if (Looper.myLooper() == null) { 74 Looper.prepare() 75 } 76 77 val looper: Looper = 78 Looper.myLooper().takeIf { it != null } ?: Looper.getMainLooper().also { 79 Log.d(TAG, "Current thread looper is null, fallback to MainLooper") 80 } 81 82 val disabledAppsObserver = object : ContentObserver(Handler(looper)) { 83 override fun onChange(selfChange: Boolean) { 84 super.onChange(selfChange) 85 trySend( 86 getLauncherActivitiesForRestrictedApps( 87 packageManager, 88 contentResolver, 89 CarSettings.Secure.KEY_PACKAGES_DISABLED_ON_RESOURCE_OVERUSE, 90 PACKAGES_DISABLED_ON_RESOURCE_OVERUSE_SEPARATOR, 91 MATCH_DISABLED_UNTIL_USED_COMPONENTS 92 ) 93 ) 94 } 95 } 96 97 contentResolver.registerContentObserver( 98 Settings.Secure.getUriFor( 99 CarSettings.Secure.KEY_PACKAGES_DISABLED_ON_RESOURCE_OVERUSE 100 ), 101 false, 102 disabledAppsObserver 103 ) 104 105 awaitClose { 106 // If MainLooper do not quit it. MainLooper always stays alive. 107 if (looper != Looper.getMainLooper()) { 108 looper.quitSafely() 109 } 110 contentResolver.unregisterContentObserver(disabledAppsObserver) 111 } 112 }.flowOn(bgDispatcher).conflate() 113 } 114 115 companion object { 116 val TAG: String = DisabledAppsDataSourceImpl::class.java.simpleName 117 const val PACKAGES_DISABLED_ON_RESOURCE_OVERUSE_SEPARATOR = ";" 118 } 119 } 120