Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7d746a33 authored by Vladimir Komsiyski's avatar Vladimir Komsiyski
Browse files

Fix VDM UID tracking.

Current problems:
 - Only a single display per device is tracked at a time. If a
   virtual device has more than one display, any listeners will receive
   wrong UIDs
 - UIDs not updated when a display/device is removed. The mapping is
   never updated in that case and any UIDs will forever be considered
   as running on a virtual device
 - AudioController is device-level but tracks only a single display at
   a time as well. Starting a new audio session/device will lose track
   of all UIDs on the previously tracked display.
 - Overall, the UID tracking has an implicit assumption that a virtual
   device can only ever have a single display.

Changes:
 - Remove the GWPC-level listeners. VirtualDeviceImpl now processes all
   UIDs coming from each display's GWPC
 - Combine all display UIDs into device-level UIDs and pass that to all
   relevant clients (AudioController, CameraAccessController, VDMS).
   All they ever care about is the set of all UIDs on a device, they
   do not need display level granularity
 - Notify about empty UID set whenever a display is removed
 - VDMS simply dispatches the new UIDs to any system listeners that are
   registered with VDMInternal
 - Change the VDMInternal listener to take set of UIDs per device,
   instead of all UIDs running on ANY virtual device
 - Simplify the single VDMInternal listener use case (A11y Proxy)
 - Clean up redudndant VDMInternal methods and move the listeners from
   the LocalService to VDMS top level

Implications:
 - Fixing A11yProxy issues with re-launching apps on a new display or
   device after the old one was closed. The A11yProxy is now notified
   about the changes.
 - Fix issues with the AudioController, which will now track all UIDs
   running on the device, regardless of the display.

Bug: 440355882
Bug: 440353845
Fix: 428281968
Flag: EXEMPT bugfix
Test: atest & manual
Change-Id: I27cdea489eefddeb8238d61bcd0d40a8f742befe
parent ca506fc7
Loading
Loading
Loading
Loading
+12 −24
Original line number Diff line number Diff line
@@ -183,8 +183,7 @@ public class ProxyManager {
        synchronized (mLock) {
            mProxyA11yServiceConnections.put(displayId, connection);
            if (mAppsOnVirtualDeviceListener == null) {
                mAppsOnVirtualDeviceListener = allRunningUids ->
                        notifyProxyOfRunningAppsChange(allRunningUids);
                mAppsOnVirtualDeviceListener = this::notifyProxyOfRunningAppsChange;
                final VirtualDeviceManagerInternal localVdm = getLocalVdm();
                if (localVdm != null) {
                    localVdm.registerAppsOnVirtualDeviceListener(mAppsOnVirtualDeviceListener);
@@ -870,34 +869,23 @@ public class ProxyManager {
    }

    @VisibleForTesting
    void notifyProxyOfRunningAppsChange(Set<Integer> allRunningUids) {
    void notifyProxyOfRunningAppsChange(int deviceId, @NonNull ArraySet<Integer> runningUids) {
        if (DEBUG) {
            Slog.v(LOG_TAG, "notifyProxyOfRunningAppsChange: " + allRunningUids);
            Slog.v(LOG_TAG, "notifyProxyOfRunningAppsChange: deviceId=" + deviceId
                    + " runningUids=" + runningUids);
        }
        boolean changed = false;
        synchronized (mLock) {
            if (mProxyA11yServiceConnections.size() == 0) {
                return;
            }
            final VirtualDeviceManagerInternal localVdm = getLocalVdm();
            if  (localVdm == null) {
                return;
            }
            final ArraySet<Integer> deviceIdsToUpdate = new ArraySet<>();
            for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
                final ProxyAccessibilityServiceConnection proxy =
                        mProxyA11yServiceConnections.valueAt(i);
                if (proxy != null) {
                    final int proxyDeviceId = proxy.getDeviceId();
                    for (Integer uid : allRunningUids) {
                        if (localVdm.getDeviceIdsForUid(uid).contains(proxyDeviceId)) {
                            deviceIdsToUpdate.add(proxyDeviceId);
                        }
                    }
                ProxyAccessibilityServiceConnection proxy = mProxyA11yServiceConnections.valueAt(i);
                if (proxy != null && deviceId == proxy.getDeviceId()) {
                    changed = true;
                    break;
                }
            }
            for (Integer proxyDeviceId : deviceIdsToUpdate) {
                onProxyChanged(proxyDeviceId, true);
        }
        if (changed) {
            onProxyChanged(deviceId, true);
        }
    }

+9 −46
Original line number Diff line number Diff line
@@ -44,7 +44,6 @@ import android.view.Display;
import android.window.DisplayWindowPolicyController;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.BlockedAppStreamingActivity;
import com.android.modules.expresslog.Counter;

@@ -56,21 +55,13 @@ import java.util.function.Supplier;
/**
 * A controller to control the policies of the windows that can be displayed on the virtual display.
 */
public class GenericWindowPolicyController extends DisplayWindowPolicyController {
class GenericWindowPolicyController extends DisplayWindowPolicyController {

    private static final String TAG = "GenericWindowPolicyController";

    private static final ComponentName BLOCKED_APP_STREAMING_COMPONENT =
            new ComponentName("android", BlockedAppStreamingActivity.class.getName());

    /** Interface to listen running applications change on virtual display. */
    public interface RunningAppsChangedListener {
        /**
         * Notifies the running applications change.
         */
        void onRunningAppsChanged(ArraySet<Integer> runningUids);
    }

    /** Interface to react to activity changes on the virtual display. */
    public interface ActivityListener {

@@ -93,6 +84,9 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController

        /** Returns true when an intent should be intercepted */
        boolean shouldInterceptIntent(@NonNull Intent intent);

        /** Called when the set of running apps on this display changes. */
        void onRunningAppsChanged(int displayId, @NonNull ArraySet<Integer> runningUids);
    }

    /**
@@ -135,10 +129,6 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
    private final ArraySet<Integer> mRunningUids = new ArraySet<>();
    @NonNull private final ActivityListener mActivityListener;
    private final Handler mHandler = new Handler(Looper.getMainLooper());
    @NonNull
    @GuardedBy("mGenericWindowPolicyControllerLock")
    private final ArraySet<RunningAppsChangedListener> mRunningAppsChangedListeners =
            new ArraySet<>();
    @NonNull private final Set<String> mDisplayCategories;

    @GuardedBy("mGenericWindowPolicyControllerLock")
@@ -279,20 +269,6 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
        }
    }

    /** Register a listener for running applications changes. */
    public void registerRunningAppsChangedListener(@NonNull RunningAppsChangedListener listener) {
        synchronized (mGenericWindowPolicyControllerLock) {
            mRunningAppsChangedListeners.add(listener);
        }
    }

    /** Unregister a listener for running applications changes. */
    public void unregisterRunningAppsChangedListener(@NonNull RunningAppsChangedListener listener) {
        synchronized (mGenericWindowPolicyControllerLock) {
            mRunningAppsChangedListeners.remove(listener);
        }
    }

    @Override
    public boolean canActivityBeLaunched(@NonNull ActivityInfo activityInfo,
            @Nullable Intent intent, @WindowConfiguration.WindowingMode int windowingMode,
@@ -421,18 +397,12 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
            mRunningUids.clear();
            mRunningUids.addAll(runningUids);
            int displayId = waitAndGetDisplayId();
            if (mRunningUids.isEmpty() && displayId != INVALID_DISPLAY) {
                // Post callback on the main thread so it doesn't block activity launching
                mHandler.post(() -> mActivityListener.onDisplayEmpty(displayId));
            }
            if (!mRunningAppsChangedListeners.isEmpty()) {
                final ArraySet<RunningAppsChangedListener> listeners =
                        new ArraySet<>(mRunningAppsChangedListeners);
                mHandler.post(() -> {
                    for (RunningAppsChangedListener listener : listeners) {
                        listener.onRunningAppsChanged(runningUids);
            if (displayId == INVALID_DISPLAY) {
                return;
            }
                });
            mHandler.post(() -> mActivityListener.onRunningAppsChanged(displayId, runningUids));
            if (mRunningUids.isEmpty()) {
                mHandler.post(() -> mActivityListener.onDisplayEmpty(displayId));
            }
        }
    }
@@ -496,11 +466,4 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
        // or disallowed and the exemptions contain the component.
        return allowedByDefault != exemptions.contains(component);
    }

    @VisibleForTesting
    int getRunningAppsChangedListenersSizeForTesting() {
        synchronized (mGenericWindowPolicyControllerLock) {
            return mRunningAppsChangedListeners.size();
        }
    }
}
+45 −29
Original line number Diff line number Diff line
@@ -35,7 +35,6 @@ import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_RECENTS;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.StringRes;
import android.annotation.UserIdInt;
import android.app.Activity;
@@ -116,7 +115,6 @@ import com.android.internal.app.BlockedAppStreamingActivity;
import com.android.modules.expresslog.Counter;
import com.android.server.LocalServices;
import com.android.server.UiModeManagerInternal;
import com.android.server.companion.virtual.GenericWindowPolicyController.RunningAppsChangedListener;
import com.android.server.companion.virtual.audio.VirtualAudioController;
import com.android.server.companion.virtual.camera.VirtualCameraController;
import com.android.server.inputmethod.InputMethodManagerInternal;
@@ -129,10 +127,8 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;

final class VirtualDeviceImpl extends IVirtualDevice.Stub
        implements IBinder.DeathRecipient, RunningAppsChangedListener {
final class VirtualDeviceImpl extends IVirtualDevice.Stub implements IBinder.DeathRecipient {

    private static final String TAG = "VirtualDeviceImpl";

@@ -219,8 +215,12 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
    private final PowerManager mPowerManager;
    @GuardedBy("mIntentInterceptors")
    private final Map<IBinder, IntentFilter> mIntentInterceptors = new ArrayMap<>();
    @NonNull
    private final Consumer<ArraySet<Integer>> mRunningAppsChangedCallback;

    // Mapping from displayId to all UIDs running on that display.
    @GuardedBy("mVirtualDeviceLock")
    private final SparseArray<ArraySet<Integer>> mRunningUids = new SparseArray<>();
    @GuardedBy("mVirtualDeviceLock")
    private ArraySet<Integer> mAllRunningUids = new ArraySet<>();

    // The default setting for showing the pointer on new displays.
    @GuardedBy("mVirtualDeviceLock")
@@ -378,6 +378,39 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
                return hasInterceptedIntent;
            }
        }

        @SuppressWarnings("AndroidFrameworkRequiresPermission")
        @Override
        public void onRunningAppsChanged(int displayId, @NonNull ArraySet<Integer> runningUids) {
            final ArraySet<Integer> newAllUids;
            synchronized (mVirtualDeviceLock) {
                if (Objects.equals(runningUids, mRunningUids.get(displayId))) {
                    return;
                }
                if (runningUids.isEmpty()) {
                    mRunningUids.remove(displayId);
                } else {
                    mRunningUids.put(displayId, runningUids);
                }

                newAllUids = new ArraySet<>();
                for (int i = 0; i < mRunningUids.size(); i++) {
                    newAllUids.addAll(mRunningUids.valueAt(i));
                }
                if (newAllUids.equals(mAllRunningUids)) {
                    return;
                }
                mAllRunningUids = newAllUids;
            }

            mService.onRunningAppsChanged(mDeviceId, newAllUids);
            if (mVirtualAudioController != null) {
                mVirtualAudioController.onRunningAppsChanged(newAllUids);
            }
            if (mCameraAccessController != null) {
                mCameraAccessController.blockCameraAccessIfNeeded(newAllUids);
            }
        }
    }

    VirtualDeviceImpl(
@@ -392,7 +425,6 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
            PendingTrampolineCallback pendingTrampolineCallback,
            IVirtualDeviceActivityListener activityListener,
            IVirtualDeviceSoundEffectListener soundEffectListener,
            Consumer<ArraySet<Integer>> runningAppsChangedCallback,
            VirtualDeviceParams params) {
        this(
                context,
@@ -407,7 +439,6 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
                pendingTrampolineCallback,
                activityListener,
                soundEffectListener,
                runningAppsChangedCallback,
                params,
                DisplayManagerGlobal.getInstance(),
                isVirtualCameraEnabled()
@@ -433,7 +464,6 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
            PendingTrampolineCallback pendingTrampolineCallback,
            IVirtualDeviceActivityListener activityListener,
            IVirtualDeviceSoundEffectListener soundEffectListener,
            Consumer<ArraySet<Integer>> runningAppsChangedCallback,
            VirtualDeviceParams params,
            DisplayManagerGlobal displayManager,
            VirtualCameraController virtualCameraController,
@@ -451,7 +481,6 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
        mPendingTrampolineCallback = pendingTrampolineCallback;
        mActivityListener = activityListener;
        mSoundEffectListener = soundEffectListener;
        mRunningAppsChangedCallback = runningAppsChangedCallback;
        mOwnerUid = attributionSource.getUid();
        mDeviceId = deviceId;
        mAppToken = token;
@@ -876,15 +905,6 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
        close();
    }

    @Override
    @RequiresPermission(android.Manifest.permission.CAMERA_INJECT_EXTERNAL_CAMERA)
    public void onRunningAppsChanged(ArraySet<Integer> runningUids) {
        if (mCameraAccessController != null) {
            mCameraAccessController.blockCameraAccessIfNeeded(runningUids);
        }
        mRunningAppsChangedCallback.accept(runningUids);
    }

    @VisibleForTesting
    VirtualAudioController getVirtualAudioControllerForTesting() {
        return mVirtualAudioController;
@@ -899,10 +919,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
            checkDisplayOwnedByVirtualDeviceLocked(displayId);
            if (mVirtualAudioController == null) {
                mVirtualAudioController = new VirtualAudioController(mContext, mAttributionSource);
                GenericWindowPolicyController gwpc =
                        mVirtualDisplays.get(displayId).getWindowPolicyController();
                mVirtualAudioController.startListening(gwpc, routingCallback,
                        configChangedCallback);
                mVirtualAudioController.startListening(routingCallback, configChangedCallback);
            }
        }
    }
@@ -1387,7 +1404,6 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
            showPointer = mDefaultShowPointerIcon;
        }
        displayWrapper.acquireWakeLock();
        gwpc.registerRunningAppsChangedListener(/* listener= */ this);

        Binder.withCleanCallingIdentity(() -> {
            mInputController.setMouseScalingEnabled(false, displayId);
@@ -1499,8 +1515,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
     * This is callback invoked by VirtualDeviceManagerService when VirtualDisplay was released
     * by DisplayManager (most probably caused by someone calling VirtualDisplay.close()).
     * At this point, the display is already released, but we still need to release the
     * corresponding wakeLock and unregister the RunningAppsChangedListener from corresponding
     * WindowPolicyController.
     * corresponding wakeLock.
     *
     * Note that when the display is destroyed during VirtualDeviceImpl.close() call,
     * this callback won't be invoked because the display is removed from
@@ -1604,8 +1619,9 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
     */
    private void releaseOwnedVirtualDisplayResources(VirtualDisplayWrapper virtualDisplayWrapper) {
        virtualDisplayWrapper.releaseWakeLock();
        virtualDisplayWrapper.getWindowPolicyController().unregisterRunningAppsChangedListener(
                this);
        // Notify the clients that nothing is running on this display anymore.
        mActivityListenerAdapter.onRunningAppsChanged(
                virtualDisplayWrapper.getDisplayId(), new ArraySet<>());
        // UiModeManagerService keeps all UI mode overrides in a map, so this call effectively
        // removes the entry for this display.
        mUiModeManagerInternal.setDisplayUiMode(
+37 −81
Original line number Diff line number Diff line
@@ -171,17 +171,18 @@ public class VirtualDeviceManagerService extends SystemService {
    private final RemoteCallbackList<IVirtualDeviceListener> mVirtualDeviceListeners =
            new RemoteCallbackList<>();

    /**
     * Mapping from device IDs to virtual devices.
     */
    @GuardedBy("mVirtualDeviceManagerLock")
    private final SparseArray<VirtualDeviceImpl> mVirtualDevices = new SparseArray<>();
    private final ArrayList<VirtualDeviceManagerInternal.AppsOnVirtualDeviceListener>
            mAppsOnVirtualDeviceListeners = new ArrayList<>();
    @GuardedBy("mVirtualDeviceManagerLock")
    private final ArrayList<Consumer<String>> mPersistentDeviceIdRemovedListeners =
            new ArrayList<>();

    /**
     * Mapping from device IDs to app UIDs running on the corresponding virtual device.
     * Mapping from device IDs to virtual devices.
     */
    @GuardedBy("mVirtualDeviceManagerLock")
    private final SparseArray<ArraySet<Integer>> mAppsOnVirtualDevices = new SparseArray<>();
    private final SparseArray<VirtualDeviceImpl> mVirtualDevices = new SparseArray<>();

    public VirtualDeviceManagerService(Context context) {
        super(context);
@@ -292,16 +293,31 @@ public class VirtualDeviceManagerService extends SystemService {
    }

    @VisibleForTesting
    void notifyRunningAppsChanged(int deviceId, ArraySet<Integer> uids) {
    void onRunningAppsChanged(int deviceId, @NonNull ArraySet<Integer> runningUids) {
        final List<VirtualDeviceManagerInternal.AppsOnVirtualDeviceListener> listeners;
        synchronized (mVirtualDeviceManagerLock) {
            if (!mVirtualDevices.contains(deviceId)) {
                Slog.e(TAG, "notifyRunningAppsChanged called for unknown deviceId:" + deviceId
                        + " (maybe it was recently closed?)");
                return;
            listeners = List.copyOf(mAppsOnVirtualDeviceListeners);
        }
            mAppsOnVirtualDevices.put(deviceId, uids);
        mHandler.post(() -> {
            for (int i = 0; i < listeners.size(); ++i) {
                listeners.get(i).onAppsRunningOnVirtualDeviceChanged(deviceId, runningUids);
            }
        mLocalService.onAppsOnVirtualDeviceChanged();
        });
    }

    @VisibleForTesting
    void onPersistentDeviceIdsRemoved(Set<String> removedPersistentDeviceIds) {
        final List<Consumer<String>> listeners;
        synchronized (mVirtualDeviceManagerLock) {
            listeners = List.copyOf(mPersistentDeviceIdRemovedListeners);
        }
        mHandler.post(() -> {
            for (String persistentDeviceId : removedPersistentDeviceIds) {
                for (int i = 0; i < listeners.size(); ++i) {
                    listeners.get(i).accept(persistentDeviceId);
                }
            }
        });
    }

    @VisibleForTesting
@@ -322,8 +338,6 @@ public class VirtualDeviceManagerService extends SystemService {
            if (!mVirtualDevices.contains(deviceId)) {
                return false;
            }

            mAppsOnVirtualDevices.remove(deviceId);
            mVirtualDevices.remove(deviceId);
        }

@@ -370,7 +384,7 @@ public class VirtualDeviceManagerService extends SystemService {
        }

        if (!removedPersistentDeviceIds.isEmpty()) {
            mLocalService.onPersistentDeviceIdsRemoved(removedPersistentDeviceIds);
            onPersistentDeviceIdsRemoved(removedPersistentDeviceIds);
        }
    }

@@ -496,12 +510,10 @@ public class VirtualDeviceManagerService extends SystemService {
                    getCameraAccessController(userHandle, params,
                            attributionSource.getPackageName());
            final int deviceId = sNextUniqueIndex.getAndIncrement();
            final Consumer<ArraySet<Integer>> runningAppsChangedCallback =
                    runningUids -> notifyRunningAppsChanged(deviceId, runningUids);
            VirtualDeviceImpl virtualDevice = new VirtualDeviceImpl(getContext(), associationInfo,
                    VirtualDeviceManagerService.this, mVirtualDeviceLog, token, attributionSource,
                    deviceId, cameraAccessController, mPendingTrampolineCallback, activityListener,
                    soundEffectListener, runningAppsChangedCallback, params);
                    soundEffectListener, params);
            Counter.logIncrement("virtual_devices.value_virtual_devices_created_count");

            synchronized (mVirtualDeviceManagerLock) {
@@ -733,15 +745,6 @@ public class VirtualDeviceManagerService extends SystemService {
    }

    private final class LocalService extends VirtualDeviceManagerInternal {
        @GuardedBy("mVirtualDeviceManagerLock")
        private final ArrayList<AppsOnVirtualDeviceListener> mAppsOnVirtualDeviceListeners =
                new ArrayList<>();
        @GuardedBy("mVirtualDeviceManagerLock")
        private final ArrayList<Consumer<String>> mPersistentDeviceIdRemovedListeners =
                new ArrayList<>();

        @GuardedBy("mVirtualDeviceManagerLock")
        private final ArraySet<Integer> mAllUidsOnVirtualDevice = new ArraySet<>();

        @Override
        public @NonNull VirtualDeviceManager.VirtualDevice createVirtualDevice(
@@ -808,50 +811,6 @@ public class VirtualDeviceManagerService extends SystemService {
            }
        }

        @Override
        public void onAppsOnVirtualDeviceChanged() {
            ArraySet<Integer> latestRunningUids = new ArraySet<>();
            final AppsOnVirtualDeviceListener[] listeners;
            synchronized (mVirtualDeviceManagerLock) {
                int size = mAppsOnVirtualDevices.size();
                for (int i = 0; i < size; i++) {
                    latestRunningUids.addAll(mAppsOnVirtualDevices.valueAt(i));
                }
                if (!mAllUidsOnVirtualDevice.equals(latestRunningUids)) {
                    mAllUidsOnVirtualDevice.clear();
                    mAllUidsOnVirtualDevice.addAll(latestRunningUids);
                    listeners =
                            mAppsOnVirtualDeviceListeners.toArray(
                                    new AppsOnVirtualDeviceListener[0]);
                } else {
                    listeners = null;
                }
            }
            if (listeners != null) {
                mHandler.post(() -> {
                    for (AppsOnVirtualDeviceListener listener : listeners) {
                        listener.onAppsOnAnyVirtualDeviceChanged(latestRunningUids);
                    }
                });
            }
        }

        @Override
        public void onPersistentDeviceIdsRemoved(Set<String> removedPersistentDeviceIds) {
            final List<Consumer<String>> persistentDeviceIdRemovedListeners;
            synchronized (mVirtualDeviceManagerLock) {
                persistentDeviceIdRemovedListeners = List.copyOf(
                        mPersistentDeviceIdRemovedListeners);
            }
            mHandler.post(() -> {
                for (String persistentDeviceId : removedPersistentDeviceIds) {
                    for (Consumer<String> listener : persistentDeviceIdRemovedListeners) {
                        listener.accept(persistentDeviceId);
                    }
                }
            });
        }

        @Override
        public void onAuthenticationPrompt(int uid) {
            ArrayList<VirtualDeviceImpl> virtualDevicesSnapshot = getVirtualDevicesSnapshot();
@@ -872,17 +831,14 @@ public class VirtualDeviceManagerService extends SystemService {
        @Nullable
        public LocaleList getPreferredLocaleListForUid(int uid) {
            // TODO: b/263188984 support the case where an app is running on multiple VDs
            VirtualDeviceImpl virtualDevice = null;
            synchronized (mVirtualDeviceManagerLock) {
                for (int i = 0; i < mAppsOnVirtualDevices.size(); i++) {
                    if (mAppsOnVirtualDevices.valueAt(i).contains(uid)) {
                        int deviceId = mAppsOnVirtualDevices.keyAt(i);
                        virtualDevice = mVirtualDevices.get(deviceId);
                        break;
                    }
            ArrayList<VirtualDeviceImpl> virtualDevicesSnapshot = getVirtualDevicesSnapshot();
            for (int i = 0; i < virtualDevicesSnapshot.size(); i++) {
                VirtualDeviceImpl virtualDevice = virtualDevicesSnapshot.get(i);
                if (virtualDevice.isAppRunningOnVirtualDevice(uid)) {
                    return virtualDevice.getDeviceLocaleList();
                }
            }
            return virtualDevice == null ? null : virtualDevice.getDeviceLocaleList();
            return null;
        }

        @Override
+16 −15

File changed.

Preview size limit exceeded, changes collapsed.

Loading