Loading core/java/com/android/internal/infra/AndroidFuture.java +14 −0 Original line number Diff line number Diff line Loading @@ -115,6 +115,20 @@ public class AndroidFuture<T> extends CompletableFuture<T> implements Parcelable } } /** * Create a completed future with the given value. * * @param value the value for the completed future * @param <U> the type of the value * @return the completed future */ @NonNull public static <U> AndroidFuture<U> completedFuture(U value) { AndroidFuture<U> future = new AndroidFuture<>(); future.complete(value); return future; } @Override public boolean complete(@Nullable T value) { boolean changed = super.complete(value); Loading core/java/com/android/internal/infra/ThrottledRunnable.java 0 → 100644 +75 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.infra; import android.annotation.NonNull; import android.os.Handler; import android.os.SystemClock; import com.android.internal.annotations.GuardedBy; /** * A throttled runnable that can wrap around a runnable and throttle calls to its run(). * * The throttling logic makes sure that the original runnable will be called only after the * specified interval passes since the last actual call. The first call in a while (after the * specified interval passes since the last actual call) will always result in the original runnable * being called immediately, and then subsequent calls will start to be throttled. It is guaranteed * that any call to this throttled runnable will always result in the original runnable being called * afterwards, within the specified interval. */ public class ThrottledRunnable implements Runnable { @NonNull private final Handler mHandler; private final long mIntervalMillis; @NonNull private final Runnable mRunnable; @NonNull private final Object mLock = new Object(); @GuardedBy("mLock") private long mScheduledUptimeMillis; public ThrottledRunnable(@NonNull Handler handler, long intervalMillis, @NonNull Runnable runnable) { mHandler = handler; mIntervalMillis = intervalMillis; mRunnable = runnable; } @Override public void run() { synchronized (mLock) { if (mHandler.hasCallbacks(mRunnable)) { // We have a scheduled runnable. return; } long currentUptimeMillis = SystemClock.uptimeMillis(); if (mScheduledUptimeMillis == 0 || currentUptimeMillis > mScheduledUptimeMillis + mIntervalMillis) { // First time in a while, schedule immediately. mScheduledUptimeMillis = currentUptimeMillis; } else { // We were scheduled not long ago, so schedule with delay for throttling. mScheduledUptimeMillis = mScheduledUptimeMillis + mIntervalMillis; } mHandler.postAtTime(mRunnable, mScheduledUptimeMillis); } } } services/core/java/com/android/server/role/RoleManagerService.java +73 −49 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.server.role; import android.Manifest; import android.annotation.AnyThread; import android.annotation.CheckResult; import android.annotation.MainThread; import android.annotation.NonNull; Loading Loading @@ -49,7 +50,6 @@ import android.os.ResultReceiver; import android.os.ShellCallback; import android.os.UserHandle; import android.os.UserManagerInternal; import android.provider.Telephony; import android.service.sms.FinancialSmsService; import android.telephony.IFinancialSmsCallback; import android.text.TextUtils; Loading @@ -61,6 +61,8 @@ import android.util.SparseArray; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.infra.AndroidFuture; import com.android.internal.infra.ThrottledRunnable; import com.android.internal.telephony.SmsApplication; import com.android.internal.util.ArrayUtils; import com.android.internal.util.BitUtils; Loading @@ -82,7 +84,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; Loading @@ -99,6 +100,8 @@ public class RoleManagerService extends SystemService implements RoleUserState.C private static final boolean DEBUG = false; private static final long GRANT_DEFAULT_ROLES_INTERVAL_MILLIS = 1000; @NonNull private final UserManagerInternal mUserManagerInternal; @NonNull Loading Loading @@ -142,6 +145,14 @@ public class RoleManagerService extends SystemService implements RoleUserState.C @NonNull private final Handler mListenerHandler = FgThread.getHandler(); /** * Maps user id to its throttled runnable for granting default roles. */ @GuardedBy("mLock") @NonNull private final SparseArray<ThrottledRunnable> mGrantDefaultRolesThrottledRunnables = new SparseArray<>(); public RoleManagerService(@NonNull Context context, @NonNull RoleHoldersResolver legacyRoleResolver) { super(context); Loading Loading @@ -182,8 +193,6 @@ public class RoleManagerService extends SystemService implements RoleUserState.C public void onStart() { publishBinderService(Context.ROLE_SERVICE, new Stub()); //TODO add watch for new user creation and run default grants for them IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); Loading @@ -203,64 +212,78 @@ public class RoleManagerService extends SystemService implements RoleUserState.C // Package is being upgraded - we're about to get ACTION_PACKAGE_ADDED return; } performInitialGrantsIfNecessaryAsync(userId); maybeGrantDefaultRolesAsync(userId); } }, UserHandle.ALL, intentFilter, null, null); } @Override public void onStartUser(@UserIdInt int userId) { performInitialGrantsIfNecessary(userId); maybeGrantDefaultRolesSync(userId); } private CompletableFuture<Void> performInitialGrantsIfNecessaryAsync(@UserIdInt int userId) { RoleUserState userState; userState = getOrCreateUserState(userId); @MainThread private void maybeGrantDefaultRolesSync(@UserIdInt int userId) { AndroidFuture<Void> future = maybeGrantDefaultRolesInternal(userId); try { future.get(30, TimeUnit.SECONDS); } catch (InterruptedException | ExecutionException | TimeoutException e) { Slog.e(LOG_TAG, "Failed to grant default roles for user " + userId, e); } } String packagesHash = computeComponentStateHash(userId); private void maybeGrantDefaultRolesAsync(@UserIdInt int userId) { ThrottledRunnable runnable; synchronized (mLock) { runnable = mGrantDefaultRolesThrottledRunnables.get(userId); if (runnable == null) { runnable = new ThrottledRunnable(FgThread.getHandler(), GRANT_DEFAULT_ROLES_INTERVAL_MILLIS, () -> maybeGrantDefaultRolesInternal(userId)); mGrantDefaultRolesThrottledRunnables.put(userId, runnable); } } runnable.run(); } @AnyThread @NonNull private AndroidFuture<Void> maybeGrantDefaultRolesInternal(@UserIdInt int userId) { RoleUserState userState = getOrCreateUserState(userId); String oldPackagesHash = userState.getPackagesHash(); boolean needGrant = !Objects.equals(packagesHash, oldPackagesHash); if (needGrant) { String newPackagesHash = computeComponentStateHash(userId); if (Objects.equals(oldPackagesHash, newPackagesHash)) { if (DEBUG) { Slog.i(LOG_TAG, "Already granted default roles for packages hash " + newPackagesHash); } return AndroidFuture.completedFuture(null); } //TODO gradually add more role migrations statements here for remaining roles // Make sure to implement LegacyRoleResolutionPolicy#getRoleHolders // for a given role before adding a migration statement for it here migrateRoleIfNecessary(RoleManager.ROLE_SMS, userId); migrateRoleIfNecessary(RoleManager.ROLE_ASSISTANT, userId); migrateRoleIfNecessary(RoleManager.ROLE_DIALER, userId); migrateRoleIfNecessary(RoleManager.ROLE_EMERGENCY, userId); // Some vital packages state has changed since last role grant // Run grants again Slog.i(LOG_TAG, "Granting default permissions..."); CompletableFuture<Void> result = new CompletableFuture<>(); maybeMigrateRole(RoleManager.ROLE_SMS, userId); maybeMigrateRole(RoleManager.ROLE_ASSISTANT, userId); maybeMigrateRole(RoleManager.ROLE_DIALER, userId); maybeMigrateRole(RoleManager.ROLE_EMERGENCY, userId); // Some package state has changed, so grant default roles again. Slog.i(LOG_TAG, "Granting default roles..."); AndroidFuture<Void> future = new AndroidFuture<>(); getOrCreateController(userId).grantDefaultRoles(FgThread.getExecutor(), successful -> { if (successful) { userState.setPackagesHash(packagesHash); result.complete(null); userState.setPackagesHash(newPackagesHash); future.complete(null); } else { result.completeExceptionally(new RuntimeException()); future.completeExceptionally(new RuntimeException()); } }); return result; } else if (DEBUG) { Slog.i(LOG_TAG, "Already ran grants for package state " + packagesHash); } return CompletableFuture.completedFuture(null); } @MainThread private void performInitialGrantsIfNecessary(@UserIdInt int userId) { CompletableFuture<Void> result = performInitialGrantsIfNecessaryAsync(userId); try { result.get(30, TimeUnit.SECONDS); } catch (InterruptedException | ExecutionException | TimeoutException e) { Slog.e(LOG_TAG, "Failed to grant defaults for user " + userId, e); } return future; } private void migrateRoleIfNecessary(String role, @UserIdInt int userId) { private void maybeMigrateRole(String role, @UserIdInt int userId) { // Any role for which we have a record are already migrated RoleUserState userState = getOrCreateUserState(userId); if (!userState.isRoleAvailable(role)) { Loading Loading @@ -366,6 +389,7 @@ public class RoleManagerService extends SystemService implements RoleUserState.C RemoteCallbackList<IOnRoleHoldersChangedListener> listeners; RoleUserState userState; synchronized (mLock) { mGrantDefaultRolesThrottledRunnables.remove(userId); listeners = mListeners.removeReturnOld(userId); mControllers.remove(userId); userState = mUserStates.removeReturnOld(userId); Loading Loading @@ -742,7 +766,7 @@ public class RoleManagerService extends SystemService implements RoleUserState.C @Override public boolean setDefaultBrowser(@Nullable String packageName, @UserIdInt int userId) { CompletableFuture<Void> future = new CompletableFuture<>(); AndroidFuture<Void> future = new AndroidFuture<>(); RemoteCallback callback = new RemoteCallback(result -> { boolean successful = result != null; if (successful) { Loading Loading
core/java/com/android/internal/infra/AndroidFuture.java +14 −0 Original line number Diff line number Diff line Loading @@ -115,6 +115,20 @@ public class AndroidFuture<T> extends CompletableFuture<T> implements Parcelable } } /** * Create a completed future with the given value. * * @param value the value for the completed future * @param <U> the type of the value * @return the completed future */ @NonNull public static <U> AndroidFuture<U> completedFuture(U value) { AndroidFuture<U> future = new AndroidFuture<>(); future.complete(value); return future; } @Override public boolean complete(@Nullable T value) { boolean changed = super.complete(value); Loading
core/java/com/android/internal/infra/ThrottledRunnable.java 0 → 100644 +75 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.infra; import android.annotation.NonNull; import android.os.Handler; import android.os.SystemClock; import com.android.internal.annotations.GuardedBy; /** * A throttled runnable that can wrap around a runnable and throttle calls to its run(). * * The throttling logic makes sure that the original runnable will be called only after the * specified interval passes since the last actual call. The first call in a while (after the * specified interval passes since the last actual call) will always result in the original runnable * being called immediately, and then subsequent calls will start to be throttled. It is guaranteed * that any call to this throttled runnable will always result in the original runnable being called * afterwards, within the specified interval. */ public class ThrottledRunnable implements Runnable { @NonNull private final Handler mHandler; private final long mIntervalMillis; @NonNull private final Runnable mRunnable; @NonNull private final Object mLock = new Object(); @GuardedBy("mLock") private long mScheduledUptimeMillis; public ThrottledRunnable(@NonNull Handler handler, long intervalMillis, @NonNull Runnable runnable) { mHandler = handler; mIntervalMillis = intervalMillis; mRunnable = runnable; } @Override public void run() { synchronized (mLock) { if (mHandler.hasCallbacks(mRunnable)) { // We have a scheduled runnable. return; } long currentUptimeMillis = SystemClock.uptimeMillis(); if (mScheduledUptimeMillis == 0 || currentUptimeMillis > mScheduledUptimeMillis + mIntervalMillis) { // First time in a while, schedule immediately. mScheduledUptimeMillis = currentUptimeMillis; } else { // We were scheduled not long ago, so schedule with delay for throttling. mScheduledUptimeMillis = mScheduledUptimeMillis + mIntervalMillis; } mHandler.postAtTime(mRunnable, mScheduledUptimeMillis); } } }
services/core/java/com/android/server/role/RoleManagerService.java +73 −49 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.server.role; import android.Manifest; import android.annotation.AnyThread; import android.annotation.CheckResult; import android.annotation.MainThread; import android.annotation.NonNull; Loading Loading @@ -49,7 +50,6 @@ import android.os.ResultReceiver; import android.os.ShellCallback; import android.os.UserHandle; import android.os.UserManagerInternal; import android.provider.Telephony; import android.service.sms.FinancialSmsService; import android.telephony.IFinancialSmsCallback; import android.text.TextUtils; Loading @@ -61,6 +61,8 @@ import android.util.SparseArray; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.infra.AndroidFuture; import com.android.internal.infra.ThrottledRunnable; import com.android.internal.telephony.SmsApplication; import com.android.internal.util.ArrayUtils; import com.android.internal.util.BitUtils; Loading @@ -82,7 +84,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; Loading @@ -99,6 +100,8 @@ public class RoleManagerService extends SystemService implements RoleUserState.C private static final boolean DEBUG = false; private static final long GRANT_DEFAULT_ROLES_INTERVAL_MILLIS = 1000; @NonNull private final UserManagerInternal mUserManagerInternal; @NonNull Loading Loading @@ -142,6 +145,14 @@ public class RoleManagerService extends SystemService implements RoleUserState.C @NonNull private final Handler mListenerHandler = FgThread.getHandler(); /** * Maps user id to its throttled runnable for granting default roles. */ @GuardedBy("mLock") @NonNull private final SparseArray<ThrottledRunnable> mGrantDefaultRolesThrottledRunnables = new SparseArray<>(); public RoleManagerService(@NonNull Context context, @NonNull RoleHoldersResolver legacyRoleResolver) { super(context); Loading Loading @@ -182,8 +193,6 @@ public class RoleManagerService extends SystemService implements RoleUserState.C public void onStart() { publishBinderService(Context.ROLE_SERVICE, new Stub()); //TODO add watch for new user creation and run default grants for them IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); Loading @@ -203,64 +212,78 @@ public class RoleManagerService extends SystemService implements RoleUserState.C // Package is being upgraded - we're about to get ACTION_PACKAGE_ADDED return; } performInitialGrantsIfNecessaryAsync(userId); maybeGrantDefaultRolesAsync(userId); } }, UserHandle.ALL, intentFilter, null, null); } @Override public void onStartUser(@UserIdInt int userId) { performInitialGrantsIfNecessary(userId); maybeGrantDefaultRolesSync(userId); } private CompletableFuture<Void> performInitialGrantsIfNecessaryAsync(@UserIdInt int userId) { RoleUserState userState; userState = getOrCreateUserState(userId); @MainThread private void maybeGrantDefaultRolesSync(@UserIdInt int userId) { AndroidFuture<Void> future = maybeGrantDefaultRolesInternal(userId); try { future.get(30, TimeUnit.SECONDS); } catch (InterruptedException | ExecutionException | TimeoutException e) { Slog.e(LOG_TAG, "Failed to grant default roles for user " + userId, e); } } String packagesHash = computeComponentStateHash(userId); private void maybeGrantDefaultRolesAsync(@UserIdInt int userId) { ThrottledRunnable runnable; synchronized (mLock) { runnable = mGrantDefaultRolesThrottledRunnables.get(userId); if (runnable == null) { runnable = new ThrottledRunnable(FgThread.getHandler(), GRANT_DEFAULT_ROLES_INTERVAL_MILLIS, () -> maybeGrantDefaultRolesInternal(userId)); mGrantDefaultRolesThrottledRunnables.put(userId, runnable); } } runnable.run(); } @AnyThread @NonNull private AndroidFuture<Void> maybeGrantDefaultRolesInternal(@UserIdInt int userId) { RoleUserState userState = getOrCreateUserState(userId); String oldPackagesHash = userState.getPackagesHash(); boolean needGrant = !Objects.equals(packagesHash, oldPackagesHash); if (needGrant) { String newPackagesHash = computeComponentStateHash(userId); if (Objects.equals(oldPackagesHash, newPackagesHash)) { if (DEBUG) { Slog.i(LOG_TAG, "Already granted default roles for packages hash " + newPackagesHash); } return AndroidFuture.completedFuture(null); } //TODO gradually add more role migrations statements here for remaining roles // Make sure to implement LegacyRoleResolutionPolicy#getRoleHolders // for a given role before adding a migration statement for it here migrateRoleIfNecessary(RoleManager.ROLE_SMS, userId); migrateRoleIfNecessary(RoleManager.ROLE_ASSISTANT, userId); migrateRoleIfNecessary(RoleManager.ROLE_DIALER, userId); migrateRoleIfNecessary(RoleManager.ROLE_EMERGENCY, userId); // Some vital packages state has changed since last role grant // Run grants again Slog.i(LOG_TAG, "Granting default permissions..."); CompletableFuture<Void> result = new CompletableFuture<>(); maybeMigrateRole(RoleManager.ROLE_SMS, userId); maybeMigrateRole(RoleManager.ROLE_ASSISTANT, userId); maybeMigrateRole(RoleManager.ROLE_DIALER, userId); maybeMigrateRole(RoleManager.ROLE_EMERGENCY, userId); // Some package state has changed, so grant default roles again. Slog.i(LOG_TAG, "Granting default roles..."); AndroidFuture<Void> future = new AndroidFuture<>(); getOrCreateController(userId).grantDefaultRoles(FgThread.getExecutor(), successful -> { if (successful) { userState.setPackagesHash(packagesHash); result.complete(null); userState.setPackagesHash(newPackagesHash); future.complete(null); } else { result.completeExceptionally(new RuntimeException()); future.completeExceptionally(new RuntimeException()); } }); return result; } else if (DEBUG) { Slog.i(LOG_TAG, "Already ran grants for package state " + packagesHash); } return CompletableFuture.completedFuture(null); } @MainThread private void performInitialGrantsIfNecessary(@UserIdInt int userId) { CompletableFuture<Void> result = performInitialGrantsIfNecessaryAsync(userId); try { result.get(30, TimeUnit.SECONDS); } catch (InterruptedException | ExecutionException | TimeoutException e) { Slog.e(LOG_TAG, "Failed to grant defaults for user " + userId, e); } return future; } private void migrateRoleIfNecessary(String role, @UserIdInt int userId) { private void maybeMigrateRole(String role, @UserIdInt int userId) { // Any role for which we have a record are already migrated RoleUserState userState = getOrCreateUserState(userId); if (!userState.isRoleAvailable(role)) { Loading Loading @@ -366,6 +389,7 @@ public class RoleManagerService extends SystemService implements RoleUserState.C RemoteCallbackList<IOnRoleHoldersChangedListener> listeners; RoleUserState userState; synchronized (mLock) { mGrantDefaultRolesThrottledRunnables.remove(userId); listeners = mListeners.removeReturnOld(userId); mControllers.remove(userId); userState = mUserStates.removeReturnOld(userId); Loading Loading @@ -742,7 +766,7 @@ public class RoleManagerService extends SystemService implements RoleUserState.C @Override public boolean setDefaultBrowser(@Nullable String packageName, @UserIdInt int userId) { CompletableFuture<Void> future = new CompletableFuture<>(); AndroidFuture<Void> future = new AndroidFuture<>(); RemoteCallback callback = new RemoteCallback(result -> { boolean successful = result != null; if (successful) { Loading