Track (and use) UserHandle to make connections

When binding to services, use the UserHandle which registered the
PhoneAccount in order to ensure that the correct user receives the
Intent.

Also make sure that TelecomManager does not leak PhoneAccounts across
profiles, unless the caller is a system-privileged app. PhoneAccounts
will not leak across users, even for system-privileged apps.

Bug: 17421902
Bug: 17421044
Change-Id: I673904a7ae038c88688bba62642e93174e55c308

Conflicts:
	src/com/android/server/telecom/PhoneAccountRegistrar.java
	src/com/android/server/telecom/ServiceBinder.java
	src/com/android/server/telecom/TelecomServiceImpl.java
diff --git a/src/com/android/server/telecom/ConnectionServiceRepository.java b/src/com/android/server/telecom/ConnectionServiceRepository.java
index 9019296..0d73371 100644
--- a/src/com/android/server/telecom/ConnectionServiceRepository.java
+++ b/src/com/android/server/telecom/ConnectionServiceRepository.java
@@ -18,6 +18,8 @@
 
 import android.content.ComponentName;
 import android.content.Context;
+import android.os.UserHandle;
+import android.util.Pair;
 
 import com.android.internal.util.IndentingPrintWriter;
 
@@ -28,8 +30,8 @@
  */
 final class ConnectionServiceRepository
         implements ServiceBinder.Listener<ConnectionServiceWrapper> {
-    private final HashMap<ComponentName, ConnectionServiceWrapper> mServiceCache =
-            new HashMap<ComponentName, ConnectionServiceWrapper>();
+    private final HashMap<Pair<ComponentName, UserHandle>, ConnectionServiceWrapper> mServiceCache =
+            new HashMap<>();
     private final PhoneAccountRegistrar mPhoneAccountRegistrar;
     private final Context mContext;
 
@@ -38,16 +40,18 @@
         mContext = context;
     }
 
-    ConnectionServiceWrapper getService(ComponentName componentName) {
-        ConnectionServiceWrapper service = mServiceCache.get(componentName);
+    ConnectionServiceWrapper getService(ComponentName componentName, UserHandle userHandle) {
+        Pair<ComponentName, UserHandle> cacheKey = Pair.create(componentName, userHandle);
+        ConnectionServiceWrapper service = mServiceCache.get(cacheKey);
         if (service == null) {
             service = new ConnectionServiceWrapper(
                     componentName,
                     this,
                     mPhoneAccountRegistrar,
-                    mContext);
+                    mContext,
+                    userHandle);
             service.addListener(this);
-            mServiceCache.put(componentName, service);
+            mServiceCache.put(cacheKey, service);
         }
         return service;
     }
@@ -70,7 +74,8 @@
     public void dump(IndentingPrintWriter pw) {
         pw.println("mServiceCache:");
         pw.increaseIndent();
-        for (ComponentName componentName : mServiceCache.keySet()) {
+        for (Pair<ComponentName, UserHandle> cacheKey : mServiceCache.keySet()) {
+            ComponentName componentName = cacheKey.first;
             pw.println(componentName);
         }
         pw.decreaseIndent();
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index 8e29d63..d06dded 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -24,6 +24,7 @@
 import android.os.IBinder;
 import android.os.Message;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.telecom.AudioState;
 import android.telecom.Connection;
 import android.telecom.ConnectionRequest;
@@ -706,13 +707,15 @@
      * @param connectionServiceRepository Connection service repository.
      * @param phoneAccountRegistrar Phone account registrar
      * @param context The context.
+     * @param userHandle The {@link UserHandle} to use when binding.
      */
     ConnectionServiceWrapper(
             ComponentName componentName,
             ConnectionServiceRepository connectionServiceRepository,
             PhoneAccountRegistrar phoneAccountRegistrar,
-            Context context) {
-        super(ConnectionService.SERVICE_INTERFACE, componentName, context);
+            Context context,
+            UserHandle userHandle) {
+        super(ConnectionService.SERVICE_INTERFACE, componentName, context, userHandle);
         mConnectionServiceRepository = connectionServiceRepository;
         phoneAccountRegistrar.addListener(new PhoneAccountRegistrar.Listener() {
             // TODO -- Upon changes to PhoneAccountRegistrar, need to re-wire connections
@@ -1117,7 +1120,8 @@
             PhoneAccount account = mPhoneAccountRegistrar.getPhoneAccount(handle);
             if ((account.getCapabilities() & PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION) != 0) {
                 ConnectionServiceWrapper service =
-                        mConnectionServiceRepository.getService(handle.getComponentName());
+                        mConnectionServiceRepository.getService(handle.getComponentName(),
+                                handle.getUserHandle());
                 if (service != null) {
                     simServices.add(service);
                 }
diff --git a/src/com/android/server/telecom/CreateConnectionProcessor.java b/src/com/android/server/telecom/CreateConnectionProcessor.java
index 39d61d2..f33f89d 100644
--- a/src/com/android/server/telecom/CreateConnectionProcessor.java
+++ b/src/com/android/server/telecom/CreateConnectionProcessor.java
@@ -160,9 +160,11 @@
 
         if (mResponse != null && attempt != null) {
             Log.i(this, "Trying attempt %s", attempt);
+            PhoneAccountHandle phoneAccount = attempt.connectionManagerPhoneAccount;
             ConnectionServiceWrapper service =
                     mRepository.getService(
-                            attempt.connectionManagerPhoneAccount.getComponentName());
+                            phoneAccount.getComponentName(),
+                            phoneAccount.getUserHandle());
             if (service == null) {
                 Log.i(this, "Found no connection service for attempt %s", attempt);
                 attemptNextPhoneAccount();
diff --git a/src/com/android/server/telecom/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java
index c21ed98..2371c51 100644
--- a/src/com/android/server/telecom/PhoneAccountRegistrar.java
+++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java
@@ -17,22 +17,25 @@
 package com.android.server.telecom;
 
 import android.Manifest;
+import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.Settings;
 import android.telecom.ConnectionService;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.SubscriptionManager;
-import android.content.ComponentName;
-import android.content.Context;
-import android.net.Uri;
-import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.AtomicFile;
@@ -95,8 +98,10 @@
     private final List<Listener> mListeners = new CopyOnWriteArrayList<>();
     private final AtomicFile mAtomicFile;
     private final Context mContext;
+    private final UserManager mUserManager;
     private final SubscriptionManager mSubscriptionManager;
     private State mState;
+    private UserHandle mCurrentUserHandle;
 
     @VisibleForTesting
     public PhoneAccountRegistrar(Context context) {
@@ -117,7 +122,9 @@
 
         mState = new State();
         mContext = context;
+        mUserManager = UserManager.get(context);
         mSubscriptionManager = SubscriptionManager.from(mContext);
+        mCurrentUserHandle = Process.myUserHandle();
         read();
     }
 
@@ -130,9 +137,11 @@
      * @return The value of the subscription id or -1 if it does not exist or is not valid.
      */
     public int getSubscriptionIdForPhoneAccount(PhoneAccountHandle accountHandle) {
-        PhoneAccount account = getPhoneAccount(accountHandle);
-        if (account == null || !account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION) ||
-                !TextUtils.isDigitsOnly(accountHandle.getId())) {
+        PhoneAccount account = getPhoneAccountInternal(accountHandle);
+        if (account == null
+                || !account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
+                || !TextUtils.isDigitsOnly(accountHandle.getId())
+                || !isVisibleForUser(accountHandle)) {
             // Since no decimals or negative numbers can be valid subscription ids, only a string of
             // numbers can be subscription id
             return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -141,7 +150,10 @@
     }
 
     /**
-     * Retrieves the default outgoing phone account supporting the specified uriScheme.
+     * Retrieves the default outgoing phone account supporting the specified uriScheme. Note that if
+     * {@link #mCurrentUserHandle} does not have visibility into the current default, {@code null}
+     * will be returned.
+     *
      * @param uriScheme The URI scheme for the outgoing call.
      * @return The {@link PhoneAccountHandle} to use.
      */
@@ -151,8 +163,9 @@
         if (userSelected != null) {
             // If there is a default PhoneAccount, ensure it supports calls to handles with the
             // specified uriScheme.
-            final PhoneAccount userSelectedAccount = getPhoneAccount(userSelected);
-            if (userSelectedAccount.supportsUriScheme(uriScheme)) {
+            final PhoneAccount userSelectedAccount = getPhoneAccountInternal(userSelected);
+            if (userSelectedAccount.supportsUriScheme(uriScheme)
+                    && isVisibleForUser(userSelected)) {
                 return userSelected;
             }
         }
@@ -163,14 +176,22 @@
                 // There are no accounts, so there can be no default
                 return null;
             case 1:
-                // There is only one account, which is by definition the default
-                return outgoing.get(0);
+                // There is only one account, which is by definition the default.
+                PhoneAccountHandle onlyHandle = outgoing.get(0);
+                if (isVisibleForUser(onlyHandle)) {
+                    return outgoing.get(0);
+                }
+                return null;
             default:
                 // There are multiple accounts with no selected default
                 return null;
         }
     }
 
+    /**
+     * @return The user-selected outgoing {@link PhoneAccount}, or null if it hasn't been set (or
+     *      if it was set by another user).
+     */
     PhoneAccountHandle getUserSelectedOutgoingPhoneAccount() {
         if (TelephonyManager.getDefault().getPhoneCount() > 1) {
             return getUserSelectedVoicePhoneAccount();
@@ -180,7 +201,8 @@
             // Return the registered outgoing default iff it still exists (we keep a sticky
             // default to survive account deletion and re-addition)
             for (int i = 0; i < mState.accounts.size(); i++) {
-                if (mState.accounts.get(i).getAccountHandle().equals(mState.defaultOutgoing)) {
+                if (mState.accounts.get(i).getAccountHandle().equals(mState.defaultOutgoing)
+                        && isVisibleForUser(mState.defaultOutgoing)) {
                     return mState.defaultOutgoing;
                 }
             }
@@ -243,14 +265,14 @@
                 return;
             }
 
-            if (!getPhoneAccount(accountHandle).hasCapabilities(
+            if (!getPhoneAccountInternal(accountHandle).hasCapabilities(
                     PhoneAccount.CAPABILITY_CALL_PROVIDER)) {
                 Log.w(this, "Trying to set non-call-provider default outgoing %s",
                         accountHandle);
                 return;
             }
 
-            if (getPhoneAccount(accountHandle).hasCapabilities(
+            if (getPhoneAccountInternal(accountHandle).hasCapabilities(
                     PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
                 // If the account selected is a SIM account, propagate down to the subscription
                 // record.
@@ -305,7 +327,7 @@
 
     public void setSimCallManager(PhoneAccountHandle callManager) {
         if (callManager != null) {
-            PhoneAccount callManagerAccount = getPhoneAccount(callManager);
+            PhoneAccount callManagerAccount = getPhoneAccountInternal(callManager);
             if (callManagerAccount == null) {
                 Log.d(this, "setSimCallManager: Nonexistent call manager: %s", callManager);
                 return;
@@ -323,6 +345,9 @@
         fireSimCallManagerChanged();
     }
 
+    /**
+     * @return The {@link PhoneAccount}s which are visible to {@link #mCurrentUserHandle}.
+     */
     public PhoneAccountHandle getSimCallManager() {
         if (mState.simCallManager != null) {
             if (NO_ACCOUNT_SELECTED.equals(mState.simCallManager)) {
@@ -332,7 +357,8 @@
             // setting to survive account deletion and re-addition)
             for (int i = 0; i < mState.accounts.size(); i++) {
                 if (mState.accounts.get(i).getAccountHandle().equals(mState.simCallManager)
-                        && !resolveComponent(mState.simCallManager.getComponentName()).isEmpty()) {
+                        && !resolveComponent(mState.simCallManager).isEmpty()
+                        && isVisibleForUser(mState.simCallManager)) {
                     return mState.simCallManager;
                 }
             }
@@ -344,12 +370,13 @@
         if (!TextUtils.isEmpty(defaultConnectionMgr)) {
             ComponentName componentName = ComponentName.unflattenFromString(defaultConnectionMgr);
             // Make sure that the component can be resolved.
-            List<ResolveInfo> resolveInfos = resolveComponent(componentName);
+            List<ResolveInfo> resolveInfos = resolveComponent(componentName, null);
             if (!resolveInfos.isEmpty()) {
                 // See if there is registered PhoneAccount by this component.
                 List<PhoneAccountHandle> handles = getAllPhoneAccountHandles();
                 for (PhoneAccountHandle handle : handles) {
-                    if (componentName.equals(handle.getComponentName())) {
+                    if (componentName.equals(handle.getComponentName())
+                            && isVisibleForUser(handle)) {
                         return handle;
                     }
                 }
@@ -364,11 +391,95 @@
         return null;
     }
 
-    private List<ResolveInfo> resolveComponent(ComponentName componentName) {
+    /**
+     * A version of {@link #getPhoneAccount} which does not guard for the current user.
+     *
+     * @param handle
+     * @return
+     */
+    PhoneAccount getPhoneAccountInternal(PhoneAccountHandle handle) {
+        for (PhoneAccount m : mState.accounts) {
+            if (Objects.equals(handle, m.getAccountHandle())) {
+                return m;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Update the current UserHandle to track when users are switched. This will allow the
+     * PhoneAccountRegistar to self-filter the PhoneAccounts to make sure we don't leak anything
+     * across users.
+     *
+     * @param userHandle The {@link UserHandle}, as delivered by
+     *          {@link Intent#ACTION_USER_SWITCHED}.
+     */
+    public void setCurrentUserHandle(UserHandle userHandle) {
+        if (userHandle == null) {
+            Log.d(this, "setCurrentUserHandle, userHandle = null");
+            userHandle = Process.myUserHandle();
+        }
+        Log.d(this, "setCurrentUserHandle, %s", userHandle);
+        mCurrentUserHandle = userHandle;
+    }
+
+    private boolean isVisibleForUser(PhoneAccountHandle accountHandle) {
+        if (accountHandle == null) {
+            return false;
+        }
+
+        return isVisibleForUser(getPhoneAccountInternal(accountHandle));
+    }
+
+    private boolean isVisibleForUser(PhoneAccount account) {
+        if (account == null) {
+            return false;
+        }
+
+        // If this PhoneAccount has CAPABILITY_MULTI_USER, it should be visible to all users and
+        // all profiles. Only Telephony and SIP accounts should have this capability.
+        if (account.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) {
+            return true;
+        }
+
+        UserHandle phoneAccountUserHandle = account.getAccountHandle().getUserHandle();
+        if (phoneAccountUserHandle == null) {
+            return false;
+        }
+
+        if (mCurrentUserHandle == null) {
+            Log.d(this, "Current user is null; assuming true");
+            return true;
+        }
+
+        // Unlike in TelecomServiceImpl, we only care about *profiles* here. We want to make sure
+        // that we don't resolve PhoneAccount across *users*, but resolving across *profiles* is
+        // fine.
+        List<UserInfo> profileUsers = mUserManager.getProfiles(mCurrentUserHandle.getIdentifier());
+
+        for (UserInfo profileInfo : profileUsers) {
+            if (profileInfo.getUserHandle().equals(phoneAccountUserHandle)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private List<ResolveInfo> resolveComponent(PhoneAccountHandle phoneAccountHandle) {
+        return resolveComponent(phoneAccountHandle.getComponentName(),
+                    phoneAccountHandle.getUserHandle());
+    }
+
+    private List<ResolveInfo> resolveComponent(ComponentName componentName,
+            UserHandle userHandle) {
         PackageManager pm = mContext.getPackageManager();
         Intent intent = new Intent(ConnectionService.SERVICE_INTERFACE);
         intent.setComponent(componentName);
-        return pm.queryIntentServices(intent, 0);
+        if (userHandle != null) {
+            return pm.queryIntentServicesAsUser(intent, 0, userHandle.getIdentifier());
+        } else {
+            return pm.queryIntentServices(intent, 0);
+        }
     }
 
     /**
@@ -379,22 +490,21 @@
     public List<PhoneAccountHandle> getAllPhoneAccountHandles() {
         List<PhoneAccountHandle> accountHandles = new ArrayList<>();
         for (PhoneAccount m : mState.accounts) {
-            accountHandles.add(m.getAccountHandle());
+            if (isVisibleForUser(m)) {
+                accountHandles.add(m.getAccountHandle());
+            }
         }
         return accountHandles;
     }
 
     public List<PhoneAccount> getAllPhoneAccounts() {
-        return new ArrayList<>(mState.accounts);
-    }
-
-    /**
-     * Determines the number of all {@link PhoneAccount}s.
-     *
-     * @return The total number {@link PhoneAccount}s.
-     */
-    public int getAllPhoneAccountsCount() {
-        return mState.accounts.size();
+        List<PhoneAccount> accounts = new ArrayList<>(mState.accounts.size());
+        for (PhoneAccount account : mState.accounts) {
+            if (isVisibleForUser(account)) {
+                accounts.add(account);
+            }
+        }
+        return accounts;
     }
 
     /**
@@ -428,7 +538,8 @@
         for (PhoneAccount m : mState.accounts) {
             if (Objects.equals(
                     packageName,
-                    m.getAccountHandle().getComponentName().getPackageName())) {
+                    m.getAccountHandle().getComponentName().getPackageName())
+                    && isVisibleForUser(m)) {
                 accountHandles.add(m.getAccountHandle());
             }
         }
@@ -447,7 +558,8 @@
 
     public PhoneAccount getPhoneAccount(PhoneAccountHandle handle) {
         for (PhoneAccount m : mState.accounts) {
-            if (Objects.equals(handle, m.getAccountHandle())) {
+            if (Objects.equals(handle, m.getAccountHandle())
+                    && isVisibleForUser(m)) {
                 return m;
             }
         }
@@ -495,7 +607,8 @@
 
     public void unregisterPhoneAccount(PhoneAccountHandle accountHandle) {
         for (int i = 0; i < mState.accounts.size(); i++) {
-            if (Objects.equals(accountHandle, mState.accounts.get(i).getAccountHandle())) {
+            PhoneAccountHandle handle = mState.accounts.get(i).getAccountHandle();
+            if (Objects.equals(accountHandle, handle)) {
                 mState.accounts.remove(i);
                 break;
             }
@@ -509,15 +622,16 @@
      * Un-registers all phone accounts associated with a specified package.
      *
      * @param packageName The package for which phone accounts will be removed.
+     * @param userHandle The {@link UserHandle} the package is running under.
      */
-    public void clearAccounts(String packageName) {
+    public void clearAccounts(String packageName, UserHandle userHandle) {
         boolean accountsRemoved = false;
         Iterator<PhoneAccount> it = mState.accounts.iterator();
         while (it.hasNext()) {
             PhoneAccount phoneAccount = it.next();
-            if (Objects.equals(
-                    packageName,
-                    phoneAccount.getAccountHandle().getComponentName().getPackageName())) {
+            PhoneAccountHandle handle = phoneAccount.getAccountHandle();
+            if (Objects.equals(packageName, handle.getComponentName().getPackageName())
+                    && Objects.equals(userHandle, handle.getUserHandle())) {
                 Log.i(this, "Removing phone account " + phoneAccount.getLabel());
                 it.remove();
                 accountsRemoved = true;
@@ -606,12 +720,23 @@
     private List<PhoneAccountHandle> getPhoneAccountHandles(int flags, String uriScheme) {
         List<PhoneAccountHandle> accountHandles = new ArrayList<>();
         for (PhoneAccount m : mState.accounts) {
-            if (m.hasCapabilities(flags) && (uriScheme == null || m.supportsUriScheme(uriScheme))) {
-                // Also filter out unresolveable accounts
-                if (!resolveComponent(m.getAccountHandle().getComponentName()).isEmpty()) {
-                    accountHandles.add(m.getAccountHandle());
-                }
+            if (!m.hasCapabilities(flags)) {
+                // Account doesn't have the right capabilities; skip this one.
+                continue;
             }
+            if (uriScheme != null && !m.supportsUriScheme(uriScheme)) {
+                // Account doesn't support this URI scheme; skip this one.
+                continue;
+            }
+            if (resolveComponent(m.getAccountHandle()).isEmpty()) {
+                // This component cannot be resolved anymore; skip this one.
+                continue;
+            }
+            if (!isVisibleForUser(m)) {
+                // Account is not visible for the current user; skip this one.
+                continue;
+            }
+            accountHandles.add(m.getAccountHandle());
         }
         return accountHandles;
     }
@@ -678,7 +803,7 @@
             try {
                 XmlSerializer serializer = new FastXmlSerializer();
                 serializer.setOutput(new BufferedOutputStream(os), "utf-8");
-                writeToXml(mState, serializer);
+                writeToXml(mState, serializer, mContext);
                 serializer.flush();
                 success = true;
             } finally {
@@ -722,15 +847,29 @@
             }
         }
 
+        // Verify all of the UserHandles.
+        List<PhoneAccount> badAccounts = new ArrayList<>();
+        for (PhoneAccount phoneAccount : mState.accounts) {
+            UserHandle userHandle = phoneAccount.getAccountHandle().getUserHandle();
+            if (userHandle == null) {
+                Log.w(this, "Missing UserHandle for %s", phoneAccount);
+                badAccounts.add(phoneAccount);
+            } else if (mUserManager.getSerialNumberForUser(userHandle) == -1) {
+                Log.w(this, "User does not exist for %s", phoneAccount);
+                badAccounts.add(phoneAccount);
+            }
+        }
+        mState.accounts.removeAll(badAccounts);
+
         // If an upgrade occurred, write out the changed data.
-        if (versionChanged) {
+        if (versionChanged || !badAccounts.isEmpty()) {
             write();
         }
     }
 
-    private static void writeToXml(State state, XmlSerializer serializer)
+    private static void writeToXml(State state, XmlSerializer serializer, Context context)
             throws IOException {
-        sStateXml.writeToXml(state, serializer);
+        sStateXml.writeToXml(state, serializer, context);
     }
 
     private static State readFromXml(XmlPullParser parser, Context context)
@@ -752,7 +891,7 @@
         /**
          * Write the supplied object to XML
          */
-        public abstract void writeToXml(T o, XmlSerializer serializer)
+        public abstract void writeToXml(T o, XmlSerializer serializer, Context context)
                 throws IOException;
 
         /**
@@ -816,6 +955,13 @@
             }
         }
 
+        protected void writeLong(String tagName, long value, XmlSerializer serializer)
+                throws IOException {
+            serializer.startTag(null, tagName);
+            serializer.text(Long.valueOf(value).toString());
+            serializer.endTag(null, tagName);
+        }
+
         /**
          * Reads a string array from the XML parser.
          *
@@ -864,7 +1010,7 @@
         private static final String VERSION = "version";
 
         @Override
-        public void writeToXml(State o, XmlSerializer serializer)
+        public void writeToXml(State o, XmlSerializer serializer, Context context)
                 throws IOException {
             if (o != null) {
                 serializer.startTag(null, CLASS_STATE);
@@ -872,19 +1018,19 @@
 
                 if (o.defaultOutgoing != null) {
                     serializer.startTag(null, DEFAULT_OUTGOING);
-                    sPhoneAccountHandleXml.writeToXml(o.defaultOutgoing, serializer);
+                    sPhoneAccountHandleXml.writeToXml(o.defaultOutgoing, serializer, context);
                     serializer.endTag(null, DEFAULT_OUTGOING);
                 }
 
                 if (o.simCallManager != null) {
                     serializer.startTag(null, SIM_CALL_MANAGER);
-                    sPhoneAccountHandleXml.writeToXml(o.simCallManager, serializer);
+                    sPhoneAccountHandleXml.writeToXml(o.simCallManager, serializer, context);
                     serializer.endTag(null, SIM_CALL_MANAGER);
                 }
 
                 serializer.startTag(null, ACCOUNTS);
                 for (PhoneAccount m : o.accounts) {
-                    sPhoneAccountXml.writeToXml(m, serializer);
+                    sPhoneAccountXml.writeToXml(m, serializer, context);
                 }
                 serializer.endTag(null, ACCOUNTS);
 
@@ -912,6 +1058,13 @@
                         parser.nextTag();
                         s.simCallManager = sPhoneAccountHandleXml.readFromXml(parser,
                                 s.versionNumber, context);
+                        if (s.simCallManager.getUserHandle() == null) {
+                            // This should never happen, but handle the upgrade case.
+                            s.simCallManager = new PhoneAccountHandle(
+                                    s.simCallManager.getComponentName(),
+                                    s.simCallManager.getId(),
+                                    Process.myUserHandle());
+                        }
                     } else if (parser.getName().equals(ACCOUNTS)) {
                         int accountsDepth = parser.getDepth();
                         while (XmlUtils.nextElementWithin(parser, accountsDepth)) {
@@ -948,14 +1101,14 @@
         private static final String SUPPORTED_URI_SCHEMES = "supported_uri_schemes";
 
         @Override
-        public void writeToXml(PhoneAccount o, XmlSerializer serializer)
+        public void writeToXml(PhoneAccount o, XmlSerializer serializer, Context context)
                 throws IOException {
             if (o != null) {
                 serializer.startTag(null, CLASS_PHONE_ACCOUNT);
 
                 if (o.getAccountHandle() != null) {
                     serializer.startTag(null, ACCOUNT_HANDLE);
-                    sPhoneAccountHandleXml.writeToXml(o.getAccountHandle(), serializer);
+                    sPhoneAccountHandleXml.writeToXml(o.getAccountHandle(), serializer, context);
                     serializer.endTag(null, ACCOUNT_HANDLE);
                 }
 
@@ -1101,9 +1254,10 @@
         private static final String CLASS_PHONE_ACCOUNT_HANDLE = "phone_account_handle";
         private static final String COMPONENT_NAME = "component_name";
         private static final String ID = "id";
+        private static final String USER_SERIAL_NUMBER = "user_serial_number";
 
         @Override
-        public void writeToXml(PhoneAccountHandle o, XmlSerializer serializer)
+        public void writeToXml(PhoneAccountHandle o, XmlSerializer serializer, Context context)
                 throws IOException {
             if (o != null) {
                 serializer.startTag(null, CLASS_PHONE_ACCOUNT_HANDLE);
@@ -1115,6 +1269,12 @@
 
                 writeTextIfNonNull(ID, o.getId(), serializer);
 
+                if (o.getUserHandle() != null && context != null) {
+                    UserManager userManager = UserManager.get(context);
+                    writeLong(USER_SERIAL_NUMBER,
+                            userManager.getSerialNumberForUser(o.getUserHandle()), serializer);
+                }
+
                 serializer.endTag(null, CLASS_PHONE_ACCOUNT_HANDLE);
             }
         }
@@ -1125,7 +1285,11 @@
             if (parser.getName().equals(CLASS_PHONE_ACCOUNT_HANDLE)) {
                 String componentNameString = null;
                 String idString = null;
+                String userSerialNumberString = null;
                 int outerDepth = parser.getDepth();
+
+                UserManager userManager = UserManager.get(context);
+
                 while (XmlUtils.nextElementWithin(parser, outerDepth)) {
                     if (parser.getName().equals(COMPONENT_NAME)) {
                         parser.next();
@@ -1133,12 +1297,25 @@
                     } else if (parser.getName().equals(ID)) {
                         parser.next();
                         idString = parser.getText();
+                    } else if (parser.getName().equals(USER_SERIAL_NUMBER)) {
+                        parser.next();
+                        userSerialNumberString = parser.getText();
                     }
                 }
                 if (componentNameString != null) {
+                    UserHandle userHandle = null;
+                    if (userSerialNumberString != null) {
+                        try {
+                            long serialNumber = Long.parseLong(userSerialNumberString);
+                            userHandle = userManager.getUserForSerialNumber(serialNumber);
+                        } catch (NumberFormatException e) {
+                            Log.e(this, e, "Could not parse UserHandle " + userSerialNumberString);
+                        }
+                    }
                     return new PhoneAccountHandle(
                             ComponentName.unflattenFromString(componentNameString),
-                            idString);
+                            idString,
+                            userHandle);
                 }
             }
             return null;
diff --git a/src/com/android/server/telecom/ServiceBinder.java b/src/com/android/server/telecom/ServiceBinder.java
index fb747f2..faf41c8 100644
--- a/src/com/android/server/telecom/ServiceBinder.java
+++ b/src/com/android/server/telecom/ServiceBinder.java
@@ -22,6 +22,8 @@
 import android.content.ServiceConnection;
 import android.os.IBinder;
 import android.os.IInterface;
+import android.os.Process;
+import android.os.UserHandle;
 
 import com.android.internal.util.Preconditions;
 import com.google.common.base.Strings;
@@ -83,7 +85,15 @@
                 ServiceConnection connection = new ServiceBinderConnection();
 
                 Log.d(ServiceBinder.this, "Binding to service with intent: %s", serviceIntent);
-                if (!mContext.bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE)) {
+                final boolean binding;
+                if (mUserHandle != null) {
+                    binding = mContext.bindServiceAsUser(serviceIntent, connection,
+                        Context.BIND_AUTO_CREATE, mUserHandle);
+                } else {
+                    binding = mContext.bindService(serviceIntent, connection,
+                        Context.BIND_AUTO_CREATE);
+                }
+                if (!binding) {
                     handleFailedConnection();
                     return;
                 }
@@ -141,6 +151,9 @@
     /** Used to bind and unbind from the service. */
     private ServiceConnection mServiceConnection;
 
+    /** {@link UserHandle} to use for binding, to support work profiles and multi-user. */
+    private UserHandle mUserHandle;
+
     /** The binder provided by {@link ServiceConnection#onServiceConnected} */
     private IBinder mBinder;
 
@@ -167,14 +180,17 @@
      * @param serviceAction The intent-action used with {@link Context#bindService}.
      * @param componentName The component name of the service with which to bind.
      * @param context The context.
+     * @param userHandle The {@link UserHandle} to use for binding.
      */
-    protected ServiceBinder(String serviceAction, ComponentName componentName, Context context) {
+    protected ServiceBinder(String serviceAction, ComponentName componentName, Context context,
+            UserHandle userHandle) {
         Preconditions.checkState(!Strings.isNullOrEmpty(serviceAction));
         Preconditions.checkNotNull(componentName);
 
         mContext = context;
         mServiceAction = serviceAction;
         mComponentName = componentName;
+        mUserHandle = userHandle;
     }
 
     final void incrementAssociatedCallCount() {
diff --git a/src/com/android/server/telecom/TelecomApp.java b/src/com/android/server/telecom/TelecomApp.java
index 4941a35..09f62c7 100644
--- a/src/com/android/server/telecom/TelecomApp.java
+++ b/src/com/android/server/telecom/TelecomApp.java
@@ -17,16 +17,21 @@
 package com.android.server.telecom;
 
 import android.app.Application;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.ServiceManager;
 import android.os.UserHandle;
+import android.os.UserManager;
 
 /**
  * Top-level Application class for Telecom.
  */
 public final class TelecomApp extends Application {
+    private static final IntentFilter USER_SWITCHED_FILTER =
+            new IntentFilter(Intent.ACTION_USER_SWITCHED);
+
     /**
      * The Telecom service implementation.
      */
@@ -48,6 +53,15 @@
      */
     private CallsManager mCallsManager;
 
+    private final BroadcastReceiver mUserSwitchedReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            int userHandleId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
+            UserHandle currentUserHandle = new UserHandle(userHandleId);
+            mPhoneAccountRegistrar.setCurrentUserHandle(currentUserHandle);
+        }
+    };
+
     /** {@inheritDoc} */
     @Override
     public void onCreate() {
@@ -71,6 +85,7 @@
             // Start the BluetoothPhoneService
             BluetoothPhoneService.start(this);
         }
+        registerReceiver(mUserSwitchedReceiver, USER_SWITCHED_FILTER);
     }
 
     MissedCallNotifier getMissedCallNotifier() {
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index dcbdd4c..3b1d4de 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -21,6 +21,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.os.Binder;
@@ -29,8 +30,8 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
-import android.os.ServiceManager;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.telecom.CallState;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
@@ -45,6 +46,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -140,6 +142,8 @@
     private final MissedCallNotifier mMissedCallNotifier;
     private final PhoneAccountRegistrar mPhoneAccountRegistrar;
     private final AppOpsManager mAppOpsManager;
+    private final UserManager mUserManager;
+    private final PackageManager mPackageManager;
 
     public TelecomServiceImpl(
             MissedCallNotifier missedCallNotifier, PhoneAccountRegistrar phoneAccountRegistrar,
@@ -149,6 +153,8 @@
         mCallsManager = callsManager;
         mContext = context;
         mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
+        mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+        mPackageManager = mContext.getPackageManager();
     }
 
     //
@@ -158,7 +164,15 @@
     @Override
     public PhoneAccountHandle getDefaultOutgoingPhoneAccount(String uriScheme) {
         try {
-            return mPhoneAccountRegistrar.getDefaultOutgoingPhoneAccount(uriScheme);
+            PhoneAccountHandle defaultOutgoingPhoneAccount =
+                    mPhoneAccountRegistrar.getDefaultOutgoingPhoneAccount(uriScheme);
+            // Make sure that the calling user can see this phone account.
+            if (defaultOutgoingPhoneAccount != null
+                    && !isVisibleToCaller(defaultOutgoingPhoneAccount)) {
+                Log.w(this, "No account found for the calling user");
+                return null;
+            }
+            return defaultOutgoingPhoneAccount;
         } catch (Exception e) {
             Log.e(this, e, "getDefaultOutgoingPhoneAccount");
             throw e;
@@ -168,7 +182,14 @@
     @Override
     public PhoneAccountHandle getUserSelectedOutgoingPhoneAccount() {
         try {
-            return mPhoneAccountRegistrar.getUserSelectedOutgoingPhoneAccount();
+            PhoneAccountHandle userSelectedOutgoingPhoneAccount =
+                    mPhoneAccountRegistrar.getUserSelectedOutgoingPhoneAccount();
+            // Make sure that the calling user can see this phone account.
+            if (!isVisibleToCaller(userSelectedOutgoingPhoneAccount)) {
+                Log.w(this, "No account found for the calling user");
+                return null;
+            }
+            return userSelectedOutgoingPhoneAccount;
         } catch (Exception e) {
             Log.e(this, e, "getUserSelectedOutgoingPhoneAccount");
             throw e;
@@ -190,7 +211,8 @@
     @Override
     public List<PhoneAccountHandle> getCallCapablePhoneAccounts() {
         try {
-            return mPhoneAccountRegistrar.getCallCapablePhoneAccounts();
+            return filterForAccountsVisibleToCaller(
+                    mPhoneAccountRegistrar.getCallCapablePhoneAccounts());
         } catch (Exception e) {
             Log.e(this, e, "getCallCapablePhoneAccounts");
             throw e;
@@ -200,9 +222,10 @@
     @Override
     public List<PhoneAccountHandle> getPhoneAccountsSupportingScheme(String uriScheme) {
         try {
-            return mPhoneAccountRegistrar.getCallCapablePhoneAccounts(uriScheme);
+            return filterForAccountsVisibleToCaller(
+                    mPhoneAccountRegistrar.getCallCapablePhoneAccounts(uriScheme));
         } catch (Exception e) {
-            Log.e(this, e, "getPhoneAccountsSupportingScheme");
+            Log.e(this, e, "getPhoneAccountsSupportingScheme %s", uriScheme);
             throw e;
         }
     }
@@ -210,9 +233,10 @@
     @Override
     public List<PhoneAccountHandle> getPhoneAccountsForPackage(String packageName) {
         try {
-            return mPhoneAccountRegistrar.getPhoneAccountsForPackage(packageName);
+            return filterForAccountsVisibleToCaller(
+                    mPhoneAccountRegistrar.getPhoneAccountsForPackage(packageName));
         } catch (Exception e) {
-            Log.e(this, e, "getPhoneAccountsForPackage");
+            Log.e(this, e, "getPhoneAccountsForPackage %s", packageName);
             throw e;
         }
     }
@@ -220,7 +244,11 @@
     @Override
     public PhoneAccount getPhoneAccount(PhoneAccountHandle accountHandle) {
         try {
-            return mPhoneAccountRegistrar.getPhoneAccount(accountHandle);
+            if (!isVisibleToCaller(accountHandle)) {
+                Log.w(this, "%s is not visible for the calling user", accountHandle);
+                return null;
+            }
+            return mPhoneAccountRegistrar.getPhoneAccountInternal(accountHandle);
         } catch (Exception e) {
             Log.e(this, e, "getPhoneAccount %s", accountHandle);
             throw e;
@@ -230,7 +258,8 @@
     @Override
     public int getAllPhoneAccountsCount() {
         try {
-            return mPhoneAccountRegistrar.getAllPhoneAccountsCount();
+            // This list is pre-filtered for the calling user.
+            return getAllPhoneAccounts().size();
         } catch (Exception e) {
             Log.e(this, e, "getAllPhoneAccountsCount");
             throw e;
@@ -240,7 +269,14 @@
     @Override
     public List<PhoneAccount> getAllPhoneAccounts() {
         try {
-            return mPhoneAccountRegistrar.getAllPhoneAccounts();
+            List<PhoneAccount> allPhoneAccounts = mPhoneAccountRegistrar.getAllPhoneAccounts();
+            List<PhoneAccount> profilePhoneAccounts = new ArrayList<>(allPhoneAccounts.size());
+            for (PhoneAccount phoneAccount : profilePhoneAccounts) {
+                if (isVisibleToCaller(phoneAccount)) {
+                    profilePhoneAccounts.add(phoneAccount);
+                }
+            }
+            return profilePhoneAccounts;
         } catch (Exception e) {
             Log.e(this, e, "getAllPhoneAccounts");
             throw e;
@@ -250,7 +286,8 @@
     @Override
     public List<PhoneAccountHandle> getAllPhoneAccountHandles() {
         try {
-            return mPhoneAccountRegistrar.getAllPhoneAccountHandles();
+            return filterForAccountsVisibleToCaller(
+                    mPhoneAccountRegistrar.getAllPhoneAccountHandles());
         } catch (Exception e) {
             Log.e(this, e, "getAllPhoneAccounts");
             throw e;
@@ -260,7 +297,12 @@
     @Override
     public PhoneAccountHandle getSimCallManager() {
         try {
-            return mPhoneAccountRegistrar.getSimCallManager();
+            PhoneAccountHandle accountHandle = mPhoneAccountRegistrar.getSimCallManager();
+            if (!isVisibleToCaller(accountHandle)) {
+                Log.w(this, "%s is not visible for the calling user", accountHandle);
+                return null;
+            }
+            return accountHandle;
         } catch (Exception e) {
             Log.e(this, e, "getSimCallManager");
             throw e;
@@ -282,7 +324,8 @@
     @Override
     public List<PhoneAccountHandle> getSimCallManagers() {
         try {
-            return mPhoneAccountRegistrar.getConnectionManagerPhoneAccounts();
+            return filterForAccountsVisibleToCaller(
+                    mPhoneAccountRegistrar.getConnectionManagerPhoneAccounts());
         } catch (Exception e) {
             Log.e(this, e, "getSimCallManagers");
             throw e;
@@ -301,6 +344,10 @@
             if (account.hasCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER)) {
                 enforceRegisterConnectionManagerPermission();
             }
+            if (account.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) {
+                enforceRegisterMultiUser();
+            }
+            enforceUserHandleMatchesCaller(account.getAccountHandle());
 
             mPhoneAccountRegistrar.registerPhoneAccount(account);
         } catch (Exception e) {
@@ -314,6 +361,7 @@
         try {
             enforcePhoneAccountModificationForPackage(
                     accountHandle.getComponentName().getPackageName());
+            enforceUserHandleMatchesCaller(accountHandle);
             mPhoneAccountRegistrar.unregisterPhoneAccount(accountHandle);
         } catch (Exception e) {
             Log.e(this, e, "unregisterPhoneAccount %s", accountHandle);
@@ -325,7 +373,7 @@
     public void clearAccounts(String packageName) {
         try {
             enforcePhoneAccountModificationForPackage(packageName);
-            mPhoneAccountRegistrar.clearAccounts(packageName);
+            mPhoneAccountRegistrar.clearAccounts(packageName, Binder.getCallingUserHandle());
         } catch (Exception e) {
             Log.e(this, e, "clearAccounts %s", packageName);
             throw e;
@@ -333,6 +381,47 @@
     }
 
     /**
+     * @see android.telecom.TelecomManager#isVoiceMailNumber
+     */
+    @Override
+    public boolean isVoiceMailNumber(PhoneAccountHandle accountHandle, String number) {
+        enforceReadPermissionOrDefaultDialer();
+        try {
+            if (!isVisibleToCaller(accountHandle)) {
+                Log.w(this, "%s is not visible for the calling user", accountHandle);
+                return false;
+            }
+            return mPhoneAccountRegistrar.isVoiceMailNumber(accountHandle, number);
+        } catch (Exception e) {
+            Log.e(this, e, "getSubscriptionIdForPhoneAccount");
+            throw e;
+        }
+    }
+
+    /**
+     * @see android.telecom.TelecomManager#hasVoiceMailNumber
+     */
+    @Override
+    public boolean hasVoiceMailNumber(PhoneAccountHandle accountHandle) {
+        enforceReadPermissionOrDefaultDialer();
+        try {
+            if (!isVisibleToCaller(accountHandle)) {
+                Log.w(this, "%s is not visible for the calling user", accountHandle);
+                return false;
+            }
+
+            int subId = SubscriptionManager.getDefaultVoiceSubId();
+            if (accountHandle != null) {
+                subId = mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(accountHandle);
+            }
+            return !TextUtils.isEmpty(getTelephonyManager().getVoiceMailNumber(subId));
+        } catch (Exception e) {
+            Log.e(this, e, "getSubscriptionIdForPhoneAccount");
+            throw e;
+        }
+    }
+
+    /**
      * @see android.telecom.TelecomManager#silenceRinger
      */
     @Override
@@ -458,6 +547,57 @@
     }
 
     /**
+     * @see android.telecom.TelecomManager#handleMmi
+     */
+    @Override
+    public boolean handlePinMmiForPhoneAccount(PhoneAccountHandle accountHandle,
+            String dialString) {
+        enforceModifyPermissionOrDefaultDialer();
+
+        if (!isVisibleToCaller(accountHandle)) {
+            Log.w(this, "%s is not visible for the calling user", accountHandle);
+            return false;
+        }
+
+        // Switch identity so that TelephonyManager checks Telecom's permissions instead.
+        long token = Binder.clearCallingIdentity();
+        boolean retval = false;
+        try {
+            int subId = mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(accountHandle);
+            retval = getTelephonyManager().handlePinMmiForSubscriber(subId, dialString);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+
+        return retval;
+    }
+
+    /**
+     * @see android.telecom.TelecomManager#getAdnUriForPhoneAccount
+     */
+    @Override
+    public Uri getAdnUriForPhoneAccount(PhoneAccountHandle accountHandle) {
+        enforceModifyPermissionOrDefaultDialer();
+
+        if (!isVisibleToCaller(accountHandle)) {
+            Log.w(this, "%s is not visible for the calling user", accountHandle);
+            return null;
+        }
+
+        // Switch identity so that TelephonyManager checks Telecom's permissions instead.
+        long token = Binder.clearCallingIdentity();
+        String retval = "content://icc/adn/";
+        try {
+            long subId = mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(accountHandle);
+            retval = retval + "subId/" + subId;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+
+        return Uri.parse(retval);
+    }
+
+    /**
      * @see android.telecom.TelecomManager#isTtySupported
      */
     @Override
@@ -484,6 +624,9 @@
             mAppOpsManager.checkPackage(
                     Binder.getCallingUid(), phoneAccountHandle.getComponentName().getPackageName());
 
+            // Make sure it doesn't cross the UserHandle boundary
+            enforceUserHandleMatchesCaller(phoneAccountHandle);
+
             Intent intent = new Intent(TelecomManager.ACTION_INCOMING_CALL);
             intent.setPackage(mContext.getPackageName());
             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -508,13 +651,16 @@
             mAppOpsManager.checkPackage(
                     Binder.getCallingUid(), phoneAccountHandle.getComponentName().getPackageName());
 
+            // Make sure it doesn't cross the UserHandle boundary
+            enforceUserHandleMatchesCaller(phoneAccountHandle);
+
             Intent intent = new Intent(TelecomManager.ACTION_NEW_UNKNOWN_CALL);
             intent.setClass(mContext, CallReceiver.class);
             intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
             intent.putExtras(extras);
             intent.putExtra(CallReceiver.KEY_IS_UNKNOWN_CALL, true);
             intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
-            mContext.sendBroadcastAsUser(intent, UserHandle.OWNER);
+            mContext.sendBroadcastAsUser(intent, phoneAccountHandle.getUserHandle());
         } else {
             Log.i(this, "Null phoneAccountHandle or not initiated by Telephony. Ignoring request"
                     + " to add new unknown call.");
@@ -525,6 +671,87 @@
     // Supporting methods for the ITelecomService interface implementation.
     //
 
+    private boolean isVisibleToCaller(PhoneAccountHandle accountHandle) {
+        if (accountHandle == null) {
+            return false;
+        }
+
+        return isVisibleToCaller(mPhoneAccountRegistrar.getPhoneAccountInternal(accountHandle));
+    }
+
+    private boolean isVisibleToCaller(PhoneAccount account) {
+        if (account == null) {
+            return false;
+        }
+
+        // If this PhoneAccount has CAPABILITY_MULTI_USER, it should be visible to all users and
+        // all profiles. Only Telephony and SIP accounts should have this capability.
+        if (account.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) {
+            return true;
+        }
+
+        UserHandle phoneAccountUserHandle = account.getAccountHandle().getUserHandle();
+        if (phoneAccountUserHandle == null) {
+            return false;
+        }
+
+        List<UserHandle> profileUserHandles;
+        if (isCallerSystemApp()) {
+            // If the caller lives in /system/priv-app, it can see PhoneAccounts for all of the
+            // *profiles* that the calling user owns, but not for any other *users*.
+            profileUserHandles = mUserManager.getUserProfiles();
+        } else {
+            // Otherwise, it has to be owned by the current caller's profile.
+            profileUserHandles = new ArrayList<>(1);
+            profileUserHandles.add(Binder.getCallingUserHandle());
+        }
+
+        return profileUserHandles.contains(phoneAccountUserHandle);
+    }
+
+    /**
+     * Given a list of {@link PhoneAccountHandle}s, filter them to the ones that the calling
+     * user can see.
+     *
+     * @param phoneAccountHandles Unfiltered list of account handles.
+     *
+     * @return {@link PhoneAccountHandle}s visible to the calling user and its profiles.
+     */
+    private List<PhoneAccountHandle> filterForAccountsVisibleToCaller(
+            List<PhoneAccountHandle> phoneAccountHandles) {
+        List<PhoneAccountHandle> profilePhoneAccountHandles =
+                new ArrayList<>(phoneAccountHandles.size());
+        for (PhoneAccountHandle phoneAccountHandle : phoneAccountHandles) {
+            if (isVisibleToCaller(phoneAccountHandle)) {
+                profilePhoneAccountHandles.add(phoneAccountHandle);
+            }
+        }
+        return profilePhoneAccountHandles;
+    }
+
+    private boolean isCallerSystemApp() {
+        int uid = Binder.getCallingUid();
+        String[] packages = mPackageManager.getPackagesForUid(uid);
+        for (String packageName : packages) {
+            if (isPackageSystemApp(packageName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean isPackageSystemApp(String packageName) {
+        try {
+            ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(packageName,
+                    PackageManager.GET_META_DATA);
+            if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+                return true;
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+        }
+        return false;
+    }
+
     private void acceptRingingCallInternal() {
         Call call = mCallsManager.getFirstCallWithState(CallState.RINGING);
         if (call != null) {
@@ -614,6 +841,18 @@
         mContext.enforceCallingOrSelfPermission(permission, null);
     }
 
+    private void enforceRegisterMultiUser() {
+        if (!isCallerSystemApp()) {
+            throw new SecurityException("CAPABILITY_MULTI_USER is only available to system apps.");
+        }
+    }
+
+    private void enforceUserHandleMatchesCaller(PhoneAccountHandle accountHandle) {
+        if (!Binder.getCallingUserHandle().equals(accountHandle.getUserHandle())) {
+            throw new SecurityException("Calling UserHandle does not match PhoneAccountHandle's");
+        }
+    }
+
     private void enforceFeature(String feature) {
         PackageManager pm = mContext.getPackageManager();
         if (!pm.hasSystemFeature(feature)) {
diff --git a/tests/src/com/android/server/telecom/tests/unit/PhoneAccountRegistrarTest.java b/tests/src/com/android/server/telecom/tests/unit/PhoneAccountRegistrarTest.java
index 2610720..cec8ffe 100644
--- a/tests/src/com/android/server/telecom/tests/unit/PhoneAccountRegistrarTest.java
+++ b/tests/src/com/android/server/telecom/tests/unit/PhoneAccountRegistrarTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.telecom.tests.unit;
 
+import android.os.UserHandle;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.server.telecom.Log;
 import com.android.server.telecom.PhoneAccountRegistrar;
@@ -190,7 +191,8 @@
                         "com.android.server.telecom.tests",
                         "com.android.server.telecom.tests.MockConnectionService"
                 ),
-                id);
+                id,
+                new UserHandle(5));
     }
 
     private PhoneAccount.Builder makeQuickAccountBuilder(String id, int idx) {
@@ -222,7 +224,7 @@
             XmlSerializer serializer = new FastXmlSerializer();
             ByteArrayOutputStream baos = new ByteArrayOutputStream();
             serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
-            xml.writeToXml(input, serializer);
+            xml.writeToXml(input, serializer, context);
             serializer.flush();
             data = baos.toByteArray();
         }