summaryrefslogtreecommitdiff
path: root/PermissionController/src/com/android/permissioncontroller/safetylabel/SafetyLabelChangedBroadcastReceiver.kt
blob: cc9487210150090ea52e269f33a7d9256e69d8b2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.permissioncontroller.safetylabel

import android.Manifest
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.Intent.ACTION_PACKAGE_ADDED
import android.content.pm.PackageManager
import android.os.Build
import android.os.Process
import android.os.UserHandle
import android.os.UserManager
import android.util.Log
import androidx.annotation.MainThread
import androidx.annotation.RequiresApi
import com.android.permission.safetylabel.SafetyLabel as AppMetadataSafetyLabel
import com.android.permissioncontroller.permission.data.v34.LightInstallSourceInfoLiveData
import com.android.permissioncontroller.permission.data.LightPackageInfoLiveData
import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo
import com.android.permissioncontroller.permission.utils.KotlinUtils
import com.android.permissioncontroller.permission.utils.PermissionMapping
import com.android.permissioncontroller.permission.utils.Utils
import com.android.permissioncontroller.safetylabel.AppsSafetyLabelHistory.SafetyLabel as SafetyLabelForPersistence
import java.time.Instant
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch

/**
 * Listens for package installs and updates, and updates the [AppsSafetyLabelHistoryPersistence] if
 * the app safety label has changed.
 */
// TODO(b/264884476): Remove excess logging from this class once feature is stable.
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
class SafetyLabelChangedBroadcastReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        if (!KotlinUtils.isSafetyLabelChangeNotificationsEnabled(context)) {
            return
        }

        val packageChangeEvent = getPackageChangeEvent(intent)
        if (!(packageChangeEvent == PackageChangeEvent.NEW_INSTALL ||
            packageChangeEvent == PackageChangeEvent.UPDATE)) {
            return
        }

        val packageName = intent.data?.schemeSpecificPart
        if (packageName == null) {
            Log.w(TAG, "Received broadcast without package name")
            return
        }

        val currentUser = Process.myUserHandle()
        if (DEBUG) {
            Log.i(
                TAG,
                "received broadcast packageName: $packageName, current user: $currentUser," +
                    " packageChangeEvent: $packageChangeEvent, intent user:" +
                    " ${intent.getParcelableExtra(Intent.EXTRA_USER, UserHandle::class.java)
                                    ?: currentUser}")
        }
        val userManager = Utils.getSystemServiceSafe(context, UserManager::class.java)
        if (userManager.isProfile) {
            forwardBroadcastToParentUser(context, userManager, intent)
            return
        }

        val user: UserHandle =
            intent.getParcelableExtra(Intent.EXTRA_USER, UserHandle::class.java) ?: currentUser

        val pendingResult: PendingResult = goAsync()

        GlobalScope.launch(Dispatchers.Main) {
            processPackageChange(context, packageName, user)
            pendingResult.finish()
        }
    }

    /** Processes the package change for the given package and user. */
    private suspend fun processPackageChange(
        context: Context,
        packageName: String,
        user: UserHandle,
    ) {
        val lightPackageInfo =
            LightPackageInfoLiveData[Pair(packageName, user)].getInitializedValue() ?: return
        if (!isAppRequestingLocationPermission(lightPackageInfo)) {
            return
        }
        // TODO(b/261607291): Enable safety label change notifications feature for
        //  preinstalled apps.
        if (!isStoreInstalledPackage(Pair(packageName, user))) {
            return
        }
        writeSafetyLabel(context, lightPackageInfo, user)
    }

    /**
     * Retrieves and writes the safety label for a package to the safety labels store.
     *
     * As I/O operations are invoked, we run this method on the main thread.
     */
    @MainThread
    private fun writeSafetyLabel(
        context: Context,
        lightPackageInfo: LightPackageInfo,
        user: UserHandle,
    ) {
        val packageName = lightPackageInfo.packageName
        if (DEBUG) {
            Log.i(
                TAG,
                "writeSafetyLabel called for packageName: $packageName, currentUser:" +
                    " ${Process.myUserHandle()}")
        }

        // Get the context for the user in which the app is installed.
        val userContext =
            if (user == Process.myUserHandle()) {
                context
            } else {
                context.createContextAsUser(user, 0)
            }
        val appMetadataBundle =
            try {
                userContext.packageManager.getAppMetadata(packageName)
            } catch (e: PackageManager.NameNotFoundException) {
                Log.w(TAG, "Package $packageName not found while retrieving app metadata")
                return
            }

        if (DEBUG) {
            Log.i(TAG, "appMetadataBundle $appMetadataBundle")
        }
        val safetyLabel: AppMetadataSafetyLabel =
            AppMetadataSafetyLabel.getSafetyLabelFromMetadata(appMetadataBundle) ?: return

        val receivedAtMs: Long = lightPackageInfo.lastUpdateTime

        val safetyLabelForPersistence: SafetyLabelForPersistence =
            AppsSafetyLabelHistory.SafetyLabel.extractLocationSharingSafetyLabel(
                packageName, Instant.ofEpochMilli(receivedAtMs), safetyLabel)
        val historyFile = AppsSafetyLabelHistoryPersistence.getSafetyLabelHistoryFile(context)

        AppsSafetyLabelHistoryPersistence.recordSafetyLabel(safetyLabelForPersistence, historyFile)
    }

    private fun getPackageChangeEvent(intent: Intent): PackageChangeEvent {
        val action = intent.action
        val replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)

        return if (isPackageAddedBroadcast(action) && replacing) {
            PackageChangeEvent.UPDATE
        } else if (isPackageAddedBroadcast(action)) {
            PackageChangeEvent.NEW_INSTALL
        } else {
            PackageChangeEvent.INSIGNIFICANT
        }
    }

    /** Companion object for [SafetyLabelChangedBroadcastReceiver]. */
    companion object {
        private val TAG = SafetyLabelChangedBroadcastReceiver::class.simpleName
        private const val ACTION_PACKAGE_ADDED_PERMISSIONCONTROLLER_FORWARDED =
            "com.android.permissioncontroller.action.PACKAGE_ADDED_PERMISSIONCONTROLLER_FORWARDED"
        private const val DEBUG = true
        private val LOCATION_PERMISSIONS =
            PermissionMapping.getPlatformPermissionNamesOfGroup(Manifest.permission_group.LOCATION)

        private fun isAppRequestingLocationPermission(lightPackageInfo: LightPackageInfo): Boolean {
            return lightPackageInfo.requestedPermissions.any { LOCATION_PERMISSIONS.contains(it) }
        }

        private suspend fun isStoreInstalledPackage(
            packageUser: Pair<String, UserHandle>
        ): Boolean {
            val lightInstallSourceInfo =
                LightInstallSourceInfoLiveData[packageUser].getInitializedValue()
            return lightInstallSourceInfo.isStoreInstalled()
        }

        private fun isPackageAddedBroadcast(intentAction: String?) =
            intentAction == ACTION_PACKAGE_ADDED ||
                intentAction == ACTION_PACKAGE_ADDED_PERMISSIONCONTROLLER_FORWARDED

        private fun forwardBroadcastToParentUser(
            context: Context,
            userManager: UserManager,
            intent: Intent
        ) {
            val currentUser = Process.myUserHandle()
            val profileParent = userManager.getProfileParent(currentUser)
            if (profileParent == null) {
                Log.w(TAG, "Could not find profile parent for $currentUser")
                return
            }

            Log.i(
                TAG,
                "Forwarding intent from current user: $currentUser to profile parent" +
                    " $profileParent")
            context.sendBroadcastAsUser(
                Intent(intent)
                    .setAction(ACTION_PACKAGE_ADDED_PERMISSIONCONTROLLER_FORWARDED)
                    .putExtra(Intent.EXTRA_USER, currentUser),
                profileParent)
        }

        /** Types of package change events. */
        enum class PackageChangeEvent {
            INSIGNIFICANT,
            NEW_INSTALL,
            UPDATE,
        }
    }
}