• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.nfc.cardemulation;
18 
19 import static com.android.permission.flags.Flags.crossUserRoleEnabled;
20 
21 import android.app.ActivityManager;
22 import android.app.role.OnRoleHoldersChangedListener;
23 import android.app.role.RoleManager;
24 import android.content.Context;
25 import android.nfc.PackageAndUser;
26 import android.os.Binder;
27 import android.os.UserHandle;
28 import android.os.UserManager;
29 import android.permission.flags.Flags;
30 import android.sysprop.NfcProperties;
31 import android.util.Log;
32 
33 import com.android.internal.annotations.VisibleForTesting;
34 import com.android.nfc.NfcEventLog;
35 import com.android.nfc.NfcInjector;
36 import com.android.nfc.proto.NfcEventProto;
37 
38 import java.util.List;
39 import java.util.Objects;
40 
41 public class WalletRoleObserver {
42     static final boolean DBG = NfcProperties.debug_enabled().orElse(true);
43     private static final String TAG = "WalletRoleObserver";
44 
45     public interface Callback {
onWalletRoleHolderChanged(String holder, int userId)46         void onWalletRoleHolderChanged(String holder, int userId);
47     }
48     private Context mContext;
49     private NfcEventLog mNfcEventLog;
50     private RoleManager mRoleManager;
51     @VisibleForTesting
52     final OnRoleHoldersChangedListener mOnRoleHoldersChangedListener;
53     private Callback mCallback;
54     private int mCurrentUser;
55 
WalletRoleObserver(Context context, RoleManager roleManager, Callback callback, NfcInjector nfcInjector)56     public WalletRoleObserver(Context context, RoleManager roleManager,
57             Callback callback, NfcInjector nfcInjector) {
58         this.mContext = context;
59         this.mRoleManager = roleManager;
60         this.mCallback = callback;
61         this.mNfcEventLog = nfcInjector.getNfcEventLog();
62         this.mCurrentUser = ActivityManager.getCurrentUser();
63         this.mOnRoleHoldersChangedListener = (roleName, user) -> {
64             if (!roleName.equals(RoleManager.ROLE_WALLET)) {
65                 return;
66             }
67 
68             Context userContext = mContext.createContextAsUser(
69                     UserHandle.of(mCurrentUser), 0);
70             RoleManager userRoleManager = userContext.getSystemService(RoleManager.class);
71             if (userRoleManager == null) {
72                 return;
73             }
74 
75             if (Flags.walletRoleCrossUserEnabled()) {
76                 if (!Objects.equals(user,
77                         userRoleManager.getActiveUserForRole(RoleManager.ROLE_WALLET))) {
78                     return;
79                 }
80 
81                 UserManager userManager = mContext.getSystemService(UserManager.class);
82                 if (!Objects.equals(user, UserHandle.of(mCurrentUser)) &&
83                         !Objects.equals(userManager.getProfileParent(user),
84                                 UserHandle.of(mCurrentUser))) {
85                     return;
86                 }
87             }
88 
89             List<String> roleHolders = roleManager.getRoleHoldersAsUser(RoleManager.ROLE_WALLET,
90                     user);
91             String roleHolder = roleHolders.isEmpty() ? null : roleHolders.get(0);
92             if (DBG) {
93                 Log.i(TAG, "WalletRoleObserver: Wallet role changed for user "
94                         + user.getIdentifier() + " to " + roleHolder);
95             }
96             mNfcEventLog.logEvent(
97                     NfcEventProto.EventType.newBuilder()
98                             .setWalletRoleHolderChange(
99                                 NfcEventProto.NfcWalletRoleHolderChange.newBuilder()
100                                 .setPackageName(roleHolder != null ? roleHolder : "none")
101                                 .build())
102                             .build());
103             callback.onWalletRoleHolderChanged(roleHolder, user.getIdentifier());
104         };
105         this.mRoleManager.addOnRoleHoldersChangedListenerAsUser(context.getMainExecutor(),
106                 mOnRoleHoldersChangedListener, UserHandle.ALL);
107     }
108 
getDefaultWalletRoleHolder(int userId)109     public PackageAndUser getDefaultWalletRoleHolder(int userId) {
110         final long token = Binder.clearCallingIdentity();
111         final PackageAndUser noRoleHolderResult = new PackageAndUser(userId, null);
112         try {
113             UserHandle roleUserHandle = UserHandle.of(userId);
114             Context userContext = mContext.createContextAsUser(UserHandle.of(userId), 0);
115             RoleManager userRoleManager = userContext.getSystemService(RoleManager.class);
116             if (userRoleManager == null) {
117                 return noRoleHolderResult;
118             }
119 
120             if (Flags.walletRoleCrossUserEnabled() && crossUserRoleEnabled()) {
121                 roleUserHandle = userRoleManager.getActiveUserForRole(
122                         RoleManager.ROLE_WALLET);
123 
124                 if (roleUserHandle == null) {
125                     Log.d(TAG, "No active user for role");
126                     return noRoleHolderResult;
127                 }
128             } else if (!userRoleManager.isRoleAvailable(RoleManager.ROLE_WALLET)) {
129                 return noRoleHolderResult;
130             }
131 
132             List<String> roleHolders = mRoleManager.getRoleHoldersAsUser(RoleManager.ROLE_WALLET,
133                     roleUserHandle);
134             if (roleHolders.isEmpty()) {
135                 return new PackageAndUser(roleUserHandle.getIdentifier(), null);
136             }
137 
138             return new PackageAndUser(roleUserHandle.getIdentifier(), roleHolders.get(0));
139         } finally {
140             Binder.restoreCallingIdentity(token);
141         }
142     }
143 
isWalletRoleFeatureEnabled()144     boolean isWalletRoleFeatureEnabled() {
145         final long token = Binder.clearCallingIdentity();
146         try {
147             return Flags.walletRoleEnabled();
148         } finally {
149             Binder.restoreCallingIdentity(token);
150         }
151     }
152 
onUserSwitched(int userId)153     public void onUserSwitched(int userId) {
154         mCurrentUser = userId;
155         PackageAndUser roleHolder = getDefaultWalletRoleHolder(userId);
156         if (DBG) {
157             Log.i(TAG, "onUserSwitched: Wallet role for user " + userId + ": "
158                     + roleHolder.getUserId() + " (" + roleHolder.getPackage() + ")");
159         }
160         mCallback.onWalletRoleHolderChanged(roleHolder.getPackage(), roleHolder.getUserId());
161     }
162 }
163