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

Commit fa988c63 authored by Willie Koomson's avatar Willie Koomson
Browse files

Disable widgets for stopped packages

Disable widgets whose package has been stopped (PACKAGE_RESTARTED
broadcast). Widgets in this state show the same UI as widgets that are
disabled due to a paused profile or suspended package.

Clicking the disabled UI will send an APPWIDGET_UPDATE broadcast to the
provider, which will also restart the stopped package. The package can
also be started by resizing the disabled widget, or clicking the app
icon.

Bug: 329156796
Test: manual - force close app and check for UI
Flag: ACONFIG android.content.pm.stay_stopped NEXTFOOD
Change-Id: Ie2411fce82d3ec6a0a70973f7a789f5dce268d04
parent b28430af
Loading
Loading
Loading
Loading
+165 −16
Original line number Diff line number Diff line
@@ -202,6 +202,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
    // Default max API calls per reset interval for generated preview API rate limiting.
    private static final int DEFAULT_GENERATED_PREVIEW_MAX_CALLS_PER_INTERVAL = 2;

    private static final String PENDING_DELETED_IDS_ATTR = "pending_deleted_ids";

    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
        @Override
@@ -228,6 +229,11 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
                    onPackageBroadcastReceived(intent, getSendingUserId());
                    updateWidgetPackageSuspensionMaskedState(intent, false, getSendingUserId());
                    break;
                case Intent.ACTION_PACKAGE_RESTARTED:
                case Intent.ACTION_PACKAGE_UNSTOPPED:
                    if (!android.content.pm.Flags.stayStopped()) return;
                    updateWidgetPackageStoppedMaskedState(intent);
                    break;
                default:
                    onPackageBroadcastReceived(intent, getSendingUserId());
                    break;
@@ -396,7 +402,10 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
        packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
        packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
        packageFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
        packageFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
        packageFilter.addAction(Intent.ACTION_PACKAGE_UNSTOPPED);
        packageFilter.addDataScheme("package");
        packageFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
        mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
                packageFilter, null, mCallbackHandler);

@@ -581,14 +590,19 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
                changed |= provider.setMaskedByQuietProfileLocked(quietProfile);
                try {
                    boolean suspended;
                    boolean stopped;
                    try {
                        suspended = mPackageManager.isPackageSuspendedForUser(
                                provider.id.componentName.getPackageName(), provider.getUserId());
                        stopped = mPackageManager.isPackageStoppedForUser(
                                provider.id.componentName.getPackageName(), provider.getUserId());
                    } catch (IllegalArgumentException ex) {
                        // Package not found.
                        suspended = false;
                        stopped = false;
                    }
                    changed |= provider.setMaskedBySuspendedPackageLocked(suspended);
                    changed |= provider.setMaskedByStoppedPackageLocked(stopped);
                } catch (RemoteException e) {
                    Slog.e(TAG, "Failed to query application info", e);
                }
@@ -635,6 +649,82 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
        }
    }

    /**
     * Update the masked state for a stopped or unstopped package.
     */
    private void updateWidgetPackageStoppedMaskedState(@NonNull Intent intent) {
        final int providerUid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID);
        final Uri uri = intent.getData();
        if (providerUid == Process.INVALID_UID || uri == null) {
            return;
        }

        final String packageName = uri.getSchemeSpecificPart();
        if (packageName == null) {
            return;
        }

        boolean isStopped;
        try {
            isStopped = mPackageManager.isPackageStoppedForUser(packageName,
                    UserHandle.getUserId(providerUid));
        } catch (Exception e) {
            Slog.e(TAG, "Failed to query package stopped state", e);
            return;
        }

        if (DEBUG) {
            Slog.i(TAG, "Updating package stopped masked state for uid " + providerUid + " package "
                    + packageName + " isStopped " + isStopped);
        }
        synchronized (mLock) {
            final int count = mProviders.size();
            for (int i = 0; i < count; i++) {
                Provider provider = mProviders.get(i);
                if (providerUid != provider.id.uid
                        || !packageName.equals(provider.id.componentName.getPackageName())) {
                    continue;
                }
                if (provider.setMaskedByStoppedPackageLocked(isStopped)) {
                    if (provider.isMaskedLocked()) {
                        maskWidgetsViewsLocked(provider, null);
                        cancelBroadcastsLocked(provider);
                    } else {
                        unmaskWidgetsViewsLocked(provider);
                        final int widgetCount = provider.widgets.size();
                        if (widgetCount > 0) {
                            final int[] widgetIds = new int[widgetCount];
                            for (int j = 0; j < widgetCount; j++) {
                                widgetIds[j] = provider.widgets.get(j).appWidgetId;
                            }
                            registerForBroadcastsLocked(provider, widgetIds);
                            sendUpdateIntentLocked(provider, widgetIds, /* interactive= */ false);
                        }

                        final int pendingIdsCount = provider.pendingDeletedWidgetIds.size();
                        if (pendingIdsCount > 0) {
                            if (DEBUG) {
                                Slog.i(TAG, "Sending missed deleted broadcasts for "
                                        + provider.id.componentName + " "
                                        + provider.pendingDeletedWidgetIds);
                            }
                            for (int j = 0; j < pendingIdsCount; j++) {
                                sendDeletedIntentLocked(provider.id.componentName,
                                        provider.id.getProfile(),
                                        provider.pendingDeletedWidgetIds.get(j));
                            }
                            provider.pendingDeletedWidgetIds.clear();
                            if (widgetCount == 0) {
                                sendDisabledIntentLocked(provider);
                            }
                            saveGroupStateAsync(provider.id.getProfile().getIdentifier());
                        }
                    }
                }
            }
        }
    }

    /**
     * Mask the target widget belonging to the specified provider, or all active widgets
     * of the provider if target widget == null.
@@ -648,11 +738,11 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
                R.layout.work_widget_mask_view);
        ApplicationInfo appInfo = provider.info.providerInfo.applicationInfo;
        final int appUserId = provider.getUserId();
        boolean showBadge;
        boolean showBadge = false;

        final long identity = Binder.clearCallingIdentity();
        try {
            final Intent onClickIntent;
            Intent onClickIntent = null;

            if (provider.maskedByQuietProfile) {
                showBadge = true;
@@ -676,7 +766,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
                            appInfo.packageName, suspendingPackage, dialogInfo, null, null,
                            appUserId);
                }
            } else /* provider.maskedByLockedProfile */ {
            } else if (provider.maskedByLockedProfile) {
                showBadge = true;
                onClickIntent = mKeyguardManager
                        .createConfirmDeviceCredentialIntent(null, null, appUserId);
@@ -684,6 +774,8 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
                    onClickIntent.setFlags(
                            FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
                }
            } else if (provider.maskedByStoppedPackage) {
                showBadge = mUserManager.hasBadge(appUserId);
            }

            Icon icon = appInfo.icon != 0
@@ -697,7 +789,14 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
            for (int j = 0; j < widgetCount; j++) {
                Widget widget = provider.widgets.get(j);
                if (targetWidget != null && targetWidget != widget) continue;
                if (onClickIntent != null) {
                if (provider.maskedByStoppedPackage) {
                    Intent intent = createUpdateIntentLocked(provider,
                            new int[] { widget.appWidgetId });
                    views.setOnClickPendingIntent(android.R.id.background,
                            PendingIntent.getBroadcast(mContext, widget.appWidgetId,
                                    intent, PendingIntent.FLAG_UPDATE_CURRENT
                                            | PendingIntent.FLAG_IMMUTABLE));
                } else if (onClickIntent != null) {
                    views.setOnClickPendingIntent(android.R.id.background,
                            PendingIntent.getActivity(mContext, widget.appWidgetId, onClickIntent,
                                    PendingIntent.FLAG_UPDATE_CURRENT
@@ -1950,19 +2049,27 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
        if (provider != null) {
            provider.widgets.remove(widget);
            if (!provider.zombie) {
                // send the broacast saying that this appWidgetId has been deleted
                // If the package is not stopped, send the broadcast saying that this appWidgetId
                // has been deleted. Otherwise, save the ID and send the broadcast when the package
                // is unstopped.
                if (!provider.maskedByStoppedPackage) {
                    sendDeletedIntentLocked(widget);
                } else {
                    provider.pendingDeletedWidgetIds.add(widget.appWidgetId);
                }

                if (provider.widgets.isEmpty()) {
                    // cancel the future updates
                    cancelBroadcastsLocked(provider);

                    // send the broacast saying that the provider is not in use any more
                    // send the broadcast saying that the provider is not in use any more
                    if (!provider.maskedByStoppedPackage) {
                        sendDisabledIntentLocked(provider);
                    }
                }
            }
        }
    }

    private void cancelBroadcastsLocked(Provider provider) {
        if (DEBUG) {
@@ -2033,8 +2140,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
            final HashSet<Integer> ids = mRemoteViewsServicesAppWidgets.get(key);
            if (ids.remove(widget.appWidgetId)) {
                // If we have removed the last app widget referencing this service, then we
                // should destroy it and remove it from this set
                if (ids.isEmpty()) {
                // should destroy it and remove it from this set. This is skipped for widgets whose
                // provider is in a stopped package, to avoid waking up the package.
                if (ids.isEmpty() && !widget.provider.maskedByStoppedPackage) {
                    destroyRemoteViewsService(key.second.getIntent(), widget);
                    it.remove();
                }
@@ -2544,18 +2652,29 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku

    private void sendUpdateIntentLocked(Provider provider, int[] appWidgetIds,
            boolean interactive) {
        Intent intent = createUpdateIntentLocked(provider, appWidgetIds);
        sendBroadcastAsUser(intent, provider.id.getProfile(), interactive);
    }

    private Intent createUpdateIntentLocked(Provider provider, int[] appWidgetIds) {
        Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
        intent.setComponent(provider.id.componentName);
        sendBroadcastAsUser(intent, provider.id.getProfile(), interactive);
        return intent;
    }

    private void sendDeletedIntentLocked(Widget widget) {
        sendDeletedIntentLocked(widget.provider.id.componentName, widget.provider.id.getProfile(),
                widget.appWidgetId);
    }

    private void sendDeletedIntentLocked(ComponentName provider, UserHandle profile,
            int appWidgetId) {
        Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DELETED);
        intent.setComponent(widget.provider.id.componentName);
        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widget.appWidgetId);
        intent.setComponent(provider);
        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
        // Cleanup after deletion isn't an interactive UX case
        sendBroadcastAsUser(intent, widget.provider.id.getProfile(), false);
        sendBroadcastAsUser(intent, profile, false);
    }

    private void sendDisabledIntentLocked(Provider provider) {
@@ -2684,6 +2803,14 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
        if (persistsProviderInfo && p.mInfoParsed) {
            AppWidgetXmlUtil.writeAppWidgetProviderInfoLocked(out, p.info);
        }
        final int pendingIdsCount = p.pendingDeletedWidgetIds.size();
        if (pendingIdsCount > 0) {
            final List<String> idStrings = new ArrayList<>();
            for (int i = 0; i < pendingIdsCount; i++) {
                idStrings.add(String.valueOf(p.pendingDeletedWidgetIds.get(i)));
            }
            out.attribute(null, PENDING_DELETED_IDS_ATTR, String.join(",", idStrings));
        }
        out.endTag(null, "p");
    }

@@ -3022,7 +3149,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
                    continue;
                }

                if (provider.widgets.size() > 0) {
                if (provider.widgets.size() > 0 && !provider.maskedByStoppedPackage) {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                            "appwidget init " + provider.id.componentName.getPackageName());
                    provider.widgets.forEach(widget -> {
@@ -3440,6 +3567,16 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
                                legacyProviderIndex);
                        provider.tag = providerTag;
                        provider.infoTag = parser.getAttributeValue(null, "info_tag");

                        final String pendingDeletedIds = parser.getAttributeValue(null,
                                PENDING_DELETED_IDS_ATTR);
                        if (pendingDeletedIds != null && !pendingDeletedIds.isEmpty()) {
                            final String[] idStrings = pendingDeletedIds.split(",");
                            for (int i = 0; i < idStrings.length; i++) {
                                provider.pendingDeletedWidgetIds.add(
                                        Integer.parseInt(idStrings[i]));
                            }
                        }
                    } else if ("h".equals(tag)) {
                        legacyHostIndex++;
                        Host host = new Host();
@@ -4443,6 +4580,11 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
        boolean maskedByLockedProfile;
        boolean maskedByQuietProfile;
        boolean maskedBySuspendedPackage;
        // This provider's package has been stopped
        boolean maskedByStoppedPackage;
        // Widget IDs for which we haven't yet sent DELETED broadcasts because the package was
        // stopped.
        IntArray pendingDeletedWidgetIds = new IntArray();

        boolean mInfoParsed = false;

@@ -4598,8 +4740,15 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
            return masked != oldState;
        }

        public boolean setMaskedByStoppedPackageLocked(boolean masked) {
            boolean oldState = maskedByStoppedPackage;
            maskedByStoppedPackage = masked;
            return masked != oldState;
        }

        public boolean isMaskedLocked() {
            return maskedByQuietProfile || maskedByLockedProfile || maskedBySuspendedPackage;
            return maskedByQuietProfile || maskedByLockedProfile || maskedBySuspendedPackage
                    || maskedByStoppedPackage;
        }

        public boolean shouldBePersisted() {