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