Merge "Import translations. DO NOT MERGE" into qt-dev
diff --git a/Android.bp b/Android.bp
index b4110fa..0fcc0d5 100644
--- a/Android.bp
+++ b/Android.bp
@@ -320,7 +320,7 @@
"core/java/android/service/vr/IVrManager.aidl",
"core/java/android/service/vr/IVrStateCallbacks.aidl",
"core/java/android/service/watchdog/IExplicitHealthCheckService.aidl",
- "core/java/android/service/watchdog/PackageInfo.aidl",
+ "core/java/android/service/watchdog/PackageConfig.aidl",
"core/java/android/print/ILayoutResultCallback.aidl",
"core/java/android/print/IPrinterDiscoveryObserver.aidl",
"core/java/android/print/IPrintDocumentAdapter.aidl",
diff --git a/api/current.txt b/api/current.txt
index b7a951c..f62c689 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3763,6 +3763,7 @@
method public void onDetachedFromWindow();
method public void onEnterAnimationComplete();
method public boolean onGenericMotionEvent(android.view.MotionEvent);
+ method @NonNull public java.util.List<android.app.DirectAction> onGetDirectActions();
method public boolean onKeyDown(int, android.view.KeyEvent);
method public boolean onKeyLongPress(int, android.view.KeyEvent);
method public boolean onKeyMultiple(int, int, android.view.KeyEvent);
@@ -3782,6 +3783,7 @@
method public void onOptionsMenuClosed(android.view.Menu);
method public void onPanelClosed(int, @NonNull android.view.Menu);
method @CallSuper protected void onPause();
+ method public void onPerformDirectAction(@NonNull String, @Nullable android.os.Bundle, @Nullable android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.os.Bundle>);
method public void onPictureInPictureModeChanged(boolean, android.content.res.Configuration);
method @Deprecated public void onPictureInPictureModeChanged(boolean);
method @CallSuper protected void onPostCreate(@Nullable android.os.Bundle);
@@ -4603,6 +4605,22 @@
field @Deprecated public static final int STYLE_NO_TITLE = 1; // 0x1
}
+ public final class DirectAction implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public android.os.Bundle getExtras();
+ method @NonNull public String getId();
+ method @Nullable public android.content.LocusId getLocusId();
+ method public void writeToParcel(android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.DirectAction> CREATOR;
+ }
+
+ public static final class DirectAction.Builder {
+ ctor public DirectAction.Builder(@NonNull String);
+ method @NonNull public android.app.DirectAction build();
+ method @NonNull public android.app.DirectAction.Builder setExtras(@Nullable android.os.Bundle);
+ method @NonNull public android.app.DirectAction.Builder setLocusId(@Nullable android.content.LocusId);
+ }
+
public class DownloadManager {
method public long addCompletedDownload(String, String, boolean, String, String, long, boolean);
method public long addCompletedDownload(String, String, boolean, String, String, long, boolean, android.net.Uri, android.net.Uri);
@@ -6338,9 +6356,13 @@
public final class VoiceInteractor {
method public android.app.VoiceInteractor.Request getActiveRequest(String);
method public android.app.VoiceInteractor.Request[] getActiveRequests();
+ method public boolean isDestroyed();
+ method public void notifyDirectActionsChanged();
+ method public boolean registerOnDestroyedCallback(@NonNull java.util.concurrent.Executor, @NonNull Runnable);
method public boolean submitRequest(android.app.VoiceInteractor.Request);
method public boolean submitRequest(android.app.VoiceInteractor.Request, String);
method public boolean[] supportsCommands(String[]);
+ method public boolean unregisterOnDestroyedCallback(@NonNull Runnable);
}
public static class VoiceInteractor.AbortVoiceRequest extends android.app.VoiceInteractor.Request {
@@ -41790,9 +41812,11 @@
method public void onCreate();
method public android.view.View onCreateContentView();
method public void onDestroy();
+ method public void onDirectActionsInvalidated(@NonNull android.service.voice.VoiceInteractionSession.ActivityId);
method public boolean[] onGetSupportedCommands(String[]);
- method public void onHandleAssist(@Nullable android.os.Bundle, @Nullable android.app.assist.AssistStructure, @Nullable android.app.assist.AssistContent);
- method public void onHandleAssistSecondary(@Nullable android.os.Bundle, @Nullable android.app.assist.AssistStructure, @Nullable android.app.assist.AssistContent, int, int);
+ method @Deprecated public void onHandleAssist(@Nullable android.os.Bundle, @Nullable android.app.assist.AssistStructure, @Nullable android.app.assist.AssistContent);
+ method public void onHandleAssist(@NonNull android.service.voice.VoiceInteractionSession.AssistState);
+ method @Deprecated public void onHandleAssistSecondary(@Nullable android.os.Bundle, @Nullable android.app.assist.AssistStructure, @Nullable android.app.assist.AssistContent, int, int);
method public void onHandleScreenshot(@Nullable android.graphics.Bitmap);
method public void onHide();
method public boolean onKeyDown(int, android.view.KeyEvent);
@@ -41811,6 +41835,8 @@
method public void onTaskFinished(android.content.Intent, int);
method public void onTaskStarted(android.content.Intent, int);
method public void onTrimMemory(int);
+ method public final void performDirectAction(@NonNull android.app.DirectAction, @Nullable android.os.Bundle, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.os.Bundle>);
+ method public final void requestDirectActions(@NonNull android.service.voice.VoiceInteractionSession.ActivityId, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<android.app.DirectAction>>);
method public void setContentView(android.view.View);
method public void setDisabledShowContext(int);
method public void setKeepAwake(boolean);
@@ -41835,6 +41861,19 @@
method public void sendAbortResult(android.os.Bundle);
}
+ public static class VoiceInteractionSession.ActivityId {
+ }
+
+ public static final class VoiceInteractionSession.AssistState {
+ method @NonNull public android.service.voice.VoiceInteractionSession.ActivityId getActivityId();
+ method @Nullable public android.app.assist.AssistContent getAssistContent();
+ method @Nullable public android.os.Bundle getAssistData();
+ method @Nullable public android.app.assist.AssistStructure getAssistStructure();
+ method @IntRange(from=0) public int getCount();
+ method @IntRange(from=0xffffffff) public int getIndex();
+ method public boolean isFocused();
+ }
+
public static final class VoiceInteractionSession.CommandRequest extends android.service.voice.VoiceInteractionSession.Request {
method public String getCommand();
method public void sendIntermediateResult(android.os.Bundle);
diff --git a/api/system-current.txt b/api/system-current.txt
index 8cd722b..3350ed7 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -6839,19 +6839,19 @@
method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
method public abstract void onCancelHealthCheck(@NonNull String);
method @NonNull public abstract java.util.List<java.lang.String> onGetRequestedPackages();
- method @NonNull public abstract java.util.List<android.service.watchdog.PackageInfo> onGetSupportedPackages();
+ method @NonNull public abstract java.util.List<android.service.watchdog.ExplicitHealthCheckService.PackageConfig> onGetSupportedPackages();
method public abstract void onRequestHealthCheck(@NonNull String);
field public static final String BIND_PERMISSION = "android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE";
field public static final String SERVICE_INTERFACE = "android.service.watchdog.ExplicitHealthCheckService";
}
- public final class PackageInfo implements android.os.Parcelable {
- ctor public PackageInfo(@NonNull String, long);
+ public static final class ExplicitHealthCheckService.PackageConfig implements android.os.Parcelable {
+ ctor public ExplicitHealthCheckService.PackageConfig(@NonNull String, long);
method public int describeContents();
method public long getHealthCheckTimeoutMillis();
method @NonNull public String getPackageName();
method public void writeToParcel(android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.service.watchdog.PackageInfo> CREATOR;
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.watchdog.ExplicitHealthCheckService.PackageConfig> CREATOR;
}
}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index c8bd275..9ae0aa0 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -65,6 +65,7 @@
import android.os.BadParcelableException;
import android.os.Build;
import android.os.Bundle;
+import android.os.CancellationSignal;
import android.os.GraphicsEnvironment;
import android.os.Handler;
import android.os.IBinder;
@@ -148,8 +149,11 @@
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
@@ -788,6 +792,7 @@
private Instrumentation mInstrumentation;
@UnsupportedAppUsage
private IBinder mToken;
+ private IBinder mAssistToken;
@UnsupportedAppUsage
private int mIdent;
@UnsupportedAppUsage
@@ -866,7 +871,7 @@
private boolean mEnableDefaultActionBarUp;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
- private VoiceInteractor mVoiceInteractor;
+ VoiceInteractor mVoiceInteractor;
@UnsupportedAppUsage
private CharSequence mTitle;
@@ -1858,9 +1863,12 @@
void setVoiceInteractor(IVoiceInteractor voiceInteractor) {
if (mVoiceInteractor != null) {
- for (Request activeRequest: mVoiceInteractor.getActiveRequests()) {
- activeRequest.cancel();
- activeRequest.clear();
+ final Request[] requests = mVoiceInteractor.getActiveRequests();
+ if (requests != null) {
+ for (Request activeRequest : mVoiceInteractor.getActiveRequests()) {
+ activeRequest.cancel();
+ activeRequest.clear();
+ }
}
}
if (voiceInteractor == null) {
@@ -2316,6 +2324,47 @@
}
/**
+ * Returns the list of direct actions supported by the app.
+ *
+ * <p>You should return the list of actions that could be executed in the
+ * current context, which is in the current state of the app. If the actions
+ * that could be executed by the app changes you should report that via
+ * calling {@link VoiceInteractor#notifyDirectActionsChanged()}.
+ *
+ * <p>To get the voice interactor you need to call {@link #getVoiceInteractor()}
+ * which would return non <code>null<c/ode> only if there is an ongoing voice
+ * interaction session. You an also detect when the voice interactor is no
+ * longer valid because the voice interaction session that is backing is finished
+ * by calling {@link VoiceInteractor#registerOnDestroyedCallback(Executor, Runnable)}.
+ *
+ * <p>This method will be called only after {@link #onStart()} is being called and
+ * before {@link #onStop()} is being called.
+ *
+ * @return The currently supported direct actions which cannot be <code>null</code>
+ * or contain <code>null</null> elements.
+ */
+ @NonNull
+ public List<DirectAction> onGetDirectActions() {
+ return Collections.emptyList();
+ }
+
+ /**
+ * This is called to perform an action previously defined by the app.
+ * Apps also have access to {@link #getVoiceInteractor()} to follow up on the action.
+ *
+ * @param actionId The ID for the action
+ * @param arguments Any additional arguments provided by the caller
+ * @param cancellationSignal A signal to cancel the operation in progress, or {@code null}
+ * if none.
+ * @param resultListener The callback to provide the result back to the caller
+ *
+ * @see #onGetDirectActions()
+ */
+ public void onPerformDirectAction(@NonNull String actionId,
+ @Nullable Bundle arguments, @Nullable CancellationSignal cancellationSignal,
+ @NonNull Consumer<Bundle> resultListener) { }
+
+ /**
* Request the Keyboard Shortcuts screen to show up. This will trigger
* {@link #onProvideKeyboardShortcuts} to retrieve the shortcuts for the foreground activity.
*/
@@ -7626,7 +7675,7 @@
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
- Window window, ActivityConfigCallback activityConfigCallback) {
+ Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
@@ -7647,6 +7696,7 @@
mMainThread = aThread;
mInstrumentation = instr;
mToken = token;
+ mAssistToken = assistToken;
mIdent = ident;
mApplication = application;
mIntent = intent;
@@ -7698,6 +7748,11 @@
}
/** @hide */
+ public final IBinder getAssistToken() {
+ return mParent != null ? mParent.getAssistToken() : mAssistToken;
+ }
+
+ /** @hide */
@VisibleForTesting
public final ActivityThread getActivityThread() {
return mMainThread;
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 9cd42a5..6552a77 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -88,6 +88,7 @@
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
+import android.os.CancellationSignal;
import android.os.Debug;
import android.os.Environment;
import android.os.FileUtils;
@@ -95,6 +96,7 @@
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.IBinder;
+import android.os.ICancellationSignal;
import android.os.LocaleList;
import android.os.Looper;
import android.os.Message;
@@ -121,6 +123,7 @@
import android.renderscript.RenderScriptCacheDir;
import android.security.NetworkSecurityPolicy;
import android.security.net.config.NetworkSecurityConfigProvider;
+import android.service.voice.VoiceInteractionSession;
import android.system.ErrnoException;
import android.system.OsConstants;
import android.system.StructStat;
@@ -160,6 +163,7 @@
import com.android.internal.os.SomeArgs;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastPrintWriter;
+import com.android.internal.util.Preconditions;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.org.conscrypt.OpenSSLSocketImpl;
import com.android.org.conscrypt.TrustedCertificateStore;
@@ -450,6 +454,7 @@
public static final class ActivityClientRecord {
@UnsupportedAppUsage
public IBinder token;
+ public IBinder assistToken;
int ident;
@UnsupportedAppUsage
Intent intent;
@@ -526,8 +531,10 @@
String referrer, IVoiceInteractor voiceInteractor, Bundle state,
PersistableBundle persistentState, List<ResultInfo> pendingResults,
List<ReferrerIntent> pendingNewIntents, boolean isForward,
- ProfilerInfo profilerInfo, ClientTransactionHandler client) {
+ ProfilerInfo profilerInfo, ClientTransactionHandler client,
+ IBinder assistToken) {
this.token = token;
+ this.assistToken = assistToken;
this.ident = ident;
this.intent = intent;
this.referrer = referrer;
@@ -1645,6 +1652,33 @@
public void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
ActivityThread.this.scheduleTransaction(transaction);
}
+
+ @Override
+ public void requestDirectActions(@NonNull IBinder activityToken,
+ @NonNull IVoiceInteractor interactor, @NonNull RemoteCallback callback) {
+ mH.sendMessage(PooledLambda.obtainMessage(ActivityThread::handleRequestDirectActions,
+ ActivityThread.this, activityToken, interactor, callback));
+ }
+
+ @Override
+ public void performDirectAction(IBinder activityToken, String actionId, Bundle arguments,
+ RemoteCallback cancellationCallback, RemoteCallback resultCallback) {
+ final CancellationSignal cancellationSignal;
+ if (cancellationCallback != null) {
+ final ICancellationSignal transport = CancellationSignal.createTransport();
+ cancellationSignal = CancellationSignal.fromTransport(transport);
+ final Bundle cancellationResult = new Bundle();
+ cancellationResult.putBinder(VoiceInteractor.KEY_CANCELLATION_SIGNAL,
+ transport.asBinder());
+ cancellationCallback.sendResult(cancellationResult);
+ } else {
+ cancellationSignal = new CancellationSignal();
+ }
+
+ mH.sendMessage(PooledLambda.obtainMessage(ActivityThread::handlePerformDirectAction,
+ ActivityThread.this, activityToken, actionId, arguments,
+ cancellationSignal, resultCallback));
+ }
}
class H extends Handler {
@@ -2877,9 +2911,10 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public final Activity startActivityNow(Activity parent, String id,
Intent intent, ActivityInfo activityInfo, IBinder token, Bundle state,
- Activity.NonConfigurationInstances lastNonConfigurationInstances) {
+ Activity.NonConfigurationInstances lastNonConfigurationInstances, IBinder assistToken) {
ActivityClientRecord r = new ActivityClientRecord();
r.token = token;
+ r.assistToken = assistToken;
r.ident = 0;
r.intent = intent;
r.state = state;
@@ -3120,7 +3155,8 @@
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
- r.referrer, r.voiceInteractor, window, r.configCallback);
+ r.referrer, r.voiceInteractor, window, r.configCallback,
+ r.assistToken);
if (customIntent != null) {
activity.mIntent = customIntent;
@@ -3352,7 +3388,6 @@
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
-
}
private void deliverNewIntents(ActivityClientRecord r, List<ReferrerIntent> intents) {
@@ -3432,6 +3467,7 @@
r.activity.onProvideAssistContent(content);
}
}
+
}
if (structure == null) {
structure = new AssistStructure();
@@ -3451,6 +3487,68 @@
}
}
+ /** Fetches the user actions for the corresponding activity */
+ private void handleRequestDirectActions(@NonNull IBinder activityToken,
+ @NonNull IVoiceInteractor interactor, @NonNull RemoteCallback callback) {
+ final ActivityClientRecord r = mActivities.get(activityToken);
+ if (r != null) {
+ final int lifecycleState = r.getLifecycleState();
+ if (lifecycleState < ON_START || lifecycleState >= ON_STOP) {
+ callback.sendResult(null);
+ return;
+ }
+ if (r.activity.mVoiceInteractor == null
+ || r.activity.mVoiceInteractor.mInteractor.asBinder()
+ != interactor.asBinder()) {
+ if (r.activity.mVoiceInteractor != null) {
+ r.activity.mVoiceInteractor.destroy();
+ }
+ r.activity.mVoiceInteractor = new VoiceInteractor(interactor, r.activity,
+ r.activity, Looper.myLooper());
+ }
+ final List<DirectAction> actions = r.activity.onGetDirectActions();
+ Preconditions.checkNotNull(actions);
+ Preconditions.checkCollectionElementsNotNull(actions, "actions");
+ if (actions != null && !actions.isEmpty()) {
+ final int actionCount = actions.size();
+ for (int i = 0; i < actionCount; i++) {
+ final DirectAction action = actions.get(i);
+ action.setSource(r.activity.getTaskId(), r.activity.getAssistToken());
+ }
+ final Bundle result = new Bundle();
+ result.putParcelable(DirectAction.KEY_ACTIONS_LIST,
+ new ParceledListSlice<>(actions));
+ callback.sendResult(result);
+ }
+ }
+ callback.sendResult(null);
+ }
+
+ /** Performs an actions in the corresponding activity */
+ private void handlePerformDirectAction(@NonNull IBinder activityToken,
+ @NonNull String actionId, @Nullable Bundle arguments,
+ @NonNull CancellationSignal cancellationSignal,
+ @NonNull RemoteCallback resultCallback) {
+ final ActivityClientRecord r = mActivities.get(activityToken);
+ if (r != null) {
+ final int lifecycleState = r.getLifecycleState();
+ if (lifecycleState < ON_START || lifecycleState >= ON_STOP) {
+ resultCallback.sendResult(null);
+ return;
+ }
+ final Bundle nonNullArguments = (arguments != null) ? arguments : Bundle.EMPTY;
+ final WeakReference<RemoteCallback> weakCallback = new WeakReference<>(resultCallback);
+ r.activity.onPerformDirectAction(actionId, nonNullArguments, cancellationSignal,
+ (b) -> {
+ final RemoteCallback strongCallback = weakCallback.get();
+ if (strongCallback != null) {
+ strongCallback.sendResult(b);
+ }
+ });
+ }
+ resultCallback.sendResult(null);
+ }
+
public void handleTranslucentConversionComplete(IBinder token, boolean drawComplete) {
ActivityClientRecord r = mActivities.get(token);
if (r != null) {
diff --git a/core/java/android/app/DirectAction.java b/core/java/android/app/DirectAction.java
new file mode 100644
index 0000000..d191f4b
--- /dev/null
+++ b/core/java/android/app/DirectAction.java
@@ -0,0 +1,188 @@
+/*
+ * 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 android.app;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.LocusId;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Represents a abstract action that can be perform on this app. This are requested from
+ * outside the app's UI (eg by SystemUI or assistant).
+ */
+public final class DirectAction implements Parcelable {
+
+ /**
+ * @hide
+ */
+ public static final String KEY_ACTIONS_LIST = "actions_list";
+
+ private int mTaskId;
+ private IBinder mActivityId;
+
+ @NonNull
+ private final String mID;
+ @Nullable
+ private final Bundle mExtras;
+ @Nullable
+ private final LocusId mLocusId;
+
+ /** @hide */
+ public DirectAction(@NonNull String id, @Nullable Bundle extras,
+ @Nullable LocusId locusId) {
+ mID = Preconditions.checkStringNotEmpty(id);
+ mExtras = extras;
+ mLocusId = locusId;
+ }
+
+ /** @hide */
+ public void setSource(int taskId, IBinder activityId) {
+ mTaskId = taskId;
+ mActivityId = activityId;
+ }
+
+ /**
+ * @hide
+ */
+ public DirectAction(@NonNull DirectAction original) {
+ mTaskId = original.mTaskId;
+ mActivityId = original.mActivityId;
+ mID = original.mID;
+ mExtras = original.mExtras;
+ mLocusId = original.mLocusId;
+ }
+
+ private DirectAction(Parcel in) {
+ mTaskId = in.readInt();
+ mActivityId = in.readStrongBinder();
+ mID = in.readString();
+ mExtras = in.readBundle();
+ final String idString = in.readString();
+ mLocusId = (idString != null) ? new LocusId(idString) : null;
+ }
+
+ /** @hide */
+ public int getTaskId() {
+ return mTaskId;
+ }
+
+ /** @hide */
+ public IBinder getActivityId() {
+ return mActivityId;
+ }
+
+ /**
+ * Returns the ID for this action.
+ */
+ @NonNull
+ public String getId() {
+ return mID;
+ }
+
+ /**
+ * Returns any extras associated with this action.
+ */
+ @Nullable
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ /**
+ * Returns the LocusId for the current state for the app
+ */
+ @Nullable
+ public LocusId getLocusId() {
+ return mLocusId;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mTaskId);
+ dest.writeStrongBinder(mActivityId);
+ dest.writeString(mID);
+ dest.writeBundle(mExtras);
+ dest.writeString(mLocusId.getId());
+ }
+
+ /**
+ * Builder for construction of DirectAction.
+ */
+ public static final class Builder {
+ private @NonNull String mId;
+ private @Nullable Bundle mExtras;
+ private @Nullable LocusId mLocusId;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param id The mandatory action id.
+ */
+ public Builder(@NonNull String id) {
+ Preconditions.checkNotNull(id);
+ mId = id;
+ }
+
+ /**
+ * Sets the optional action extras.
+ *
+ * @param extras The extras.
+ * @return This builder.
+ */
+ public @NonNull Builder setExtras(@Nullable Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Sets the optional locus id.
+ *
+ * @param locusId The locus id.
+ * @return This builder.
+ */
+ public @NonNull Builder setLocusId(@Nullable LocusId locusId) {
+ mLocusId = locusId;
+ return this;
+ }
+
+ /**
+ * @return A newly constructed instance.
+ */
+ public @NonNull DirectAction build() {
+ return new DirectAction(mId, mExtras, mLocusId);
+ }
+ }
+
+ public static final @NonNull Parcelable.Creator<DirectAction> CREATOR =
+ new Parcelable.Creator<DirectAction>() {
+ public DirectAction createFromParcel(Parcel in) {
+ return new DirectAction(in);
+ }
+ public DirectAction[] newArray(int size) {
+ return new DirectAction[size];
+ }
+ };
+}
diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java
index 7ae88fd..49ba65f 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -23,6 +23,7 @@
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
+import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
@@ -33,9 +34,11 @@
import android.net.NetworkPolicyManager;
import android.net.Uri;
import android.os.Build;
+import android.os.Bundle;
import android.os.Environment;
import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
import android.provider.Downloads;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
@@ -471,6 +474,15 @@
* By default, downloads are saved to a generated filename in the shared download cache and
* may be deleted by the system at any time to reclaim space.
*
+ * <p> For applications targeting {@link android.os.Build.VERSION_CODES#Q} or above,
+ * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE WRITE EXTERNAL_STORAGE}
+ * permission is not needed and the {@code uri} must refer to a path within the
+ * directories owned by the application (e.g. {@link Context#getExternalFilesDir(String)})
+ * or a path within the top-level Downloads directory (as returned by
+ * {@link Environment#getExternalStoragePublicDirectory(String)} with
+ * {@link Environment#DIRECTORY_DOWNLOADS}).
+ *
+ * @param uri a file {@link Uri} indicating the destination for the downloaded file.
* @return this object
*/
public Request setDestinationUri(Uri uri) {
@@ -524,6 +536,11 @@
* The downloaded file is not scanned by MediaScanner. But it can be
* made scannable by calling {@link #allowScanningByMediaScanner()}.
*
+ * <p> For applications targeting {@link android.os.Build.VERSION_CODES#Q} or above,
+ * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE WRITE_EXTERNAL_STORAGE}
+ * permission is not needed and the {@code dirType} must
+ * be {@link Environment#DIRECTORY_DOWNLOADS}.
+ *
* @param dirType the directory type to pass to {@link Environment#getExternalStoragePublicDirectory(String)}
* @param subPath the path within the external directory, including the
* destination filename
@@ -535,15 +552,29 @@
File file = Environment.getExternalStoragePublicDirectory(dirType);
if (file == null) {
throw new IllegalStateException("Failed to get external storage public directory");
- } else if (file.exists()) {
- if (!file.isDirectory()) {
- throw new IllegalStateException(file.getAbsolutePath() +
- " already exists and is not a directory");
+ }
+
+ final Context context = AppGlobals.getInitialApplication();
+ if (context.getApplicationInfo().targetSdkVersion
+ >= Build.VERSION_CODES.Q || !Environment.isExternalStorageLegacy()) {
+ try (ContentProviderClient client = context.getContentResolver()
+ .acquireContentProviderClient(Downloads.Impl.AUTHORITY)) {
+ final Bundle extras = new Bundle();
+ extras.putString(Downloads.DIR_TYPE, dirType);
+ client.call(Downloads.CALL_CREATE_EXTERNAL_PUBLIC_DIR, null, extras);
+ } catch (RemoteException e) {
+ throw new IllegalStateException("Unable to create directory: "
+ + file.getAbsolutePath());
}
} else {
- if (!file.mkdirs()) {
- throw new IllegalStateException("Unable to create directory: "+
- file.getAbsolutePath());
+ if (file.exists()) {
+ if (!file.isDirectory()) {
+ throw new IllegalStateException(file.getAbsolutePath()
+ + " already exists and is not a directory");
+ }
+ } else if (!file.mkdirs()) {
+ throw new IllegalStateException("Unable to create directory: "
+ + file.getAbsolutePath());
}
}
setDestinationFromBase(file, subPath);
@@ -1316,6 +1347,16 @@
* isMediaScannerScannable to true. It makes the file visible in media managing
* applications such as Gallery App, which could be a useful purpose of using this API.
*
+ * <p> For applications targeting {@link android.os.Build.VERSION_CODES#Q} or above,
+ * {@code path} must be within directories owned by the application
+ * {e.g. {@link Context#getExternalFilesDir(String)}} or if the application is running under
+ * the legacy storage model (see
+ * {@link android.R.styleable#AndroidManifestApplication_requestLegacyExternalStorage
+ * android:requestLegacyExternalStorage}), {@code path} can also be within the top-level
+ * Downloads directory (as returned by
+ * {@link Environment#getExternalStoragePublicDirectory(String)} with
+ * {@link Environment#DIRECTORY_DOWNLOADS}).
+ *
* @param title the title that would appear for this file in Downloads App.
* @param description the description that would appear for this file in Downloads App.
* @param isMediaScannerScannable true if the file is to be scanned by MediaScanner. Files
@@ -1345,6 +1386,16 @@
* isMediaScannerScannable to true. It makes the file visible in media managing
* applications such as Gallery App, which could be a useful purpose of using this API.
*
+ * <p> For applications targeting {@link android.os.Build.VERSION_CODES#Q} or above,
+ * {@code path} must be within directories owned by the application
+ * {e.g. {@link Context#getExternalFilesDir(String)}} or if the application is running under
+ * the legacy storage model (see
+ * {@link android.R.styleable#AndroidManifestApplication_requestLegacyExternalStorage
+ * android:requestLegacyExternalStorage}), {@code path} can also be within the top-level
+ * Downloads directory (as returned by
+ * {@link Environment#getExternalStoragePublicDirectory(String)} with
+ * {@link Environment#DIRECTORY_DOWNLOADS}).
+ *
* @param title the title that would appear for this file in Downloads App.
* @param description the description that would appear for this file in Downloads App.
* @param isMediaScannerScannable true if the file is to be scanned by MediaScanner. Files
@@ -1368,7 +1419,19 @@
length, showNotification, false, uri, referer);
}
- /** {@hide} */
+ /**
+ * <p> For applications targeting {@link android.os.Build.VERSION_CODES#Q} or above,
+ * {@code path} must be within directories owned by the application
+ * {e.g. {@link Context#getExternalFilesDir(String)}} or if the application is running under
+ * the legacy storage model (see
+ * {@link android.R.styleable#AndroidManifestApplication_requestLegacyExternalStorage
+ * android:requestLegacyExternalStorage}), {@code path} can also be within the top-level
+ * Downloads directory (as returned by
+ * {@link Environment#getExternalStoragePublicDirectory(String)} with
+ * {@link Environment#DIRECTORY_DOWNLOADS}).
+ *
+ * {@hide}
+ */
public long addCompletedDownload(String title, String description,
boolean isMediaScannerScannable, String mimeType, String path, long length,
boolean showNotification, boolean allowWrite) {
@@ -1376,7 +1439,19 @@
length, showNotification, allowWrite, null, null);
}
- /** {@hide} */
+ /**
+ * <p> For applications targeting {@link android.os.Build.VERSION_CODES#Q} or above,
+ * {@code path} must be within directories owned by the application
+ * {e.g. {@link Context#getExternalFilesDir(String)}} or if the application is running under
+ * the legacy storage model (see
+ * {@link android.R.styleable#AndroidManifestApplication_requestLegacyExternalStorage
+ * android:requestLegacyExternalStorage}), {@code path} can also be within the top-level
+ * Downloads directory (as returned by
+ * {@link Environment#getExternalStoragePublicDirectory(String)} with
+ * {@link Environment#DIRECTORY_DOWNLOADS}).
+ *
+ * {@hide}
+ */
public long addCompletedDownload(String title, String description,
boolean isMediaScannerScannable, String mimeType, String path, long length,
boolean showNotification, boolean allowWrite, Uri uri, Uri referer) {
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 3a09c4c..ac55c53 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -140,4 +140,9 @@
void scheduleApplicationInfoChanged(in ApplicationInfo ai);
void setNetworkBlockSeq(long procStateSeq);
void scheduleTransaction(in ClientTransaction transaction);
+ void requestDirectActions(IBinder activityToken, IVoiceInteractor intractor,
+ in RemoteCallback callback);
+ void performDirectAction(IBinder activityToken, String actionId,
+ in Bundle arguments, in RemoteCallback cancellationCallback,
+ in RemoteCallback resultCallback);
}
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 4f94209..41733b3 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1206,7 +1206,7 @@
public Activity newActivity(Class<?> clazz, Context context,
IBinder token, Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
- Object lastNonConfigurationInstance) throws InstantiationException,
+ Object lastNonConfigurationInstance) throws InstantiationException,
IllegalAccessException {
Activity activity = (Activity)clazz.newInstance();
ActivityThread aThread = null;
@@ -1218,7 +1218,7 @@
info, title, parent, id,
(Activity.NonConfigurationInstances)lastNonConfigurationInstance,
new Configuration(), null /* referrer */, null /* voiceInteractor */,
- null /* window */, null /* activityConfigCallback */);
+ null /* window */, null /* activityConfigCallback */, null /*assistToken*/);
return activity;
}
diff --git a/core/java/android/app/LocalActivityManager.java b/core/java/android/app/LocalActivityManager.java
index 94b1d77..19575b2 100644
--- a/core/java/android/app/LocalActivityManager.java
+++ b/core/java/android/app/LocalActivityManager.java
@@ -144,7 +144,7 @@
r.activityInfo = mActivityThread.resolveActivityInfo(r.intent);
}
r.activity = mActivityThread.startActivityNow(
- mParent, r.id, r.intent, r.activityInfo, r, r.instanceState, instance);
+ mParent, r.id, r.intent, r.activityInfo, r, r.instanceState, instance, r);
if (r.activity == null) {
return;
}
diff --git a/core/java/android/app/VoiceInteractor.java b/core/java/android/app/VoiceInteractor.java
index 36ba78b..7828573 100644
--- a/core/java/android/app/VoiceInteractor.java
+++ b/core/java/android/app/VoiceInteractor.java
@@ -16,11 +16,13 @@
package android.app;
+import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.ICancellationSignal;
import android.os.Looper;
import android.os.Message;
import android.os.Parcel;
@@ -29,15 +31,20 @@
import android.util.ArrayMap;
import android.util.DebugUtils;
import android.util.Log;
+
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.app.IVoiceInteractorCallback;
import com.android.internal.app.IVoiceInteractorRequest;
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.function.pooled.PooledLambda;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.concurrent.Executor;
/**
* Interface for an {@link Activity} to interact with the user through voice. Use
@@ -67,7 +74,12 @@
static final Request[] NO_REQUESTS = new Request[0];
- final IVoiceInteractor mInteractor;
+ /** @hide */
+ public static final String KEY_CANCELLATION_SIGNAL = "key_cancellation_signal";
+ /** @hide */
+ public static final String KEY_KILL_SIGNAL = "key_kill_signal";
+
+ IVoiceInteractor mInteractor;
Context mContext;
Activity mActivity;
@@ -189,13 +201,20 @@
}
@Override
- public void deliverCancel(IVoiceInteractorRequest request) throws RemoteException {
+ public void deliverCancel(IVoiceInteractorRequest request) {
mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOO(
MSG_CANCEL_RESULT, request, null));
}
+
+ @Override
+ public void destroy() {
+ mHandlerCaller.getHandler().sendMessage(PooledLambda.obtainMessage(
+ VoiceInteractor::destroy, VoiceInteractor.this));
+ }
};
final ArrayMap<IBinder, Request> mActiveRequests = new ArrayMap<>();
+ final ArrayMap<Runnable, Executor> mOnDestroyCallbacks = new ArrayMap<>();
static final int MSG_CONFIRMATION_RESULT = 1;
static final int MSG_PICK_OPTION_RESULT = 2;
@@ -887,6 +906,11 @@
mContext = context;
mActivity = activity;
mHandlerCaller = new HandlerCaller(context, looper, mHandlerCallerCallback, true);
+ try {
+ mInteractor.setKillCallback(new KillCallback(this));
+ } catch (RemoteException e) {
+ /* ignore */
+ }
}
Request pullRequest(IVoiceInteractorRequest request, boolean complete) {
@@ -957,6 +981,27 @@
mActivity = null;
}
+ void destroy() {
+ final int requestCount = mActiveRequests.size();
+ for (int i = requestCount - 1; i >= 0; i--) {
+ final Request request = mActiveRequests.valueAt(i);
+ mActiveRequests.removeAt(i);
+ request.cancel();
+ }
+
+ final int callbackCount = mOnDestroyCallbacks.size();
+ for (int i = callbackCount - 1; i >= 0; i--) {
+ final Runnable callback = mOnDestroyCallbacks.keyAt(i);
+ final Executor executor = mOnDestroyCallbacks.valueAt(i);
+ executor.execute(callback);
+ mOnDestroyCallbacks.removeAt(i);
+ }
+
+ // destroyed now
+ mInteractor = null;
+ mActivity.setVoiceInteractor(null);
+ }
+
public boolean submitRequest(Request request) {
return submitRequest(request, null);
}
@@ -973,6 +1018,10 @@
* @return Returns true of the request was successfully submitted, else false.
*/
public boolean submitRequest(Request request, String name) {
+ if (isDestroyed()) {
+ Log.w(TAG, "Cannot interact with a destroyed voice interactor");
+ return false;
+ }
try {
if (request.mRequestInterface != null) {
throw new IllegalStateException("Given " + request + " is already active");
@@ -997,6 +1046,10 @@
* Return all currently active requests.
*/
public Request[] getActiveRequests() {
+ if (isDestroyed()) {
+ Log.w(TAG, "Cannot interact with a destroyed voice interactor");
+ return null;
+ }
synchronized (mActiveRequests) {
final int N = mActiveRequests.size();
if (N <= 0) {
@@ -1018,6 +1071,10 @@
* @return Returns the active request with that name, or null if there was none.
*/
public Request getActiveRequest(String name) {
+ if (isDestroyed()) {
+ Log.w(TAG, "Cannot interact with a destroyed voice interactor");
+ return null;
+ }
synchronized (mActiveRequests) {
final int N = mActiveRequests.size();
for (int i=0; i<N; i++) {
@@ -1040,6 +1097,10 @@
* @return Array of booleans indicating whether each command is supported or not.
*/
public boolean[] supportsCommands(String[] commands) {
+ if (isDestroyed()) {
+ Log.w(TAG, "Cannot interact with a destroyed voice interactor");
+ return new boolean[commands.length];
+ }
try {
boolean[] res = mInteractor.supportsCommands(mContext.getOpPackageName(), commands);
if (DEBUG) Log.d(TAG, "supportsCommands: cmds=" + commands + " res=" + res);
@@ -1049,6 +1110,64 @@
}
}
+ /**
+ * @return whether the voice interactor is destroyed. You should not interact
+ * with a destroyed voice interactor.
+ */
+ public boolean isDestroyed() {
+ return mInteractor == null;
+ }
+
+ /**
+ * Registers a callback to be called when the VoiceInteractor is destroyed.
+ *
+ * @param executor Executor on which to run the callback.
+ * @param callback The callback to run.
+ * @return whether the callback was registered.
+ */
+ public boolean registerOnDestroyedCallback(@NonNull @CallbackExecutor Executor executor,
+ @NonNull Runnable callback) {
+ Preconditions.checkNotNull(executor);
+ Preconditions.checkNotNull(callback);
+ if (isDestroyed()) {
+ Log.w(TAG, "Cannot interact with a destroyed voice interactor");
+ return false;
+ }
+ mOnDestroyCallbacks.put(callback, executor);
+ return true;
+ }
+
+ /**
+ * Unregisters a previously registered onDestroy callback
+ *
+ * @param callback The callback to remove.
+ * @return whether the callback was unregistered.
+ */
+ public boolean unregisterOnDestroyedCallback(@NonNull Runnable callback) {
+ Preconditions.checkNotNull(callback);
+ if (isDestroyed()) {
+ Log.w(TAG, "Cannot interact with a destroyed voice interactor");
+ return false;
+ }
+ return mOnDestroyCallbacks.remove(callback) != null;
+ }
+
+ /**
+ * Notifies the assist framework that the direct actions supported by the app changed.
+ */
+ public void notifyDirectActionsChanged() {
+ if (isDestroyed()) {
+ Log.w(TAG, "Cannot interact with a destroyed voice interactor");
+ return;
+ }
+ try {
+ mInteractor.notifyDirectActionsChanged(mActivity.getTaskId(),
+ mActivity.getAssistToken());
+ } catch (RemoteException e) {
+ Log.w(TAG, "Voice interactor has died", e);
+ }
+ }
+
void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
String innerPrefix = prefix + " ";
if (mActiveRequests.size() > 0) {
@@ -1066,4 +1185,21 @@
writer.println(mInteractor.asBinder());
writer.print(prefix); writer.print(" mActivity="); writer.println(mActivity);
}
+
+ private static final class KillCallback extends ICancellationSignal.Stub {
+ private final WeakReference<VoiceInteractor> mInteractor;
+
+ KillCallback(VoiceInteractor interactor) {
+ mInteractor= new WeakReference<>(interactor);
+ }
+
+ @Override
+ public void cancel() {
+ final VoiceInteractor voiceInteractor = mInteractor.get();
+ if (voiceInteractor != null) {
+ voiceInteractor.mHandlerCaller.getHandler().sendMessage(PooledLambda
+ .obtainMessage(VoiceInteractor::destroy, voiceInteractor));
+ }
+ }
+ }
}
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index ed3a296..0d5a763 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -2300,6 +2300,7 @@
/**
* @return The task id for the associated activity.
+ *
* @hide
*/
public int getTaskId() {
diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java
index cdf5d49..1236e0a 100644
--- a/core/java/android/app/servertransaction/LaunchActivityItem.java
+++ b/core/java/android/app/servertransaction/LaunchActivityItem.java
@@ -63,6 +63,7 @@
private List<ReferrerIntent> mPendingNewIntents;
private boolean mIsForward;
private ProfilerInfo mProfilerInfo;
+ private IBinder mAssistToken;
@Override
public void preExecute(ClientTransactionHandler client, IBinder token) {
@@ -78,7 +79,7 @@
ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo,
mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState,
mPendingResults, mPendingNewIntents, mIsForward,
- mProfilerInfo, client);
+ mProfilerInfo, client, mAssistToken);
client.handleLaunchActivity(r, pendingActions, null /* customIntent */);
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -99,14 +100,15 @@
Configuration curConfig, Configuration overrideConfig, CompatibilityInfo compatInfo,
String referrer, IVoiceInteractor voiceInteractor, int procState, Bundle state,
PersistableBundle persistentState, List<ResultInfo> pendingResults,
- List<ReferrerIntent> pendingNewIntents, boolean isForward, ProfilerInfo profilerInfo) {
+ List<ReferrerIntent> pendingNewIntents, boolean isForward, ProfilerInfo profilerInfo,
+ IBinder assistToken) {
LaunchActivityItem instance = ObjectPool.obtain(LaunchActivityItem.class);
if (instance == null) {
instance = new LaunchActivityItem();
}
setValues(instance, intent, ident, info, curConfig, overrideConfig, compatInfo, referrer,
voiceInteractor, procState, state, persistentState, pendingResults,
- pendingNewIntents, isForward, profilerInfo);
+ pendingNewIntents, isForward, profilerInfo, assistToken);
return instance;
}
@@ -114,7 +116,7 @@
@Override
public void recycle() {
setValues(this, null, 0, null, null, null, null, null, null, 0, null, null, null, null,
- false, null);
+ false, null, null);
ObjectPool.recycle(this);
}
@@ -139,6 +141,7 @@
dest.writeTypedList(mPendingNewIntents, flags);
dest.writeBoolean(mIsForward);
dest.writeTypedObject(mProfilerInfo, flags);
+ dest.writeStrongBinder(mAssistToken);
}
/** Read from Parcel. */
@@ -152,7 +155,8 @@
in.readPersistableBundle(getClass().getClassLoader()),
in.createTypedArrayList(ResultInfo.CREATOR),
in.createTypedArrayList(ReferrerIntent.CREATOR), in.readBoolean(),
- in.readTypedObject(ProfilerInfo.CREATOR));
+ in.readTypedObject(ProfilerInfo.CREATOR),
+ in.readStrongBinder());
}
public static final @android.annotation.NonNull Creator<LaunchActivityItem> CREATOR =
@@ -187,7 +191,8 @@
&& Objects.equals(mPendingResults, other.mPendingResults)
&& Objects.equals(mPendingNewIntents, other.mPendingNewIntents)
&& mIsForward == other.mIsForward
- && Objects.equals(mProfilerInfo, other.mProfilerInfo);
+ && Objects.equals(mProfilerInfo, other.mProfilerInfo)
+ && Objects.equals(mAssistToken, other.mAssistToken);
}
@Override
@@ -206,6 +211,7 @@
result = 31 * result + Objects.hashCode(mPendingNewIntents);
result = 31 * result + (mIsForward ? 1 : 0);
result = 31 * result + Objects.hashCode(mProfilerInfo);
+ result = 31 * result + Objects.hashCode(mAssistToken);
return result;
}
@@ -247,6 +253,7 @@
+ ",referrer=" + mReferrer + ",procState=" + mProcState + ",state=" + mState
+ ",persistentState=" + mPersistentState + ",pendingResults=" + mPendingResults
+ ",pendingNewIntents=" + mPendingNewIntents + ",profilerInfo=" + mProfilerInfo
+ + " assistToken=" + mAssistToken
+ "}";
}
@@ -256,7 +263,7 @@
CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
int procState, Bundle state, PersistableBundle persistentState,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
- boolean isForward, ProfilerInfo profilerInfo) {
+ boolean isForward, ProfilerInfo profilerInfo, IBinder assistToken) {
instance.mIntent = intent;
instance.mIdent = ident;
instance.mInfo = info;
@@ -272,5 +279,6 @@
instance.mPendingNewIntents = pendingNewIntents;
instance.mIsForward = isForward;
instance.mProfilerInfo = profilerInfo;
+ instance.mAssistToken = assistToken;
}
}
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 02145f2..3e325b7 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -822,6 +822,11 @@
mEstablishingVpnAppUid = uid;
}
+ /** @hide */
+ public int getEstablishingVpnAppUid() {
+ return mEstablishingVpnAppUid;
+ }
+
/**
* Value indicating that link bandwidth is unspecified.
* @hide
diff --git a/core/java/android/net/UidRange.java b/core/java/android/net/UidRange.java
index e56f059..d75c43d 100644
--- a/core/java/android/net/UidRange.java
+++ b/core/java/android/net/UidRange.java
@@ -21,6 +21,8 @@
import android.os.Parcel;
import android.os.Parcelable;
+import java.util.Collection;
+
/**
* An inclusive range of UIDs.
*
@@ -42,10 +44,16 @@
return new UidRange(userId * PER_USER_RANGE, (userId + 1) * PER_USER_RANGE - 1);
}
+ /** Returns the smallest user Id which is contained in this UidRange */
public int getStartUser() {
return start / PER_USER_RANGE;
}
+ /** Returns the largest user Id which is contained in this UidRange */
+ public int getEndUser() {
+ return stop / PER_USER_RANGE;
+ }
+
public boolean contains(int uid) {
return start <= uid && uid <= stop;
}
@@ -117,4 +125,23 @@
return new UidRange[size];
}
};
+
+ /**
+ * Returns whether any of the UidRange in the collection contains the specified uid
+ *
+ * @param ranges The collection of UidRange to check
+ * @param uid the uid in question
+ * @return {@code true} if the uid is contained within the ranges, {@code false} otherwise
+ *
+ * @see UidRange#contains(int)
+ */
+ public static boolean containsUid(Collection<UidRange> ranges, int uid) {
+ if (ranges == null) return false;
+ for (UidRange range : ranges) {
+ if (range.contains(uid)) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index e85561c..45946e3 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -54,7 +54,6 @@
/** {@hide} */
public static final String DIR_ANDROID = "Android";
- private static final String DIR_SANDBOX = "sandbox";
private static final String DIR_DATA = "data";
private static final String DIR_MEDIA = "media";
private static final String DIR_OBB = "obb";
@@ -128,10 +127,6 @@
return buildPaths(getExternalDirs(), type);
}
- public File[] buildExternalStorageAndroidSandboxDirs() {
- return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_SANDBOX);
- }
-
public File[] buildExternalStorageAndroidDataDirs() {
return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_DATA);
}
@@ -842,15 +837,6 @@
* Returns the path for android-specific data on the SD card.
* @hide
*/
- public static File[] buildExternalStorageAndroidSandboxDirs() {
- throwIfUserRequired();
- return sCurrentUser.buildExternalStorageAndroidSandboxDirs();
- }
-
- /**
- * Returns the path for android-specific data on the SD card.
- * @hide
- */
@UnsupportedAppUsage
public static File[] buildExternalStorageAndroidDataDirs() {
throwIfUserRequired();
@@ -907,6 +893,12 @@
return sCurrentUser.buildExternalStorageAppCacheDirs(packageName);
}
+ /** @hide */
+ public static File[] buildExternalStoragePublicDirs(@NonNull String dirType) {
+ throwIfUserRequired();
+ return sCurrentUser.buildExternalStoragePublicDirs(dirType);
+ }
+
/**
* Return the download/cache content directory.
*/
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index 03e8c15..db23cfa 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -396,9 +396,6 @@
*/
void setNetworkPermission(int netId, int permission);
- void setPermission(String permission, in int[] uids);
- void clearPermission(in int[] uids);
-
/**
* Allow UID to call protect().
*/
diff --git a/core/java/android/provider/Downloads.java b/core/java/android/provider/Downloads.java
index 89d1c44..d507447 100644
--- a/core/java/android/provider/Downloads.java
+++ b/core/java/android/provider/Downloads.java
@@ -846,12 +846,16 @@
}
/** @hide */
- public static final String MEDIASTORE_DOWNLOADS_DELETED_CALL = "mediastore_downloads_deleted";
+ public static final String CALL_MEDIASTORE_DOWNLOADS_DELETED = "mediastore_downloads_deleted";
+ /** @hide */
+ public static final String CALL_CREATE_EXTERNAL_PUBLIC_DIR = "create_external_public_dir";
/** @hide */
public static final String EXTRA_IDS = "ids";
/** @hide */
public static final String EXTRA_MIME_TYPES = "mime_types";
+ /** @hide */
+ public static final String DIR_TYPE = "dir_type";
/**
* Query where clause for general querying.
diff --git a/core/java/android/service/voice/IVoiceInteractionSession.aidl b/core/java/android/service/voice/IVoiceInteractionSession.aidl
index 78e6bc3..c142a53 100644
--- a/core/java/android/service/voice/IVoiceInteractionSession.aidl
+++ b/core/java/android/service/voice/IVoiceInteractionSession.aidl
@@ -21,6 +21,7 @@
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Bundle;
+import android.os.IBinder;
import com.android.internal.app.IVoiceInteractionSessionShowCallback;
@@ -30,8 +31,8 @@
oneway interface IVoiceInteractionSession {
void show(in Bundle sessionArgs, int flags, IVoiceInteractionSessionShowCallback showCallback);
void hide();
- void handleAssist(in Bundle assistData, in AssistStructure structure, in AssistContent content,
- int index, int count);
+ void handleAssist(int taskId, in IBinder activityId, in Bundle assistData,
+ in AssistStructure structure, in AssistContent content, int index, int count);
void handleScreenshot(in Bitmap screenshot);
void taskStarted(in Intent intent, int taskId);
void taskFinished(in Intent intent, int taskId);
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index 5b5f3b8..81b84e1 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -18,9 +18,13 @@
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import android.annotation.CallbackExecutor;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Activity;
import android.app.Dialog;
+import android.app.DirectAction;
import android.app.Instrumentation;
import android.app.VoiceInteractor;
import android.app.assist.AssistContent;
@@ -28,6 +32,7 @@
import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ParceledListSlice;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
@@ -36,9 +41,12 @@
import android.inputmethodservice.SoftInputWindow;
import android.os.Binder;
import android.os.Bundle;
+import android.os.CancellationSignal;
import android.os.Handler;
import android.os.IBinder;
+import android.os.ICancellationSignal;
import android.os.Message;
+import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.ArrayMap;
@@ -52,6 +60,7 @@
import android.view.WindowManager;
import android.widget.FrameLayout;
+import com.android.internal.annotations.Immutable;
import com.android.internal.app.IVoiceInteractionManagerService;
import com.android.internal.app.IVoiceInteractionSessionShowCallback;
import com.android.internal.app.IVoiceInteractor;
@@ -59,10 +68,16 @@
import com.android.internal.app.IVoiceInteractorRequest;
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.function.pooled.PooledLambda;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
* An active voice interaction session, providing a facility for the implementation
@@ -156,6 +171,8 @@
final WeakReference<VoiceInteractionSession> mWeakRef
= new WeakReference<VoiceInteractionSession>(this);
+ ICancellationSignal mKillCallback;
+
final IVoiceInteractor mInteractor = new IVoiceInteractor.Stub() {
@Override
public IVoiceInteractorRequest startConfirmation(String callingPackage,
@@ -230,6 +247,19 @@
}
return new boolean[commands.length];
}
+
+ @Override
+ public void notifyDirectActionsChanged(int taskId, IBinder assistToken) {
+ mHandlerCaller.getHandler().sendMessage(PooledLambda.obtainMessage(
+ VoiceInteractionSession::onDirectActionsInvalidated,
+ VoiceInteractionSession.this, new ActivityId(taskId, assistToken))
+ );
+ }
+
+ @Override
+ public void setKillCallback(ICancellationSignal callback) {
+ mKillCallback = callback;
+ }
};
final IVoiceInteractionSession mSession = new IVoiceInteractionSession.Stub() {
@@ -248,8 +278,9 @@
}
@Override
- public void handleAssist(final Bundle data, final AssistStructure structure,
- final AssistContent content, final int index, final int count) {
+ public void handleAssist(final int taskId, final IBinder assistToken, final Bundle data,
+ final AssistStructure structure, final AssistContent content, final int index,
+ final int count) {
// We want to pre-warm the AssistStructure before handing it off to the main
// thread. We also want to do this on a separate thread, so that if the app
// is for some reason slow (due to slow filling in of async children in the
@@ -267,9 +298,19 @@
failure = e;
}
}
- mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOOII(MSG_HANDLE_ASSIST,
- data, failure == null ? structure : null, failure, content,
- index, count));
+
+ SomeArgs args = SomeArgs.obtain();
+ args.argi1 = taskId;
+ args.arg1 = data;
+ args.arg2 = (failure == null) ? structure : null;
+ args.arg3 = failure;
+ args.arg4 = content;
+ args.arg5 = assistToken;
+ args.argi5 = index;
+ args.argi6 = count;
+
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(
+ MSG_HANDLE_ASSIST, args));
}
};
retriever.start();
@@ -855,17 +896,13 @@
break;
case MSG_HANDLE_ASSIST:
args = (SomeArgs)msg.obj;
- if (DEBUG) Log.d(TAG, "onHandleAssist: data=" + args.arg1
+ if (DEBUG) Log.d(TAG, "onHandleAssist: taskId=" + args.argi1
+ + "assistToken=" + args.arg5 + " data=" + args.arg1
+ " structure=" + args.arg2 + " content=" + args.arg3
+ " activityIndex=" + args.argi5 + " activityCount=" + args.argi6);
- if (args.argi5 == 0) {
- doOnHandleAssist((Bundle) args.arg1, (AssistStructure) args.arg2,
- (Throwable) args.arg3, (AssistContent) args.arg4);
- } else {
- doOnHandleAssistSecondary((Bundle) args.arg1, (AssistStructure) args.arg2,
- (Throwable) args.arg3, (AssistContent) args.arg4,
- args.argi5, args.argi6);
- }
+ doOnHandleAssist(args.argi1, (IBinder) args.arg5, (Bundle) args.arg1,
+ (AssistStructure) args.arg2, (Throwable) args.arg3,
+ (AssistContent) args.arg4, args.argi5, args.argi6);
break;
case MSG_HANDLE_SCREENSHOT:
if (DEBUG) Log.d(TAG, "onHandleScreenshot: " + msg.obj);
@@ -1062,6 +1099,13 @@
void doDestroy() {
onDestroy();
+ if (mKillCallback != null) {
+ try {
+ mKillCallback.cancel();
+ } catch (RemoteException e) {
+ /* ignore */
+ }
+ }
if (mInitialized) {
mRootView.getViewTreeObserver().removeOnComputeInternalInsetsListener(
mInsetsComputer);
@@ -1301,6 +1345,94 @@
}
/**
+ * Requests a list of supported actions from an app.
+ *
+ * @param activityId Ths activity id of the app to get the actions from.
+ * @param resultExecutor The handler to receive the callback
+ * @param callback The callback to receive the response
+ */
+ public final void requestDirectActions(@NonNull ActivityId activityId,
+ @NonNull @CallbackExecutor Executor resultExecutor,
+ @NonNull Consumer<List<DirectAction>> callback) {
+ if (mToken == null) {
+ throw new IllegalStateException("Can't call before onCreate()");
+ }
+ try {
+ mSystemService.requestDirectActions(mToken, activityId.getTaskId(),
+ activityId.getAssistToken(), new RemoteCallback(new DirectActionsReceiver(
+ Preconditions.checkNotNull(resultExecutor),
+ Preconditions.checkNotNull(callback))));
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Called when the direct actions are invalidated.
+ */
+ public void onDirectActionsInvalidated(@NonNull ActivityId activityId) {
+
+ }
+
+ /**
+ * Asks that an action be performed by the app. This will send a request to the app which
+ * provided this action.
+ *
+ * <p> An action could take time to execute and the result is provided asynchronously
+ * via a callback. If the action is taking longer and you want to cancel its execution
+ * you can pass in a cancellation signal through which to notify the app to abort the
+ * action.
+ *
+ * @param action The action to be performed.
+ * @param extras Any optional extras sent to the app as part of the request
+ * @param cancellationSignal A signal to cancel the operation in progress,
+ * or {@code null} if none.
+ * @param resultExecutor The handler to receive the callback.
+ * @param resultListener The callback to receive the response.
+ *
+ * @see #requestDirectActions(ActivityId, Executor, Consumer)
+ * @see Activity#onGetDirectActions()
+ */
+ public final void performDirectAction(@NonNull DirectAction action, @Nullable Bundle extras,
+ @Nullable CancellationSignal cancellationSignal,
+ @NonNull @CallbackExecutor Executor resultExecutor,
+ @NonNull Consumer<Bundle> resultListener) {
+ if (mToken == null) {
+ throw new IllegalStateException("Can't call before onCreate()");
+ }
+ Preconditions.checkNotNull(resultExecutor);
+ Preconditions.checkNotNull(resultListener);
+
+ if (cancellationSignal != null) {
+ cancellationSignal.throwIfCanceled();
+ }
+
+ final RemoteCallback remoteCallback = new RemoteCallback(b -> {
+ if (b != null) {
+ final IBinder cancellation = b.getBinder(VoiceInteractor.KEY_CANCELLATION_SIGNAL);
+ if (cancellation != null) {
+ if (cancellationSignal != null) {
+ cancellationSignal.setRemote(ICancellationSignal.Stub.asInterface(
+ cancellation));
+ }
+ } else {
+ resultExecutor.execute(() -> resultListener.accept(b));
+ }
+ } else {
+ resultExecutor.execute(() -> resultListener.accept(Bundle.EMPTY));
+ }
+ });
+
+ try {
+ mSystemService.performDirectAction(mToken, action.getId(), extras,
+ action.getTaskId(), action.getActivityId(), remoteCallback,
+ remoteCallback);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Set whether this session will keep the device awake while it is running a voice
* activity. By default, the system holds a wake lock for it while in this state,
* so that it can work even if the screen is off. Setting this to false removes that
@@ -1434,20 +1566,14 @@
mContentFrame.requestApplyInsets();
}
- void doOnHandleAssist(Bundle data, AssistStructure structure, Throwable failure,
- AssistContent content) {
+ void doOnHandleAssist(int taskId, IBinder assistToken, Bundle data, AssistStructure structure,
+ Throwable failure, AssistContent content, int index, int count) {
if (failure != null) {
onAssistStructureFailure(failure);
}
- onHandleAssist(data, structure, content);
- }
-
- void doOnHandleAssistSecondary(Bundle data, AssistStructure structure, Throwable failure,
- AssistContent content, int index, int count) {
- if (failure != null) {
- onAssistStructureFailure(failure);
- }
- onHandleAssistSecondary(data, structure, content, index, count);
+ AssistState assistState = new AssistState(new ActivityId(taskId, assistToken),
+ data, structure, content, index, count);
+ onHandleAssist(assistState);
}
/**
@@ -1480,12 +1606,41 @@
* May be null if assist data has been disabled by the user or device policy; will
* not be automatically filled in with data from the app if the app has marked its
* window as secure.
+ *
+ * @deprecated use {@link #onHandleAssist(AssistState)}
*/
+ @Deprecated
public void onHandleAssist(@Nullable Bundle data, @Nullable AssistStructure structure,
@Nullable AssistContent content) {
}
/**
+ * Called to receive data from the application that the user was currently viewing when
+ * an assist session is started. If the original show request did not specify
+ * {@link #SHOW_WITH_ASSIST}, this method will not be called.
+ *
+ * <p>This method is called for all activities along with an index and count that indicates
+ * which activity the data is for. {@code index} will be between 0 and {@code count}-1 and
+ * this method is called once for each activity in no particular order. The {@code count}
+ * indicates how many activities to expect assist data for, including the top focused one.
+ * The focused activity can be determined by calling {@link AssistState#isFocused()}.
+ *
+ * <p>To be responsive to assist requests, process assist data as soon as it is received,
+ * without waiting for all queued activities to return assist data.
+ *
+ * @param state The state object capturing the state of an activity.
+ */
+ public void onHandleAssist(@NonNull AssistState state) {
+ if (state.getIndex() == 0) {
+ onHandleAssist(state.getAssistData(), state.getAssistStructure(),
+ state.getAssistContent());
+ } else {
+ onHandleAssistSecondary(state.getAssistData(), state.getAssistStructure(),
+ state.getAssistContent(), state.getIndex(), state.getCount());
+ }
+ }
+
+ /**
* Called to receive data from other applications that the user was or is interacting with,
* that are currently on the screen in a multi-window display environment, not including the
* currently focused activity. This could be
@@ -1519,7 +1674,10 @@
* @param count the total number of additional activities for which the assist data is being
* returned, including the focused activity that is returned via
* {@link #onHandleAssist}.
+ *
+ * @deprecated use {@link #onHandleAssist(AssistState)}
*/
+ @Deprecated
public void onHandleAssistSecondary(@Nullable Bundle data, @Nullable AssistStructure structure,
@Nullable AssistContent content, int index, int count) {
}
@@ -1742,4 +1900,166 @@
}
}
}
+
+ private static class DirectActionsReceiver implements RemoteCallback.OnResultListener {
+
+ @NonNull
+ private final Executor mResultExecutor;
+ private final Consumer<List<DirectAction>> mCallback;
+
+ DirectActionsReceiver(Executor resultExecutor, Consumer<List<DirectAction>> callback) {
+ mResultExecutor = resultExecutor;
+ mCallback = callback;
+ }
+
+ @Override
+ public void onResult(Bundle result) {
+ final List<DirectAction> list;
+ if (result == null) {
+ list = Collections.emptyList();
+ } else {
+ final ParceledListSlice<DirectAction> pls = result.getParcelable(
+ DirectAction.KEY_ACTIONS_LIST);
+ if (pls != null) {
+ final List<DirectAction> receivedList = pls.getList();
+ list = (receivedList != null) ? receivedList : Collections.emptyList();
+ } else {
+ list = Collections.emptyList();
+ }
+ }
+ mResultExecutor.execute(() -> mCallback.accept(list));
+ }
+ }
+
+ /**
+ * Represents assist state captured when this session was started.
+ * It contains the various assist data objects and a reference to
+ * the source activity.
+ */
+ @Immutable
+ public static final class AssistState {
+ private final @NonNull ActivityId mActivityId;
+ private final int mIndex;
+ private final int mCount;
+ private final @Nullable Bundle mData;
+ private final @Nullable AssistStructure mStructure;
+ private final @Nullable AssistContent mContent;
+
+ AssistState(@NonNull ActivityId activityId, @Nullable Bundle data,
+ @Nullable AssistStructure structure, @Nullable AssistContent content,
+ int index, int count) {
+ mActivityId = activityId;
+ mIndex = index;
+ mCount = count;
+ mData = data;
+ mStructure = structure;
+ mContent = content;
+ }
+
+ /**
+ * @return whether the source activity is focused.
+ */
+ public boolean isFocused() {
+ return mIndex == 0;
+ }
+
+ /**
+ * @return the index of the activity that this state is for.
+ */
+ public @IntRange(from = -1) int getIndex() {
+ return mIndex;
+ }
+
+ /**s
+ * @return the total number of activities for which the assist data is
+ * being returned.
+ */
+ public @IntRange(from = 0) int getCount() {
+ return mCount;
+ }
+
+ /**
+ * @return the id of the source activity
+ */
+ public @NonNull ActivityId getActivityId() {
+ return mActivityId;
+ }
+
+ /**
+ * @return Arbitrary data supplied by the app through
+ * {@link android.app.Activity#onProvideAssistData Activity.onProvideAssistData}.
+ * May be null if assist data has been disabled by the user or device policy.
+ */
+ public @Nullable Bundle getAssistData() {
+ return mData;
+ }
+
+ /**
+ * @return If available, the structure definition of all windows currently
+ * displayed by the app. May be null if assist data has been disabled by the user
+ * or device policy; will be an empty stub if the application has disabled assist
+ * by marking its window as secure.
+ */
+ public @Nullable AssistStructure getAssistStructure() {
+ return mStructure;
+ }
+
+ /**
+ * @return Additional content data supplied by the app through
+ * {@link android.app.Activity#onProvideAssistContent Activity.onProvideAssistContent}.
+ * May be null if assist data has been disabled by the user or device policy; will
+ * not be automatically filled in with data from the app if the app has marked its
+ * window as secure.
+ */
+ public @Nullable AssistContent getAssistContent() {
+ return mContent;
+ }
+ }
+
+ /**
+ * Represents the id of an assist source activity.
+ */
+ public static class ActivityId {
+ private final int mTaskId;
+ private final IBinder mAssistToken;
+
+ ActivityId(int taskId, IBinder assistToken) {
+ mTaskId = taskId;
+ mAssistToken = assistToken;
+ }
+
+ int getTaskId() {
+ return mTaskId;
+ }
+
+ IBinder getAssistToken() {
+ return mAssistToken;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ ActivityId that = (ActivityId) o;
+
+ if (mTaskId != that.mTaskId) {
+ return false;
+ }
+ return mAssistToken != null
+ ? mAssistToken.equals(that.mAssistToken)
+ : that.mAssistToken == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mTaskId;
+ result = 31 * result + (mAssistToken != null ? mAssistToken.hashCode() : 0);
+ return result;
+ }
+ }
}
diff --git a/core/java/android/service/watchdog/ExplicitHealthCheckService.java b/core/java/android/service/watchdog/ExplicitHealthCheckService.java
index 682b872..eeefb4a 100644
--- a/core/java/android/service/watchdog/ExplicitHealthCheckService.java
+++ b/core/java/android/service/watchdog/ExplicitHealthCheckService.java
@@ -16,6 +16,8 @@
package android.service.watchdog;
+import static android.os.Parcelable.Creator;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
@@ -26,13 +28,18 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.util.Log;
+import com.android.internal.util.Preconditions;
+
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.TimeUnit;
/**
* A service to provide packages supporting explicit health checks and route checks to these
@@ -61,7 +68,7 @@
private static final String TAG = "ExplicitHealthCheckService";
/**
- * {@link Bundle} key for a {@link List} of {@link PackageInfo} value.
+ * {@link Bundle} key for a {@link List} of {@link PackageConfig} value.
*
* {@hide}
*/
@@ -130,7 +137,7 @@
*
* @return all packages supporting explicit health checks
*/
- @NonNull public abstract List<PackageInfo> onGetSupportedPackages();
+ @NonNull public abstract List<PackageConfig> onGetSupportedPackages();
/**
* Called when the system requests for all the packages that it has currently requested
@@ -167,6 +174,112 @@
});
}
+ /**
+ * A PackageConfig contains a package supporting explicit health checks and the
+ * timeout in {@link System#uptimeMillis} across reboots after which health
+ * check requests from clients are failed.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class PackageConfig implements Parcelable {
+ // TODO: Receive from DeviceConfig flag
+ private static final long DEFAULT_HEALTH_CHECK_TIMEOUT_MILLIS = TimeUnit.HOURS.toMillis(1);
+
+ private final String mPackageName;
+ private final long mHealthCheckTimeoutMillis;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param packageName the package name
+ * @param durationMillis the duration in milliseconds, must be greater than or
+ * equal to 0. If it is 0, it will use a system default value.
+ */
+ public PackageConfig(@NonNull String packageName, long healthCheckTimeoutMillis) {
+ mPackageName = Preconditions.checkNotNull(packageName);
+ if (healthCheckTimeoutMillis == 0) {
+ mHealthCheckTimeoutMillis = DEFAULT_HEALTH_CHECK_TIMEOUT_MILLIS;
+ } else {
+ mHealthCheckTimeoutMillis = Preconditions.checkArgumentNonnegative(
+ healthCheckTimeoutMillis);
+ }
+ }
+
+ private PackageConfig(Parcel parcel) {
+ mPackageName = parcel.readString();
+ mHealthCheckTimeoutMillis = parcel.readLong();
+ }
+
+ /**
+ * Gets the package name.
+ *
+ * @return the package name
+ */
+ public @NonNull String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * Gets the timeout in milliseconds to evaluate an explicit health check result after a
+ * request.
+ *
+ * @return the duration in {@link System#uptimeMillis} across reboots
+ */
+ public long getHealthCheckTimeoutMillis() {
+ return mHealthCheckTimeoutMillis;
+ }
+
+ @Override
+ public String toString() {
+ return "PackageConfig{" + mPackageName + ", " + mHealthCheckTimeoutMillis + "}";
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ if (!(other instanceof PackageConfig)) {
+ return false;
+ }
+
+ PackageConfig otherInfo = (PackageConfig) other;
+ return Objects.equals(otherInfo.getHealthCheckTimeoutMillis(),
+ mHealthCheckTimeoutMillis)
+ && Objects.equals(otherInfo.getPackageName(), mPackageName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPackageName, mHealthCheckTimeoutMillis);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeString(mPackageName);
+ parcel.writeLong(mHealthCheckTimeoutMillis);
+ }
+
+ public static final @NonNull Creator<PackageConfig> CREATOR = new Creator<PackageConfig>() {
+ @Override
+ public PackageConfig createFromParcel(Parcel source) {
+ return new PackageConfig(source);
+ }
+
+ @Override
+ public PackageConfig[] newArray(int size) {
+ return new PackageConfig[size];
+ }
+ };
+ }
+
+
private class ExplicitHealthCheckServiceWrapper extends IExplicitHealthCheckService.Stub {
@Override
public void setCallback(RemoteCallback callback) throws RemoteException {
@@ -188,7 +301,7 @@
@Override
public void getSupportedPackages(RemoteCallback callback) throws RemoteException {
mHandler.post(() -> {
- List<PackageInfo> packages =
+ List<PackageConfig> packages =
ExplicitHealthCheckService.this.onGetSupportedPackages();
Objects.requireNonNull(packages, "Supported package list must be non-null");
Bundle bundle = new Bundle();
diff --git a/core/java/android/service/watchdog/PackageInfo.aidl b/core/java/android/service/watchdog/PackageConfig.aidl
similarity index 95%
rename from core/java/android/service/watchdog/PackageInfo.aidl
rename to core/java/android/service/watchdog/PackageConfig.aidl
index 5605aec..0131586 100644
--- a/core/java/android/service/watchdog/PackageInfo.aidl
+++ b/core/java/android/service/watchdog/PackageConfig.aidl
@@ -19,4 +19,4 @@
/**
* @hide
*/
-parcelable PackageInfo;
+parcelable PackageConfig;
diff --git a/core/java/android/service/watchdog/PackageInfo.java b/core/java/android/service/watchdog/PackageInfo.java
deleted file mode 100644
index cee9b6d..0000000
--- a/core/java/android/service/watchdog/PackageInfo.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * 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 android.service.watchdog;
-
-import android.annotation.NonNull;
-import android.annotation.SystemApi;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.internal.util.Preconditions;
-
-import java.util.Objects;
-import java.util.concurrent.TimeUnit;
-
-/**
- * A PackageInfo contains a package supporting explicit health checks and the
- * timeout in {@link System#uptimeMillis} across reboots after which health
- * check requests from clients are failed.
- *
- * @hide
- */
-@SystemApi
-public final class PackageInfo implements Parcelable {
- // TODO: Receive from DeviceConfig flag
- private static final long DEFAULT_HEALTH_CHECK_TIMEOUT_MILLIS = TimeUnit.HOURS.toMillis(1);
-
- private final String mPackageName;
- private final long mHealthCheckTimeoutMillis;
-
- /**
- * Creates a new instance.
- *
- * @param packageName the package name
- * @param durationMillis the duration in milliseconds, must be greater than or
- * equal to 0. If it is 0, it will use a system default value.
- */
- public PackageInfo(@NonNull String packageName, long healthCheckTimeoutMillis) {
- mPackageName = Preconditions.checkNotNull(packageName);
- if (healthCheckTimeoutMillis == 0) {
- mHealthCheckTimeoutMillis = DEFAULT_HEALTH_CHECK_TIMEOUT_MILLIS;
- } else {
- mHealthCheckTimeoutMillis = Preconditions.checkArgumentNonnegative(
- healthCheckTimeoutMillis);
- }
- }
-
- private PackageInfo(Parcel parcel) {
- mPackageName = parcel.readString();
- mHealthCheckTimeoutMillis = parcel.readLong();
- }
-
- /**
- * Gets the package name.
- *
- * @return the package name
- */
- public @NonNull String getPackageName() {
- return mPackageName;
- }
-
- /**
- * Gets the timeout in milliseconds to evaluate an explicit health check result after a request.
- *
- * @return the duration in {@link System#uptimeMillis} across reboots
- */
- public long getHealthCheckTimeoutMillis() {
- return mHealthCheckTimeoutMillis;
- }
-
- @Override
- public String toString() {
- return "PackageInfo{" + mPackageName + ", " + mHealthCheckTimeoutMillis + "}";
- }
-
- @Override
- public boolean equals(Object other) {
- if (other == this) {
- return true;
- }
- if (!(other instanceof PackageInfo)) {
- return false;
- }
-
- PackageInfo otherInfo = (PackageInfo) other;
- return Objects.equals(otherInfo.getHealthCheckTimeoutMillis(), mHealthCheckTimeoutMillis)
- && Objects.equals(otherInfo.getPackageName(), mPackageName);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mPackageName, mHealthCheckTimeoutMillis);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel parcel, int flags) {
- parcel.writeString(mPackageName);
- parcel.writeLong(mHealthCheckTimeoutMillis);
- }
-
- public static final @NonNull Creator<PackageInfo> CREATOR = new Creator<PackageInfo>() {
- @Override
- public PackageInfo createFromParcel(Parcel source) {
- return new PackageInfo(source);
- }
-
- @Override
- public PackageInfo[] newArray(int size) {
- return new PackageInfo[size];
- }
- };
-}
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 420749e..fb5e006 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -19,6 +19,7 @@
import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
+import android.os.RemoteCallback;
import com.android.internal.app.IVoiceActionCheckCallback;
import com.android.internal.app.IVoiceInteractionSessionShowCallback;
@@ -157,4 +158,17 @@
* Provide hints for showing UI.
*/
void setUiHints(in IVoiceInteractionService service, in Bundle hints);
+
+ /**
+ * Requests a list of supported actions from a specific activity.
+ */
+ void requestDirectActions(in IBinder token, int taskId, IBinder assistToken,
+ in RemoteCallback callback);
+
+ /**
+ * Requests performing an action from a specific activity.
+ */
+ void performDirectAction(in IBinder token, String actionId, in Bundle arguments, int taskId,
+ IBinder assistToken, in RemoteCallback cancellationCallback,
+ in RemoteCallback resultCallback);
}
diff --git a/core/java/com/android/internal/app/IVoiceInteractor.aidl b/core/java/com/android/internal/app/IVoiceInteractor.aidl
index 44feafb..d50dc0b 100644
--- a/core/java/com/android/internal/app/IVoiceInteractor.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractor.aidl
@@ -18,6 +18,7 @@
import android.app.VoiceInteractor;
import android.os.Bundle;
+import android.os.ICancellationSignal;
import com.android.internal.app.IVoiceInteractorCallback;
import com.android.internal.app.IVoiceInteractorRequest;
@@ -38,4 +39,6 @@
IVoiceInteractorRequest startCommand(String callingPackage,
IVoiceInteractorCallback callback, String command, in Bundle extras);
boolean[] supportsCommands(String callingPackage, in String[] commands);
+ void notifyDirectActionsChanged(int taskId, IBinder assistToken);
+ void setKillCallback(in ICancellationSignal callback);
}
diff --git a/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl b/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl
index 1331e74..2d13b03 100644
--- a/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl
@@ -33,4 +33,5 @@
void deliverAbortVoiceResult(IVoiceInteractorRequest request, in Bundle result);
void deliverCommandResult(IVoiceInteractorRequest request, boolean finished, in Bundle result);
void deliverCancel(IVoiceInteractorRequest request);
+ void destroy();
}
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 943c726..cfc32cf 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -153,7 +153,7 @@
void showBiometricDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver, int type,
boolean requireConfirmation, int userId);
// Used to hide the dialog when a biometric is authenticated
- void onBiometricAuthenticated(boolean authenticated);
+ void onBiometricAuthenticated(boolean authenticated, String failureReason);
// Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc
void onBiometricHelp(String message);
// Used to set a message - the dialog will dismiss after a certain amount of time
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index f22b6cd..598c391 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -103,7 +103,7 @@
void showBiometricDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver, int type,
boolean requireConfirmation, int userId);
// Used to hide the dialog when a biometric is authenticated
- void onBiometricAuthenticated(boolean authenticated);
+ void onBiometricAuthenticated(boolean authenticated, String failureReason);
// Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc
void onBiometricHelp(String message);
// Used to set a message - the dialog will dismiss after a certain amount of time
diff --git a/core/res/res/values-night/themes_device_defaults.xml b/core/res/res/values-night/themes_device_defaults.xml
index 98f209d..cc6a311 100644
--- a/core/res/res/values-night/themes_device_defaults.xml
+++ b/core/res/res/values-night/themes_device_defaults.xml
@@ -62,6 +62,19 @@
<item name="listDivider">@color/list_divider_color_dark</item>
</style>
+ <style name="Theme.DeviceDefault.Settings.DialogBase" parent="Theme.Material.BaseDialog">
+ <!-- Color palette -->
+ <item name="colorPrimary">@color/primary_dark_device_default_settings</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_settings</item>
+ <item name="colorSecondary">@color/secondary_device_default_settings</item>
+ <item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorError">@color/error_color_device_default_dark</item>
+ <item name="colorBackground">@color/primary_dark_device_default_settings</item>
+
+ <!-- Dialog attributes -->
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
+ </style>
+
<!-- Theme for the dialog shown when an app crashes or ANRs. -->
<style name="Theme.DeviceDefault.Dialog.AppError" parent="Theme.DeviceDefault.Dialog.Alert" />
<style name="Theme.DeviceDefault.Dialog.Alert.DayNight" parent="Theme.DeviceDefault.Dialog.Alert" />
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index 0ed9860..2e98460 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -1564,10 +1564,7 @@
<item name="toolbarStyle">@style/Widget.DeviceDefault.Toolbar</item>
</style>
- <style name="Theme.DeviceDefault.Settings.Dialog" parent="Theme.Material.Settings.Dialog">
- <item name="windowTitleStyle">@style/DialogWindowTitle.DeviceDefault</item>
- <item name="windowAnimationStyle">@style/Animation.DeviceDefault.Dialog</item>
-
+ <style name="Theme.DeviceDefault.Settings.DialogBase" parent="Theme.Material.Light.BaseDialog">
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_settings</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_settings</item>
@@ -1576,8 +1573,15 @@
<item name="colorError">@color/error_color_device_default_light</item>
<!-- Dialog attributes -->
- <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+ </style>
+
+ <style name="Theme.DeviceDefault.Settings.Dialog" parent="Theme.DeviceDefault.Settings.DialogBase">
+ <item name="windowTitleStyle">@style/DialogWindowTitle.DeviceDefault</item>
+ <item name="windowAnimationStyle">@style/Animation.DeviceDefault.Dialog</item>
+
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<!-- Text styles -->
<item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
index 5c8bced..1e49c0a 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
@@ -33,6 +33,7 @@
import android.content.res.Configuration;
import android.os.Binder;
import android.os.Bundle;
+import android.os.IBinder;
import android.os.PersistableBundle;
import android.platform.test.annotations.Presubmit;
@@ -140,13 +141,14 @@
bundle.putString("key", "value");
PersistableBundle persistableBundle = new PersistableBundle();
persistableBundle.putInt("k", 4);
+ IBinder assistToken = new Binder();
LaunchActivityItem emptyItem = LaunchActivityItem.obtain(null, 0, null, null, null, null,
- null, null, 0, null, null, null, null, false, null);
+ null, null, 0, null, null, null, null, false, null, null);
LaunchActivityItem item = LaunchActivityItem.obtain(intent, ident, activityInfo,
config(), overrideConfig, compat, referrer, null /* voiceInteractor */,
procState, bundle, persistableBundle, resultInfoList(), referrerIntentList(),
- true /* isForward */, null /* profilerInfo */);
+ true /* isForward */, null /* profilerInfo */, assistToken);
assertNotSame(item, emptyItem);
assertFalse(item.equals(emptyItem));
@@ -156,7 +158,7 @@
LaunchActivityItem item2 = LaunchActivityItem.obtain(intent, ident, activityInfo,
config(), overrideConfig, compat, referrer, null /* voiceInteractor */,
procState, bundle, persistableBundle, resultInfoList(), referrerIntentList(),
- true /* isForward */, null /* profilerInfo */);
+ true /* isForward */, null /* profilerInfo */, assistToken);
assertSame(item, item2);
assertFalse(item2.equals(emptyItem));
}
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
index 1cca799..1410f4f 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
@@ -266,7 +266,8 @@
null, /* overrideConfig */ null /* compatInfo */, null /* referrer */ ,
null /* voiceInteractor */, 0 /* procState */, null /* state */,
null /* persistentState */, null /* pendingResults */,
- null /* pendingNewIntents */, false /* isForward */, null /* profilerInfo */));
+ null /* pendingNewIntents */, false /* isForward */, null /* profilerInfo */,
+ null /* assistToken*/));
launchTransaction.addCallback(launchItem);
mExecutor.execute(launchTransaction);
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index bffeb2a..1b63b7e 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -242,7 +242,7 @@
LaunchActivityItem item = LaunchActivityItem.obtain(intent, ident, activityInfo,
config(), overrideConfig, compat, referrer, null /* voiceInteractor */,
procState, bundle, persistableBundle, resultInfoList(), referrerIntentList(),
- true /* isForward */, null /* profilerInfo */);
+ true /* isForward */, null /* profilerInfo */, new Binder());
writeAndPrepareForReading(item);
// Read from parcel and assert
@@ -626,5 +626,15 @@
@Override
public final void runIsolatedEntryPoint(String entryPoint, String[] entryPointArgs) {
}
+
+ @Override
+ public void requestDirectActions(IBinder activityToken, IVoiceInteractor intractor,
+ RemoteCallback callback) {
+ }
+
+ @Override
+ public void performDirectAction(IBinder activityToken, String actionId, Bundle arguments,
+ RemoteCallback cancellationCallback, RemoteCallback resultCallback) {
+ }
}
}
diff --git a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
index 86d55ea..b3f6fdb 100644
--- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
+++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
@@ -347,7 +347,7 @@
CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null /* referrer */,
null /* voiceInteractor */, null /* state */, null /* persistentState */,
null /* pendingResults */, null /* pendingNewIntents */, true /* isForward */,
- null /* profilerInfo */, mThread /* client */);
+ null /* profilerInfo */, mThread /* client */, null /* asssitToken */);
}
@Override
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index 5edf330..62fd489 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -58,10 +58,6 @@
#define GET_DEV_PROC(F) m##F = (PFN_vk##F)vkGetDeviceProcAddr(mDevice, "vk" #F)
void VulkanManager::destroy() {
- // We don't need to explicitly free the command buffer since it automatically gets freed when we
- // delete the VkCommandPool below.
- mDummyCB = VK_NULL_HANDLE;
-
if (VK_NULL_HANDLE != mCommandPool) {
mDestroyCommandPool(mDevice, mCommandPool, nullptr);
mCommandPool = VK_NULL_HANDLE;
@@ -376,12 +372,6 @@
}
LOG_ALWAYS_FATAL_IF(mCommandPool == VK_NULL_HANDLE);
- if (!setupDummyCommandBuffer()) {
- this->destroy();
- // Pass through will crash on next line.
- }
- LOG_ALWAYS_FATAL_IF(mDummyCB == VK_NULL_HANDLE);
-
mGetDeviceQueue(mDevice, mPresentQueueIndex, 0, &mPresentQueue);
if (Properties::enablePartialUpdates && Properties::useBufferAge) {
@@ -488,6 +478,22 @@
return Frame(surface->logicalWidth(), surface->logicalHeight(), bufferAge);
}
+struct DestroySemaphoreInfo {
+ PFN_vkDestroySemaphore mDestroyFunction;
+ VkDevice mDevice;
+ VkSemaphore mSemaphore;
+
+ DestroySemaphoreInfo(PFN_vkDestroySemaphore destroyFunction, VkDevice device,
+ VkSemaphore semaphore)
+ : mDestroyFunction(destroyFunction), mDevice(device), mSemaphore(semaphore) {}
+};
+
+static void destroy_semaphore(void* context) {
+ DestroySemaphoreInfo* info = reinterpret_cast<DestroySemaphoreInfo*>(context);
+ info->mDestroyFunction(info->mDevice, info->mSemaphore, nullptr);
+ delete info;
+}
+
void VulkanManager::swapBuffers(VulkanSurface* surface, const SkRect& dirtyRect) {
if (CC_UNLIKELY(Properties::waitForGpuCompletion)) {
ATRACE_NAME("Finishing GPU work");
@@ -517,9 +523,12 @@
backendSemaphore.initVulkan(semaphore);
int fenceFd = -1;
+ DestroySemaphoreInfo* destroyInfo = new DestroySemaphoreInfo(mDestroySemaphore, mDevice,
+ semaphore);
GrSemaphoresSubmitted submitted =
bufferInfo->skSurface->flush(SkSurface::BackendSurfaceAccess::kPresent,
- SkSurface::kNone_FlushFlags, 1, &backendSemaphore);
+ kNone_GrFlushFlags, 1, &backendSemaphore,
+ destroy_semaphore, destroyInfo);
if (submitted == GrSemaphoresSubmitted::kYes) {
VkSemaphoreGetFdInfoKHR getFdInfo;
getFdInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR;
@@ -535,12 +544,6 @@
}
surface->presentCurrentBuffer(dirtyRect, fenceFd);
-
- // Exporting a semaphore with copy transference via vkGetSemaphoreFdKHR, has the same effect of
- // destroying the semaphore and creating a new one with the same handle, and the payloads
- // ownership is move to the Fd we created. Thus the semaphore is in a state that we can delete
- // it and we don't need to wait on the command buffer we submitted to finish.
- mDestroySemaphore(mDevice, semaphore, nullptr);
}
void VulkanManager::destroySurface(VulkanSurface* surface) {
@@ -566,38 +569,7 @@
*this, extraBuffers);
}
-bool VulkanManager::setupDummyCommandBuffer() {
- if (mDummyCB != VK_NULL_HANDLE) {
- return true;
- }
-
- VkCommandBufferAllocateInfo commandBuffersInfo;
- memset(&commandBuffersInfo, 0, sizeof(VkCommandBufferAllocateInfo));
- commandBuffersInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
- commandBuffersInfo.pNext = nullptr;
- commandBuffersInfo.commandPool = mCommandPool;
- commandBuffersInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
- commandBuffersInfo.commandBufferCount = 1;
-
- VkResult err = mAllocateCommandBuffers(mDevice, &commandBuffersInfo, &mDummyCB);
- if (err != VK_SUCCESS) {
- // It is probably unnecessary to set this back to VK_NULL_HANDLE, but we set it anyways to
- // make sure the driver didn't set a value and then return a failure.
- mDummyCB = VK_NULL_HANDLE;
- return false;
- }
-
- VkCommandBufferBeginInfo beginInfo;
- memset(&beginInfo, 0, sizeof(VkCommandBufferBeginInfo));
- beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
- beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT;
-
- mBeginCommandBuffer(mDummyCB, &beginInfo);
- mEndCommandBuffer(mDummyCB);
- return true;
-}
-
-status_t VulkanManager::fenceWait(sp<Fence>& fence) {
+status_t VulkanManager::fenceWait(sp<Fence>& fence, GrContext* grContext) {
if (!hasVkContext()) {
ALOGE("VulkanManager::fenceWait: VkDevice not initialized");
return INVALID_OPERATION;
@@ -630,36 +602,22 @@
err = mImportSemaphoreFdKHR(mDevice, &importInfo);
if (VK_SUCCESS != err) {
+ mDestroySemaphore(mDevice, semaphore, nullptr);
ALOGE("Failed to import semaphore, err: %d", err);
return UNKNOWN_ERROR;
}
- LOG_ALWAYS_FATAL_IF(mDummyCB == VK_NULL_HANDLE);
+ GrBackendSemaphore beSemaphore;
+ beSemaphore.initVulkan(semaphore);
- VkPipelineStageFlags waitDstStageFlags = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
+ // Skia takes ownership of the semaphore and will delete it once the wait has finished.
+ grContext->wait(1, &beSemaphore);
+ grContext->flush();
- VkSubmitInfo submitInfo;
- memset(&submitInfo, 0, sizeof(VkSubmitInfo));
- submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
- submitInfo.waitSemaphoreCount = 1;
- // Wait to make sure aquire semaphore set above has signaled.
- submitInfo.pWaitSemaphores = &semaphore;
- submitInfo.pWaitDstStageMask = &waitDstStageFlags;
- submitInfo.commandBufferCount = 1;
- submitInfo.pCommandBuffers = &mDummyCB;
- submitInfo.signalSemaphoreCount = 0;
-
- mQueueSubmit(mGraphicsQueue, 1, &submitInfo, VK_NULL_HANDLE);
-
- // On Android when we import a semaphore, it is imported using temporary permanence. That
- // means as soon as we queue the semaphore for a wait it reverts to its previous permanent
- // state before importing. This means it will now be in an idle state with no pending
- // signal or wait operations, so it is safe to immediately delete it.
- mDestroySemaphore(mDevice, semaphore, nullptr);
return OK;
}
-status_t VulkanManager::createReleaseFence(sp<Fence>& nativeFence) {
+status_t VulkanManager::createReleaseFence(sp<Fence>& nativeFence, GrContext* grContext) {
if (!hasVkContext()) {
ALOGE("VulkanManager::createReleaseFence: VkDevice not initialized");
return INVALID_OPERATION;
@@ -681,20 +639,20 @@
return INVALID_OPERATION;
}
- LOG_ALWAYS_FATAL_IF(mDummyCB == VK_NULL_HANDLE);
+ GrBackendSemaphore backendSemaphore;
+ backendSemaphore.initVulkan(semaphore);
- VkSubmitInfo submitInfo;
- memset(&submitInfo, 0, sizeof(VkSubmitInfo));
- submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
- submitInfo.waitSemaphoreCount = 0;
- submitInfo.pWaitSemaphores = nullptr;
- submitInfo.pWaitDstStageMask = nullptr;
- submitInfo.commandBufferCount = 1;
- submitInfo.pCommandBuffers = &mDummyCB;
- submitInfo.signalSemaphoreCount = 1;
- submitInfo.pSignalSemaphores = &semaphore;
+ DestroySemaphoreInfo* destroyInfo = new DestroySemaphoreInfo(mDestroySemaphore, mDevice,
+ semaphore);
+ GrSemaphoresSubmitted submitted =
+ grContext->flush(kNone_GrFlushFlags, 1, &backendSemaphore,
+ destroy_semaphore, destroyInfo);
- mQueueSubmit(mGraphicsQueue, 1, &submitInfo, VK_NULL_HANDLE);
+ if (submitted == GrSemaphoresSubmitted::kNo) {
+ ALOGE("VulkanManager::createReleaseFence: Failed to submit semaphore");
+ mDestroySemaphore(mDevice, semaphore, nullptr);
+ return INVALID_OPERATION;
+ }
VkSemaphoreGetFdInfoKHR getFdInfo;
getFdInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR;
@@ -711,12 +669,6 @@
}
nativeFence = new Fence(fenceFd);
- // Exporting a semaphore with copy transference via vkGetSemahporeFdKHR, has the same effect of
- // destroying the semaphore and creating a new one with the same handle, and the payloads
- // ownership is move to the Fd we created. Thus the semahpore is in a state that we can delete
- // it and we don't need to wait on the command buffer we submitted to finish.
- mDestroySemaphore(mDevice, semaphore, nullptr);
-
return OK;
}
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index 1a3a0e4..31de803 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -70,10 +70,11 @@
void destroy();
// Inserts a wait on fence command into the Vulkan command buffer.
- status_t fenceWait(sp<Fence>& fence);
+ status_t fenceWait(sp<Fence>& fence, GrContext* grContext);
- // Creates a fence that is signaled, when all the pending Vulkan commands are flushed.
- status_t createReleaseFence(sp<Fence>& nativeFence);
+ // Creates a fence that is signaled when all the pending Vulkan commands are finished on the
+ // GPU.
+ status_t createReleaseFence(sp<Fence>& nativeFence, GrContext* grContext);
// Returned pointers are owned by VulkanManager.
// An instance of VkFunctorInitParams returned from getVkFunctorInitParams refers to
@@ -89,7 +90,6 @@
// Sets up the VkInstance and VkDevice objects. Also fills out the passed in
// VkPhysicalDeviceFeatures struct.
void setupDevice(GrVkExtensions&, VkPhysicalDeviceFeatures2&);
- bool setupDummyCommandBuffer();
// simple wrapper class that exists only to initialize a pointer to NULL
template <typename FNPTR_TYPE>
@@ -164,8 +164,6 @@
VkQueue mPresentQueue = VK_NULL_HANDLE;
VkCommandPool mCommandPool = VK_NULL_HANDLE;
- VkCommandBuffer mDummyCB = VK_NULL_HANDLE;
-
// Variables saved to populate VkFunctorInitParams.
static const uint32_t mAPIVersion = VK_MAKE_VERSION(1, 1, 0);
std::vector<VkExtensionProperties> mInstanceExtensionsOwner;
diff --git a/libs/hwui/surfacetexture/ImageConsumer.cpp b/libs/hwui/surfacetexture/ImageConsumer.cpp
index 65d95ad..bae616b 100644
--- a/libs/hwui/surfacetexture/ImageConsumer.cpp
+++ b/libs/hwui/surfacetexture/ImageConsumer.cpp
@@ -212,7 +212,8 @@
uirenderer::RenderPipelineType::SkiaGL) {
err = renderState.getRenderThread().eglManager().fenceWait(item.mFence);
} else {
- err = renderState.getRenderThread().vulkanManager().fenceWait(item.mFence);
+ err = renderState.getRenderThread().vulkanManager().fenceWait(
+ item.mFence, renderState.getRenderThread().getGrContext());
}
if (err != OK) {
st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer, EGL_NO_DISPLAY,
@@ -234,7 +235,8 @@
err = eglManager.createReleaseFence(st.mUseFenceSync, &mImageSlots[slot].eglFence(),
releaseFence);
} else {
- err = renderState.getRenderThread().vulkanManager().createReleaseFence(releaseFence);
+ err = renderState.getRenderThread().vulkanManager().createReleaseFence(
+ releaseFence, renderState.getRenderThread().getGrContext());
}
if (OK != err) {
st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer, EGL_NO_DISPLAY,
diff --git a/packages/ExtServices/src/android/ext/services/watchdog/ExplicitHealthCheckServiceImpl.java b/packages/ExtServices/src/android/ext/services/watchdog/ExplicitHealthCheckServiceImpl.java
index 69e91d1..765e9f9 100644
--- a/packages/ExtServices/src/android/ext/services/watchdog/ExplicitHealthCheckServiceImpl.java
+++ b/packages/ExtServices/src/android/ext/services/watchdog/ExplicitHealthCheckServiceImpl.java
@@ -16,11 +16,12 @@
package android.ext.services.watchdog;
+import static android.service.watchdog.ExplicitHealthCheckService.PackageConfig;
+
import android.content.ComponentName;
import android.content.Intent;
import android.provider.DeviceConfig;
import android.service.watchdog.ExplicitHealthCheckService;
-import android.service.watchdog.PackageInfo;
import android.util.Log;
import java.util.ArrayList;
@@ -74,8 +75,8 @@
}
@Override
- public List<PackageInfo> onGetSupportedPackages() {
- List<PackageInfo> packages = new ArrayList<>();
+ public List<PackageConfig> onGetSupportedPackages() {
+ List<PackageConfig> packages = new ArrayList<>();
long requestTimeoutMillis = DeviceConfig.getLong(
DeviceConfig.NAMESPACE_ROLLBACK,
PROPERTY_WATCHDOG_REQUEST_TIMEOUT_MILLIS,
@@ -84,7 +85,7 @@
requestTimeoutMillis = DEFAULT_REQUEST_TIMEOUT_MILLIS;
}
for (ExplicitHealthChecker checker : mSupportedCheckers.values()) {
- PackageInfo pkg = new PackageInfo(checker.getSupportedPackageName(),
+ PackageConfig pkg = new PackageConfig(checker.getSupportedPackageName(),
requestTimeoutMillis);
packages.add(pkg);
}
diff --git a/packages/FusedLocation/res/values-or/strings.xml b/packages/FusedLocation/res/values-or/strings.xml
index b95bc37..b50e63d 100644
--- a/packages/FusedLocation/res/values-or/strings.xml
+++ b/packages/FusedLocation/res/values-or/strings.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_label" msgid="5379477904423203699">"ଫ୍ୟୁଜ୍ଡ୍ ଲୋକେଶନ୍"</string>
+ <string name="app_label" msgid="5379477904423203699">"ଫ୍ୟୁଜ୍ଡ୍ ଲୋକେସନ୍"</string>
</resources>
diff --git a/packages/InputDevices/res/values-ar/strings.xml b/packages/InputDevices/res/values-ar/strings.xml
index bc23b7a..b161c9a 100644
--- a/packages/InputDevices/res/values-ar/strings.xml
+++ b/packages/InputDevices/res/values-ar/strings.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_label" msgid="8016145283189546017">"أجهزة إدخال بيانات"</string>
+ <string name="app_label" msgid="8016145283189546017">"أجهزة إدخال البيانات"</string>
<string name="keyboard_layouts_label" msgid="6688773268302087545">"لوحة مفاتيح Android"</string>
<string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"الإنجليزية (المملكة المتحدة)"</string>
<string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"الإنجليزية (الولايات المتحدة)"</string>
diff --git a/packages/NetworkStack/AndroidManifestBase.xml b/packages/NetworkStack/AndroidManifestBase.xml
index d00a551..3da566f 100644
--- a/packages/NetworkStack/AndroidManifestBase.xml
+++ b/packages/NetworkStack/AndroidManifestBase.xml
@@ -24,6 +24,7 @@
android:label="NetworkStack"
android:defaultToDeviceProtectedStorage="true"
android:directBootAware="true"
+ android:persistent="true"
android:usesCleartextTraffic="true">
<service android:name="com.android.server.connectivity.ipmemorystore.RegularMaintenanceJobService"
diff --git a/packages/SettingsLib/SearchWidget/res/values-sw/strings.xml b/packages/SettingsLib/SearchWidget/res/values-sw/strings.xml
index d446b20..297ecdb 100644
--- a/packages/SettingsLib/SearchWidget/res/values-sw/strings.xml
+++ b/packages/SettingsLib/SearchWidget/res/values-sw/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="search_menu" msgid="1604061903696928905">"Mipangilio ya utafutaji"</string>
+ <string name="search_menu" msgid="1604061903696928905">"Tafuta mipangilio"</string>
</resources>
diff --git a/packages/SettingsLib/SearchWidget/res/values-vi/strings.xml b/packages/SettingsLib/SearchWidget/res/values-vi/strings.xml
index 95f98c8..f54ef3b 100644
--- a/packages/SettingsLib/SearchWidget/res/values-vi/strings.xml
+++ b/packages/SettingsLib/SearchWidget/res/values-vi/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="search_menu" msgid="1604061903696928905">"Tìm kiếm mục cài đặt"</string>
+ <string name="search_menu" msgid="1604061903696928905">"Tìm kiếm trong các mục cài đặt"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index e280d62..5eccd1b 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -21,7 +21,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="wifi_fail_to_scan" msgid="1265540342578081461">"لا يمكن فحص الشبكات"</string>
- <string name="wifi_security_none" msgid="7985461072596594400">"بدون"</string>
+ <string name="wifi_security_none" msgid="7985461072596594400">"بلا أمان"</string>
<string name="wifi_remembered" msgid="4955746899347821096">"تم الحفظ"</string>
<string name="wifi_disabled_generic" msgid="4259794910584943386">"غير مفعّلة"</string>
<string name="wifi_disabled_network_failure" msgid="2364951338436007124">"تعذّرت تهيئة عنوان IP"</string>
@@ -421,7 +421,7 @@
<string name="charge_length_format" msgid="8978516217024434156">"قبل <xliff:g id="ID_1">%1$s</xliff:g>"</string>
<string name="remaining_length_format" msgid="7886337596669190587">"يتبقى <xliff:g id="ID_1">%1$s</xliff:g>"</string>
<string name="screen_zoom_summary_small" msgid="5867245310241621570">"صغير"</string>
- <string name="screen_zoom_summary_default" msgid="2247006805614056507">"تلقائي"</string>
+ <string name="screen_zoom_summary_default" msgid="2247006805614056507">"الإعداد التلقائي"</string>
<string name="screen_zoom_summary_large" msgid="4835294730065424084">"كبير"</string>
<string name="screen_zoom_summary_very_large" msgid="7108563375663670067">"أكبر"</string>
<string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"أكبر مستوى"</string>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index 1adaa46..8b128e5 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -238,7 +238,7 @@
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Трансляция: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="select_private_dns_configuration_title" msgid="3700456559305263922">"Жеке DNS"</string>
<string name="select_private_dns_configuration_dialog_title" msgid="9221994415765826811">"Жеке DNS режимін таңдаңыз"</string>
- <string name="private_dns_mode_off" msgid="8236575187318721684">"Өшіру"</string>
+ <string name="private_dns_mode_off" msgid="8236575187318721684">"Өшірулі"</string>
<string name="private_dns_mode_opportunistic" msgid="8314986739896927399">"Автоматты"</string>
<string name="private_dns_mode_provider" msgid="8354935160639360804">"Жеке DNS провайдерінің хост атауы"</string>
<string name="private_dns_mode_provider_hostname_hint" msgid="2487492386970928143">"DNS провайдерінің хост атауын енгізіңіз"</string>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index cf04db9..095d80f 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -462,6 +462,6 @@
<string name="zen_mode_duration_settings_title" msgid="229547412251222757">"Trajanje"</string>
<string name="zen_mode_duration_always_prompt_title" msgid="6478923750878945501">"Vedno vprašaj"</string>
<string name="zen_mode_forever" msgid="2704305038191592967">"Dokler ne izklopite"</string>
- <string name="time_unit_just_now" msgid="6363336622778342422">"Pravkar"</string>
+ <string name="time_unit_just_now" msgid="6363336622778342422">"pravkar"</string>
<string name="media_transfer_this_device_name" msgid="1636276898262571213">"Ta naprava"</string>
</resources>
diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml
index a94ae0f..58af4a2 100644
--- a/packages/SystemUI/res/layout/notification_info.xml
+++ b/packages/SystemUI/res/layout/notification_info.xml
@@ -70,6 +70,7 @@
android:layout_marginStart="2dp"
android:layout_marginEnd="2dp"
android:ellipsize="end"
+ android:text="@string/notification_delegate_header"
android:layout_toEndOf="@id/pkg_divider"
android:maxLines="1" />
<!-- Optional link to app. Only appears if the channel is not disabled and the app
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index a2039d0..acc03c4 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1679,7 +1679,7 @@
<string name="notification_multichannel_desc">This group of notifications cannot be configured here</string>
<!-- Notification: Control panel: Label for the app that posted this notification, if it's not the package that the notification was posted for -->
- <string name="notification_delegate_header">via <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
+ <string name="notification_delegate_header">Proxied notification</string>
<!-- Notification Inline controls: describes what the app is doing in the background [CHAR_LIMIT=NONE] -->
<string name="appops_camera">This app is using the camera.</string>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index 5764fe8..6f44623 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -73,17 +73,6 @@
void onStatusBarMotionEvent(in MotionEvent event) = 9;
/**
- * Get the corner radius of windows in pixels.
- */
- float getWindowCornerRadius() = 10;
-
- /**
- * If device supports live rounded corners on windows.
- * This might be turned off for performance reasons
- */
- boolean supportsRoundedCornersOnWindows() = 11;
-
- /**
* Proxies the assistant gesture's progress started from navigation bar.
*/
void onAssistantProgress(float progress) = 12;
@@ -97,4 +86,14 @@
* Creates a new gesture monitor
*/
Bundle monitorGestureInput(String name, int displayId) = 14;
+
+ /**
+ * Notifies that the accessibility button in the system's navigation area has been clicked
+ */
+ void notifyAccessibilityButtonClicked(int displayId) = 15;
+
+ /**
+ * Notifies that the accessibility button in the system's navigation area has been long clicked
+ */
+ void notifyAccessibilityButtonLongClicked() = 16;
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 4054784..b89b9ef 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -21,7 +21,6 @@
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
import android.annotation.IntDef;
-import android.content.Context;
import android.content.res.Resources;
import android.view.WindowManagerPolicyConstants;
@@ -52,11 +51,16 @@
public static final int SYSUI_STATE_NAV_BAR_HIDDEN = 1 << 1;
public static final int SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED = 1 << 2;
public static final int SYSUI_STATE_BOUNCER_SHOWING = 1 << 3;
+ public static final int SYSUI_STATE_A11Y_BUTTON_CLICKABLE = 1 << 4;
+ public static final int SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE = 1 << 5;
@Retention(RetentionPolicy.SOURCE)
@IntDef({SYSUI_STATE_SCREEN_PINNING,
SYSUI_STATE_NAV_BAR_HIDDEN,
- SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
+ SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED,
+ SYSUI_STATE_BOUNCER_SHOWING,
+ SYSUI_STATE_A11Y_BUTTON_CLICKABLE,
+ SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE
})
public @interface SystemUiStateFlags {}
@@ -90,13 +94,6 @@
}
/**
- * @return whether the current nav bar mode is gestural
- */
- public static boolean isGesturalMode(Context context) {
- return isGesturalMode(getCurrentInteractionMode(context));
- }
-
- /**
* @return whether this nav bar mode is swipe up
*/
public static boolean isSwipeUpMode(int mode) {
@@ -104,13 +101,6 @@
}
/**
- * @return whether the current nav bar mode is swipe up
- */
- public static boolean isSwipeUpMode(Context context) {
- return isSwipeUpMode(getCurrentInteractionMode(context));
- }
-
- /**
* @return whether this nav bar mode is 3 button
*/
public static boolean isLegacyMode(int mode) {
@@ -118,37 +108,6 @@
}
/**
- * @return whether this nav bar mode is 3 button
- */
- public static boolean isLegacyMode(Context context) {
- return isLegacyMode(getCurrentInteractionMode(context));
- }
-
- /**
- * @return the current nav bar interaction mode
- */
- public static int getCurrentInteractionMode(Context context) {
- return context.getResources().getInteger(
- com.android.internal.R.integer.config_navBarInteractionMode);
- }
-
- /**
- * @return {@code true} if the navbar can be clicked through
- */
- public static boolean isNavBarClickThrough(Context context) {
- return context.getResources().getBoolean(
- com.android.internal.R.bool.config_navBarTapThrough);
- }
-
- /**
- * @return the edge sensitivity width in px
- */
- public static int getEdgeSensitivityWidth(Context context) {
- return context.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.config_backGestureInset);
- }
-
- /**
* Corner radius that should be used on windows in order to cover the display.
* These values are expressed in pixels because they should not respect display or font
* scaling, this means that we don't have to reload them on config changes.
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java
index 7d1587c..123e138 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java
@@ -54,6 +54,11 @@
private final SysuiColorExtractor mColorExtractor;
/**
+ * Computes preferred position of clock.
+ */
+ private final SmallClockPosition mClockPosition;
+
+ /**
* Renders preview from clock view.
*/
private final ViewPreviewer mRenderer = new ViewPreviewer();
@@ -82,6 +87,7 @@
mResources = res;
mLayoutInflater = inflater;
mColorExtractor = colorExtractor;
+ mClockPosition = new SmallClockPosition(res);
}
private void createViews() {
@@ -153,7 +159,7 @@
@Override
public int getPreferredY(int totalHeight) {
- return totalHeight / 4;
+ return mClockPosition.getPreferredY();
}
@Override
@@ -181,7 +187,9 @@
}
@Override
- public void setDarkAmount(float darkAmount) { }
+ public void setDarkAmount(float darkAmount) {
+ mClockPosition.setDarkAmount(darkAmount);
+ }
@Override
public void onTimeZoneChanged(TimeZone timeZone) {
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java
index fbbf64c..8db61b8 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java
@@ -54,6 +54,11 @@
private final SysuiColorExtractor mColorExtractor;
/**
+ * Computes preferred position of clock.
+ */
+ private final SmallClockPosition mClockPosition;
+
+ /**
* Renders preview from clock view.
*/
private final ViewPreviewer mRenderer = new ViewPreviewer();
@@ -82,6 +87,7 @@
mResources = res;
mLayoutInflater = inflater;
mColorExtractor = colorExtractor;
+ mClockPosition = new SmallClockPosition(res);
}
private void createViews() {
@@ -152,7 +158,7 @@
@Override
public int getPreferredY(int totalHeight) {
- return totalHeight / 4;
+ return mClockPosition.getPreferredY();
}
@Override
@@ -173,7 +179,9 @@
}
@Override
- public void setDarkAmount(float darkAmount) { }
+ public void setDarkAmount(float darkAmount) {
+ mClockPosition.setDarkAmount(darkAmount);
+ }
@Override
public void onTimeTick() {
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/SmallClockPosition.java b/packages/SystemUI/src/com/android/keyguard/clock/SmallClockPosition.java
new file mode 100644
index 0000000..9b15dc6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/clock/SmallClockPosition.java
@@ -0,0 +1,75 @@
+/*
+ * 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.keyguard.clock;
+
+import android.content.res.Resources;
+import android.util.MathUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Computes preferred position of clock by considering height of status bar and lock icon.
+ */
+class SmallClockPosition {
+
+ /**
+ * Dimensions used to determine preferred clock position.
+ */
+ private final int mStatusBarHeight;
+ private final int mKeyguardLockPadding;
+ private final int mKeyguardLockHeight;
+ private final int mBurnInOffsetY;
+
+ /**
+ * Amount of transition between AOD and lock screen.
+ */
+ private float mDarkAmount;
+
+ SmallClockPosition(Resources res) {
+ this(res.getDimensionPixelSize(com.android.keyguard.R.dimen.status_bar_height),
+ res.getDimensionPixelSize(com.android.keyguard.R.dimen.keyguard_lock_padding),
+ res.getDimensionPixelSize(com.android.keyguard.R.dimen.keyguard_lock_height),
+ res.getDimensionPixelSize(com.android.keyguard.R.dimen.burn_in_prevention_offset_y)
+ );
+ }
+
+ @VisibleForTesting
+ SmallClockPosition(int statusBarHeight, int lockPadding, int lockHeight, int burnInY) {
+ mStatusBarHeight = statusBarHeight;
+ mKeyguardLockPadding = lockPadding;
+ mKeyguardLockHeight = lockHeight;
+ mBurnInOffsetY = burnInY;
+ }
+
+ /**
+ * See {@link ClockPlugin#setDarkAmount}.
+ */
+ void setDarkAmount(float darkAmount) {
+ mDarkAmount = darkAmount;
+ }
+
+ /**
+ * Gets the preferred Y position accounting for status bar and lock icon heights.
+ */
+ int getPreferredY() {
+ // On AOD, clock needs to appear below the status bar with enough room for pixel shifting
+ int aodY = mStatusBarHeight + mKeyguardLockPadding + mBurnInOffsetY;
+ // On lock screen, clock needs to appear below the lock icon
+ int lockY = mStatusBarHeight + mKeyguardLockHeight + 2 * mKeyguardLockPadding;
+ return (int) MathUtils.lerp(lockY, aodY, mDarkAmount);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 4b338f7..1feb63d 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -74,6 +74,7 @@
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
import com.android.systemui.statusbar.phone.ManagedProfileController;
+import com.android.systemui.statusbar.phone.NavigationModeController;
import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.ShadeController;
@@ -244,6 +245,7 @@
@Inject Lazy<LightBarController> mLightBarController;
@Inject Lazy<IWindowManager> mIWindowManager;
@Inject Lazy<OverviewProxyService> mOverviewProxyService;
+ @Inject Lazy<NavigationModeController> mNavBarModeController;
@Inject Lazy<EnhancedEstimates> mEnhancedEstimates;
@Inject Lazy<VibratorHelper> mVibratorHelper;
@Inject Lazy<IStatusBarService> mIStatusBarService;
@@ -407,6 +409,8 @@
mProviders.put(OverviewProxyService.class, mOverviewProxyService::get);
+ mProviders.put(NavigationModeController.class, mNavBarModeController::get);
+
mProviders.put(EnhancedEstimates.class, mEnhancedEstimates::get);
mProviders.put(VibratorHelper.class, mVibratorHelper::get);
diff --git a/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java b/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java
index 7a54344..c54f6306 100644
--- a/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java
@@ -129,8 +129,11 @@
restartButton = createRestartButton(context);
restartButton.updateLastTargetActivity(activityToken);
- restartButton.show();
- mActiveButtons.append(displayId, restartButton);
+ if (restartButton.show()) {
+ mActiveButtons.append(displayId, restartButton);
+ } else {
+ onDisplayRemoved(displayId);
+ }
}
@VisibleForTesting
@@ -208,8 +211,16 @@
mLastActivityToken = activityToken;
}
- void show() {
- getContext().getSystemService(WindowManager.class).addView(this, mWinParams);
+ /** @return {@code false} if the target display is invalid. */
+ boolean show() {
+ try {
+ getContext().getSystemService(WindowManager.class).addView(this, mWinParams);
+ } catch (WindowManager.InvalidDisplayException e) {
+ // The target display may have been removed when the callback has just arrived.
+ Log.w(TAG, "Cannot show on display " + getContext().getDisplayId(), e);
+ return false;
+ }
+ return true;
}
void remove() {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 47ad0c1..ffb5e81 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -22,12 +22,15 @@
import android.annotation.Nullable;
import android.app.AlarmManager;
import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
import android.util.Log;
import android.view.ViewGroup;
import com.android.internal.colorextraction.ColorExtractor.GradientColors;
import com.android.internal.util.function.TriConsumer;
import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.classifier.FalsingManager;
@@ -125,7 +128,8 @@
DismissCallbackRegistry dismissCallbackRegistry,
KeyguardBouncer.BouncerExpansionCallback expansionCallback) {
return new KeyguardBouncer(context, callback, lockPatternUtils, container,
- dismissCallbackRegistry, FalsingManager.getInstance(context), expansionCallback);
+ dismissCallbackRegistry, FalsingManager.getInstance(context), expansionCallback,
+ KeyguardUpdateMonitor.getInstance(context), new Handler(Looper.getMainLooper()));
}
public ScrimController createScrimController(ScrimView scrimBehind, ScrimView scrimInFront,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java
index 4028109..5860230 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java
@@ -24,6 +24,7 @@
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import android.os.Bundle;
import android.os.Handler;
+import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
@@ -58,7 +59,7 @@
private boolean mDialogShowing;
private Callback mCallback = new Callback();
- private Handler mHandler = new Handler() {
+ private Handler mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
switch(msg.what) {
@@ -66,15 +67,20 @@
handleShowDialog((SomeArgs) msg.obj, false /* skipAnimation */,
null /* savedState */);
break;
- case MSG_BIOMETRIC_AUTHENTICATED:
- handleBiometricAuthenticated((boolean) msg.obj);
+ case MSG_BIOMETRIC_AUTHENTICATED: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ handleBiometricAuthenticated((boolean) args.arg1 /* authenticated */,
+ (String) args.arg2 /* failureReason */);
+ args.recycle();
break;
- case MSG_BIOMETRIC_HELP:
+ }
+ case MSG_BIOMETRIC_HELP: {
SomeArgs args = (SomeArgs) msg.obj;
handleBiometricHelp((String) args.arg1 /* message */,
(boolean) args.arg2 /* requireTryAgain */);
args.recycle();
break;
+ }
case MSG_BIOMETRIC_ERROR:
handleBiometricError((String) msg.obj);
break;
@@ -161,9 +167,14 @@
}
@Override
- public void onBiometricAuthenticated(boolean authenticated) {
- if (DEBUG) Log.d(TAG, "onBiometricAuthenticated: " + authenticated);
- mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED, authenticated).sendToTarget();
+ public void onBiometricAuthenticated(boolean authenticated, String failureReason) {
+ if (DEBUG) Log.d(TAG, "onBiometricAuthenticated: " + authenticated
+ + " reason: " + failureReason);
+
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = authenticated;
+ args.arg2 = failureReason;
+ mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED, args).sendToTarget();
}
@Override
@@ -230,7 +241,7 @@
mDialogShowing = true;
}
- private void handleBiometricAuthenticated(boolean authenticated) {
+ private void handleBiometricAuthenticated(boolean authenticated, String failureReason) {
if (DEBUG) Log.d(TAG, "handleBiometricAuthenticated: " + authenticated);
if (authenticated) {
@@ -246,9 +257,7 @@
}, mCurrentDialog.getDelayAfterAuthenticatedDurationMs());
}
} else {
- handleBiometricHelp(mContext.getResources()
- .getString(com.android.internal.R.string.biometric_not_recognized),
- true /* requireTryAgain */);
+ handleBiometricHelp(failureReason, true /* requireTryAgain */);
mCurrentDialog.showTryAgainButton(true /* show */);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 346ffa2..e688007 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -519,9 +519,18 @@
@Override
public void onClick(View v) {
- if (v == mClockView || v == mNextAlarmContainer) {
+ if (v == mClockView) {
mActivityStarter.postStartActivityDismissingKeyguard(new Intent(
- AlarmClock.ACTION_SHOW_ALARMS),0);
+ AlarmClock.ACTION_SHOW_ALARMS), 0);
+ } else if (v == mNextAlarmContainer) {
+ if (mNextAlarm.getShowIntent() != null
+ && mNextAlarm.getShowIntent().getIntent() != null) {
+ mActivityStarter.postStartActivityDismissingKeyguard(
+ mNextAlarm.getShowIntent().getIntent(), 0);
+ } else {
+ mActivityStarter.postStartActivityDismissingKeyguard(new Intent(
+ AlarmClock.ACTION_SHOW_ALARMS), 0);
+ }
} else if (v == mPrivacyChip) {
// Makes sure that the builder is grabbed as soon as the chip is pressed
PrivacyDialogBuilder builder = mPrivacyChip.getBuilder();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 898f64b..3ace705 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -20,6 +20,7 @@
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_UP;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_SWIPE_UP;
import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_SHOW_OVERVIEW_BUTTON;
@@ -54,6 +55,7 @@
import android.util.Log;
import android.view.InputMonitor;
import android.view.MotionEvent;
+import android.view.accessibility.AccessibilityManager;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.systemui.Dependency;
@@ -69,6 +71,7 @@
import com.android.systemui.stackdivider.Divider;
import com.android.systemui.statusbar.NavigationBarController;
import com.android.systemui.statusbar.phone.NavigationBarFragment;
+import com.android.systemui.statusbar.phone.NavigationModeController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.CallbackController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -86,7 +89,8 @@
* Class to send information from overview to launcher with a binder.
*/
@Singleton
-public class OverviewProxyService implements CallbackController<OverviewProxyListener>, Dumpable {
+public class OverviewProxyService implements CallbackController<OverviewProxyListener>,
+ NavigationModeController.ModeChangedListener, Dumpable {
private static final String ACTION_QUICKSTEP = "android.intent.action.QUICKSTEP_SERVICE";
@@ -123,9 +127,11 @@
private MotionEvent mStatusBarGestureDownEvent;
private float mWindowCornerRadius;
private boolean mSupportsRoundedCornersOnWindows;
+ private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
private ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() {
+ @Override
public void startScreenPinning(int taskId) {
if (!verifyCaller("startScreenPinning")) {
return;
@@ -144,6 +150,7 @@
}
}
+ @Override
public void onStatusBarMotionEvent(MotionEvent event) {
if (!verifyCaller("onStatusBarMotionEvent")) {
return;
@@ -172,6 +179,7 @@
}
}
+ @Override
public void onSplitScreenInvoked() {
if (!verifyCaller("onSplitScreenInvoked")) {
return;
@@ -187,6 +195,7 @@
}
}
+ @Override
public void onOverviewShown(boolean fromHome) {
if (!verifyCaller("onOverviewShown")) {
return;
@@ -203,6 +212,7 @@
}
}
+ @Override
public void setInteractionState(@InteractionType int flags) {
if (!verifyCaller("setInteractionState")) {
return;
@@ -223,6 +233,7 @@
}
}
+ @Override
public Rect getNonMinimizedSplitScreenSecondaryBounds() {
if (!verifyCaller("getNonMinimizedSplitScreenSecondaryBounds")) {
return null;
@@ -239,6 +250,7 @@
}
}
+ @Override
public void setBackButtonAlpha(float alpha, boolean animate) {
if (!verifyCaller("setBackButtonAlpha")) {
return;
@@ -254,30 +266,7 @@
}
}
- public float getWindowCornerRadius() {
- if (!verifyCaller("getWindowCornerRadius")) {
- return 0;
- }
- long token = Binder.clearCallingIdentity();
- try {
- return mWindowCornerRadius;
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- public boolean supportsRoundedCornersOnWindows() {
- if (!verifyCaller("supportsRoundedCornersOnWindows")) {
- return false;
- }
- long token = Binder.clearCallingIdentity();
- try {
- return mSupportsRoundedCornersOnWindows;
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
+ @Override
public void onAssistantProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {
if (!verifyCaller("onAssistantProgress")) {
return;
@@ -290,6 +279,7 @@
}
}
+ @Override
public void startAssistant(Bundle bundle) {
if (!verifyCaller("startAssistant")) {
return;
@@ -302,6 +292,7 @@
}
}
+ @Override
public Bundle monitorGestureInput(String name, int displayId) {
if (!verifyCaller("monitorGestureInput")) {
return null;
@@ -318,6 +309,35 @@
}
}
+ @Override
+ public void notifyAccessibilityButtonClicked(int displayId) {
+ if (!verifyCaller("notifyAccessibilityButtonClicked")) {
+ return;
+ }
+ long token = Binder.clearCallingIdentity();
+ try {
+ AccessibilityManager.getInstance(mContext)
+ .notifyAccessibilityButtonClicked(displayId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void notifyAccessibilityButtonLongClicked() {
+ if (!verifyCaller("notifyAccessibilityButtonLongClicked")) {
+ return;
+ }
+ long token = Binder.clearCallingIdentity();
+ try {
+ Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
private boolean verifyCaller(String reason) {
final int callerId = Binder.getCallingUserHandle().getIdentifier();
if (callerId != mCurrentBoundedUserId) {
@@ -411,16 +431,6 @@
private final DeviceProvisionedListener mDeviceProvisionedCallback =
new DeviceProvisionedListener() {
-
- @Override
- public void onDeviceProvisionedChanged() {
- /*
- on initialize, keep track of the previous gestural state (nothing is enabled by default)
- restore to a non gestural state if device is not provisioned
- once the device is provisioned, restore to the original state
- */
- }
-
@Override
public void onUserSetupChanged() {
if (mDeviceProvisionedController.isCurrentUserSetup()) {
@@ -458,6 +468,8 @@
// Assumes device always starts with back button until launcher tells it that it does not
mBackButtonAlpha = 1.0f;
+ mNavBarMode = Dependency.get(NavigationModeController.class).addListener(this);
+
// Listen for the package update changes.
if (mDeviceProvisionedController.getCurrentUser() == UserHandle.USER_SYSTEM) {
updateEnabledState();
@@ -467,6 +479,7 @@
filter.addDataSchemeSpecificPart(mRecentsComponentName.getPackageName(),
PatternMatcher.PATTERN_LITERAL);
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ // TODO: Shouldn't this be per-user?
mContext.registerReceiver(mLauncherStateChangedReceiver, filter);
}
}
@@ -515,6 +528,7 @@
? SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED : 0;
mSysUiStateFlags |= bouncerShowing
? SYSUI_STATE_BOUNCER_SHOWING : 0;
+ mSysUiStateFlags |= navBarFragment != null ? navBarFragment.getA11yButtonState(null) : 0;
notifySystemUiStateFlags(mSysUiStateFlags);
}
@@ -661,7 +675,7 @@
private int getDefaultInteractionFlags() {
// If there is no settings available use device default or get it from settings
- return QuickStepContract.isLegacyMode(mContext)
+ return QuickStepContract.isLegacyMode(mNavBarMode)
? DEFAULT_DISABLE_SWIPE_UP_STATE
: 0;
}
@@ -719,6 +733,11 @@
}
@Override
+ public void onNavigationModeChanged(int mode) {
+ mNavBarMode = mode;
+ }
+
+ @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println(TAG_OPS + " state:");
pw.print(" recentsComponentName="); pw.println(mRecentsComponentName);
@@ -730,8 +749,6 @@
pw.print(" quickStepIntent="); pw.println(mQuickStepIntent);
pw.print(" quickStepIntentResolved="); pw.println(isEnabled());
- pw.print(" navBarMode=");
- pw.println(QuickStepContract.getCurrentInteractionMode(mContext));
pw.print(" mSysUiStateFlags="); pw.println(mSysUiStateFlags);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
index 33a2acf..d0c4734 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
@@ -18,6 +18,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
import static com.android.systemui.Prefs.Key.DISMISSED_RECENTS_SWIPE_UP_ONBOARDING_COUNT;
import static com.android.systemui.Prefs.Key.HAS_DISMISSED_RECENTS_QUICK_SCRUB_ONBOARDING_ONCE;
@@ -111,6 +112,7 @@
private final int mOnboardingToastColor;
private final int mOnboardingToastArrowRadius;
private int mNavBarHeight;
+ private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
private boolean mOverviewProxyListenerRegistered;
private boolean mTaskListenerRegistered;
@@ -339,8 +341,12 @@
} catch (RemoteException e) {}
}
+ public void onNavigationModeChanged(int mode) {
+ mNavBarMode = mode;
+ }
+
public void onConnectedToLauncher() {
- if (!ONBOARDING_ENABLED || QuickStepContract.isGesturalMode(mContext)) {
+ if (!ONBOARDING_ENABLED || QuickStepContract.isGesturalMode(mNavBarMode)) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index d584959..a688f36 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -280,7 +280,7 @@
default void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
int type, boolean requireConfirmation, int userId) { }
- default void onBiometricAuthenticated(boolean authenticated) { }
+ default void onBiometricAuthenticated(boolean authenticated, String failureReason) { }
default void onBiometricHelp(String message) { }
default void onBiometricError(String error) { }
default void hideBiometricDialog() { }
@@ -760,9 +760,12 @@
}
@Override
- public void onBiometricAuthenticated(boolean authenticated) {
+ public void onBiometricAuthenticated(boolean authenticated, String failureReason) {
synchronized (mLock) {
- mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED, authenticated).sendToTarget();
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = authenticated;
+ args.arg2 = failureReason;
+ mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED, args).sendToTarget();
}
}
@@ -1023,7 +1026,7 @@
mCallbacks.get(i).onRotationProposal(msg.arg1, msg.arg2 != 0);
}
break;
- case MSG_BIOMETRIC_SHOW:
+ case MSG_BIOMETRIC_SHOW: {
mHandler.removeMessages(MSG_BIOMETRIC_ERROR);
mHandler.removeMessages(MSG_BIOMETRIC_HELP);
mHandler.removeMessages(MSG_BIOMETRIC_AUTHENTICATED);
@@ -1038,11 +1041,17 @@
}
someArgs.recycle();
break;
- case MSG_BIOMETRIC_AUTHENTICATED:
+ }
+ case MSG_BIOMETRIC_AUTHENTICATED: {
+ SomeArgs someArgs = (SomeArgs) msg.obj;
for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).onBiometricAuthenticated((boolean) msg.obj);
+ mCallbacks.get(i).onBiometricAuthenticated(
+ (boolean) someArgs.arg1 /* authenticated */,
+ (String) someArgs.arg2 /* failureReason */);
}
+ someArgs.recycle();
break;
+ }
case MSG_BIOMETRIC_HELP:
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).onBiometricHelp((String) msg.obj);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 6b2efaab..1ef29c1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -2641,7 +2641,7 @@
private int getHeadsUpHeight() {
- return mPrivateLayout.getHeadsUpHeight();
+ return getShowingLayout().getHeadsUpHeight();
}
public boolean areGutsExposed() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
index e6875e4..1f8ca37 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
@@ -448,22 +448,6 @@
CharSequence delegatePkg = null;
if (!TextUtils.equals(mPackageName, mDelegatePkg)) {
// this notification was posted by a delegate!
- ApplicationInfo info;
- try {
- info = mPm.getApplicationInfo(
- mDelegatePkg,
- PackageManager.MATCH_UNINSTALLED_PACKAGES
- | PackageManager.MATCH_DISABLED_COMPONENTS
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
- | PackageManager.MATCH_DIRECT_BOOT_AWARE);
- if (info != null) {
- delegatePkg = String.valueOf(mPm.getApplicationLabel(info));
- }
- } catch (PackageManager.NameNotFoundException e) { }
- }
- if (delegatePkg != null) {
- delegateView.setText(mContext.getResources().getString(
- R.string.notification_delegate_header, delegatePkg));
delegateView.setVisibility(View.VISIBLE);
dividerView.setVisibility(View.VISIBLE);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
index a964849..9b3f05e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
@@ -156,7 +156,9 @@
mWm = context.getSystemService(WindowManager.class);
mOverviewProxyService = overviewProxyService;
- mEdgeWidth = QuickStepContract.getEdgeSensitivityWidth(context);
+ // TODO: Get this for the current user
+ mEdgeWidth = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.config_backGestureInset);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
mSwipeThreshold = res.getDimension(R.dimen.navigation_edge_action_drag_threshold);
@@ -168,7 +170,7 @@
*/
public void onNavBarAttached() {
mIsAttached = true;
- onOverlaysChanged();
+ updateIsEnabled();
}
/**
@@ -179,11 +181,8 @@
updateIsEnabled();
}
- /**
- * Called when system overlays has changed
- */
- public void onOverlaysChanged() {
- mIsGesturalModeEnabled = QuickStepContract.isGesturalMode(mContext);
+ public void onNavigationModeChanged(int mode) {
+ mIsGesturalModeEnabled = QuickStepContract.isGesturalMode(mode);
updateIsEnabled();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index b00d874..3c2881d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -54,6 +54,7 @@
public class KeyguardBouncer {
private static final String TAG = "KeyguardBouncer";
+ static final long BOUNCER_FACE_DELAY = 800;
static final float ALPHA_EXPANSION_THRESHOLD = 0.95f;
static final float EXPANSION_HIDDEN = 1f;
static final float EXPANSION_VISIBLE = 0f;
@@ -66,6 +67,7 @@
private final DismissCallbackRegistry mDismissCallbackRegistry;
private final Handler mHandler;
private final BouncerExpansionCallback mExpansionCallback;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
new KeyguardUpdateMonitorCallback() {
@Override
@@ -93,16 +95,18 @@
public KeyguardBouncer(Context context, ViewMediatorCallback callback,
LockPatternUtils lockPatternUtils, ViewGroup container,
DismissCallbackRegistry dismissCallbackRegistry, FalsingManager falsingManager,
- BouncerExpansionCallback expansionCallback) {
+ BouncerExpansionCallback expansionCallback,
+ KeyguardUpdateMonitor keyguardUpdateMonitor, Handler handler) {
mContext = context;
mCallback = callback;
mLockPatternUtils = lockPatternUtils;
mContainer = container;
- KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback);
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mFalsingManager = falsingManager;
mDismissCallbackRegistry = dismissCallbackRegistry;
mExpansionCallback = expansionCallback;
- mHandler = new Handler();
+ mHandler = handler;
+ mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback);
}
public void show(boolean resetSecuritySelection) {
@@ -164,7 +168,11 @@
// Split up the work over multiple frames.
DejankUtils.removeCallbacks(mResetRunnable);
- DejankUtils.postAfterTraversal(mShowRunnable);
+ if (mKeyguardUpdateMonitor.isFaceDetectionRunning()) {
+ mHandler.postDelayed(mShowRunnable, BOUNCER_FACE_DELAY);
+ } else {
+ DejankUtils.postAfterTraversal(mShowRunnable);
+ }
mCallback.onBouncerVisiblityChanged(true /* shown */);
mExpansionCallback.onStartingToShow();
@@ -266,6 +274,7 @@
private void cancelShowRunnable() {
DejankUtils.removeCallbacks(mShowRunnable);
+ mHandler.removeCallbacks(mShowRunnable);
mShowingSoon = false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index 26e0a70..1b86693 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -158,7 +158,12 @@
}
private int getPreferredClockY() {
- return mClockPreferredY - mKeyguardStatusHeight - mClockNotificationsMargin;
+ return mClockPreferredY;
+ }
+
+ private int getExpandedPreferredClockY() {
+ return (mHasCustomClock && !mHasVisibleNotifs) ? getPreferredClockY()
+ : getExpandedClockPosition();
}
/**
@@ -187,13 +192,11 @@
private int getClockY() {
// Dark: Align the bottom edge of the clock at about half of the screen:
- float clockYDark = getPreferredClockY() + burnInPreventionOffsetY();
+ float clockYDark = (mHasCustomClock ? getPreferredClockY() : getMaxClockY())
+ + burnInPreventionOffsetY();
clockYDark = MathUtils.max(0, clockYDark);
- float clockYRegular = getExpandedClockPosition();
- if (mHasCustomClock && !mHasVisibleNotifs) {
- clockYRegular = clockYDark;
- }
+ float clockYRegular = getExpandedPreferredClockY();
float clockYBouncer = -mKeyguardStatusHeight;
// Move clock up while collapsing the shade
@@ -213,7 +216,7 @@
* @return Alpha from 0 to 1.
*/
private float getClockAlpha(int y) {
- float alphaKeyguard = Math.max(0, y / Math.max(1f, getExpandedClockPosition()));
+ float alphaKeyguard = Math.max(0, y / Math.max(1f, getExpandedPreferredClockY()));
alphaKeyguard = Interpolators.ACCELERATE.getInterpolation(alphaKeyguard);
return MathUtils.lerp(alphaKeyguard, 1f, mDarkAmount);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavBarTintController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavBarTintController.java
index bf5b60a..2f245ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavBarTintController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavBarTintController.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.phone;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
import android.content.Context;
import android.content.res.Resources;
@@ -43,6 +44,7 @@
private final Handler mHandler = new Handler();
private final NavigationBarView mNavigationBarView;
private final LightBarTransitionsController mLightBarController;
+ private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
private final CompositionSamplingListener mSamplingListener;
private final Runnable mUpdateSamplingListener = this::updateSamplingListener;
@@ -91,7 +93,7 @@
}
void start() {
- if (!isEnabled(mNavigationBarView.getContext())) {
+ if (!isEnabled(mNavigationBarView.getContext(), mNavBarMode)) {
return;
}
mSamplingEnabled = true;
@@ -178,6 +180,10 @@
}
}
+ public void onNavigationModeChanged(int mode) {
+ mNavBarMode = mode;
+ }
+
void dump(PrintWriter pw) {
pw.println("NavBarTintController:");
pw.println(" navBar isAttached: " + mNavigationBarView.isAttachedToWindow());
@@ -190,8 +196,8 @@
pw.println(" mCurrentMedianLuma: " + mCurrentMedianLuma);
}
- public static boolean isEnabled(Context context) {
+ public static boolean isEnabled(Context context, int navBarMode) {
return context.getDisplayId() == DEFAULT_DISPLAY
- && QuickStepContract.isGesturalMode(context);
+ && QuickStepContract.isGesturalMode(navBarMode);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 742fdda..de57066 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -21,9 +21,12 @@
import static android.app.StatusBarManager.WindowType;
import static android.app.StatusBarManager.WindowVisibleState;
import static android.app.StatusBarManager.windowStateToString;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
import static com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
import static com.android.systemui.shared.system.NavigationBarCompat.InteractionType;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
@@ -117,7 +120,8 @@
* Fragment containing the NavigationBarFragment. Contains logic for what happens
* on clicks and view states of the nav bar.
*/
-public class NavigationBarFragment extends LifecycleFragment implements Callbacks {
+public class NavigationBarFragment extends LifecycleFragment implements Callbacks,
+ NavigationModeController.ModeChangedListener {
public static final String TAG = "NavigationBar";
private static final boolean DEBUG = false;
@@ -158,6 +162,7 @@
private int mLayoutDirection;
private int mSystemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE;
+ private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
private LightBarController mLightBarController;
private AutoHideController mAutoHideController;
@@ -207,7 +212,7 @@
final ButtonDispatcher backButton = mNavigationBarView.getBackButton();
final boolean useAltBack =
(mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
- if (QuickStepContract.isGesturalMode(getContext()) && !useAltBack) {
+ if (QuickStepContract.isGesturalMode(mNavBarMode) && !useAltBack) {
// If property was changed to hide/show back button, going home will trigger
// launcher to to change the back button alpha to reflect property change
backButton.setVisibility(View.GONE);
@@ -244,13 +249,15 @@
@Inject
public NavigationBarFragment(AccessibilityManagerWrapper accessibilityManagerWrapper,
DeviceProvisionedController deviceProvisionedController, MetricsLogger metricsLogger,
- AssistManager assistManager, OverviewProxyService overviewProxyService) {
+ AssistManager assistManager, OverviewProxyService overviewProxyService,
+ NavigationModeController navigationModeController) {
mAccessibilityManagerWrapper = accessibilityManagerWrapper;
mDeviceProvisionedController = deviceProvisionedController;
mMetricsLogger = metricsLogger;
mAssistManager = assistManager;
mAssistantAvailable = mAssistManager.getAssistInfoForUser(UserHandle.USER_CURRENT) != null;
mOverviewProxyService = overviewProxyService;
+ mNavBarMode = navigationModeController.addListener(this);
}
// ----- Fragment Lifecycle Callbacks -----
@@ -867,6 +874,25 @@
}
private void updateAccessibilityServicesState(AccessibilityManager accessibilityManager) {
+ boolean[] feedbackEnabled = new boolean[1];
+ int flags = getA11yButtonState(feedbackEnabled);
+
+ mNavigationBarView.getRotateSuggestionButton()
+ .setAccessibilityFeedbackEnabled(feedbackEnabled[0]);
+
+ boolean clickable = (flags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
+ boolean longClickable = (flags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
+ mNavigationBarView.setAccessibilityButtonState(clickable, longClickable);
+ mOverviewProxyService.setSystemUiStateFlag(SYSUI_STATE_A11Y_BUTTON_CLICKABLE, clickable);
+ mOverviewProxyService.setSystemUiStateFlag(
+ SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE, longClickable);
+ }
+
+ /**
+ * Returns the system UI flags corresponding the the current accessibility button state
+ * @param outFeedbackEnabled if non-null, sets it to true if accessibility feedback is enabled.
+ */
+ public int getA11yButtonState(@Nullable boolean[] outFeedbackEnabled) {
int requestingServices = 0;
try {
if (Settings.Secure.getIntForUser(mContentResolver,
@@ -881,7 +907,7 @@
// AccessibilityManagerService resolves services for the current user since the local
// AccessibilityManager is created from a Context with the INTERACT_ACROSS_USERS permission
final List<AccessibilityServiceInfo> services =
- accessibilityManager.getEnabledAccessibilityServiceList(
+ mAccessibilityManager.getEnabledAccessibilityServiceList(
AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
for (int i = services.size() - 1; i >= 0; --i) {
AccessibilityServiceInfo info = services.get(i);
@@ -895,19 +921,19 @@
}
}
- mNavigationBarView.getRotateSuggestionButton()
- .setAccessibilityFeedbackEnabled(feedbackEnabled);
+ if (outFeedbackEnabled != null) {
+ outFeedbackEnabled[0] = feedbackEnabled;
+ }
- final boolean showAccessibilityButton = requestingServices >= 1;
- final boolean targetSelection = requestingServices >= 2;
- mNavigationBarView.setAccessibilityButtonState(showAccessibilityButton, targetSelection);
+ return (requestingServices >= 1 ? SYSUI_STATE_A11Y_BUTTON_CLICKABLE : 0)
+ | (requestingServices >= 2 ? SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE : 0);
}
private void sendAssistantAvailability(boolean available) {
if (mOverviewProxyService.getProxy() != null) {
try {
mOverviewProxyService.getProxy().onAssistantAvailable(available
- && QuickStepContract.isGesturalMode(getContext()));
+ && QuickStepContract.isGesturalMode(mNavBarMode));
} catch (RemoteException e) {
Log.w(TAG, "Unable to send assistant availability data to launcher");
}
@@ -963,6 +989,11 @@
mNavigationBarView.getBarTransitions().transitionTo(mNavigationBarMode, anim);
}
+ @Override
+ public void onNavigationModeChanged(int mode) {
+ mNavBarMode = mode;
+ }
+
public void disableAnimationsDuringHide(long delay) {
mNavigationBarView.setLayoutTransitionsEnabled(false);
mNavigationBarView.postDelayed(() -> mNavigationBarView.setLayoutTransitionsEnabled(true),
@@ -1019,7 +1050,7 @@
if (Intent.ACTION_SCREEN_ON.equals(action)) {
// Enabled and screen is on, start it again if enabled
- if (NavBarTintController.isEnabled(getContext())) {
+ if (NavBarTintController.isEnabled(getContext(), mNavBarMode)) {
mNavigationBarView.getTintController().start();
}
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
index a522ed1c..404c07b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
@@ -15,6 +15,7 @@
package com.android.systemui.statusbar.phone;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
import android.annotation.Nullable;
import android.content.Context;
@@ -49,7 +50,8 @@
import java.util.Objects;
public class NavigationBarInflaterView extends FrameLayout
- implements Tunable, PluginListener<NavBarButtonProvider> {
+ implements Tunable, PluginListener<NavBarButtonProvider>,
+ NavigationModeController.ModeChangedListener {
private static final String TAG = "NavBarInflater";
@@ -102,11 +104,13 @@
private boolean mUsingCustomLayout;
private OverviewProxyService mOverviewProxyService;
+ private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
public NavigationBarInflaterView(Context context, AttributeSet attrs) {
super(context, attrs);
createInflaters();
mOverviewProxyService = Dependency.get(OverviewProxyService.class);
+ mNavBarMode = Dependency.get(NavigationModeController.class).addListener(this);
}
@VisibleForTesting
@@ -138,7 +142,7 @@
}
protected String getDefaultLayout() {
- final int defaultResource = QuickStepContract.isGesturalMode(getContext())
+ final int defaultResource = QuickStepContract.isGesturalMode(mNavBarMode)
? R.string.config_navBarLayoutHandle
: mOverviewProxyService.shouldShowSwipeUpUI()
? R.string.config_navBarLayoutQuickstep
@@ -147,6 +151,12 @@
}
@Override
+ public void onNavigationModeChanged(int mode) {
+ mNavBarMode = mode;
+ onLikelyDefaultLayoutChange();
+ }
+
+ @Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
Dependency.get(TunerService.class).addTunable(this, NAV_BAR_VIEWS, NAV_BAR_LEFT,
@@ -159,6 +169,7 @@
protected void onDetachedFromWindow() {
Dependency.get(TunerService.class).removeTunable(this);
Dependency.get(PluginManager.class).removePluginListener(this);
+ Dependency.get(NavigationModeController.class).removeListener(this);
super.onDetachedFromWindow();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
index 8ff6cc9..4e4a6aec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.phone;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
+
import static com.android.systemui.statusbar.phone.NavBarTintController.DEFAULT_COLOR_ADAPT_TRANSITION_TIME;
import static com.android.systemui.statusbar.phone.NavBarTintController.MIN_COLOR_ADAPT_TRANSITION_TIME;
@@ -46,6 +48,7 @@
private boolean mLightsOut;
private boolean mAutoDim;
private View mNavButtons;
+ private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
private final Handler mHandler = Handler.getMain();
private final IWallpaperVisibilityListener mWallpaperVisibilityListener =
@@ -176,9 +179,13 @@
@Override
public int getTintAnimationDuration() {
- if (NavBarTintController.isEnabled(mView.getContext())) {
+ if (NavBarTintController.isEnabled(mView.getContext(), mNavBarMode)) {
return Math.max(DEFAULT_COLOR_ADAPT_TRANSITION_TIME, MIN_COLOR_ADAPT_TRANSITION_TIME);
}
return LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION;
}
+
+ public void onNavigationModeChanged(int mode) {
+ mNavBarMode = mode;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 411378f..6f1e161 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -16,8 +16,8 @@
package com.android.systemui.statusbar.phone;
-import static android.content.Intent.ACTION_OVERLAY_CHANGED;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_SHOW_OVERVIEW_BUTTON;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
@@ -31,10 +31,7 @@
import android.animation.ValueAnimator;
import android.annotation.DrawableRes;
import android.app.StatusBarManager;
-import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Point;
@@ -84,7 +81,8 @@
import java.io.PrintWriter;
import java.util.function.Consumer;
-public class NavigationBarView extends FrameLayout implements PluginListener<NavGesture> {
+public class NavigationBarView extends FrameLayout implements PluginListener<NavGesture>,
+ NavigationModeController.ModeChangedListener {
final static boolean DEBUG = false;
final static String TAG = "StatusBar/NavBarView";
@@ -104,6 +102,7 @@
boolean mLongClickableAccessibilityButton;
int mDisabledFlags = 0;
int mNavigationIconHints = 0;
+ private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
private Rect mHomeButtonBounds = new Rect();
private Rect mBackButtonBounds = new Rect();
@@ -234,13 +233,6 @@
}
};
- private BroadcastReceiver mOverlaysChangedReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- onOverlaysChanged();
- }
- };
-
public NavigationBarView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -287,7 +279,6 @@
mEdgeBackGestureHandler = new EdgeBackGestureHandler(context, mOverviewProxyService);
mTintController = new NavBarTintController(this, getLightTransitionsController());
-
}
public NavBarTintController getTintController() {
@@ -470,7 +461,7 @@
return;
}
- if (QuickStepContract.isGesturalMode(getContext())) {
+ if (QuickStepContract.isGesturalMode(mNavBarMode)) {
drawable.setRotation(degrees);
return;
}
@@ -578,7 +569,7 @@
mBarTransitions.reapplyDarkIntensity();
- boolean disableHome = QuickStepContract.isGesturalMode(getContext())
+ boolean disableHome = QuickStepContract.isGesturalMode(mNavBarMode)
|| ((mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0);
// TODO(b/113914868): investigation log for disappearing home button
@@ -588,7 +579,7 @@
// Always disable recents when alternate car mode UI is active and for secondary displays.
boolean disableRecent = isRecentsButtonDisabled();
- boolean disableBack = !useAltBack && (QuickStepContract.isGesturalMode(getContext())
+ boolean disableBack = !useAltBack && (QuickStepContract.isGesturalMode(mNavBarMode)
|| ((mDisabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0));
// When screen pinning, don't hide back and home when connected service or back and
@@ -724,7 +715,7 @@
}
public void updateWindowTouchable() {
- boolean touchable = mImeVisible || !QuickStepContract.isGesturalMode(getContext());
+ boolean touchable = mImeVisible || !QuickStepContract.isGesturalMode(mNavBarMode);
setWindowFlag(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, !touchable);
}
@@ -746,16 +737,21 @@
wm.updateViewLayout(navbarView, lp);
}
- private void onOverlaysChanged() {
- mNavigationInflaterView.setNavigationBarLayout(null);
+ @Override
+ public void onNavigationModeChanged(int mode) {
+ mNavBarMode = mode;
+ mBarTransitions.onNavigationModeChanged(mNavBarMode);
+ mEdgeBackGestureHandler.onNavigationModeChanged(mNavBarMode);
+ mRecentsOnboarding.onNavigationModeChanged(mNavBarMode);
// Color adaption is tied with showing home handle, only avaliable if visible
- if (QuickStepContract.isGesturalMode(getContext())) {
+ mTintController.onNavigationModeChanged(mNavBarMode);
+ if (QuickStepContract.isGesturalMode(mNavBarMode)) {
mTintController.start();
} else {
mTintController.stop();
}
- mEdgeBackGestureHandler.onOverlaysChanged();
+ updateWindowTouchable();
}
public void setMenuVisibility(final boolean show) {
@@ -938,7 +934,7 @@
"onMeasure: (%dx%d) old: (%dx%d)", w, h, getMeasuredWidth(), getMeasuredHeight()));
final boolean newVertical = w > 0 && h > w
- && !QuickStepContract.isGesturalMode(getContext());
+ && !QuickStepContract.isGesturalMode(mNavBarMode);
if (newVertical != mIsVertical) {
mIsVertical = newVertical;
if (DEBUG) {
@@ -949,7 +945,7 @@
notifyVerticalChangedListener(newVertical);
}
- if (QuickStepContract.isGesturalMode(getContext())) {
+ if (QuickStepContract.isGesturalMode(mNavBarMode)) {
// Update the nav bar background to match the height of the visible nav bar
int height = mIsVertical
? getResources().getDimensionPixelSize(
@@ -1048,11 +1044,10 @@
onPluginDisconnected(null); // Create default gesture helper
Dependency.get(PluginManager.class).addPluginListener(this,
NavGesture.class, false /* Only one */);
+ int navBarMode = Dependency.get(NavigationModeController.class).addListener(this);
+ onNavigationModeChanged(navBarMode);
setUpSwipeUpOnboarding(isQuickStepSwipeUpEnabled());
- IntentFilter filter = new IntentFilter(ACTION_OVERLAY_CHANGED);
- filter.addDataScheme("package");
- getContext().registerReceiver(mOverlaysChangedReceiver, filter);
mEdgeBackGestureHandler.onNavBarAttached();
updateWindowTouchable();
}
@@ -1061,6 +1056,7 @@
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
Dependency.get(PluginManager.class).removePluginListener(this);
+ Dependency.get(NavigationModeController.class).removeListener(this);
if (mGestureHelper != null) {
mGestureHelper.destroy();
}
@@ -1068,8 +1064,6 @@
for (int i = 0; i < mButtonDispatchers.size(); ++i) {
mButtonDispatchers.valueAt(i).onDestroy();
}
-
- getContext().unregisterReceiver(mOverlaysChangedReceiver);
mEdgeBackGestureHandler.onNavBarDetached();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
new file mode 100644
index 0000000..a00feeb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
@@ -0,0 +1,146 @@
+/*
+ * 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.systemui.statusbar.phone;
+
+import static android.content.Intent.ACTION_OVERLAY_CHANGED;
+import static android.content.Intent.ACTION_USER_SWITCHED;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.om.IOverlayManager;
+import android.content.pm.PackageManager;
+import android.content.res.ApkAssets;
+import android.os.PatternMatcher;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.systemui.Dumpable;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Controller for tracking the current navigation bar mode.
+ */
+@Singleton
+public class NavigationModeController implements Dumpable {
+
+ private static final String TAG = NavigationModeController.class.getName();
+ private static final boolean DEBUG = true;
+
+ public interface ModeChangedListener {
+ void onNavigationModeChanged(int mode);
+ }
+
+ private final Context mContext;
+ private final IOverlayManager mOverlayManager;
+
+ private int mMode = NAV_BAR_MODE_3BUTTON;
+ private ArrayList<ModeChangedListener> mListeners = new ArrayList<>();
+
+ private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ context = getCurrentUserContext();
+ int mode = getCurrentInteractionMode(context);
+ mMode = mode;
+ if (DEBUG) {
+ Log.e(TAG, "ACTION_OVERLAY_CHANGED: mode=" + mMode
+ + " contextUser=" + context.getUserId());
+ dumpAssetPaths(context);
+ }
+
+ for (int i = 0; i < mListeners.size(); i++) {
+ mListeners.get(i).onNavigationModeChanged(mode);
+ }
+ }
+ };
+
+ @Inject
+ public NavigationModeController(Context context) {
+ mContext = context;
+ mOverlayManager = IOverlayManager.Stub.asInterface(
+ ServiceManager.getService(Context.OVERLAY_SERVICE));
+
+ IntentFilter overlayFilter = new IntentFilter(ACTION_OVERLAY_CHANGED);
+ overlayFilter.addDataScheme("package");
+ overlayFilter.addDataSchemeSpecificPart("android", PatternMatcher.PATTERN_LITERAL);
+ mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, overlayFilter, null, null);
+
+ IntentFilter userFilter = new IntentFilter(ACTION_USER_SWITCHED);
+ mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, userFilter, null, null);
+
+ mMode = getCurrentInteractionMode(getCurrentUserContext());
+ }
+
+ public int addListener(ModeChangedListener listener) {
+ mListeners.add(listener);
+ return getCurrentInteractionMode(mContext);
+ }
+
+ public void removeListener(ModeChangedListener listener) {
+ mListeners.remove(listener);
+ }
+
+ private int getCurrentInteractionMode(Context context) {
+ int mode = context.getResources().getInteger(
+ com.android.internal.R.integer.config_navBarInteractionMode);
+ return mode;
+ }
+
+ private Context getCurrentUserContext() {
+ int userId = ActivityManagerWrapper.getInstance().getCurrentUserId();
+ if (DEBUG) {
+ Log.d(TAG, "getCurrentUserContext: contextUser=" + mContext.getUserId()
+ + " currentUser=" + userId);
+ }
+ if (mContext.getUserId() == userId) {
+ return mContext;
+ }
+ try {
+ return mContext.createPackageContextAsUser(mContext.getPackageName(),
+ 0 /* flags */, UserHandle.of(userId));
+ } catch (PackageManager.NameNotFoundException e) {
+ // Never happens for the sysui package
+ return null;
+ }
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("NavigationModeController:");
+ pw.println(" mode=" + mMode);
+ dumpAssetPaths(getCurrentUserContext());
+ }
+
+ private void dumpAssetPaths(Context context) {
+ Log.d(TAG, "assetPaths=");
+ ApkAssets[] assets = context.getResources().getAssets().getApkAssets();
+ for (ApkAssets a : assets) {
+ Log.d(TAG, " " + a.getAssetPath());
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java
index 47a1054..9988c85 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.phone;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
+
import android.annotation.IntDef;
import android.content.ComponentCallbacks;
import android.content.Context;
@@ -119,7 +121,7 @@
mListener.onHomeButtonVisibilityChanged(!hideHomeButton());
} else if (path.endsWith(NAV_COLOR_ADAPT_ENABLE_SETTING)) {
mListener.onColorAdaptChanged(
- NavBarTintController.isEnabled(mContext));
+ NavBarTintController.isEnabled(mContext, NAV_BAR_MODE_GESTURAL));
} else if (path.endsWith(SHOW_HOME_HANDLE_SETTING)) {
mListener.onHomeHandleVisiblilityChanged(showHomeHandle());
} else if (path.endsWith(ENABLE_ASSISTANT_GESTURE)) {
@@ -132,7 +134,8 @@
* @return the width for edge swipe
*/
public int getEdgeSensitivityWidth() {
- return QuickStepContract.getEdgeSensitivityWidth(mContext);
+ return mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.config_backGestureInset);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index cc159b4..002313c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -71,7 +71,7 @@
*/
public class StatusBarKeyguardViewManager implements RemoteInputController.Callback,
StatusBarStateController.StateListener, ConfigurationController.ConfigurationListener,
- NotificationPanelView.PanelExpansionListener {
+ NotificationPanelView.PanelExpansionListener, NavigationModeController.ModeChangedListener {
// When hiding the Keyguard with timing supplied from WindowManager, better be early than late.
private static final long HIDE_TIMING_CORRECTION_MS = - 16 * 3;
@@ -192,10 +192,11 @@
mViewMediatorCallback = callback;
mLockPatternUtils = lockPatternUtils;
mStatusBarWindowController = Dependency.get(StatusBarWindowController.class);
- mGesturalNav = QuickStepContract.isGesturalMode(context);
KeyguardUpdateMonitor.getInstance(context).registerCallback(mUpdateMonitorCallback);
mStatusBarStateController.addCallback(this);
Dependency.get(ConfigurationController.class).addCallback(this);
+ mLastGesturalNav = QuickStepContract.isGesturalMode(
+ Dependency.get(NavigationModeController.class).addListener(this));
mDockManager = SysUiServiceProvider.getComponent(context, DockManager.class);
if (mDockManager != null) {
mDockManager.addListener(mDockEventListener);
@@ -587,8 +588,8 @@
}
@Override
- public void onOverlayChanged() {
- boolean gesturalNav = QuickStepContract.isGesturalMode(mContext);
+ public void onNavigationModeChanged(int mode) {
+ boolean gesturalNav = QuickStepContract.isGesturalMode(mode);
if (gesturalNav != mGesturalNav) {
mGesturalNav = gesturalNav;
updateStates();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/SmallClockPositionTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/clock/SmallClockPositionTest.kt
new file mode 100644
index 0000000..f4d59cc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/clock/SmallClockPositionTest.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.keyguard.clock
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class SmallClockPositionTest : SysuiTestCase() {
+
+ private val statusBarHeight = 100
+ private val lockPadding = 15
+ private val lockHeight = 35
+ private val burnInY = 20
+
+ private lateinit var position: SmallClockPosition
+
+ @Before
+ fun setUp() {
+ position = SmallClockPosition(statusBarHeight, lockPadding, lockHeight, burnInY)
+ }
+
+ @Test
+ fun loadResources() {
+ // Cover constructor taking Resources object.
+ position = SmallClockPosition(context.resources)
+ position.setDarkAmount(1f)
+ assertThat(position.preferredY).isGreaterThan(0)
+ }
+
+ @Test
+ fun darkPosition() {
+ // GIVEN on AOD
+ position.setDarkAmount(1f)
+ // THEN Y position is statusBarHeight + lockPadding + burnInY (100 + 15 + 20 = 135)
+ assertThat(position.preferredY).isEqualTo(135)
+ }
+
+ @Test
+ fun lockPosition() {
+ // GIVEN on lock screen
+ position.setDarkAmount(0f)
+ // THEN Y position is statusBarHeight + lockPadding + lockHeight + lockPadding
+ // (100 + 15 + 35 + 15 = 165)
+ assertThat(position.preferredY).isEqualTo(165)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SizeCompatModeActivityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/SizeCompatModeActivityControllerTest.java
index 587005e..3ea7150 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SizeCompatModeActivityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SizeCompatModeActivityControllerTest.java
@@ -56,6 +56,7 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ doReturn(true).when(mMockButton).show();
mController = new SizeCompatModeActivityController(mMockAm) {
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
index 02731c0..25995eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
@@ -238,7 +238,7 @@
IMPORTANCE_DEFAULT, true);
final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
assertEquals(VISIBLE, nameView.getVisibility());
- assertTrue(nameView.getText().toString().contains("Other"));
+ assertTrue(nameView.getText().toString().contains("Proxied"));
final TextView dividerView = mNotificationInfo.findViewById(R.id.pkg_divider);
assertEquals(VISIBLE, dividerView.getVisibility());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
index 575ff16..8fe0c70 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
@@ -18,6 +18,7 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -31,6 +32,7 @@
import android.content.res.ColorStateList;
import android.graphics.Color;
+import android.os.Handler;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.ViewGroup;
@@ -42,6 +44,7 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardHostView;
import com.android.keyguard.KeyguardSecurityModel;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.DejankUtils;
import com.android.systemui.SysuiTestCase;
@@ -53,6 +56,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -75,6 +79,10 @@
private ViewTreeObserver mViewTreeObserver;
@Mock
private KeyguardBouncer.BouncerExpansionCallback mExpansionCallback;
+ @Mock
+ private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @Mock
+ private Handler mHandler;
private KeyguardBouncer mBouncer;
@@ -88,7 +96,7 @@
when(mKeyguardHostView.getHeight()).thenReturn(500);
mBouncer = new KeyguardBouncer(getContext(), mViewMediatorCallback,
mLockPatternUtils, container, mDismissCallbackRegistry, mFalsingManager,
- mExpansionCallback) {
+ mExpansionCallback, mKeyguardUpdateMonitor, mHandler) {
@Override
protected void inflateView() {
super.inflateView();
@@ -366,4 +374,22 @@
when(mKeyguardHostView.hasDismissActions()).thenReturn(true);
Assert.assertTrue("Action should exist", mBouncer.willDismissWithAction());
}
+
+ @Test
+ public void testShow_delaysIfFaceAuthIsRunning() {
+ when(mKeyguardUpdateMonitor.isFaceDetectionRunning()).thenReturn(true);
+ mBouncer.show(true /* reset */);
+
+ ArgumentCaptor<Runnable> showRunnable = ArgumentCaptor.forClass(Runnable.class);
+ verify(mHandler).postDelayed(showRunnable.capture(),
+ eq(KeyguardBouncer.BOUNCER_FACE_DELAY));
+
+ mBouncer.hide(false /* destroyView */);
+ verify(mHandler).removeCallbacks(eq(showRunnable.getValue()));
+ }
+
+ @Test
+ public void testRegisterUpdateMonitorCallback() {
+ verify(mKeyguardUpdateMonitor).registerCallback(any());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
index f191cab..f8394f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -316,6 +316,7 @@
positionClock();
// THEN the clock Y position is the preferred Y position.
assertThat(mClockPosition.clockY).isEqualTo(100);
+ assertThat(mClockPosition.clockAlpha).isEqualTo(OPAQUE);
}
@Test
@@ -333,6 +334,7 @@
// THEN the clock Y position is the middle of the screen (SCREEN_HEIGHT / 2) and not
// preferred.
assertThat(mClockPosition.clockY).isEqualTo(1000);
+ assertThat(mClockPosition.clockAlpha).isEqualTo(OPAQUE);
}
@Test
@@ -349,6 +351,7 @@
positionClock();
// THEN the clock Y position is the middle of the screen (SCREEN_HEIGHT / 2).
assertThat(mClockPosition.clockY).isEqualTo(1000);
+ assertThat(mClockPosition.clockAlpha).isEqualTo(OPAQUE);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
index 3ae57e3..ad9c729 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
@@ -214,7 +214,8 @@
deviceProvisionedController,
new MetricsLogger(),
mock(AssistManager.class),
- mOverviewProxyService);
+ mOverviewProxyService,
+ mock(NavigationModeController.class));
}
private class HostCallbacksForExternalDisplay extends
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index e885684..6573c3b 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -1031,7 +1031,6 @@
mAppOps.checkPackage(uid, packageName);
} catch (SecurityException e) {
Slog.w(TAG, "checkPackage(): " + packageName + " does not belong to uid " + uid);
- android.util.EventLog.writeEvent(0x534e4554, "120574260", uid, "");
throw new SecurityException(e.getMessage());
}
}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index ee8bb05..f5710e3 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -277,7 +277,8 @@
private Tethering mTethering;
- private final PermissionMonitor mPermissionMonitor;
+ @VisibleForTesting
+ protected final PermissionMonitor mPermissionMonitor;
private KeyStore mKeyStore;
@@ -832,13 +833,13 @@
public ConnectivityService(Context context, INetworkManagementService netManager,
INetworkStatsService statsService, INetworkPolicyManager policyManager) {
this(context, netManager, statsService, policyManager,
- getDnsResolver(), new IpConnectivityLog());
+ getDnsResolver(), new IpConnectivityLog(), NetdService.getInstance());
}
@VisibleForTesting
protected ConnectivityService(Context context, INetworkManagementService netManager,
INetworkStatsService statsService, INetworkPolicyManager policyManager,
- IDnsResolver dnsresolver, IpConnectivityLog logger) {
+ IDnsResolver dnsresolver, IpConnectivityLog logger, INetd netd) {
if (DBG) log("ConnectivityService starting up");
mSystemProperties = getSystemProperties();
@@ -878,7 +879,7 @@
mDnsResolver = checkNotNull(dnsresolver, "missing IDnsResolver");
mProxyTracker = makeProxyTracker();
- mNetd = NetdService.getInstance();
+ mNetd = netd;
mKeyStore = KeyStore.getInstance();
mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
@@ -964,7 +965,7 @@
mTethering = makeTethering();
- mPermissionMonitor = new PermissionMonitor(mContext, mNMS, mNetd);
+ mPermissionMonitor = new PermissionMonitor(mContext, mNetd);
// Set up the listener for user state for creating user VPNs.
// Should run on mHandler to avoid any races.
@@ -2446,6 +2447,13 @@
pw.println("NetworkStackClient logs:");
pw.increaseIndent();
NetworkStackClient.getInstance().dump(pw);
+ pw.decreaseIndent();
+
+ pw.println();
+ pw.println("Permission Monitor:");
+ pw.increaseIndent();
+ mPermissionMonitor.dump(pw);
+ pw.decreaseIndent();
}
private void dumpNetworks(IndentingPrintWriter pw) {
@@ -2854,7 +2862,7 @@
try {
nai.networkMonitor().notifyPrivateDnsChanged(cfg.toParcel());
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ e.rethrowAsRuntimeException();
}
// With Private DNS bypass support, we can proceed to update the
@@ -3024,7 +3032,7 @@
try {
nai.networkMonitor().notifyNetworkDisconnected();
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ e.rethrowAsRuntimeException();
}
mNetworkAgentInfos.remove(nai.messenger);
nai.clatd.update();
@@ -3413,7 +3421,7 @@
try {
nai.networkMonitor().setAcceptPartialConnectivity();
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ e.rethrowAsRuntimeException();
}
}
}
@@ -3449,7 +3457,7 @@
try {
nai.networkMonitor().launchCaptivePortalApp();
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ e.rethrowAsRuntimeException();
}
});
}
@@ -4077,7 +4085,7 @@
try {
nai.networkMonitor().forceReevaluation(uid);
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ e.rethrowAsRuntimeException();
}
}
@@ -5450,7 +5458,7 @@
try {
networkMonitor.start();
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ e.rethrowAsRuntimeException();
}
nai.asyncChannel.connect(mContext, mTrackerHandler, nai.messenger);
NetworkInfo networkInfo = nai.networkInfo;
@@ -5469,6 +5477,11 @@
networkAgent.clatd.fixupLinkProperties(oldLp, newLp);
updateInterfaces(newLp, oldLp, netId, networkAgent.networkCapabilities);
+
+ // update filtering rules, need to happen after the interface update so netd knows about the
+ // new interface (the interface name -> index map becomes initialized)
+ updateVpnFiltering(newLp, oldLp, networkAgent);
+
updateMtu(newLp, oldLp);
// TODO - figure out what to do for clat
// for (LinkProperties lp : newLp.getStackedLinks()) {
@@ -5502,7 +5515,7 @@
try {
networkAgent.networkMonitor().notifyLinkPropertiesChanged(newLp);
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ e.rethrowAsRuntimeException();
}
if (networkAgent.everConnected) {
notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_IP_CHANGED);
@@ -5634,6 +5647,37 @@
}
}
+ private void updateVpnFiltering(LinkProperties newLp, LinkProperties oldLp,
+ NetworkAgentInfo nai) {
+ final String oldIface = oldLp != null ? oldLp.getInterfaceName() : null;
+ final String newIface = newLp != null ? newLp.getInterfaceName() : null;
+ final boolean wasFiltering = requiresVpnIsolation(nai, nai.networkCapabilities, oldLp);
+ final boolean needsFiltering = requiresVpnIsolation(nai, nai.networkCapabilities, newLp);
+
+ if (!wasFiltering && !needsFiltering) {
+ // Nothing to do.
+ return;
+ }
+
+ if (Objects.equals(oldIface, newIface) && (wasFiltering == needsFiltering)) {
+ // Nothing changed.
+ return;
+ }
+
+ final Set<UidRange> ranges = nai.networkCapabilities.getUids();
+ final int vpnAppUid = nai.networkCapabilities.getEstablishingVpnAppUid();
+ // TODO: this create a window of opportunity for apps to receive traffic between the time
+ // when the old rules are removed and the time when new rules are added. To fix this,
+ // make eBPF support two whitelisted interfaces so here new rules can be added before the
+ // old rules are being removed.
+ if (wasFiltering) {
+ mPermissionMonitor.onVpnUidRangesRemoved(oldIface, ranges, vpnAppUid);
+ }
+ if (needsFiltering) {
+ mPermissionMonitor.onVpnUidRangesAdded(newIface, ranges, vpnAppUid);
+ }
+ }
+
private int getNetworkPermission(NetworkCapabilities nc) {
if (!nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)) {
return INetd.PERMISSION_SYSTEM;
@@ -5776,6 +5820,34 @@
}
}
+ /**
+ * Returns whether VPN isolation (ingress interface filtering) should be applied on the given
+ * network.
+ *
+ * Ingress interface filtering enforces that all apps under the given network can only receive
+ * packets from the network's interface (and loopback). This is important for VPNs because
+ * apps that cannot bypass a fully-routed VPN shouldn't be able to receive packets from any
+ * non-VPN interfaces.
+ *
+ * As a result, this method should return true iff
+ * 1. the network is an app VPN (not legacy VPN)
+ * 2. the VPN does not allow bypass
+ * 3. the VPN is fully-routed
+ * 4. the VPN interface is non-null
+ *
+ * @See INetd#firewallAddUidInterfaceRules
+ * @See INetd#firewallRemoveUidInterfaceRules
+ */
+ private boolean requiresVpnIsolation(@NonNull NetworkAgentInfo nai, NetworkCapabilities nc,
+ LinkProperties lp) {
+ if (nc == null || lp == null) return false;
+ return nai.isVPN()
+ && !nai.networkMisc.allowBypass
+ && nc.getEstablishingVpnAppUid() != Process.SYSTEM_UID
+ && lp.getInterfaceName() != null
+ && (lp.hasIPv4DefaultRoute() || lp.hasIPv6DefaultRoute());
+ }
+
private void updateUids(NetworkAgentInfo nai, NetworkCapabilities prevNc,
NetworkCapabilities newNc) {
Set<UidRange> prevRanges = null == prevNc ? null : prevNc.getUids();
@@ -5788,6 +5860,12 @@
newRanges.removeAll(prevRangesCopy);
try {
+ // When updating the VPN uid routing rules, add the new range first then remove the old
+ // range. If old range were removed first, there would be a window between the old
+ // range being removed and the new range being added, during which UIDs contained
+ // in both ranges are not subject to any VPN routing rules. Adding new range before
+ // removing old range works because, unlike the filtering rules below, it's possible to
+ // add duplicate UID routing rules.
if (!newRanges.isEmpty()) {
final UidRange[] addedRangesArray = new UidRange[newRanges.size()];
newRanges.toArray(addedRangesArray);
@@ -5798,9 +5876,31 @@
prevRanges.toArray(removedRangesArray);
mNMS.removeVpnUidRanges(nai.network.netId, removedRangesArray);
}
+ final boolean wasFiltering = requiresVpnIsolation(nai, prevNc, nai.linkProperties);
+ final boolean shouldFilter = requiresVpnIsolation(nai, newNc, nai.linkProperties);
+ final String iface = nai.linkProperties.getInterfaceName();
+ // For VPN uid interface filtering, old ranges need to be removed before new ranges can
+ // be added, due to the range being expanded and stored as invidiual UIDs. For example
+ // the UIDs might be updated from [0, 99999] to ([0, 10012], [10014, 99999]) which means
+ // prevRanges = [0, 99999] while newRanges = [0, 10012], [10014, 99999]. If prevRanges
+ // were added first and then newRanges got removed later, there would be only one uid
+ // 10013 left. A consequence of removing old ranges before adding new ranges is that
+ // there is now a window of opportunity when the UIDs are not subject to any filtering.
+ // Note that this is in contrast with the (more robust) update of VPN routing rules
+ // above, where the addition of new ranges happens before the removal of old ranges.
+ // TODO Fix this window by computing an accurate diff on Set<UidRange>, so the old range
+ // to be removed will never overlap with the new range to be added.
+ if (wasFiltering && !prevRanges.isEmpty()) {
+ mPermissionMonitor.onVpnUidRangesRemoved(iface, prevRanges,
+ prevNc.getEstablishingVpnAppUid());
+ }
+ if (shouldFilter && !newRanges.isEmpty()) {
+ mPermissionMonitor.onVpnUidRangesAdded(iface, newRanges,
+ newNc.getEstablishingVpnAppUid());
+ }
} catch (Exception e) {
// Never crash!
- loge("Exception in updateUids: " + e);
+ loge("Exception in updateUids: ", e);
}
}
@@ -6421,7 +6521,7 @@
networkAgent.networkMonitor().notifyNetworkConnected(
networkAgent.linkProperties, networkAgent.networkCapabilities);
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ e.rethrowAsRuntimeException();
}
scheduleUnvalidatedPrompt(networkAgent);
diff --git a/services/core/java/com/android/server/ExplicitHealthCheckController.java b/services/core/java/com/android/server/ExplicitHealthCheckController.java
index 19ab33e..f7c4aac 100644
--- a/services/core/java/com/android/server/ExplicitHealthCheckController.java
+++ b/services/core/java/com/android/server/ExplicitHealthCheckController.java
@@ -18,6 +18,7 @@
import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_HEALTH_CHECK_PASSED_PACKAGE;
import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_REQUESTED_PACKAGES;
import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_SUPPORTED_PACKAGES;
+import static android.service.watchdog.ExplicitHealthCheckService.PackageConfig;
import android.Manifest;
import android.annotation.MainThread;
@@ -35,7 +36,6 @@
import android.os.UserHandle;
import android.service.watchdog.ExplicitHealthCheckService;
import android.service.watchdog.IExplicitHealthCheckService;
-import android.service.watchdog.PackageInfo;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Slog;
@@ -71,7 +71,7 @@
// To prevent deadlocks between the controller and watchdog threads, we have
// a lock invariant to ALWAYS acquire the PackageWatchdog#mLock before #mLock in this class.
// It's easier to just NOT hold #mLock when calling into watchdog code on this consumer.
- @GuardedBy("mLock") @Nullable private Consumer<List<PackageInfo>> mSupportedConsumer;
+ @GuardedBy("mLock") @Nullable private Consumer<List<PackageConfig>> mSupportedConsumer;
// Called everytime we need to notify the watchdog to sync requests between itself and the
// health check service. In practice, should never be null after it has been #setEnabled.
// To prevent deadlocks between the controller and watchdog threads, we have
@@ -106,7 +106,7 @@
* ensure a happens-before relationship of the set parameters and visibility on other threads.
*/
public void setCallbacks(Consumer<String> passedConsumer,
- Consumer<List<PackageInfo>> supportedConsumer, Runnable notifySyncRunnable) {
+ Consumer<List<PackageConfig>> supportedConsumer, Runnable notifySyncRunnable) {
synchronized (mLock) {
if (mPassedConsumer != null || mSupportedConsumer != null
|| mNotifySyncRunnable != null) {
@@ -146,17 +146,17 @@
return;
}
- getSupportedPackages(supportedPackageInfos -> {
+ getSupportedPackages(supportedPackageConfigs -> {
// Notify the watchdog without lock held
- mSupportedConsumer.accept(supportedPackageInfos);
+ mSupportedConsumer.accept(supportedPackageConfigs);
getRequestedPackages(previousRequestedPackages -> {
synchronized (mLock) {
// Hold lock so requests and cancellations are sent atomically.
// It is important we don't mix requests from multiple threads.
Set<String> supportedPackages = new ArraySet<>();
- for (PackageInfo info : supportedPackageInfos) {
- supportedPackages.add(info.getPackageName());
+ for (PackageConfig config : supportedPackageConfigs) {
+ supportedPackages.add(config.getPackageName());
}
// Note, this may modify newRequestedPackages
newRequestedPackages.retainAll(supportedPackages);
@@ -235,7 +235,7 @@
* Returns the packages that we can request explicit health checks for.
* The packages will be returned to the {@code consumer}.
*/
- private void getSupportedPackages(Consumer<List<PackageInfo>> consumer) {
+ private void getSupportedPackages(Consumer<List<PackageConfig>> consumer) {
synchronized (mLock) {
if (!prepareServiceLocked("get health check supported packages")) {
return;
@@ -244,7 +244,7 @@
Slog.d(TAG, "Getting health check supported packages");
try {
mRemoteService.getSupportedPackages(new RemoteCallback(result -> {
- List<PackageInfo> packages =
+ List<PackageConfig> packages =
result.getParcelableArrayList(EXTRA_SUPPORTED_PACKAGES);
Slog.i(TAG, "Explicit health check supported packages " + packages);
consumer.accept(packages);
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index ae0047f..b1aaa82 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -2149,38 +2149,6 @@
}
}
- private int parsePermission(String permission) {
- if (permission.equals("NETWORK")) {
- return INetd.PERMISSION_NETWORK;
- }
- if (permission.equals("SYSTEM")) {
- return INetd.PERMISSION_SYSTEM;
- }
- return INetd.PERMISSION_NONE;
- }
-
- @Override
- public void setPermission(String permission, int[] uids) {
- mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-
- try {
- mNetdService.networkSetPermissionForUser(parsePermission(permission), uids);
- } catch (RemoteException | ServiceSpecificException e) {
- throw new IllegalStateException(e);
- }
- }
-
- @Override
- public void clearPermission(int[] uids) {
- mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-
- try {
- mNetdService.networkClearPermissionForUser(uids);
- } catch (RemoteException | ServiceSpecificException e) {
- throw new IllegalStateException(e);
- }
- }
-
@Override
public void allowProtect(int uid) {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 7d0d834..b44009f 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -16,6 +16,8 @@
package com.android.server;
+import static android.service.watchdog.ExplicitHealthCheckService.PackageConfig;
+
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.IntDef;
@@ -27,7 +29,6 @@
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
-import android.service.watchdog.PackageInfo;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -453,13 +454,13 @@
}
}
- private void onSupportedPackages(List<PackageInfo> supportedPackages) {
+ private void onSupportedPackages(List<PackageConfig> supportedPackages) {
boolean isStateChanged = false;
Map<String, Long> supportedPackageTimeouts = new ArrayMap<>();
- Iterator<PackageInfo> it = supportedPackages.iterator();
+ Iterator<PackageConfig> it = supportedPackages.iterator();
while (it.hasNext()) {
- PackageInfo info = it.next();
+ PackageConfig info = it.next();
supportedPackageTimeouts.put(info.getPackageName(), info.getHealthCheckTimeoutMillis());
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 6da7f5f..dcb3a22 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -17832,8 +17832,10 @@
synchronized (ActivityManagerService.this) {
final ProcessRecord proc = getProcessRecordLocked(processName, uid,
true /* keepIfLarge */);
- mProcessList.removeProcessLocked(proc, false /* callerWillRestart */,
- true /* allowRestart */, reason);
+ if (proc != null) {
+ mProcessList.removeProcessLocked(proc, false /* callerWillRestart */,
+ true /* allowRestart */, reason);
+ }
}
}
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/AuthenticationClient.java
index 91da7af..74b7221 100644
--- a/services/core/java/com/android/server/biometrics/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/AuthenticationClient.java
@@ -104,6 +104,7 @@
public boolean onError(long deviceId, int error, int vendorCode) {
if (!shouldFrameworkHandleLockout()) {
switch (error) {
+ case BiometricConstants.BIOMETRIC_ERROR_TIMEOUT:
case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT:
case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT:
if (mStarted) {
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 4c59e60..6933ee8 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -306,11 +306,7 @@
}
case MSG_ON_AUTHENTICATION_FAILED: {
- SomeArgs args = (SomeArgs) msg.obj;
- handleAuthenticationFailed(
- args.argi1 /* cookie */,
- (boolean) args.arg1 /* requireConfirmation */);
- args.recycle();
+ handleAuthenticationFailed((String) msg.obj /* failureReason */);
break;
}
@@ -567,19 +563,24 @@
@Override
public void onAuthenticationFailed(int cookie, boolean requireConfirmation)
throws RemoteException {
- SomeArgs args = SomeArgs.obtain();
- args.argi1 = cookie;
- args.arg1 = requireConfirmation;
- mHandler.obtainMessage(MSG_ON_AUTHENTICATION_FAILED, args).sendToTarget();
+ String failureReason = getContext().getString(R.string.biometric_not_recognized);
+ mHandler.obtainMessage(MSG_ON_AUTHENTICATION_FAILED, failureReason).sendToTarget();
}
@Override
public void onError(int cookie, int error, String message) throws RemoteException {
- SomeArgs args = SomeArgs.obtain();
- args.argi1 = cookie;
- args.argi2 = error;
- args.arg1 = message;
- mHandler.obtainMessage(MSG_ON_ERROR, args).sendToTarget();
+ // Determine if error is hard or soft error. Certain errors (such as TIMEOUT) are
+ // soft errors and we should allow the user to try authenticating again instead of
+ // dismissing BiometricPrompt.
+ if (error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT) {
+ mHandler.obtainMessage(MSG_ON_AUTHENTICATION_FAILED, message).sendToTarget();
+ } else {
+ SomeArgs args = SomeArgs.obtain();
+ args.argi1 = cookie;
+ args.argi2 = error;
+ args.arg1 = message;
+ mHandler.obtainMessage(MSG_ON_ERROR, args).sendToTarget();
+ }
}
@Override
@@ -1151,13 +1152,13 @@
// Notify SysUI that the biometric has been authenticated. SysUI already knows
// the implicit/explicit state and will react accordingly.
- mStatusBarService.onBiometricAuthenticated(true);
+ mStatusBarService.onBiometricAuthenticated(true, null /* failureReason */);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
}
- private void handleAuthenticationFailed(int cookie, boolean requireConfirmation) {
+ private void handleAuthenticationFailed(String failureReason) {
try {
// Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded
// after user dismissed/canceled dialog).
@@ -1166,7 +1167,7 @@
return;
}
- mStatusBarService.onBiometricAuthenticated(false);
+ mStatusBarService.onBiometricAuthenticated(false, failureReason);
// TODO: This logic will need to be updated if BP is multi-modal
if ((mCurrentAuthSession.mModality & TYPE_FACE) != 0) {
diff --git a/services/core/java/com/android/server/connectivity/PermissionMonitor.java b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
index b6946023..f8582cd 100644
--- a/services/core/java/com/android/server/connectivity/PermissionMonitor.java
+++ b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
@@ -37,22 +37,27 @@
import android.content.pm.PackageManagerInternal;
import android.content.pm.UserInfo;
import android.net.INetd;
+import android.net.UidRange;
import android.os.Build;
-import android.os.INetworkManagementService;
import android.os.RemoteException;
+import android.os.ServiceSpecificException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.system.OsConstants;
import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseIntArray;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.server.LocalServices;
import com.android.server.SystemConfig;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -60,6 +65,7 @@
import java.util.Map.Entry;
import java.util.Set;
+
/**
* A utility class to inform Netd of UID permisisons.
* Does a mass update at boot and then monitors for app install/remove.
@@ -73,18 +79,29 @@
protected static final Boolean NETWORK = Boolean.FALSE;
private static final int VERSION_Q = Build.VERSION_CODES.Q;
- private final Context mContext;
private final PackageManager mPackageManager;
private final UserManager mUserManager;
- private final INetworkManagementService mNMS;
private final INetd mNetd;
// Values are User IDs.
+ @GuardedBy("this")
private final Set<Integer> mUsers = new HashSet<>();
- // Keys are App IDs. Values are true for SYSTEM permission and false for NETWORK permission.
+ // Keys are app uids. Values are true for SYSTEM permission and false for NETWORK permission.
+ @GuardedBy("this")
private final Map<Integer, Boolean> mApps = new HashMap<>();
+ // Keys are active non-bypassable and fully-routed VPN's interface name, Values are uid ranges
+ // for apps under the VPN
+ @GuardedBy("this")
+ private final Map<String, Set<UidRange>> mVpnUidRanges = new HashMap<>();
+
+ // A set of appIds for apps across all users on the device. We track appIds instead of uids
+ // directly to reduce its size and also eliminate the need to update this set when user is
+ // added/removed.
+ @GuardedBy("this")
+ private final Set<Integer> mAllApps = new HashSet<>();
+
private class PackageListObserver implements PackageManagerInternal.PackageListObserver {
private int getPermissionForUid(int uid) {
@@ -118,12 +135,10 @@
}
}
- public PermissionMonitor(Context context, INetworkManagementService nms, INetd netdService) {
- mContext = context;
+ public PermissionMonitor(Context context, INetd netd) {
mPackageManager = context.getPackageManager();
- mUserManager = UserManager.get(context);
- mNMS = nms;
- mNetd = netdService;
+ mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ mNetd = netd;
}
// Intended to be called only once at startup, after the system is ready. Installs a broadcast
@@ -151,6 +166,7 @@
if (uid < 0) {
continue;
}
+ mAllApps.add(UserHandle.getAppId(uid));
boolean isNetwork = hasNetworkPermission(app);
boolean hasRestrictedPermission = hasRestrictedNetworkPermission(app);
@@ -270,10 +286,11 @@
}
}
- private int[] toIntArray(List<Integer> list) {
+ private int[] toIntArray(Collection<Integer> list) {
int[] array = new int[list.size()];
- for (int i = 0; i < list.size(); i++) {
- array[i] = list.get(i);
+ int i = 0;
+ for (Integer item : list) {
+ array[i++] = item;
}
return array;
}
@@ -289,11 +306,11 @@
}
try {
if (add) {
- mNMS.setPermission("NETWORK", toIntArray(network));
- mNMS.setPermission("SYSTEM", toIntArray(system));
+ mNetd.networkSetPermissionForUser(INetd.PERMISSION_NETWORK, toIntArray(network));
+ mNetd.networkSetPermissionForUser(INetd.PERMISSION_SYSTEM, toIntArray(system));
} else {
- mNMS.clearPermission(toIntArray(network));
- mNMS.clearPermission(toIntArray(system));
+ mNetd.networkClearPermissionForUser(toIntArray(network));
+ mNetd.networkClearPermissionForUser(toIntArray(system));
}
} catch (RemoteException e) {
loge("Exception when updating permissions: " + e);
@@ -376,6 +393,19 @@
apps.put(uid, permission);
update(mUsers, apps, true);
}
+
+ // If the newly-installed package falls within some VPN's uid range, update Netd with it.
+ // This needs to happen after the mApps update above, since removeBypassingUids() depends
+ // on mApps to check if the package can bypass VPN.
+ for (Map.Entry<String, Set<UidRange>> vpn : mVpnUidRanges.entrySet()) {
+ if (UidRange.containsUid(vpn.getValue(), uid)) {
+ final Set<Integer> changedUids = new HashSet<>();
+ changedUids.add(uid);
+ removeBypassingUids(changedUids, /* vpnAppUid */ -1);
+ updateVpnUids(vpn.getKey(), changedUids, true);
+ }
+ }
+ mAllApps.add(UserHandle.getAppId(uid));
}
/**
@@ -386,8 +416,23 @@
* @hide
*/
public synchronized void onPackageRemoved(int uid) {
- Map<Integer, Boolean> apps = new HashMap<>();
+ // If the newly-removed package falls within some VPN's uid range, update Netd with it.
+ // This needs to happen before the mApps update below, since removeBypassingUids() depends
+ // on mApps to check if the package can bypass VPN.
+ for (Map.Entry<String, Set<UidRange>> vpn : mVpnUidRanges.entrySet()) {
+ if (UidRange.containsUid(vpn.getValue(), uid)) {
+ final Set<Integer> changedUids = new HashSet<>();
+ changedUids.add(uid);
+ removeBypassingUids(changedUids, /* vpnAppUid */ -1);
+ updateVpnUids(vpn.getKey(), changedUids, false);
+ }
+ }
+ // If the package has been removed from all users on the device, clear it form mAllApps.
+ if (mPackageManager.getNameForUid(uid) == null) {
+ mAllApps.remove(UserHandle.getAppId(uid));
+ }
+ Map<Integer, Boolean> apps = new HashMap<>();
Boolean permission = null;
String[] packages = mPackageManager.getPackagesForUid(uid);
if (packages != null && packages.length > 0) {
@@ -443,6 +488,121 @@
}
/**
+ * Called when a new set of UID ranges are added to an active VPN network
+ *
+ * @param iface The active VPN network's interface name
+ * @param rangesToAdd The new UID ranges to be added to the network
+ * @param vpnAppUid The uid of the VPN app
+ */
+ public synchronized void onVpnUidRangesAdded(@NonNull String iface, Set<UidRange> rangesToAdd,
+ int vpnAppUid) {
+ // Calculate the list of new app uids under the VPN due to the new UID ranges and update
+ // Netd about them. Because mAllApps only contains appIds instead of uids, the result might
+ // be an overestimation if an app is not installed on the user on which the VPN is running,
+ // but that's safe.
+ final Set<Integer> changedUids = intersectUids(rangesToAdd, mAllApps);
+ removeBypassingUids(changedUids, vpnAppUid);
+ updateVpnUids(iface, changedUids, true);
+ if (mVpnUidRanges.containsKey(iface)) {
+ mVpnUidRanges.get(iface).addAll(rangesToAdd);
+ } else {
+ mVpnUidRanges.put(iface, new HashSet<UidRange>(rangesToAdd));
+ }
+ }
+
+ /**
+ * Called when a set of UID ranges are removed from an active VPN network
+ *
+ * @param iface The VPN network's interface name
+ * @param rangesToRemove Existing UID ranges to be removed from the VPN network
+ * @param vpnAppUid The uid of the VPN app
+ */
+ public synchronized void onVpnUidRangesRemoved(@NonNull String iface,
+ Set<UidRange> rangesToRemove, int vpnAppUid) {
+ // Calculate the list of app uids that are no longer under the VPN due to the removed UID
+ // ranges and update Netd about them.
+ final Set<Integer> changedUids = intersectUids(rangesToRemove, mAllApps);
+ removeBypassingUids(changedUids, vpnAppUid);
+ updateVpnUids(iface, changedUids, false);
+ Set<UidRange> existingRanges = mVpnUidRanges.getOrDefault(iface, null);
+ if (existingRanges == null) {
+ loge("Attempt to remove unknown vpn uid Range iface = " + iface);
+ return;
+ }
+ existingRanges.removeAll(rangesToRemove);
+ if (existingRanges.size() == 0) {
+ mVpnUidRanges.remove(iface);
+ }
+ }
+
+ /**
+ * Compute the intersection of a set of UidRanges and appIds. Returns a set of uids
+ * that satisfies:
+ * 1. falls into one of the UidRange
+ * 2. matches one of the appIds
+ */
+ private Set<Integer> intersectUids(Set<UidRange> ranges, Set<Integer> appIds) {
+ Set<Integer> result = new HashSet<>();
+ for (UidRange range : ranges) {
+ for (int userId = range.getStartUser(); userId <= range.getEndUser(); userId++) {
+ for (int appId : appIds) {
+ final int uid = UserHandle.getUid(userId, appId);
+ if (range.contains(uid)) {
+ result.add(uid);
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Remove all apps which can elect to bypass the VPN from the list of uids
+ *
+ * An app can elect to bypass the VPN if it hold SYSTEM permission, or if its the active VPN
+ * app itself.
+ *
+ * @param uids The list of uids to operate on
+ * @param vpnAppUid The uid of the VPN app
+ */
+ private void removeBypassingUids(Set<Integer> uids, int vpnAppUid) {
+ uids.remove(vpnAppUid);
+ uids.removeIf(uid -> mApps.getOrDefault(uid, NETWORK) == SYSTEM);
+ }
+
+ /**
+ * Update netd about the list of uids that are under an active VPN connection which they cannot
+ * bypass.
+ *
+ * This is to instruct netd to set up appropriate filtering rules for these uids, such that they
+ * can only receive ingress packets from the VPN's tunnel interface (and loopback).
+ *
+ * @param iface the interface name of the active VPN connection
+ * @param add {@code true} if the uids are to be added to the interface, {@code false} if they
+ * are to be removed from the interface.
+ */
+ private void updateVpnUids(String iface, Set<Integer> uids, boolean add) {
+ if (uids.size() == 0) {
+ return;
+ }
+ try {
+ if (add) {
+ mNetd.firewallAddUidInterfaceRules(iface, toIntArray(uids));
+ } else {
+ mNetd.firewallRemoveUidInterfaceRules(toIntArray(uids));
+ }
+ } catch (ServiceSpecificException e) {
+ // Silently ignore exception when device does not support eBPF, otherwise just log
+ // the exception and do not crash
+ if (e.errorCode != OsConstants.EOPNOTSUPP) {
+ loge("Exception when updating permissions: ", e);
+ }
+ } catch (RemoteException e) {
+ loge("Exception when updating permissions: ", e);
+ }
+ }
+
+ /**
* Called by PackageListObserver when a package is installed/uninstalled. Send the updated
* permission information to netd.
*
@@ -528,6 +688,24 @@
}
}
+ /** Should only be used by unit tests */
+ @VisibleForTesting
+ public Set<UidRange> getVpnUidRanges(String iface) {
+ return mVpnUidRanges.get(iface);
+ }
+
+ /** Dump info to dumpsys */
+ public void dump(IndentingPrintWriter pw) {
+ pw.println("Interface filtering rules:");
+ pw.increaseIndent();
+ for (Map.Entry<String, Set<UidRange>> vpn : mVpnUidRanges.entrySet()) {
+ pw.println("Interface: " + vpn.getKey());
+ pw.println("UIDs: " + vpn.getValue().toString());
+ pw.println();
+ }
+ pw.decreaseIndent();
+ }
+
private static void log(String s) {
if (DBG) {
Log.d(TAG, s);
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 8005dda..0271d3b 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -1604,12 +1604,7 @@
if (mNetworkInfo.isConnected()) {
return !appliesToUid(uid);
} else {
- for (UidRange uidRange : mBlockedUsers) {
- if (uidRange.contains(uid)) {
- return true;
- }
- }
- return false;
+ return UidRange.containsUid(mBlockedUsers, uid);
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index a25f8c0..e08af6f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2470,6 +2470,7 @@
mProtectedPackages = new ProtectedPackages(mContext);
+ mApexManager = new ApexManager(context);
synchronized (mInstallLock) {
// writer
synchronized (mPackages) {
@@ -3295,7 +3296,6 @@
}
}
- mApexManager = new ApexManager(context);
mInstallerService = new PackageInstallerService(context, this, mApexManager);
final Pair<ComponentName, String> instantAppResolverComponent =
getInstantAppResolverLPr();
@@ -11702,6 +11702,11 @@
"Code and resource paths haven't been set correctly");
}
+ if (mApexManager.isApexPackage(pkg.packageName)) {
+ throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE,
+ pkg.packageName + " is an APEX package and can't be installed as an APK.");
+ }
+
// Make sure we're not adding any bogus keyset info
final KeySetManagerService ksms = mSettings.mKeySetManagerService;
ksms.assertScannedPackageValid(pkg);
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index af009ec..a5656c3 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -610,11 +610,11 @@
}
@Override
- public void onBiometricAuthenticated(boolean authenticated) {
+ public void onBiometricAuthenticated(boolean authenticated, String failureReason) {
enforceBiometricDialog();
if (mBar != null) {
try {
- mBar.onBiometricAuthenticated(authenticated);
+ mBar.onBiometricAuthenticated(authenticated, failureReason);
} catch (RemoteException ex) {
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityDisplay.java b/services/core/java/com/android/server/wm/ActivityDisplay.java
index 9d6efb4..637ad03 100644
--- a/services/core/java/com/android/server/wm/ActivityDisplay.java
+++ b/services/core/java/com/android/server/wm/ActivityDisplay.java
@@ -1323,14 +1323,17 @@
}
}
- /** Returns true if the focus activity was adjusted to the home stack top activity. */
- boolean moveHomeActivityToTop(String reason) {
+ /**
+ * Moves the focusable home activity to top. If there is no such activity, the home stack will
+ * still move to top.
+ */
+ void moveHomeActivityToTop(String reason) {
final ActivityRecord top = getHomeActivity();
if (top == null) {
- return false;
+ moveHomeStackToFront(reason);
+ return;
}
top.moveFocusableActivityToTop(reason);
- return true;
}
@Nullable
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index ed3ec94..4278860 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -39,6 +39,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.app.WindowConfiguration.activityTypeToString;
import static android.content.Intent.ACTION_MAIN;
import static android.content.Intent.CATEGORY_HOME;
@@ -417,6 +418,9 @@
private final Configuration mTmpConfig = new Configuration();
private final Rect mTmpBounds = new Rect();
+ // Token for targeting this activity for assist purposes.
+ final Binder assistToken = new Binder();
+
private static String startingWindowStateToString(int state) {
switch (state) {
case STARTING_WINDOW_NOT_SHOWN:
@@ -2794,7 +2798,7 @@
* @return {@code true} if this activity is declared as non-resizable and fixed orientation or
* aspect ratio.
*/
- private boolean shouldUseSizeCompatMode() {
+ boolean shouldUseSizeCompatMode() {
return !isResizeable() && (info.isFixedOrientation() || info.hasFixedAspectRatio())
// The configuration of non-standard type should be enforced by system.
&& isActivityTypeStandard()
@@ -2803,72 +2807,68 @@
// TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
private void updateOverrideConfiguration() {
- final boolean shouldUseSizeCompatMode = shouldUseSizeCompatMode();
- if (shouldUseSizeCompatMode) {
- if (!matchParentBounds()) {
- // The override configuration is set only once in size compatible mode.
+ final Configuration overrideConfig = mTmpConfig;
+ if (shouldUseSizeCompatMode()) {
+ if (mCompatDisplayInsets != null) {
+ // The override configuration is set only once in size compatibility mode.
return;
}
- if (!hasProcess() && !isConfigurationCompatible(task.getConfiguration())) {
+ final Configuration parentConfig = getParent().getConfiguration();
+ if (!hasProcess() && !isConfigurationCompatible(parentConfig)) {
// Don't compute when launching in fullscreen and the fixed orientation is not the
// current orientation. It is more accurately to compute the override bounds from
// the updated configuration after the fixed orientation is applied.
return;
}
- }
- computeBounds(mTmpBounds);
+ // Ensure the screen related fields are set. It is used to prevent activity relaunch
+ // when moving between displays. For screenWidthDp and screenWidthDp, because they
+ // are relative to bounds and density, they will be calculated in
+ // {@link TaskRecord#computeConfigResourceOverrides} and the result will also be
+ // relatively fixed.
+ overrideConfig.unset();
+ overrideConfig.colorMode = parentConfig.colorMode;
+ overrideConfig.densityDpi = parentConfig.densityDpi;
+ overrideConfig.screenLayout = parentConfig.screenLayout
+ & (Configuration.SCREENLAYOUT_LONG_MASK
+ | Configuration.SCREENLAYOUT_SIZE_MASK);
+ // The smallest screen width is the short side of screen bounds. Because the bounds
+ // and density won't be changed, smallestScreenWidthDp is also fixed.
+ overrideConfig.smallestScreenWidthDp = parentConfig.smallestScreenWidthDp;
- if (shouldUseSizeCompatMode && mTmpBounds.isEmpty()) {
- mTmpBounds.set(task.getWindowConfiguration().getBounds());
- }
- if (mTmpBounds.equals(getRequestedOverrideBounds())) {
- // The bounds is not changed or the activity is resizable (both the 2 bounds are empty).
- return;
- }
-
- final Configuration overrideConfig = mTmpConfig;
- overrideConfig.unset();
- if (!mTmpBounds.isEmpty()) {
- overrideConfig.windowConfiguration.setBounds(mTmpBounds);
- if (shouldUseSizeCompatMode) {
- // Ensure the screen related fields are set. It is used to prevent activity relaunch
- // when moving between displays. For screenWidthDp and screenWidthDp, because they
- // are relative to bounds and density, they will be calculated in
- // {@link TaskRecord#computeConfigResourceOverrides} and the result will also be
- // relatively fixed.
- final Configuration parentConfig = task.getConfiguration();
- // Don't account decor insets into app bounds.
- mTmpBounds.intersect(parentConfig.windowConfiguration.getAppBounds());
- overrideConfig.windowConfiguration.setAppBounds(mTmpBounds);
- overrideConfig.colorMode = parentConfig.colorMode;
- overrideConfig.densityDpi = parentConfig.densityDpi;
- overrideConfig.screenLayout = parentConfig.screenLayout
- & (Configuration.SCREENLAYOUT_LONG_MASK
- | Configuration.SCREENLAYOUT_SIZE_MASK);
- // The smallest screen width is the short side of screen bounds. Because the bounds
- // and density won't be changed, smallestScreenWidthDp is also fixed.
- overrideConfig.smallestScreenWidthDp = parentConfig.smallestScreenWidthDp;
-
- final ActivityDisplay display = getDisplay();
- if (display != null && display.mDisplayContent != null) {
- mCompatDisplayInsets = new CompatDisplayInsets(display.mDisplayContent);
- }
+ // The role of CompatDisplayInsets is like the override bounds.
+ final ActivityDisplay display = getDisplay();
+ if (display != null && display.mDisplayContent != null) {
+ mCompatDisplayInsets = new CompatDisplayInsets(display.mDisplayContent);
}
+ } else {
+ // We must base this on the parent configuration, because we set our override
+ // configuration's appBounds based on the result of this method. If we used our own
+ // configuration, it would be influenced by past invocations.
+ computeBounds(mTmpBounds, getParent().getWindowConfiguration().getAppBounds());
+
+ if (mTmpBounds.equals(getRequestedOverrideBounds())) {
+ // The bounds is not changed or the activity is resizable (both the 2 bounds are
+ // empty).
+ return;
+ }
+
+ overrideConfig.unset();
+ overrideConfig.windowConfiguration.setBounds(mTmpBounds);
}
+
onRequestedOverrideConfigurationChanged(overrideConfig);
}
@Override
void resolveOverrideConfiguration(Configuration newParentConfiguration) {
- // If the activity has override bounds, the relative configuration (e.g. screen size,
- // layout) needs to be resolved according to the bounds.
- final boolean hasOverrideBounds = !matchParentBounds();
- if (hasOverrideBounds && shouldUseSizeCompatMode()) {
+ if (mCompatDisplayInsets != null) {
resolveSizeCompatModeConfiguration(newParentConfiguration);
} else {
super.resolveOverrideConfiguration(newParentConfiguration);
- if (hasOverrideBounds) {
+ // If the activity has override bounds, the relative configuration (e.g. screen size,
+ // layout) needs to be resolved according to the bounds.
+ if (!matchParentBounds()) {
task.computeConfigResourceOverrides(getResolvedOverrideConfiguration(),
newParentConfiguration);
}
@@ -2881,18 +2881,25 @@
getResolvedOverrideConfiguration().seq = mConfigurationSeq;
}
+ /**
+ * Resolves consistent screen configuration for orientation and rotation changes without
+ * inheriting the parent bounds.
+ */
private void resolveSizeCompatModeConfiguration(Configuration newParentConfiguration) {
final Configuration resolvedConfig = getResolvedOverrideConfiguration();
final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
+ final int parentRotation = newParentConfiguration.windowConfiguration.getRotation();
+ final int parentOrientation = newParentConfiguration.orientation;
int orientation = getConfiguration().orientation;
- if (orientation != newParentConfiguration.orientation
- && isConfigurationCompatible(newParentConfiguration)) {
+ if (orientation != parentOrientation && isConfigurationCompatible(newParentConfiguration)) {
// The activity is compatible to apply the orientation change or it requests different
// fixed orientation.
- orientation = newParentConfiguration.orientation;
+ orientation = parentOrientation;
} else {
- if (!resolvedBounds.isEmpty()) {
+ if (!resolvedBounds.isEmpty()
+ // The decor insets may be different according to the rotation.
+ && getWindowConfiguration().getRotation() == parentRotation) {
// Keep the computed resolved override configuration.
return;
}
@@ -2902,49 +2909,59 @@
}
}
- // The requested override bounds will set to the resolved bounds.
super.resolveOverrideConfiguration(newParentConfiguration);
- boolean shouldSwapAppBounds = false;
- int width = resolvedBounds.width();
- int height = resolvedBounds.height();
- if ((orientation == ORIENTATION_LANDSCAPE && height > width)
- || (orientation == ORIENTATION_PORTRAIT && width > height)) {
- // Swap width and height because they are opposite to the orientation.
- width = resolvedBounds.height();
- height = resolvedBounds.width();
- // Assume the bounds always starts from zero because the size may be larger than its
- // parent (task ~ display). The actual letterboxing will be done by surface offset.
- resolvedBounds.set(0, 0, width, height);
- shouldSwapAppBounds = true;
- } else if (width == height) {
- // The bounds may contain decor insets, then its app bounds may not be 1:1 and need to
- // be adjusted according to the orientation.
- final int appWidth = resolvedConfig.windowConfiguration.getAppBounds().width();
- final int appHeight = resolvedConfig.windowConfiguration.getAppBounds().height();
- shouldSwapAppBounds = (orientation == ORIENTATION_LANDSCAPE && appHeight > appWidth)
- || (orientation == ORIENTATION_PORTRAIT && appWidth > appHeight);
+ boolean useParentOverrideBounds = false;
+ final Rect displayBounds = mTmpBounds;
+ final Rect containingAppBounds = new Rect();
+ if (task.handlesOrientationChangeFromDescendant()) {
+ // Prefer to use the orientation which is determined by this activity to calculate
+ // bounds because the parent will follow the requested orientation.
+ mCompatDisplayInsets.getDisplayBoundsByOrientation(displayBounds, orientation);
+ } else {
+ // The parent hierarchy doesn't handle the orientation changes. This is usually because
+ // the aspect ratio of display is close to square or the display rotation is fixed.
+ // In this case, task will compute override bounds to fit the app with respect to the
+ // requested orientation. So here we perform similar calculation to have consistent
+ // bounds even the original parent hierarchies were changed.
+ final int baseOrientation = task.getParent().getConfiguration().orientation;
+ mCompatDisplayInsets.getDisplayBoundsByOrientation(displayBounds, baseOrientation);
+ task.computeFullscreenBounds(containingAppBounds, this, displayBounds, baseOrientation);
+ useParentOverrideBounds = !containingAppBounds.isEmpty();
}
- final Rect resolvedAppBounds = resolvedConfig.windowConfiguration.getAppBounds();
- final Rect parentAppBounds = newParentConfiguration.windowConfiguration.getAppBounds();
- if (shouldSwapAppBounds) {
- // Preserve the original decor insets (the left and top of the resolved app bounds) if
- // the parent also has the insets at the corresponding side.
- final int left = parentAppBounds.left > 0 ? resolvedAppBounds.top : 0;
- final int top = parentAppBounds.top > 0 ? resolvedAppBounds.left : 0;
- final int appWidth = resolvedAppBounds.height();
- final int appHeight = resolvedAppBounds.width();
- resolvedAppBounds.set(left, top, appWidth + left, appHeight + top);
+ // The offsets will be non-zero if the parent has override bounds.
+ final int containingOffsetX = containingAppBounds.left;
+ final int containingOffsetY = containingAppBounds.top;
+ if (!useParentOverrideBounds) {
+ containingAppBounds.set(displayBounds);
}
+ if (parentRotation != ROTATION_UNDEFINED) {
+ // Ensure the container bounds won't overlap with the decors.
+ TaskRecord.intersectWithInsetsIfFits(containingAppBounds, displayBounds,
+ mCompatDisplayInsets.mNonDecorInsets[parentRotation]);
+ }
+
+ computeBounds(resolvedBounds, containingAppBounds);
+ if (resolvedBounds.isEmpty()) {
+ // Use the entire available bounds because there is no restriction.
+ resolvedBounds.set(useParentOverrideBounds ? containingAppBounds : displayBounds);
+ } else {
+ // The offsets are included in width and height by {@link #computeBounds}, so we have to
+ // restore it.
+ resolvedBounds.left += containingOffsetX;
+ resolvedBounds.top += containingOffsetY;
+ }
+ task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
+ mCompatDisplayInsets);
+
// The horizontal inset included in width is not needed if the activity cannot fill the
// parent, because the offset will be applied by {@link AppWindowToken#mSizeCompatBounds}.
+ final Rect resolvedAppBounds = resolvedConfig.windowConfiguration.getAppBounds();
+ final Rect parentAppBounds = newParentConfiguration.windowConfiguration.getAppBounds();
if (resolvedBounds.width() < parentAppBounds.width()) {
resolvedBounds.right -= resolvedAppBounds.left;
}
-
- task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
- mCompatDisplayInsets);
// Use parent orientation if it cannot be decided by bounds, so the activity can fit inside
// the parent bounds appropriately.
if (resolvedConfig.screenWidthDp == resolvedConfig.screenHeightDp) {
@@ -3022,7 +3039,7 @@
* Computes the bounds to fit the Activity within the bounds of the {@link Configuration}.
*/
// TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
- private void computeBounds(Rect outBounds) {
+ private void computeBounds(Rect outBounds, Rect containingAppBounds) {
outBounds.setEmpty();
final float maxAspectRatio = info.maxAspectRatio;
final ActivityStack stack = getActivityStack();
@@ -3038,12 +3055,8 @@
return;
}
- // We must base this on the parent configuration, because we set our override
- // configuration's appBounds based on the result of this method. If we used our own
- // configuration, it would be influenced by past invocations.
- final Rect appBounds = getParent().getWindowConfiguration().getAppBounds();
- final int containingAppWidth = appBounds.width();
- final int containingAppHeight = appBounds.height();
+ final int containingAppWidth = containingAppBounds.width();
+ final int containingAppHeight = containingAppBounds.height();
final float containingRatio = Math.max(containingAppWidth, containingAppHeight)
/ (float) Math.min(containingAppWidth, containingAppHeight);
@@ -3109,7 +3122,8 @@
// Also account for the left / top insets (e.g. from display cutouts), which will be clipped
// away later in {@link TaskRecord#computeConfigResourceOverrides()}. Otherwise, the app
// bounds would end up too small.
- outBounds.set(0, 0, activityWidth + appBounds.left, activityHeight + appBounds.top);
+ outBounds.set(0, 0, activityWidth + containingAppBounds.left,
+ activityHeight + containingAppBounds.top);
}
/**
@@ -3468,8 +3482,8 @@
// Reset the existing override configuration so it can be updated according to the latest
// configuration.
- getRequestedOverrideConfiguration().setToDefaults();
- getResolvedOverrideConfiguration().setToDefaults();
+ getRequestedOverrideConfiguration().unset();
+ getResolvedOverrideConfiguration().unset();
mCompatDisplayInsets = null;
if (visible) {
// Configuration will be ensured when becoming visible, so if it is already visible,
@@ -3826,7 +3840,10 @@
final int mDisplayWidth;
final int mDisplayHeight;
- /** The nonDecorInsets for each rotation. Includes the navigation bar and cutout insets. */
+ /**
+ * The nonDecorInsets for each rotation. Includes the navigation bar and cutout insets. It
+ * is used to compute the appBounds.
+ */
final Rect[] mNonDecorInsets = new Rect[4];
/**
* The stableInsets for each rotation. Includes the status bar inset and the
@@ -3839,24 +3856,33 @@
mDisplayWidth = display.mBaseDisplayWidth;
mDisplayHeight = display.mBaseDisplayHeight;
final DisplayPolicy policy = display.getDisplayPolicy();
- final DisplayCutout cutout = display.getDisplayInfo().displayCutout;
for (int rotation = 0; rotation < 4; rotation++) {
mNonDecorInsets[rotation] = new Rect();
mStableInsets[rotation] = new Rect();
final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
final int dw = rotated ? mDisplayHeight : mDisplayWidth;
final int dh = rotated ? mDisplayWidth : mDisplayHeight;
+ final DisplayCutout cutout = display.calculateDisplayCutoutForRotation(rotation)
+ .getDisplayCutout();
policy.getNonDecorInsetsLw(rotation, dw, dh, cutout, mNonDecorInsets[rotation]);
mStableInsets[rotation].set(mNonDecorInsets[rotation]);
policy.convertNonDecorInsetsToStableInsets(mStableInsets[rotation], rotation);
}
}
- void getDisplayBounds(Rect outBounds, int rotation) {
+ void getDisplayBoundsByRotation(Rect outBounds, int rotation) {
final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
final int dw = rotated ? mDisplayHeight : mDisplayWidth;
final int dh = rotated ? mDisplayWidth : mDisplayHeight;
outBounds.set(0, 0, dw, dh);
}
+
+ void getDisplayBoundsByOrientation(Rect outBounds, int orientation) {
+ final int longSide = Math.max(mDisplayWidth, mDisplayHeight);
+ final int shortSide = Math.min(mDisplayWidth, mDisplayHeight);
+ final boolean isLandscape = orientation == ORIENTATION_LANDSCAPE;
+ outBounds.set(0, 0, isLandscape ? longSide : shortSide,
+ isLandscape ? shortSide : longSide);
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index 9c97674..c992a69 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -837,7 +837,8 @@
mergedConfiguration.getOverrideConfiguration(), r.compat,
r.launchedFromPackage, task.voiceInteractor, proc.getReportedProcState(),
r.icicle, r.persistentState, results, newIntents,
- dc.isNextTransitionForward(), proc.createProfilerInfoIfNeeded()));
+ dc.isNextTransitionForward(), proc.createProfilerInfoIfNeeded(),
+ r.assistToken));
// Set desired final state.
final ActivityLifecycleItem lifecycleItem;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 48aee20..ed56501 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -34,6 +34,7 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.service.voice.IVoiceInteractionSession;
+import android.util.Pair;
import android.util.SparseIntArray;
import android.util.proto.ProtoOutputStream;
@@ -89,6 +90,16 @@
AppProtoEnums.APP_TRANSITION_RECENTS_ANIM; // 5
/**
+ * The id of the task source of assist state.
+ */
+ public static final String ASSIST_TASK_ID = "taskId";
+
+ /**
+ * The id of the activity source of assist state.
+ */
+ public static final String ASSIST_ACTIVITY_ID = "activityId";
+
+ /**
* The bundle key to extract the assist data.
*/
public static final String ASSIST_KEY_DATA = "data";
@@ -328,6 +339,40 @@
public abstract CompatibilityInfo compatibilityInfoForPackage(ApplicationInfo ai);
+ public final class ActivityTokens {
+ private final @NonNull IBinder mActivityToken;
+ private final @NonNull IBinder mAssistToken;
+ private final @NonNull IApplicationThread mAppThread;
+
+ public ActivityTokens(@NonNull IBinder activityToken,
+ @NonNull IBinder assistToken, @NonNull IApplicationThread appThread) {
+ mActivityToken = activityToken;
+ mAssistToken = assistToken;
+ mAppThread = appThread;
+ }
+
+ /**
+ * @return The activity token.
+ */
+ public @NonNull IBinder getActivityToken() {
+ return mActivityToken;
+ }
+
+ /**
+ * @return The assist token.
+ */
+ public @NonNull IBinder getAssistToken() {
+ return mAssistToken;
+ }
+
+ /**
+ * @return The assist token.
+ */
+ public @NonNull IApplicationThread getApplicationThread() {
+ return mAppThread;
+ }
+ }
+
/**
* Set the corresponding display information for the process global configuration. To be called
* when we need to show IME on a different display.
@@ -341,6 +386,14 @@
String resultWho, int requestCode, int resultCode, Intent data);
public abstract void clearPendingResultForActivity(
IBinder activityToken, WeakReference<PendingIntentRecord> pir);
+
+ /**
+ * @return the activity token and IApplicationThread for the top activity in the task or null
+ * if there isn't a top activity with a valid process.
+ */
+ @Nullable
+ public abstract ActivityTokens getTopActivityForTask(int taskId);
+
public abstract IIntentSender getIntentSender(int type, String packageName,
int callingUid, int userId, IBinder token, String resultWho,
int requestCode, Intent[] intents, String[] resolvedTypes, int flags,
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 0820b0d..7bc9600 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -108,10 +108,12 @@
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_VISIBILITY;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_ACTIVITY_ID;
import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_CONTENT;
import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_DATA;
import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_RECEIVER_EXTRAS;
import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_STRUCTURE;
+import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_TASK_ID;
import static com.android.server.wm.ActivityTaskManagerService.H.REPORT_TIME_TRACKER_MSG;
import static com.android.server.wm.ActivityTaskManagerService.UiHandler.DISMISS_DIALOG_UI_MSG;
import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
@@ -214,6 +216,7 @@
import android.util.ArrayMap;
import android.util.EventLog;
import android.util.Log;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -3000,6 +3003,10 @@
if ((sendReceiver = pae.receiver) != null) {
// Caller wants result sent back to them.
sendBundle = new Bundle();
+ sendBundle.putInt(ActivityTaskManagerInternal.ASSIST_TASK_ID,
+ pae.activity.getTaskRecord().taskId);
+ sendBundle.putBinder(ActivityTaskManagerInternal.ASSIST_ACTIVITY_ID,
+ pae.activity.assistToken);
sendBundle.putBundle(ASSIST_KEY_DATA, pae.extras);
sendBundle.putParcelable(ASSIST_KEY_STRUCTURE, pae.structure);
sendBundle.putParcelable(ASSIST_KEY_CONTENT, pae.content);
@@ -6547,6 +6554,31 @@
}
@Override
+ public ActivityTokens getTopActivityForTask(int taskId) {
+ synchronized (mGlobalLock) {
+ final TaskRecord taskRecord = mRootActivityContainer.anyTaskForId(taskId);
+ if (taskRecord == null) {
+ Slog.w(TAG, "getApplicationThreadForTopActivity failed:"
+ + " Requested task not found");
+ return null;
+ }
+ final ActivityRecord activity = taskRecord.getTopActivity();
+ if (activity == null) {
+ Slog.w(TAG, "getApplicationThreadForTopActivity failed:"
+ + " Requested activity not found");
+ return null;
+ }
+ if (!activity.attachedToProcess()) {
+ Slog.w(TAG, "getApplicationThreadForTopActivity failed: No process for "
+ + activity);
+ return null;
+ }
+ return new ActivityTokens(activity.appToken, activity.assistToken,
+ activity.app.getThread());
+ }
+ }
+
+ @Override
public IIntentSender getIntentSender(int type, String packageName,
int callingUid, int userId, IBinder token, String resultWho,
int requestCode, Intent[] intents, String[] resolvedTypes, int flags,
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 155ab41..df93195 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -142,7 +142,7 @@
/**
* Value to increment the z-layer when boosting a layer during animations. BOOST in l33tsp34k.
*/
- private static final int Z_BOOST_BASE = 800570000;
+ @VisibleForTesting static final int Z_BOOST_BASE = 800570000;
// Non-null only for application tokens.
final IApplicationToken appToken;
@@ -291,7 +291,7 @@
private boolean mFreezingScreen;
/** Whether this token should be boosted at the top of all app window tokens. */
- private boolean mNeedsZBoost;
+ @VisibleForTesting boolean mNeedsZBoost;
private Letterbox mLetterbox;
private final Point mTmpPoint = new Point();
@@ -1758,8 +1758,8 @@
*/
private void calculateCompatBoundsTransformation(Configuration newParentConfig) {
final Rect parentAppBounds = newParentConfig.windowConfiguration.getAppBounds();
- final Rect viewportBounds = parentAppBounds != null
- ? parentAppBounds : newParentConfig.windowConfiguration.getBounds();
+ final Rect parentBounds = newParentConfig.windowConfiguration.getBounds();
+ final Rect viewportBounds = parentAppBounds != null ? parentAppBounds : parentBounds;
final Rect appBounds = getWindowConfiguration().getAppBounds();
final Rect contentBounds = appBounds != null ? appBounds : getResolvedOverrideBounds();
final float contentW = contentBounds.width();
@@ -1778,6 +1778,8 @@
mSizeCompatBounds.set(contentBounds);
mSizeCompatBounds.offsetTo(0, 0);
mSizeCompatBounds.scale(mSizeCompatScale);
+ // Ensure to align the top with the parent.
+ mSizeCompatBounds.top = parentBounds.top;
// The decor inset is included in height.
mSizeCompatBounds.bottom += viewportBounds.top;
mSizeCompatBounds.left += offsetX;
@@ -2691,7 +2693,9 @@
if (mNeedsZBoost) {
layer += Z_BOOST_BASE;
}
- leash.setLayer(layer);
+ if (!mNeedsAnimationBoundsLayer) {
+ leash.setLayer(layer);
+ }
final DisplayContent dc = getDisplayContent();
dc.assignStackOrdering();
@@ -2728,6 +2732,7 @@
// Crop to stack bounds.
t.setWindowCrop(mAnimationBoundsLayer, mTmpRect);
+ t.setLayer(mAnimationBoundsLayer, layer);
// Reparent leash to animation bounds layer.
t.reparent(leash, mAnimationBoundsLayer);
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 363db54..df36b09 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -264,7 +264,8 @@
// Keyguard.
return dismissKeyguard && canDismissKeyguard() && !mAodShowing
&& (mDismissalRequested
- || getDisplay(r.getDisplayId()).mDismissingKeyguardActivity != r);
+ || (r.canShowWhenLocked()
+ && getDisplay(r.getDisplayId()).mDismissingKeyguardActivity != r));
}
/**
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 23911e6..e0ab722 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -884,7 +884,7 @@
if (isVisibleRecentTask(tr)) {
numVisibleTasks++;
- if (isInVisibleRange(tr, numVisibleTasks, withExcluded)) {
+ if (isInVisibleRange(tr, i, numVisibleTasks, withExcluded)) {
// Fall through
} else {
// Not in visible range
@@ -989,7 +989,7 @@
final TaskRecord tr = mTasks.get(i);
if (isVisibleRecentTask(tr)) {
numVisibleTasks++;
- if (isInVisibleRange(tr, numVisibleTasks, false /* skipExcludedCheck */)) {
+ if (isInVisibleRange(tr, i, numVisibleTasks, false /* skipExcludedCheck */)) {
res.put(tr.taskId, true);
}
}
@@ -1210,7 +1210,7 @@
continue;
} else {
numVisibleTasks++;
- if (isInVisibleRange(task, numVisibleTasks, false /* skipExcludedCheck */)
+ if (isInVisibleRange(task, i, numVisibleTasks, false /* skipExcludedCheck */)
|| !isTrimmable(task)) {
// Keep visible tasks in range
i++;
@@ -1325,7 +1325,7 @@
/**
* @return whether the given visible task is within the policy range.
*/
- private boolean isInVisibleRange(TaskRecord task, int numVisibleTasks,
+ private boolean isInVisibleRange(TaskRecord task, int taskIndex, int numVisibleTasks,
boolean skipExcludedCheck) {
if (!skipExcludedCheck) {
// Keep the most recent task even if it is excluded from recents
@@ -1334,7 +1334,7 @@
== FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
if (isExcludeFromRecents) {
if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "\texcludeFromRecents=true");
- return numVisibleTasks == 1;
+ return taskIndex == 0;
}
}
diff --git a/services/core/java/com/android/server/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java
index 15060e1..10fb559 100644
--- a/services/core/java/com/android/server/wm/TaskRecord.java
+++ b/services/core/java/com/android/server/wm/TaskRecord.java
@@ -1277,19 +1277,6 @@
}
/**
- * Checks if the top activity requires a particular orientation (either by override or
- * activityInfo) and returns that. Otherwise, this returns ORIENTATION_UNDEFINED.
- */
- private int getTopActivityRequestedOrientation() {
- ActivityRecord top = getTopActivity();
- if (getRequestedOverrideConfiguration().orientation != ORIENTATION_UNDEFINED
- || top == null) {
- return getRequestedOverrideConfiguration().orientation;
- }
- return top.getRequestedConfigurationOrientation();
- }
-
- /**
* Adds an activity {@param r} at the given {@param index}. The activity {@param r} must either
* be in the current task or unparented to any task.
*/
@@ -2006,7 +1993,7 @@
* @param intersectBounds the bounds to intersect with.
* @param intersectInsets insets to apply to intersectBounds before intersecting.
*/
- private static void intersectWithInsetsIfFits(
+ static void intersectWithInsetsIfFits(
Rect inOutBounds, Rect intersectBounds, Rect intersectInsets) {
if (inOutBounds.right <= intersectBounds.right) {
inOutBounds.right =
@@ -2124,19 +2111,22 @@
// {@link WindowManagerPolicy#getNonDecorInsetsLw}.
calculateInsetFrames(mTmpNonDecorBounds, mTmpStableBounds, bounds, di);
} else {
- // Set to app bounds because it excludes decor insets.
- mTmpNonDecorBounds.set(outAppBounds);
- mTmpStableBounds.set(outAppBounds);
-
// Apply the given non-decor and stable insets to calculate the corresponding bounds
// for screen size of configuration.
final int rotation = parentConfig.windowConfiguration.getRotation();
if (rotation != ROTATION_UNDEFINED && compatInsets != null) {
- compatInsets.getDisplayBounds(mTmpBounds, rotation);
+ mTmpNonDecorBounds.set(bounds);
+ mTmpStableBounds.set(bounds);
+ compatInsets.getDisplayBoundsByRotation(mTmpBounds, rotation);
intersectWithInsetsIfFits(mTmpNonDecorBounds, mTmpBounds,
compatInsets.mNonDecorInsets[rotation]);
intersectWithInsetsIfFits(mTmpStableBounds, mTmpBounds,
compatInsets.mStableInsets[rotation]);
+ outAppBounds.set(mTmpNonDecorBounds);
+ } else {
+ // Set to app bounds because it excludes decor insets.
+ mTmpNonDecorBounds.set(outAppBounds);
+ mTmpStableBounds.set(outAppBounds);
}
}
@@ -2204,34 +2194,9 @@
getResolvedOverrideConfiguration().windowConfiguration.getBounds();
if (windowingMode == WINDOWING_MODE_FULLSCREEN) {
- // In FULLSCREEN mode, always start with empty bounds to indicate "fill parent"
- outOverrideBounds.setEmpty();
-
- final boolean parentHandlesOrientationChange = mTask != null
- && mTask.getParent() != null
- && mTask.getParent().handlesOrientationChangeFromDescendant();
- // If the task or its top activity requires a different orientation, make it fit the
- // available bounds by scaling down its bounds.
- int forcedOrientation = getTopActivityRequestedOrientation();
- if (forcedOrientation != ORIENTATION_UNDEFINED
- && forcedOrientation != newParentConfig.orientation
- && !parentHandlesOrientationChange) {
- final Rect parentBounds = newParentConfig.windowConfiguration.getBounds();
- final int parentWidth = parentBounds.width();
- final int parentHeight = parentBounds.height();
- final float aspect = ((float) parentHeight) / parentWidth;
- if (forcedOrientation == ORIENTATION_LANDSCAPE) {
- final int height = (int) (parentWidth / aspect);
- final int top = parentBounds.centerY() - height / 2;
- outOverrideBounds.set(
- parentBounds.left, top, parentBounds.right, top + height);
- } else {
- final int width = (int) (parentHeight * aspect);
- final int left = parentBounds.centerX() - width / 2;
- outOverrideBounds.set(
- left, parentBounds.top, left + width, parentBounds.bottom);
- }
- }
+ computeFullscreenBounds(outOverrideBounds, null /* refActivity */,
+ newParentConfig.windowConfiguration.getBounds(),
+ newParentConfig.orientation);
}
if (outOverrideBounds.isEmpty()) {
@@ -2251,6 +2216,55 @@
computeConfigResourceOverrides(getResolvedOverrideConfiguration(), newParentConfig);
}
+ /** @see WindowContainer#handlesOrientationChangeFromDescendant */
+ boolean handlesOrientationChangeFromDescendant() {
+ return mTask != null && mTask.getParent() != null
+ && mTask.getParent().handlesOrientationChangeFromDescendant();
+ }
+
+ /**
+ * Compute bounds (letterbox or pillarbox) for {@link #WINDOWING_MODE_FULLSCREEN} when the
+ * parent doesn't handle the orientation change and the requested orientation is different from
+ * the parent.
+ */
+ void computeFullscreenBounds(@NonNull Rect outBounds, @Nullable ActivityRecord refActivity,
+ @NonNull Rect parentBounds, int parentOrientation) {
+ // In FULLSCREEN mode, always start with empty bounds to indicate "fill parent".
+ outBounds.setEmpty();
+ if (handlesOrientationChangeFromDescendant()) {
+ return;
+ }
+ if (refActivity == null) {
+ // Use the top activity as the reference of orientation. Don't include overlays because
+ // it is usually not the actual content or just temporarily shown.
+ // E.g. ForcedResizableInfoActivity.
+ refActivity = getTopActivity(false /* includeOverlays */);
+ }
+
+ // If the task or the reference activity requires a different orientation (either by
+ // override or activityInfo), make it fit the available bounds by scaling down its bounds.
+ final int overrideOrientation = getRequestedOverrideConfiguration().orientation;
+ final int forcedOrientation =
+ (overrideOrientation != ORIENTATION_UNDEFINED || refActivity == null)
+ ? overrideOrientation : refActivity.getRequestedConfigurationOrientation();
+ if (forcedOrientation == ORIENTATION_UNDEFINED || forcedOrientation == parentOrientation) {
+ return;
+ }
+
+ final int parentWidth = parentBounds.width();
+ final int parentHeight = parentBounds.height();
+ final float aspect = ((float) parentHeight) / parentWidth;
+ if (forcedOrientation == ORIENTATION_LANDSCAPE) {
+ final int height = (int) (parentWidth / aspect);
+ final int top = parentBounds.centerY() - height / 2;
+ outBounds.set(parentBounds.left, top, parentBounds.right, top + height);
+ } else {
+ final int width = (int) (parentHeight * aspect);
+ final int left = parentBounds.centerX() - width / 2;
+ outBounds.set(left, parentBounds.top, left + width, parentBounds.bottom);
+ }
+ }
+
Rect updateOverrideConfigurationFromLaunchBounds() {
final Rect bounds = getLaunchBounds();
updateOverrideConfiguration(bounds);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index c29b132..e39cd56 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2201,14 +2201,16 @@
final Region region = inputWindowHandle.touchableRegion;
setTouchableRegionCropIfNeeded(inputWindowHandle);
- if (mAppToken != null && !mAppToken.getResolvedOverrideBounds().isEmpty()) {
+ final Rect appOverrideBounds = mAppToken != null
+ ? mAppToken.getResolvedOverrideBounds() : null;
+ if (appOverrideBounds != null && !appOverrideBounds.isEmpty()) {
// There may have touchable letterboxes around the activity, so in order to let the
// letterboxes are able to receive touch event and slip to activity, the activity with
// compatibility bounds cannot occupy full screen touchable region.
if (modal) {
// A modal window uses the whole compatibility bounds.
flags |= FLAG_NOT_TOUCH_MODAL;
- mTmpRect.set(mAppToken.getResolvedOverrideBounds());
+ mTmpRect.set(0, 0, appOverrideBounds.width(), appOverrideBounds.height());
} else {
// Non-modal uses the application based frame.
mTmpRect.set(mWindowFrames.mCompatFrame);
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index b470ec7..7bc6776 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -1285,7 +1285,7 @@
if (proxyAppPackageName && otherProtocolStackName && requestorId) {
env->CallVoidMethod(mCallbacksObj, method_reportNfwNotification, proxyAppPackageName,
notification.protocolStack, otherProtocolStackName,
- notification.requestor, requestorId,
+ notification.requestor, requestorId, notification.responseType,
notification.inEmergencyMode, notification.isCachedLocation);
} else {
ALOGE("%s: OOM Error\n", __func__);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 44390b0..dc8b885 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -17,6 +17,8 @@
package com.android.server.wm;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_90;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
@@ -57,10 +59,11 @@
import android.platform.test.annotations.Presubmit;
import android.util.MergedConfiguration;
import android.util.MutableBoolean;
-import android.view.DisplayInfo;
import androidx.test.filters.MediumTest;
+import com.android.server.wm.utils.WmDisplayCutout;
+
import org.junit.Before;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
@@ -88,10 +91,6 @@
doReturn(false).when(mService).isBooting();
doReturn(true).when(mService).isBooted();
-
- final DisplayContent displayContent = mStack.getDisplay().mDisplayContent;
- doReturn(mock(DisplayPolicy.class)).when(displayContent).getDisplayPolicy();
- doReturn(mock(DisplayInfo.class)).when(displayContent).getDisplayInfo();
}
@Test
@@ -191,7 +190,7 @@
prepareFixedAspectRatioUnresizableActivity();
final Rect originalOverrideBounds = new Rect(mActivity.getBounds());
- mTask.getWindowConfiguration().setAppBounds(0, 0, 600, 1200);
+ setupDisplayAndParentSize(600, 1200);
// The visible activity should recompute configuration according to the last parent bounds.
mService.restartActivityProcessIfVisible(mActivity.appToken);
@@ -436,10 +435,21 @@
@Test
public void testSizeCompatMode_FixedAspectRatioBoundsWithDecor() {
+ setupDisplayContentForCompatDisplayInsets();
final int decorHeight = 200; // e.g. The device has cutout.
- final Rect parentAppBounds = new Rect(0, decorHeight, 600, 1000);
- mTask.getWindowConfiguration().setAppBounds(parentAppBounds);
- mTask.getConfiguration().orientation = Configuration.ORIENTATION_PORTRAIT;
+ final DisplayPolicy policy = setupDisplayAndParentSize(600, 800).getDisplayPolicy();
+ doAnswer(invocationOnMock -> {
+ final int rotation = invocationOnMock.<Integer>getArgument(0);
+ final Rect insets = invocationOnMock.<Rect>getArgument(4);
+ if (rotation == ROTATION_0) {
+ insets.top = decorHeight;
+ } else if (rotation == ROTATION_90) {
+ insets.left = decorHeight;
+ }
+ return null;
+ }).when(policy).getNonDecorInsetsLw(anyInt() /* rotation */, anyInt() /* width */,
+ anyInt() /* height */, any() /* displayCutout */, any() /* outInsets */);
+
doReturn(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)
.when(mActivity.mAppWindowToken).getOrientationIgnoreVisibility();
mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
@@ -455,7 +465,7 @@
// The decor height should be a part of the effective bounds.
assertEquals(mActivity.getBounds().height(), appBounds.height() + decorHeight);
- mTask.getConfiguration().orientation = Configuration.ORIENTATION_LANDSCAPE;
+ mTask.getConfiguration().windowConfiguration.setRotation(ROTATION_90);
mActivity.onConfigurationChanged(mTask.getConfiguration());
// After changing orientation, the aspect ratio should be the same.
assertEquals(appBounds.width(), appBounds.height());
@@ -487,6 +497,7 @@
@Test
public void testSizeCompatMode_FixedScreenBoundsWhenDisplaySizeChanged() {
+ setupDisplayContentForCompatDisplayInsets();
when(mActivity.mAppWindowToken.getOrientationIgnoreVisibility()).thenReturn(
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
mTask.getWindowConfiguration().setAppBounds(mStack.getDisplay().getBounds());
@@ -497,7 +508,7 @@
final Rect originalBounds = new Rect(mActivity.getBounds());
// Change the size of current display.
- mStack.getDisplay().setBounds(0, 0, 1000, 2000);
+ setupDisplayAndParentSize(1000, 2000);
ensureActivityConfiguration();
assertEquals(originalBounds, mActivity.getBounds());
@@ -559,11 +570,30 @@
/** Setup {@link #mActivity} as a size-compat-mode-able activity without fixed orientation. */
private void prepareFixedAspectRatioUnresizableActivity() {
+ setupDisplayContentForCompatDisplayInsets();
when(mActivity.mAppWindowToken.getOrientationIgnoreVisibility()).thenReturn(
ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
- mTask.getWindowConfiguration().setAppBounds(mStack.getDisplay().getBounds());
mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
mActivity.info.maxAspectRatio = 1.5f;
ensureActivityConfiguration();
}
+
+ private void setupDisplayContentForCompatDisplayInsets() {
+ final Rect displayBounds = mStack.getDisplay().getBounds();
+ final DisplayContent displayContent = setupDisplayAndParentSize(
+ displayBounds.width(), displayBounds.height());
+ doReturn(mock(DisplayPolicy.class)).when(displayContent).getDisplayPolicy();
+ doReturn(mock(WmDisplayCutout.class)).when(displayContent)
+ .calculateDisplayCutoutForRotation(anyInt());
+ }
+
+ private DisplayContent setupDisplayAndParentSize(int width, int height) {
+ // The DisplayContent is already a mocked object.
+ final DisplayContent displayContent = mStack.getDisplay().mDisplayContent;
+ displayContent.mBaseDisplayWidth = width;
+ displayContent.mBaseDisplayHeight = height;
+ mTask.getWindowConfiguration().setAppBounds(0, 0, width, height);
+ mTask.getWindowConfiguration().setRotation(ROTATION_0);
+ return displayContent;
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
index 1e00b30..757267e5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
@@ -930,6 +930,23 @@
}
@Test
+ public void testAdjustFocusedStackToHomeWhenNoActivity() {
+ final ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build();
+ mStack.moveToFront("testAdjustFocusedStack");
+
+ final ActivityStack homeStask = mDefaultDisplay.getHomeStack();
+ final TaskRecord homeTask = homeStask.topTask();
+ // Simulate that home activity has not been started or is force-stopped.
+ homeStask.removeTask(homeTask, "testAdjustFocusedStack", REMOVE_TASK_MODE_DESTROYING);
+
+ // Finish the only activity.
+ mStack.finishActivityLocked(topActivity, 0 /* resultCode */, null /* resultData */,
+ "testAdjustFocusedStack", false /* oomAdj */);
+ // Although home stack is empty, it should still be the focused stack.
+ assertEquals(homeStask, mDefaultDisplay.getFocusedStack());
+ }
+
+ @Test
public void testWontFinishHomeStackImmediately() {
final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java
index db04f11..623559e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java
@@ -25,6 +25,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.intThat;
import android.platform.test.annotations.Presubmit;
import android.view.SurfaceControl;
@@ -75,6 +76,16 @@
}
@Test
+ public void clipAfterAnim_boundsLayerZBoosted() {
+ mToken.mNeedsAnimationBoundsLayer = true;
+ mToken.mNeedsZBoost = true;
+
+ mToken.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
+ verify(mTransaction).setLayer(eq(mToken.mAnimationBoundsLayer),
+ intThat(layer -> layer >= AppWindowToken.Z_BOOST_BASE));
+ }
+
+ @Test
public void clipAfterAnim_boundsLayerIsDestroyed() {
mToken.mNeedsAnimationBoundsLayer = true;
mToken.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
index d87eed2..0ecd878 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
@@ -39,6 +39,7 @@
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
@@ -60,6 +61,7 @@
import com.android.internal.app.IVoiceInteractor;
import com.android.server.wm.TaskRecord.TaskRecordFactory;
+import com.android.server.wm.utils.WmDisplayCutout;
import org.junit.Before;
import org.junit.Test;
@@ -388,7 +390,8 @@
return null;
}).when(policy).convertNonDecorInsetsToStableInsets(any(), eq(ROTATION_0));
doReturn(policy).when(displayContent).getDisplayPolicy();
- doReturn(mock(DisplayInfo.class)).when(displayContent).getDisplayInfo();
+ doReturn(mock(WmDisplayCutout.class)).when(displayContent)
+ .calculateDisplayCutoutForRotation(anyInt());
// Without limiting to be inside the parent bounds, the out screen size should keep relative
// to the input bounds.
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 4d7ae73..5903005 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -19,6 +19,7 @@
import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
@@ -47,6 +48,7 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Parcel;
+import android.os.RemoteCallback;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -712,6 +714,45 @@
}
@Override
+ public void requestDirectActions(@NonNull IBinder token, int taskId, IBinder assistToken,
+ @NonNull RemoteCallback callback) {
+ synchronized (this) {
+ if (mImpl == null) {
+ Slog.w(TAG, "requestDirectActions without running voice interaction service");
+ callback.sendResult(null);
+ return;
+ }
+ final long caller = Binder.clearCallingIdentity();
+ try {
+ mImpl.requestDirectActionsLocked(token, taskId, assistToken, callback);
+ } finally {
+ Binder.restoreCallingIdentity(caller);
+ }
+ }
+ }
+
+ @Override
+ public void performDirectAction(@NonNull IBinder token, @NonNull String actionId,
+ @NonNull Bundle arguments, int taskId, IBinder assistToken,
+ @Nullable RemoteCallback cancellationCallback,
+ @NonNull RemoteCallback resultCallback) {
+ synchronized (this) {
+ if (mImpl == null) {
+ Slog.w(TAG, "performDirectAction without running voice interaction service");
+ resultCallback.sendResult(null);
+ return;
+ }
+ final long caller = Binder.clearCallingIdentity();
+ try {
+ mImpl.performDirectActionLocked(token, actionId, arguments, taskId,
+ assistToken, cancellationCallback, resultCallback);
+ } finally {
+ Binder.restoreCallingIdentity(caller);
+ }
+ }
+ }
+
+ @Override
public void setKeepAwake(IBinder token, boolean keepAwake) {
synchronized (this) {
if (mImpl == null) {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index ea52377..0d80e60 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -22,11 +22,14 @@
import static android.app.ActivityManager.START_VOICE_NOT_ACTIVE_SESSION;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
import android.app.IActivityManager;
import android.app.IActivityTaskManager;
+import android.app.IApplicationThread;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -37,6 +40,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
@@ -44,6 +48,7 @@
import android.service.voice.IVoiceInteractionSession;
import android.service.voice.VoiceInteractionService;
import android.service.voice.VoiceInteractionServiceInfo;
+import android.util.Pair;
import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.view.IWindowManager;
@@ -53,6 +58,7 @@
import com.android.internal.app.IVoiceInteractor;
import com.android.server.LocalServices;
import com.android.server.wm.ActivityTaskManagerInternal;
+import com.android.server.wm.ActivityTaskManagerInternal.ActivityTokens;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -256,6 +262,55 @@
}
}
+ public void requestDirectActionsLocked(@NonNull IBinder token, int taskId,
+ IBinder assistToken, @NonNull RemoteCallback callback) {
+ if (mActiveSession == null || token != mActiveSession.mToken) {
+ Slog.w(TAG, "requestDirectActionsLocked does not match active session");
+ callback.sendResult(null);
+ return;
+ }
+ final ActivityTokens tokens = LocalServices.getService(
+ ActivityTaskManagerInternal.class).getTopActivityForTask(taskId);
+ if (tokens == null || tokens.getAssistToken() != assistToken) {
+ Slog.w(TAG, "Unknown activity to query for direct actions");
+ callback.sendResult(null);
+ } else {
+ try {
+ tokens.getApplicationThread().requestDirectActions(tokens.getActivityToken(),
+ mActiveSession.mInteractor, callback);
+ } catch (RemoteException e) {
+ Slog.w("Unexpected remote error", e);
+ callback.sendResult(null);
+ }
+ }
+ }
+
+ void performDirectActionLocked(@NonNull IBinder token, @NonNull String actionId,
+ @Nullable Bundle arguments, int taskId, IBinder assistToken,
+ @Nullable RemoteCallback cancellationCallback,
+ @NonNull RemoteCallback resultCallback) {
+ if (mActiveSession == null || token != mActiveSession.mToken) {
+ Slog.w(TAG, "performDirectActionLocked does not match active session");
+ resultCallback.sendResult(null);
+ return;
+ }
+ final ActivityTokens tokens = LocalServices.getService(
+ ActivityTaskManagerInternal.class).getTopActivityForTask(taskId);
+ if (tokens == null || tokens.getAssistToken() != assistToken) {
+ Slog.w(TAG, "Unknown activity to perform a direct action");
+ resultCallback.sendResult(null);
+ } else {
+ try {
+ tokens.getApplicationThread().performDirectAction(tokens.getActivityToken(),
+ actionId, arguments, cancellationCallback,
+ resultCallback);
+ } catch (RemoteException e) {
+ Slog.w("Unexpected remote error", e);
+ resultCallback.sendResult(null);
+ }
+ }
+ }
+
public void setKeepAwakeLocked(IBinder token, boolean keepAwake) {
try {
if (mActiveSession == null || token != mActiveSession.mToken) {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index 24690f5..000a347 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
@@ -23,9 +23,11 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
+import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_ACTIVITY_ID;
import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_CONTENT;
import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_DATA;
import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_STRUCTURE;
+import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_TASK_ID;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
@@ -251,11 +253,13 @@
if (data == null) {
try {
- mSession.handleAssist(null, null, null, 0, 0);
+ mSession.handleAssist(-1, null, null, null, null, -1, 0);
} catch (RemoteException e) {
// Ignore
}
} else {
+ final int taskId = data.getInt(ASSIST_TASK_ID);
+ final IBinder activityId = data.getBinder(ASSIST_ACTIVITY_ID);
final Bundle assistData = data.getBundle(ASSIST_KEY_DATA);
final AssistStructure structure = data.getParcelable(ASSIST_KEY_STRUCTURE);
final AssistContent content = data.getParcelable(ASSIST_KEY_CONTENT);
@@ -279,8 +283,8 @@
}
}
try {
- mSession.handleAssist(assistData, structure, content, activityIndex,
- activityCount);
+ mSession.handleAssist(taskId, activityId, assistData, structure,
+ content, activityIndex, activityCount);
} catch (RemoteException e) {
// Ignore
}
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index fa7bf61..13e737e 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -16,6 +16,8 @@
package com.android.server;
+import static android.service.watchdog.ExplicitHealthCheckService.PackageConfig;
+
import static com.android.server.PackageWatchdog.MonitoredPackage;
import static com.android.server.PackageWatchdog.TRIGGER_FAILURE_COUNT;
@@ -28,7 +30,6 @@
import android.content.pm.VersionedPackage;
import android.os.Handler;
import android.os.test.TestLooper;
-import android.service.watchdog.PackageInfo;
import android.util.AtomicFile;
import androidx.test.InstrumentationRegistry;
@@ -741,7 +742,7 @@
private List<String> mSupportedPackages = new ArrayList<>();
private List<String> mRequestedPackages = new ArrayList<>();
private Consumer<String> mPassedConsumer;
- private Consumer<List<PackageInfo>> mSupportedConsumer;
+ private Consumer<List<PackageConfig>> mSupportedConsumer;
private Runnable mNotifySyncRunnable;
@Override
@@ -754,7 +755,7 @@
@Override
public void setCallbacks(Consumer<String> passedConsumer,
- Consumer<List<PackageInfo>> supportedConsumer, Runnable notifySyncRunnable) {
+ Consumer<List<PackageConfig>> supportedConsumer, Runnable notifySyncRunnable) {
mPassedConsumer = passedConsumer;
mSupportedConsumer = supportedConsumer;
mNotifySyncRunnable = notifySyncRunnable;
@@ -766,11 +767,11 @@
if (mIsEnabled) {
packages.retainAll(mSupportedPackages);
mRequestedPackages.addAll(packages);
- List<PackageInfo> packageInfos = new ArrayList<>();
+ List<PackageConfig> packageConfigs = new ArrayList<>();
for (String packageName: packages) {
- packageInfos.add(new PackageInfo(packageName, SHORT_DURATION));
+ packageConfigs.add(new PackageConfig(packageName, SHORT_DURATION));
}
- mSupportedConsumer.accept(packageInfos);
+ mSupportedConsumer.accept(packageConfigs);
} else {
mSupportedConsumer.accept(Collections.emptyList());
}
diff --git a/tests/net/java/android/net/RouteInfoTest.java b/tests/net/java/android/net/RouteInfoTest.java
index 831fefd..2edbd40 100644
--- a/tests/net/java/android/net/RouteInfoTest.java
+++ b/tests/net/java/android/net/RouteInfoTest.java
@@ -16,15 +16,16 @@
package android.net;
-import java.lang.reflect.Method;
-import java.net.InetAddress;
+import static android.net.RouteInfo.RTN_UNREACHABLE;
-import android.net.IpPrefix;
-import android.net.RouteInfo;
import android.os.Parcel;
+import android.test.suitebuilder.annotation.SmallTest;
import junit.framework.TestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
public class RouteInfoTest extends TestCase {
@@ -152,67 +153,85 @@
}
public void testHostAndDefaultRoutes() {
- RouteInfo r;
+ RouteInfo r;
- r = new RouteInfo(Prefix("0.0.0.0/0"), Address("0.0.0.0"), "wlan0");
- assertFalse(r.isHostRoute());
- assertTrue(r.isDefaultRoute());
- assertTrue(r.isIPv4Default());
- assertFalse(r.isIPv6Default());
+ r = new RouteInfo(Prefix("0.0.0.0/0"), Address("0.0.0.0"), "wlan0");
+ assertFalse(r.isHostRoute());
+ assertTrue(r.isDefaultRoute());
+ assertTrue(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
- r = new RouteInfo(Prefix("::/0"), Address("::"), "wlan0");
- assertFalse(r.isHostRoute());
- assertTrue(r.isDefaultRoute());
- assertFalse(r.isIPv4Default());
- assertTrue(r.isIPv6Default());
+ r = new RouteInfo(Prefix("::/0"), Address("::"), "wlan0");
+ assertFalse(r.isHostRoute());
+ assertTrue(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertTrue(r.isIPv6Default());
- r = new RouteInfo(Prefix("192.0.2.0/24"), null, "wlan0");
- assertFalse(r.isHostRoute());
- assertFalse(r.isDefaultRoute());
- assertFalse(r.isIPv4Default());
- assertFalse(r.isIPv6Default());
+ r = new RouteInfo(Prefix("192.0.2.0/24"), null, "wlan0");
+ assertFalse(r.isHostRoute());
+ assertFalse(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
- r = new RouteInfo(Prefix("2001:db8::/48"), null, "wlan0");
- assertFalse(r.isHostRoute());
- assertFalse(r.isDefaultRoute());
- assertFalse(r.isIPv4Default());
- assertFalse(r.isIPv6Default());
+ r = new RouteInfo(Prefix("2001:db8::/48"), null, "wlan0");
+ assertFalse(r.isHostRoute());
+ assertFalse(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
- r = new RouteInfo(Prefix("192.0.2.0/32"), Address("0.0.0.0"), "wlan0");
- assertTrue(r.isHostRoute());
- assertFalse(r.isDefaultRoute());
- assertFalse(r.isIPv4Default());
- assertFalse(r.isIPv6Default());
+ r = new RouteInfo(Prefix("192.0.2.0/32"), Address("0.0.0.0"), "wlan0");
+ assertTrue(r.isHostRoute());
+ assertFalse(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
- r = new RouteInfo(Prefix("2001:db8::/128"), Address("::"), "wlan0");
- assertTrue(r.isHostRoute());
- assertFalse(r.isDefaultRoute());
- assertFalse(r.isIPv4Default());
- assertFalse(r.isIPv6Default());
+ r = new RouteInfo(Prefix("2001:db8::/128"), Address("::"), "wlan0");
+ assertTrue(r.isHostRoute());
+ assertFalse(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
- r = new RouteInfo(Prefix("192.0.2.0/32"), null, "wlan0");
- assertTrue(r.isHostRoute());
- assertFalse(r.isDefaultRoute());
- assertFalse(r.isIPv4Default());
- assertFalse(r.isIPv6Default());
+ r = new RouteInfo(Prefix("192.0.2.0/32"), null, "wlan0");
+ assertTrue(r.isHostRoute());
+ assertFalse(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
- r = new RouteInfo(Prefix("2001:db8::/128"), null, "wlan0");
- assertTrue(r.isHostRoute());
- assertFalse(r.isDefaultRoute());
- assertFalse(r.isIPv4Default());
- assertFalse(r.isIPv6Default());
+ r = new RouteInfo(Prefix("2001:db8::/128"), null, "wlan0");
+ assertTrue(r.isHostRoute());
+ assertFalse(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
- r = new RouteInfo(Prefix("::/128"), Address("fe80::"), "wlan0");
- assertTrue(r.isHostRoute());
- assertFalse(r.isDefaultRoute());
- assertFalse(r.isIPv4Default());
- assertFalse(r.isIPv6Default());
+ r = new RouteInfo(Prefix("::/128"), Address("fe80::"), "wlan0");
+ assertTrue(r.isHostRoute());
+ assertFalse(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
- r = new RouteInfo(Prefix("0.0.0.0/32"), Address("192.0.2.1"), "wlan0");
- assertTrue(r.isHostRoute());
- assertFalse(r.isDefaultRoute());
- assertFalse(r.isIPv4Default());
- assertFalse(r.isIPv6Default());
+ r = new RouteInfo(Prefix("0.0.0.0/32"), Address("192.0.2.1"), "wlan0");
+ assertTrue(r.isHostRoute());
+ assertFalse(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
+
+ r = new RouteInfo(Prefix("0.0.0.0/32"), Address("192.0.2.1"), "wlan0");
+ assertTrue(r.isHostRoute());
+ assertFalse(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
+
+ r = new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE);
+ assertFalse(r.isHostRoute());
+ assertFalse(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
+
+ r = new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE);
+ assertFalse(r.isHostRoute());
+ assertFalse(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
}
public void testTruncation() {
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 5f08a34..16ec134 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -16,6 +16,8 @@
package com.android.server;
+import static android.content.pm.PackageManager.GET_PERMISSIONS;
+import static android.content.pm.PackageManager.MATCH_ANY_USER;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.NETID_UNSET;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
@@ -60,11 +62,13 @@
import static android.net.NetworkPolicyManager.RULE_NONE;
import static android.net.NetworkPolicyManager.RULE_REJECT_ALL;
import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
+import static android.net.RouteInfo.RTN_UNREACHABLE;
import static com.android.internal.util.TestUtils.waitForIdleHandler;
import static com.android.internal.util.TestUtils.waitForIdleLooper;
import static com.android.internal.util.TestUtils.waitForIdleSerialExecutor;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
@@ -72,12 +76,14 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -97,6 +103,10 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
@@ -139,6 +149,7 @@
import android.net.shared.NetworkMonitorUtils;
import android.net.shared.PrivateDnsConfig;
import android.net.util.MultinetworkPolicyTracker;
+import android.os.Binder;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
@@ -152,6 +163,7 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.Settings;
import android.system.Os;
import android.test.mock.MockContentResolver;
@@ -187,6 +199,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
@@ -195,6 +208,7 @@
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.Inet4Address;
+import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
@@ -262,6 +276,8 @@
@Mock IDnsResolver mMockDnsResolver;
@Mock INetd mMockNetd;
@Mock NetworkStackClient mNetworkStack;
+ @Mock PackageManager mPackageManager;
+ @Mock UserManager mUserManager;
private ArgumentCaptor<ResolverParamsParcel> mResolverParamsParcelCaptor =
ArgumentCaptor.forClass(ResolverParamsParcel.class);
@@ -333,6 +349,7 @@
if (Context.CONNECTIVITY_SERVICE.equals(name)) return mCm;
if (Context.NOTIFICATION_SERVICE.equals(name)) return mock(NotificationManager.class);
if (Context.NETWORK_STACK_SERVICE.equals(name)) return mNetworkStack;
+ if (Context.USER_SERVICE.equals(name)) return mUserManager;
return super.getSystemService(name);
}
@@ -345,7 +362,12 @@
public Resources getResources() {
return mResources;
}
- }
+
+ @Override
+ public PackageManager getPackageManager() {
+ return mPackageManager;
+ }
+ }
public void waitForIdle(int timeoutMsAsInt) {
long timeoutMs = timeoutMsAsInt;
@@ -1059,7 +1081,7 @@
public WrappedConnectivityService(Context context, INetworkManagementService netManager,
INetworkStatsService statsService, INetworkPolicyManager policyManager,
IpConnectivityLog log, INetd netd, IDnsResolver dnsResolver) {
- super(context, netManager, statsService, policyManager, dnsResolver, log);
+ super(context, netManager, statsService, policyManager, dnsResolver, log, netd);
mNetd = netd;
mLingerDelayMs = TEST_LINGER_DELAY_MS;
}
@@ -1198,6 +1220,11 @@
fail("ConditionVariable was blocked for more than " + TIMEOUT_MS + "ms");
}
+ private static final int VPN_USER = 0;
+ private static final int APP1_UID = UserHandle.getUid(VPN_USER, 10100);
+ private static final int APP2_UID = UserHandle.getUid(VPN_USER, 10101);
+ private static final int VPN_UID = UserHandle.getUid(VPN_USER, 10043);
+
@Before
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getContext();
@@ -1205,11 +1232,17 @@
MockitoAnnotations.initMocks(this);
when(mMetricsService.defaultNetworkMetrics()).thenReturn(mDefaultNetworkMetrics);
+ when(mUserManager.getUsers(eq(true))).thenReturn(
+ Arrays.asList(new UserInfo[] {
+ new UserInfo(VPN_USER, "", 0),
+ }));
+
// InstrumentationTestRunner prepares a looper, but AndroidJUnitRunner does not.
// http://b/25897652 .
if (Looper.myLooper() == null) {
Looper.prepare();
}
+ mockDefaultPackages();
FakeSettingsProvider.clearSettingsProvider();
mServiceContext = new MockContext(InstrumentationRegistry.getContext(),
@@ -1262,7 +1295,24 @@
FakeSettingsProvider.clearSettingsProvider();
}
- private static int transportToLegacyType(int transport) {
+ private void mockDefaultPackages() throws Exception {
+ final String testPackageName = mContext.getPackageName();
+ final PackageInfo testPackageInfo = mContext.getPackageManager().getPackageInfo(
+ testPackageName, PackageManager.GET_PERMISSIONS);
+ when(mPackageManager.getPackagesForUid(Binder.getCallingUid())).thenReturn(
+ new String[] {testPackageName});
+ when(mPackageManager.getPackageInfoAsUser(eq(testPackageName), anyInt(),
+ eq(UserHandle.getCallingUserId()))).thenReturn(testPackageInfo);
+
+ when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS | MATCH_ANY_USER))).thenReturn(
+ Arrays.asList(new PackageInfo[] {
+ buildPackageInfo(/* SYSTEM */ false, APP1_UID),
+ buildPackageInfo(/* SYSTEM */ false, APP2_UID),
+ buildPackageInfo(/* SYSTEM */ false, VPN_UID)
+ }));
+ }
+
+ private static int transportToLegacyType(int transport) {
switch (transport) {
case TRANSPORT_ETHERNET:
return TYPE_ETHERNET;
@@ -6120,4 +6170,166 @@
assertEquals(testProxyInfo, mService.getProxyForNetwork(mWiFiNetworkAgent.getNetwork()));
assertEquals(testProxyInfo, mService.getProxyForNetwork(null));
}
+
+ @Test
+ public void testFullyRoutedVpnResultsInInterfaceFilteringRules() throws Exception {
+ LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName("tun0");
+ lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null));
+ // The uid range needs to cover the test app so the network is visible to it.
+ final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
+ final MockNetworkAgent vpnNetworkAgent = establishVpn(lp, VPN_UID, vpnRange);
+
+ // Connected VPN should have interface rules set up. There are two expected invocations,
+ // one during VPN uid update, one during VPN LinkProperties update
+ ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class);
+ verify(mMockNetd, times(2)).firewallAddUidInterfaceRules(eq("tun0"), uidCaptor.capture());
+ assertContainsExactly(uidCaptor.getAllValues().get(0), APP1_UID, APP2_UID);
+ assertContainsExactly(uidCaptor.getAllValues().get(1), APP1_UID, APP2_UID);
+ assertTrue(mService.mPermissionMonitor.getVpnUidRanges("tun0").equals(vpnRange));
+
+ vpnNetworkAgent.disconnect();
+ waitForIdle();
+
+ // Disconnected VPN should have interface rules removed
+ verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture());
+ assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
+ assertNull(mService.mPermissionMonitor.getVpnUidRanges("tun0"));
+ }
+
+ @Test
+ public void testLegacyVpnDoesNotResultInInterfaceFilteringRule() throws Exception {
+ LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName("tun0");
+ lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null));
+ // The uid range needs to cover the test app so the network is visible to it.
+ final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
+ final MockNetworkAgent vpnNetworkAgent = establishVpn(lp, Process.SYSTEM_UID, vpnRange);
+
+ // Legacy VPN should not have interface rules set up
+ verify(mMockNetd, never()).firewallAddUidInterfaceRules(any(), any());
+ }
+
+ @Test
+ public void testLocalIpv4OnlyVpnDoesNotResultInInterfaceFilteringRule()
+ throws Exception {
+ LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName("tun0");
+ lp.addRoute(new RouteInfo(new IpPrefix("192.0.2.0/24"), null, "tun0"));
+ lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE));
+ // The uid range needs to cover the test app so the network is visible to it.
+ final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
+ final MockNetworkAgent vpnNetworkAgent = establishVpn(lp, Process.SYSTEM_UID, vpnRange);
+
+ // IPv6 unreachable route should not be misinterpreted as a default route
+ verify(mMockNetd, never()).firewallAddUidInterfaceRules(any(), any());
+ }
+
+ @Test
+ public void testVpnHandoverChangesInterfaceFilteringRule() throws Exception {
+ LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName("tun0");
+ lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null));
+ // The uid range needs to cover the test app so the network is visible to it.
+ final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
+ final MockNetworkAgent vpnNetworkAgent = establishVpn(lp, VPN_UID, vpnRange);
+
+ // Connected VPN should have interface rules set up. There are two expected invocations,
+ // one during VPN uid update, one during VPN LinkProperties update
+ ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class);
+ verify(mMockNetd, times(2)).firewallAddUidInterfaceRules(eq("tun0"), uidCaptor.capture());
+ assertContainsExactly(uidCaptor.getAllValues().get(0), APP1_UID, APP2_UID);
+ assertContainsExactly(uidCaptor.getAllValues().get(1), APP1_UID, APP2_UID);
+
+ reset(mMockNetd);
+ InOrder inOrder = inOrder(mMockNetd);
+ lp.setInterfaceName("tun1");
+ vpnNetworkAgent.sendLinkProperties(lp);
+ waitForIdle();
+ // VPN handover (switch to a new interface) should result in rules being updated (old rules
+ // removed first, then new rules added)
+ inOrder.verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture());
+ assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
+ inOrder.verify(mMockNetd).firewallAddUidInterfaceRules(eq("tun1"), uidCaptor.capture());
+ assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
+
+ reset(mMockNetd);
+ lp = new LinkProperties();
+ lp.setInterfaceName("tun1");
+ lp.addRoute(new RouteInfo(new IpPrefix("192.0.2.0/24"), null, "tun1"));
+ vpnNetworkAgent.sendLinkProperties(lp);
+ waitForIdle();
+ // VPN not routing everything should no longer have interface filtering rules
+ verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture());
+ assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
+
+ reset(mMockNetd);
+ lp = new LinkProperties();
+ lp.setInterfaceName("tun1");
+ lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null));
+ vpnNetworkAgent.sendLinkProperties(lp);
+ waitForIdle();
+ // Back to routing all IPv6 traffic should have filtering rules
+ verify(mMockNetd).firewallAddUidInterfaceRules(eq("tun1"), uidCaptor.capture());
+ assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
+ }
+
+ @Test
+ public void testUidUpdateChangesInterfaceFilteringRule() throws Exception {
+ LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName("tun0");
+ lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null));
+ // The uid range needs to cover the test app so the network is visible to it.
+ final UidRange vpnRange = UidRange.createForUser(VPN_USER);
+ final MockNetworkAgent vpnNetworkAgent = establishVpn(lp, VPN_UID,
+ Collections.singleton(vpnRange));
+
+ reset(mMockNetd);
+ InOrder inOrder = inOrder(mMockNetd);
+
+ // Update to new range which is old range minus APP1, i.e. only APP2
+ final Set<UidRange> newRanges = new HashSet<>(Arrays.asList(
+ new UidRange(vpnRange.start, APP1_UID - 1),
+ new UidRange(APP1_UID + 1, vpnRange.stop)));
+ vpnNetworkAgent.setUids(newRanges);
+ waitForIdle();
+
+ ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class);
+ // Verify old rules are removed before new rules are added
+ inOrder.verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture());
+ assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
+ inOrder.verify(mMockNetd).firewallAddUidInterfaceRules(eq("tun0"), uidCaptor.capture());
+ assertContainsExactly(uidCaptor.getValue(), APP2_UID);
+ }
+
+
+ private MockNetworkAgent establishVpn(LinkProperties lp, int establishingUid,
+ Set<UidRange> vpnRange) {
+ final MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN, lp);
+ vpnNetworkAgent.getNetworkCapabilities().setEstablishingVpnAppUid(establishingUid);
+ mMockVpn.setNetworkAgent(vpnNetworkAgent);
+ mMockVpn.connect();
+ mMockVpn.setUids(vpnRange);
+ vpnNetworkAgent.connect(true);
+ waitForIdle();
+ return vpnNetworkAgent;
+ }
+
+ private void assertContainsExactly(int[] actual, int... expected) {
+ int[] sortedActual = Arrays.copyOf(actual, actual.length);
+ int[] sortedExpected = Arrays.copyOf(expected, expected.length);
+ Arrays.sort(sortedActual);
+ Arrays.sort(sortedExpected);
+ assertArrayEquals(sortedExpected, sortedActual);
+ }
+
+ private static PackageInfo buildPackageInfo(boolean hasSystemPermission, int uid) {
+ final PackageInfo packageInfo = new PackageInfo();
+ packageInfo.requestedPermissions = new String[0];
+ packageInfo.applicationInfo = new ApplicationInfo();
+ packageInfo.applicationInfo.privateFlags = 0;
+ packageInfo.applicationInfo.uid = UserHandle.getUid(UserHandle.USER_SYSTEM,
+ UserHandle.getAppId(uid));
+ return packageInfo;
+ }
}
diff --git a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
index 106cd1f..62a4718 100644
--- a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
@@ -28,6 +28,7 @@
import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_VENDOR;
import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
+import static android.content.pm.PackageManager.MATCH_ANY_USER;
import static android.os.Process.SYSTEM_UID;
import static com.android.server.connectivity.PermissionMonitor.NETWORK;
@@ -36,13 +37,16 @@
import static junit.framework.Assert.fail;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.AdditionalMatchers.aryEq;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.anyInt;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -53,10 +57,12 @@
import android.content.pm.PackageList;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
+import android.content.pm.UserInfo;
import android.net.INetd;
+import android.net.UidRange;
import android.os.Build;
-import android.os.INetworkManagementService;
import android.os.UserHandle;
+import android.os.UserManager;
import android.util.SparseIntArray;
import androidx.test.filters.SmallTest;
@@ -73,7 +79,12 @@
import org.mockito.invocation.InvocationOnMock;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -84,10 +95,12 @@
private static final int MOCK_UID2 = 10086;
private static final int SYSTEM_UID1 = 1000;
private static final int SYSTEM_UID2 = 1008;
+ private static final int VPN_UID = 10002;
private static final String MOCK_PACKAGE1 = "appName1";
private static final String MOCK_PACKAGE2 = "appName2";
private static final String SYSTEM_PACKAGE1 = "sysName1";
private static final String SYSTEM_PACKAGE2 = "sysName2";
+ private static final String VPN_PACKAGE = "vpnApp";
private static final String PARTITION_SYSTEM = "system";
private static final String PARTITION_OEM = "oem";
private static final String PARTITION_PRODUCT = "product";
@@ -97,9 +110,9 @@
@Mock private Context mContext;
@Mock private PackageManager mPackageManager;
- @Mock private INetworkManagementService mNMS;
@Mock private INetd mNetdService;
@Mock private PackageManagerInternal mMockPmi;
+ @Mock private UserManager mUserManager;
private PackageManagerInternal.PackageListObserver mObserver;
private PermissionMonitor mPermissionMonitor;
@@ -108,7 +121,14 @@
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
when(mContext.getPackageManager()).thenReturn(mPackageManager);
- mPermissionMonitor = spy(new PermissionMonitor(mContext, mNMS, mNetdService));
+ when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager);
+ when(mUserManager.getUsers(eq(true))).thenReturn(
+ Arrays.asList(new UserInfo[] {
+ new UserInfo(MOCK_USER1, "", 0),
+ new UserInfo(MOCK_USER2, "", 0),
+ }));
+
+ mPermissionMonitor = spy(new PermissionMonitor(mContext, mNetdService));
LocalServices.removeServiceForTest(PackageManagerInternal.class);
LocalServices.addService(PackageManagerInternal.class, mMockPmi);
@@ -134,7 +154,7 @@
return mPermissionMonitor.hasUseBackgroundNetworksPermission(uid);
}
- private PackageInfo packageInfoWithPermissions(String[] permissions, String partition) {
+ private static PackageInfo packageInfoWithPermissions(String[] permissions, String partition) {
int[] requestedPermissionsFlags = new int[permissions.length];
for (int i = 0; i < permissions.length; i++) {
requestedPermissionsFlags[i] = REQUESTED_PERMISSION_GRANTED;
@@ -143,7 +163,7 @@
requestedPermissionsFlags);
}
- private PackageInfo packageInfoWithPermissions(String[] permissions, String partition,
+ private static PackageInfo packageInfoWithPermissions(String[] permissions, String partition,
int[] requestedPermissionsFlags) {
final PackageInfo packageInfo = new PackageInfo();
packageInfo.requestedPermissions = permissions;
@@ -165,6 +185,18 @@
return packageInfo;
}
+ private static PackageInfo buildPackageInfo(boolean hasSystemPermission, int uid, int userId) {
+ final PackageInfo pkgInfo;
+ if (hasSystemPermission) {
+ pkgInfo = packageInfoWithPermissions(new String[] {CHANGE_NETWORK_STATE, NETWORK_STACK},
+ PARTITION_SYSTEM);
+ } else {
+ pkgInfo = packageInfoWithPermissions(new String[] {}, "");
+ }
+ pkgInfo.applicationInfo.uid = UserHandle.getUid(userId, UserHandle.getAppId(uid));
+ return pkgInfo;
+ }
+
@Test
public void testHasPermission() {
PackageInfo app = packageInfoWithPermissions(new String[] {}, PARTITION_SYSTEM);
@@ -245,14 +277,14 @@
assertFalse(hasBgPermission(PARTITION_VENDOR, VERSION_Q, MOCK_UID1, CHANGE_WIFI_STATE));
}
- private class NMSMonitor {
+ private class NetdMonitor {
private final HashMap<Integer, Boolean> mApps = new HashMap<>();
- NMSMonitor(INetworkManagementService mockNMS) throws Exception {
+ NetdMonitor(INetd mockNetd) throws Exception {
// Add hook to verify and track result of setPermission.
doAnswer((InvocationOnMock invocation) -> {
final Object[] args = invocation.getArguments();
- final Boolean isSystem = args[0].equals("SYSTEM");
+ final Boolean isSystem = args[0].equals(INetd.PERMISSION_SYSTEM);
for (final int uid : (int[]) args[1]) {
// TODO: Currently, permission monitor will send duplicate commands for each uid
// corresponding to each user. Need to fix that and uncomment below test.
@@ -262,7 +294,7 @@
mApps.put(uid, isSystem);
}
return null;
- }).when(mockNMS).setPermission(anyString(), any(int[].class));
+ }).when(mockNetd).networkSetPermissionForUser(anyInt(), any(int[].class));
// Add hook to verify and track result of clearPermission.
doAnswer((InvocationOnMock invocation) -> {
@@ -276,7 +308,7 @@
mApps.remove(uid);
}
return null;
- }).when(mockNMS).clearPermission(any(int[].class));
+ }).when(mockNetd).networkClearPermissionForUser(any(int[].class));
}
public void expectPermission(Boolean permission, int[] users, int[] apps) {
@@ -307,7 +339,7 @@
@Test
public void testUserAndPackageAddRemove() throws Exception {
- final NMSMonitor mNMSMonitor = new NMSMonitor(mNMS);
+ final NetdMonitor mNetdMonitor = new NetdMonitor(mNetdService);
// MOCK_UID1: MOCK_PACKAGE1 only has network permission.
// SYSTEM_UID: SYSTEM_PACKAGE1 has system permission.
@@ -323,48 +355,123 @@
// Add SYSTEM_PACKAGE2, expect only have network permission.
mPermissionMonitor.onUserAdded(MOCK_USER1);
addPackageForUsers(new int[]{MOCK_USER1}, SYSTEM_PACKAGE2, SYSTEM_UID);
- mNMSMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1}, new int[]{SYSTEM_UID});
+ mNetdMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1}, new int[]{SYSTEM_UID});
// Add SYSTEM_PACKAGE1, expect permission escalate.
addPackageForUsers(new int[]{MOCK_USER1}, SYSTEM_PACKAGE1, SYSTEM_UID);
- mNMSMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1}, new int[]{SYSTEM_UID});
+ mNetdMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1}, new int[]{SYSTEM_UID});
mPermissionMonitor.onUserAdded(MOCK_USER2);
- mNMSMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1, MOCK_USER2},
+ mNetdMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1, MOCK_USER2},
new int[]{SYSTEM_UID});
addPackageForUsers(new int[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_UID1);
- mNMSMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1, MOCK_USER2},
+ mNetdMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1, MOCK_USER2},
new int[]{SYSTEM_UID});
- mNMSMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1, MOCK_USER2},
+ mNetdMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1, MOCK_USER2},
new int[]{MOCK_UID1});
// Remove MOCK_UID1, expect no permission left for all user.
mPermissionMonitor.onPackageRemoved(MOCK_UID1);
removePackageForUsers(new int[]{MOCK_USER1, MOCK_USER2}, MOCK_UID1);
- mNMSMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2}, new int[]{MOCK_UID1});
+ mNetdMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2}, new int[]{MOCK_UID1});
// Remove SYSTEM_PACKAGE1, expect permission downgrade.
when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(new String[]{SYSTEM_PACKAGE2});
removePackageForUsers(new int[]{MOCK_USER1, MOCK_USER2}, SYSTEM_UID);
- mNMSMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1, MOCK_USER2},
+ mNetdMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1, MOCK_USER2},
new int[]{SYSTEM_UID});
mPermissionMonitor.onUserRemoved(MOCK_USER1);
- mNMSMonitor.expectPermission(NETWORK, new int[]{MOCK_USER2}, new int[]{SYSTEM_UID});
+ mNetdMonitor.expectPermission(NETWORK, new int[]{MOCK_USER2}, new int[]{SYSTEM_UID});
// Remove all packages, expect no permission left.
when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(new String[]{});
removePackageForUsers(new int[]{MOCK_USER2}, SYSTEM_UID);
- mNMSMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2},
+ mNetdMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2},
new int[]{SYSTEM_UID, MOCK_UID1});
// Remove last user, expect no redundant clearPermission is invoked.
mPermissionMonitor.onUserRemoved(MOCK_USER2);
- mNMSMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2},
+ mNetdMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2},
new int[]{SYSTEM_UID, MOCK_UID1});
}
+ @Test
+ public void testUidFilteringDuringVpnConnectDisconnectAndUidUpdates() throws Exception {
+ when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS | MATCH_ANY_USER))).thenReturn(
+ Arrays.asList(new PackageInfo[] {
+ buildPackageInfo(/* SYSTEM */ true, SYSTEM_UID1, MOCK_USER1),
+ buildPackageInfo(/* SYSTEM */ false, MOCK_UID1, MOCK_USER1),
+ buildPackageInfo(/* SYSTEM */ false, MOCK_UID2, MOCK_USER1),
+ buildPackageInfo(/* SYSTEM */ false, VPN_UID, MOCK_USER1)
+ }));
+ when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE1), eq(GET_PERMISSIONS))).thenReturn(
+ buildPackageInfo(false, MOCK_UID1, MOCK_USER1));
+ mPermissionMonitor.startMonitoring();
+ // Every app on user 0 except MOCK_UID2 are under VPN.
+ final Set<UidRange> vpnRange1 = new HashSet<>(Arrays.asList(new UidRange[] {
+ new UidRange(0, MOCK_UID2 - 1),
+ new UidRange(MOCK_UID2 + 1, UserHandle.PER_USER_RANGE - 1)}));
+ final Set<UidRange> vpnRange2 = Collections.singleton(new UidRange(MOCK_UID2, MOCK_UID2));
+
+ // When VPN is connected, expect a rule to be set up for user app MOCK_UID1
+ mPermissionMonitor.onVpnUidRangesAdded("tun0", vpnRange1, VPN_UID);
+ verify(mNetdService).firewallAddUidInterfaceRules(eq("tun0"),
+ aryEq(new int[] {MOCK_UID1}));
+
+ reset(mNetdService);
+
+ // When MOCK_UID1 package is uninstalled and reinstalled, expect Netd to be updated
+ mPermissionMonitor.onPackageRemoved(UserHandle.getUid(MOCK_USER1, MOCK_UID1));
+ verify(mNetdService).firewallRemoveUidInterfaceRules(aryEq(new int[] {MOCK_UID1}));
+ mPermissionMonitor.onPackageAdded(MOCK_PACKAGE1, UserHandle.getUid(MOCK_USER1, MOCK_UID1));
+ verify(mNetdService).firewallAddUidInterfaceRules(eq("tun0"),
+ aryEq(new int[] {MOCK_UID1}));
+
+ reset(mNetdService);
+
+ // During VPN uid update (vpnRange1 -> vpnRange2), ConnectivityService first deletes the
+ // old UID rules then adds the new ones. Expect netd to be updated
+ mPermissionMonitor.onVpnUidRangesRemoved("tun0", vpnRange1, VPN_UID);
+ verify(mNetdService).firewallRemoveUidInterfaceRules(aryEq(new int[] {MOCK_UID1}));
+ mPermissionMonitor.onVpnUidRangesAdded("tun0", vpnRange2, VPN_UID);
+ verify(mNetdService).firewallAddUidInterfaceRules(eq("tun0"),
+ aryEq(new int[] {MOCK_UID2}));
+
+ reset(mNetdService);
+
+ // When VPN is disconnected, expect rules to be torn down
+ mPermissionMonitor.onVpnUidRangesRemoved("tun0", vpnRange2, VPN_UID);
+ verify(mNetdService).firewallRemoveUidInterfaceRules(aryEq(new int[] {MOCK_UID2}));
+ assertNull(mPermissionMonitor.getVpnUidRanges("tun0"));
+ }
+
+ @Test
+ public void testUidFilteringDuringPackageInstallAndUninstall() throws Exception {
+ when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS | MATCH_ANY_USER))).thenReturn(
+ Arrays.asList(new PackageInfo[] {
+ buildPackageInfo(true, SYSTEM_UID1, MOCK_USER1),
+ buildPackageInfo(false, VPN_UID, MOCK_USER1)
+ }));
+ when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE1), eq(GET_PERMISSIONS))).thenReturn(
+ buildPackageInfo(false, MOCK_UID1, MOCK_USER1));
+
+ mPermissionMonitor.startMonitoring();
+ final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(MOCK_USER1));
+ mPermissionMonitor.onVpnUidRangesAdded("tun0", vpnRange, VPN_UID);
+
+ // Newly-installed package should have uid rules added
+ mPermissionMonitor.onPackageAdded(MOCK_PACKAGE1, UserHandle.getUid(MOCK_USER1, MOCK_UID1));
+ verify(mNetdService).firewallAddUidInterfaceRules(eq("tun0"),
+ aryEq(new int[] {MOCK_UID1}));
+
+ // Removed package should have its uid rules removed
+ mPermissionMonitor.onPackageRemoved(UserHandle.getUid(MOCK_USER1, MOCK_UID1));
+ verify(mNetdService).firewallRemoveUidInterfaceRules(aryEq(new int[] {MOCK_UID1}));
+ }
+
+
// Normal package add/remove operations will trigger multiple intent for uids corresponding to
// each user. To simulate generic package operations, the onPackageAdded/Removed will need to be
// called multiple times with the uid corresponding to each user.
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index 1773b5a..836e199 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -267,7 +267,8 @@
// A DECL will override a USE without error. Two DECLs must match in their format for there to be
// no error.
ResourceTable::CollisionResult ResourceTable::ResolveValueCollision(Value* existing,
- Value* incoming) {
+ Value* incoming,
+ bool overlay) {
Attribute* existing_attr = ValueCast<Attribute>(existing);
Attribute* incoming_attr = ValueCast<Attribute>(incoming);
if (!incoming_attr) {
@@ -281,7 +282,7 @@
}
// The existing and incoming values are strong, this is an error
// if the values are not both attributes.
- return CollisionResult::kConflict;
+ return overlay ? CollisionResult::kTakeNew : CollisionResult::kConflict;
}
if (!existing_attr) {
@@ -292,7 +293,7 @@
}
// The existing value is not an attribute and it is strong,
// so the incoming attribute value is an error.
- return CollisionResult::kConflict;
+ return overlay ? CollisionResult::kTakeNew : CollisionResult::kConflict;
}
CHECK(incoming_attr != nullptr && existing_attr != nullptr);
@@ -323,8 +324,9 @@
return CollisionResult::kConflict;
}
-ResourceTable::CollisionResult ResourceTable::IgnoreCollision(Value* /** existing **/,
- Value* /** incoming **/) {
+ResourceTable::CollisionResult ResourceTable::IgnoreCollision(Value* /* existing */,
+ Value* /* incoming */,
+ bool /* overlay */) {
return CollisionResult::kKeepBoth;
}
@@ -440,7 +442,7 @@
// Resource does not exist, add it now.
config_value->value = std::move(value);
} else {
- switch (conflict_resolver(config_value->value.get(), value.get())) {
+ switch (conflict_resolver(config_value->value.get(), value.get(), false /* overlay */)) {
case CollisionResult::kKeepBoth:
// Insert the value ignoring for duplicate configurations
entry->values.push_back(util::make_unique<ResourceConfigValue>(config, product));
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index 30ba1ae..e879380 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -228,13 +228,13 @@
enum class CollisionResult { kKeepBoth, kKeepOriginal, kConflict, kTakeNew };
- using CollisionResolverFunc = std::function<CollisionResult(Value*, Value*)>;
+ using CollisionResolverFunc = std::function<CollisionResult(Value*, Value*, bool)>;
// When a collision of resources occurs, this method decides which value to keep.
- static CollisionResult ResolveValueCollision(Value* existing, Value* incoming);
+ static CollisionResult ResolveValueCollision(Value* existing, Value* incoming, bool overlay);
// When a collision of resources occurs, this method keeps both values
- static CollisionResult IgnoreCollision(Value* existing, Value* incoming);
+ static CollisionResult IgnoreCollision(Value* existing, Value* incoming, bool overlay);
bool AddResource(const ResourceNameRef& name, const android::ConfigDescription& config,
const android::StringPiece& product, std::unique_ptr<Value> value,
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index 34b46c5..6960127 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -574,6 +574,10 @@
}
bool Attribute::IsCompatibleWith(const Attribute& attr) const {
+ if (Equals(&attr)) {
+ return true;
+ }
+
// If the high bits are set on any of these attribute type masks, then they are incompatible.
// We don't check that flags and enums are identical.
if ((type_mask & ~android::ResTable_map::TYPE_ANY) != 0 ||
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index c0802e6..3f65e86 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -188,7 +188,7 @@
}
}
// Delegate to the default handler.
- return ResourceTable::ResolveValueCollision(existing, incoming);
+ return ResourceTable::ResolveValueCollision(existing, incoming, true /* overlay */);
}
static ResourceTable::CollisionResult MergeConfigValue(IAaptContext* context,
@@ -206,15 +206,11 @@
if (overlay) {
collision_result = ResolveMergeCollision(dst_value, src_value, pool);
} else {
- collision_result = ResourceTable::ResolveValueCollision(dst_value, src_value);
+ collision_result = ResourceTable::ResolveValueCollision(dst_value, src_value,
+ false /* overlay */);
}
if (collision_result == CollisionResult::kConflict) {
- if (overlay) {
- return CollisionResult::kTakeNew;
- }
-
- // Error!
context->GetDiagnostics()->Error(DiagMessage(src_value->GetSource())
<< "resource '" << res_name << "' has a conflicting value for "
<< "configuration (" << src_config_value->config << ")");
diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp
index 9dd31e6..be9c84b 100644
--- a/tools/aapt2/link/TableMerger_test.cpp
+++ b/tools/aapt2/link/TableMerger_test.cpp
@@ -352,6 +352,110 @@
ASSERT_TRUE(merger.Merge({}, table_b.get(), false /*overlay*/));
}
+TEST_F(TableMergerTest, OverrideAttributeSameFormatsWithOverlay) {
+ std::unique_ptr<ResourceTable> base =
+ test::ResourceTableBuilder()
+ .SetPackageId("", 0x7f)
+ .AddValue("attr/foo", test::AttributeBuilder()
+ .SetTypeMask(android::ResTable_map::TYPE_STRING)
+ .SetWeak(false)
+ .Build())
+ .Build();
+
+ std::unique_ptr<ResourceTable> overlay =
+ test::ResourceTableBuilder()
+ .SetPackageId("", 0x7f)
+ .AddValue("attr/foo", test::AttributeBuilder()
+ .SetTypeMask(android::ResTable_map::TYPE_STRING)
+ .SetWeak(false)
+ .Build())
+ .Build();
+
+ ResourceTable final_table;
+ TableMergerOptions options;
+ options.auto_add_overlay = false;
+ TableMerger merger(context_.get(), &final_table, options);
+
+ ASSERT_TRUE(merger.Merge({}, base.get(), false /*overlay*/));
+ ASSERT_TRUE(merger.Merge({}, overlay.get(), true /*overlay*/));
+}
+
+TEST_F(TableMergerTest, FailToOverrideConflictingAttributeFormatsWithOverlay) {
+ std::unique_ptr<ResourceTable> base =
+ test::ResourceTableBuilder()
+ .SetPackageId("", 0x7f)
+ .AddValue("attr/foo", test::AttributeBuilder()
+ .SetTypeMask(android::ResTable_map::TYPE_ANY)
+ .SetWeak(false)
+ .Build())
+ .Build();
+
+ std::unique_ptr<ResourceTable> overlay =
+ test::ResourceTableBuilder()
+ .SetPackageId("", 0x7f)
+ .AddValue("attr/foo", test::AttributeBuilder()
+ .SetTypeMask(android::ResTable_map::TYPE_STRING)
+ .SetWeak(false)
+ .Build())
+ .Build();
+
+ ResourceTable final_table;
+ TableMergerOptions options;
+ options.auto_add_overlay = false;
+ TableMerger merger(context_.get(), &final_table, options);
+
+ ASSERT_TRUE(merger.Merge({}, base.get(), false /*overlay*/));
+ ASSERT_FALSE(merger.Merge({}, overlay.get(), true /*overlay*/));
+}
+
+TEST_F(TableMergerTest, FailToOverrideConflictingFlagsAndEnumsWithOverlay) {
+ std::unique_ptr<ResourceTable> base =
+ test::ResourceTableBuilder()
+ .SetPackageId("", 0x7f)
+ .AddValue("attr/foo", test::AttributeBuilder()
+ .SetTypeMask(android::ResTable_map::TYPE_FLAGS)
+ .Build())
+ .Build();
+
+ std::unique_ptr<ResourceTable> overlay =
+ test::ResourceTableBuilder()
+ .SetPackageId("", 0x7f)
+ .AddValue("attr/foo", test::AttributeBuilder()
+ .SetTypeMask(android::ResTable_map::TYPE_FLAGS)
+ .SetWeak(false)
+ .Build())
+ .Build();
+
+ ResourceTable final_table;
+ TableMergerOptions options;
+ options.auto_add_overlay = false;
+ TableMerger merger(context_.get(), &final_table, options);
+
+ ASSERT_TRUE(merger.Merge({}, base.get(), false /*overlay*/));
+ ASSERT_FALSE(merger.Merge({}, overlay.get(), true /*overlay*/));
+
+ base = test::ResourceTableBuilder()
+ .SetPackageId("", 0x7f)
+ .AddValue("attr/foo", test::AttributeBuilder()
+ .SetTypeMask(android::ResTable_map::TYPE_ENUM)
+ .Build())
+ .Build();
+
+ overlay = test::ResourceTableBuilder()
+ .SetPackageId("", 0x7f)
+ .AddValue("attr/foo", test::AttributeBuilder()
+ .SetTypeMask(android::ResTable_map::TYPE_ENUM)
+ .SetWeak(false)
+ .Build())
+ .Build();
+
+ ResourceTable final_table2;
+ TableMerger merger2(context_.get(), &final_table2, options);
+
+ ASSERT_TRUE(merger2.Merge({}, base.get(), false /*overlay*/));
+ ASSERT_FALSE(merger2.Merge({}, overlay.get(), true /*overlay*/));
+}
+
TEST_F(TableMergerTest, FailToMergeNewResourceWithoutAutoAddOverlay) {
std::unique_ptr<ResourceTable> table_a =
test::ResourceTableBuilder().SetPackageId("", 0x7f).Build();