Import Android SDK Platform P [4477446]
/google/data/ro/projects/android/fetch_artifact \
--bid 4477446 \
--target sdk_phone_armv7-win_sdk \
sdk-repo-linux-sources-4477446.zip
AndroidVersion.ApiLevel has been modified to appear as 28
Change-Id: If0559643d7c328e36aafca98f0c114641d33642c
diff --git a/com/android/car/setupwizardlib/CarSetupWizardLayout.java b/com/android/car/setupwizardlib/CarSetupWizardLayout.java
index d183897..3586310 100644
--- a/com/android/car/setupwizardlib/CarSetupWizardLayout.java
+++ b/com/android/car/setupwizardlib/CarSetupWizardLayout.java
@@ -23,6 +23,7 @@
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
+import android.widget.ProgressBar;
/**
@@ -41,6 +42,8 @@
private Button mPrimaryContinueButton;
private Button mSecondaryContinueButton;
+ private ProgressBar mProgressBar;
+
public CarSetupWizardLayout(Context context) {
this(context, null);
}
@@ -84,6 +87,8 @@
String secondaryContinueButtonText;
boolean secondaryContinueButtonEnabled;
+ boolean showProgressBar;
+
try {
showBackButton = attrArray.getBoolean(
R.styleable.CarSetupWizardLayout_showBackButton, true);
@@ -99,6 +104,8 @@
R.styleable.CarSetupWizardLayout_secondaryContinueButtonText);
secondaryContinueButtonEnabled = attrArray.getBoolean(
R.styleable.CarSetupWizardLayout_secondaryContinueButtonEnabled, true);
+ showProgressBar = attrArray.getBoolean(
+ R.styleable.CarSetupWizardLayout_showProgressBar, false);
} finally {
attrArray.recycle();
}
@@ -130,14 +137,16 @@
setSecondaryContinueButtonVisible(false);
}
- // TODO: Handle loading bar logic
+ mProgressBar = findViewById(R.id.progress_bar);
+ setProgressBarVisible(showProgressBar);
+
}
/**
* Set a given button's visibility.
*/
- private void setViewVisible(View button, boolean visible) {
- button.setVisibility(visible ? View.VISIBLE : View.GONE);
+ private void setViewVisible(View view, boolean visible) {
+ view.setVisibility(visible ? View.VISIBLE : View.GONE);
}
/**
@@ -212,4 +221,11 @@
public void setSecondaryContinueButtonVisible(boolean visible) {
setViewVisible(mSecondaryContinueButton, visible);
}
+
+ /**
+ * Set the progress bar visibility to the given visibility.
+ */
+ public void setProgressBarVisible(boolean visible) {
+ setViewVisible(mProgressBar, visible);
+ }
}
diff --git a/com/android/commands/pm/Pm.java b/com/android/commands/pm/Pm.java
deleted file mode 100644
index 9490880..0000000
--- a/com/android/commands/pm/Pm.java
+++ /dev/null
@@ -1,822 +0,0 @@
-/*
- * Copyright (C) 2007 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.commands.pm;
-
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
-
-import android.accounts.IAccountManager;
-import android.app.ActivityManager;
-import android.app.PackageInstallObserver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.IIntentReceiver;
-import android.content.IIntentSender;
-import android.content.Intent;
-import android.content.IntentSender;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageDataObserver;
-import android.content.pm.IPackageInstaller;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageInstaller.SessionInfo;
-import android.content.pm.PackageInstaller.SessionParams;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageParser;
-import android.content.pm.PackageParser.ApkLite;
-import android.content.pm.PackageParser.PackageLite;
-import android.content.pm.PackageParser.PackageParserException;
-import android.content.pm.UserInfo;
-import android.net.Uri;
-import android.os.Binder;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.IUserManager;
-import android.os.ParcelFileDescriptor;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.os.SELinux;
-import android.os.ServiceManager;
-import android.os.ShellCallback;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.os.storage.StorageManager;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.util.Log;
-import android.util.Pair;
-
-import com.android.internal.content.PackageHelper;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.SizedInputStream;
-
-import libcore.io.IoUtils;
-
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.concurrent.SynchronousQueue;
-import java.util.concurrent.TimeUnit;
-
-public final class Pm {
- private static final String TAG = "Pm";
- private static final String STDIN_PATH = "-";
-
- IPackageManager mPm;
- IPackageInstaller mInstaller;
- IUserManager mUm;
- IAccountManager mAm;
-
- private String[] mArgs;
- private int mNextArg;
- private String mCurArgData;
-
- private static final String PM_NOT_RUNNING_ERR =
- "Error: Could not access the Package Manager. Is the system running?";
-
- public static void main(String[] args) {
- int exitCode = 1;
- try {
- exitCode = new Pm().run(args);
- } catch (Exception e) {
- Log.e(TAG, "Error", e);
- System.err.println("Error: " + e);
- if (e instanceof RemoteException) {
- System.err.println(PM_NOT_RUNNING_ERR);
- }
- }
- System.exit(exitCode);
- }
-
- public int run(String[] args) throws RemoteException {
- if (args.length < 1) {
- return runShellCommand("package", mArgs);
- }
- mAm = IAccountManager.Stub.asInterface(ServiceManager.getService(Context.ACCOUNT_SERVICE));
- mUm = IUserManager.Stub.asInterface(ServiceManager.getService(Context.USER_SERVICE));
- mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
-
- if (mPm == null) {
- System.err.println(PM_NOT_RUNNING_ERR);
- return 1;
- }
- mInstaller = mPm.getPackageInstaller();
-
- mArgs = args;
- String op = args[0];
- mNextArg = 1;
-
- if ("install".equals(op)) {
- return runInstall();
- }
-
- if ("install-create".equals(op)) {
- return runInstallCreate();
- }
-
- if ("install-write".equals(op)) {
- return runInstallWrite();
- }
-
- if ("install-commit".equals(op)) {
- return runInstallCommit();
- }
-
- if ("install-abandon".equals(op) || "install-destroy".equals(op)) {
- return runInstallAbandon();
- }
-
- return runShellCommand("package", mArgs);
- }
-
- static final class MyShellCallback extends ShellCallback {
- @Override public ParcelFileDescriptor onOpenFile(String path, String seLinuxContext,
- String mode) {
- File file = new File(path);
- final ParcelFileDescriptor fd;
- try {
- fd = ParcelFileDescriptor.open(file,
- ParcelFileDescriptor.MODE_CREATE |
- ParcelFileDescriptor.MODE_TRUNCATE |
- ParcelFileDescriptor.MODE_WRITE_ONLY);
- } catch (FileNotFoundException e) {
- String msg = "Unable to open file " + path + ": " + e;
- System.err.println(msg);
- throw new IllegalArgumentException(msg);
- }
- if (seLinuxContext != null) {
- final String tcon = SELinux.getFileContext(file.getAbsolutePath());
- if (!SELinux.checkSELinuxAccess(seLinuxContext, tcon, "file", "write")) {
- try {
- fd.close();
- } catch (IOException e) {
- }
- String msg = "System server has no access to file context " + tcon;
- System.err.println(msg + " (from path " + file.getAbsolutePath()
- + ", context " + seLinuxContext + ")");
- throw new IllegalArgumentException(msg);
- }
- }
- return fd;
- }
- }
-
- private int runShellCommand(String serviceName, String[] args) {
- final HandlerThread handlerThread = new HandlerThread("results");
- handlerThread.start();
- try {
- ServiceManager.getService(serviceName).shellCommand(
- FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
- args, new MyShellCallback(),
- new ResultReceiver(new Handler(handlerThread.getLooper())));
- return 0;
- } catch (RemoteException e) {
- e.printStackTrace();
- } finally {
- handlerThread.quitSafely();
- }
- return -1;
- }
-
- private static class LocalIntentReceiver {
- private final SynchronousQueue<Intent> mResult = new SynchronousQueue<>();
-
- private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
- @Override
- public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
- IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
- try {
- mResult.offer(intent, 5, TimeUnit.SECONDS);
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- }
- };
-
- public IntentSender getIntentSender() {
- return new IntentSender((IIntentSender) mLocalSender);
- }
-
- public Intent getResult() {
- try {
- return mResult.take();
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- }
- }
-
- private int translateUserId(int userId, String logContext) {
- return ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
- userId, true, true, logContext, "pm command");
- }
-
- private static String checkAbiArgument(String abi) {
- if (TextUtils.isEmpty(abi)) {
- throw new IllegalArgumentException("Missing ABI argument");
- }
- if ("-".equals(abi)) {
- return abi;
- }
- final String[] supportedAbis = Build.SUPPORTED_ABIS;
- for (String supportedAbi : supportedAbis) {
- if (supportedAbi.equals(abi)) {
- return abi;
- }
- }
- throw new IllegalArgumentException("ABI " + abi + " not supported on this device");
- }
-
- /*
- * Keep this around to support existing users of the "pm install" command that may not be
- * able to be updated [or, at least informed the API has changed] such as ddmlib.
- *
- * Moving the implementation of "pm install" to "cmd package install" changes the executing
- * context. Instead of being a stand alone process, "cmd package install" runs in the
- * system_server process. Due to SELinux rules, system_server cannot access many directories;
- * one of which being the package install staging directory [/data/local/tmp].
- *
- * The use of "adb install" or "cmd package install" over "pm install" is highly encouraged.
- */
- private int runInstall() throws RemoteException {
- long startedTime = SystemClock.elapsedRealtime();
- final InstallParams params = makeInstallParams();
- final String inPath = nextArg();
- if (params.sessionParams.sizeBytes == -1 && !STDIN_PATH.equals(inPath)) {
- File file = new File(inPath);
- if (file.isFile()) {
- try {
- ApkLite baseApk = PackageParser.parseApkLite(file, 0);
- PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null,
- null, null);
- params.sessionParams.setSize(
- PackageHelper.calculateInstalledSize(pkgLite,
- params.sessionParams.abiOverride));
- } catch (PackageParserException | IOException e) {
- System.err.println("Error: Failed to parse APK file: " + e);
- return 1;
- }
- } else {
- System.err.println("Error: Can't open non-file: " + inPath);
- return 1;
- }
- }
-
- final int sessionId = doCreateSession(params.sessionParams,
- params.installerPackageName, params.userId);
-
- try {
- if (inPath == null && params.sessionParams.sizeBytes == -1) {
- System.err.println("Error: must either specify a package size or an APK file");
- return 1;
- }
- if (doWriteSession(sessionId, inPath, params.sessionParams.sizeBytes, "base.apk",
- false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
- return 1;
- }
- Pair<String, Integer> status = doCommitSession(sessionId, false /*logSuccess*/);
- if (status.second != PackageInstaller.STATUS_SUCCESS) {
- return 1;
- }
- Log.i(TAG, "Package " + status.first + " installed in " + (SystemClock.elapsedRealtime()
- - startedTime) + " ms");
- System.out.println("Success");
- return 0;
- } finally {
- try {
- mInstaller.abandonSession(sessionId);
- } catch (Exception ignore) {
- }
- }
- }
-
- private int runInstallAbandon() throws RemoteException {
- final int sessionId = Integer.parseInt(nextArg());
- return doAbandonSession(sessionId, true /*logSuccess*/);
- }
-
- private int runInstallCommit() throws RemoteException {
- final int sessionId = Integer.parseInt(nextArg());
- return doCommitSession(sessionId, true /*logSuccess*/).second;
- }
-
- private int runInstallCreate() throws RemoteException {
- final InstallParams installParams = makeInstallParams();
- final int sessionId = doCreateSession(installParams.sessionParams,
- installParams.installerPackageName, installParams.userId);
-
- // NOTE: adb depends on parsing this string
- System.out.println("Success: created install session [" + sessionId + "]");
- return PackageInstaller.STATUS_SUCCESS;
- }
-
- private int runInstallWrite() throws RemoteException {
- long sizeBytes = -1;
-
- String opt;
- while ((opt = nextOption()) != null) {
- if (opt.equals("-S")) {
- sizeBytes = Long.parseLong(nextArg());
- } else {
- throw new IllegalArgumentException("Unknown option: " + opt);
- }
- }
-
- final int sessionId = Integer.parseInt(nextArg());
- final String splitName = nextArg();
- final String path = nextArg();
- return doWriteSession(sessionId, path, sizeBytes, splitName, true /*logSuccess*/);
- }
-
- private static class InstallParams {
- SessionParams sessionParams;
- String installerPackageName;
- int userId = UserHandle.USER_ALL;
- }
-
- private InstallParams makeInstallParams() {
- final SessionParams sessionParams = new SessionParams(SessionParams.MODE_FULL_INSTALL);
- final InstallParams params = new InstallParams();
- params.sessionParams = sessionParams;
- String opt;
- while ((opt = nextOption()) != null) {
- switch (opt) {
- case "-l":
- sessionParams.installFlags |= PackageManager.INSTALL_FORWARD_LOCK;
- break;
- case "-r":
- sessionParams.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
- break;
- case "-i":
- params.installerPackageName = nextArg();
- if (params.installerPackageName == null) {
- throw new IllegalArgumentException("Missing installer package");
- }
- break;
- case "-t":
- sessionParams.installFlags |= PackageManager.INSTALL_ALLOW_TEST;
- break;
- case "-s":
- sessionParams.installFlags |= PackageManager.INSTALL_EXTERNAL;
- break;
- case "-f":
- sessionParams.installFlags |= PackageManager.INSTALL_INTERNAL;
- break;
- case "-d":
- sessionParams.installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE;
- break;
- case "-g":
- sessionParams.installFlags |= PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS;
- break;
- case "--dont-kill":
- sessionParams.installFlags |= PackageManager.INSTALL_DONT_KILL_APP;
- break;
- case "--originating-uri":
- sessionParams.originatingUri = Uri.parse(nextOptionData());
- break;
- case "--referrer":
- sessionParams.referrerUri = Uri.parse(nextOptionData());
- break;
- case "-p":
- sessionParams.mode = SessionParams.MODE_INHERIT_EXISTING;
- sessionParams.appPackageName = nextOptionData();
- if (sessionParams.appPackageName == null) {
- throw new IllegalArgumentException("Missing inherit package name");
- }
- break;
- case "--pkg":
- sessionParams.appPackageName = nextOptionData();
- if (sessionParams.appPackageName == null) {
- throw new IllegalArgumentException("Missing package name");
- }
- break;
- case "-S":
- final long sizeBytes = Long.parseLong(nextOptionData());
- if (sizeBytes <= 0) {
- throw new IllegalArgumentException("Size must be positive");
- }
- sessionParams.setSize(sizeBytes);
- break;
- case "--abi":
- sessionParams.abiOverride = checkAbiArgument(nextOptionData());
- break;
- case "--ephemeral":
- case "--instant":
- sessionParams.setInstallAsInstantApp(true /*isInstantApp*/);
- break;
- case "--full":
- sessionParams.setInstallAsInstantApp(false /*isInstantApp*/);
- break;
- case "--user":
- params.userId = UserHandle.parseUserArg(nextOptionData());
- break;
- case "--install-location":
- sessionParams.installLocation = Integer.parseInt(nextOptionData());
- break;
- case "--force-uuid":
- sessionParams.installFlags |= PackageManager.INSTALL_FORCE_VOLUME_UUID;
- sessionParams.volumeUuid = nextOptionData();
- if ("internal".equals(sessionParams.volumeUuid)) {
- sessionParams.volumeUuid = null;
- }
- break;
- case "--force-sdk":
- sessionParams.installFlags |= PackageManager.INSTALL_FORCE_SDK;
- break;
- default:
- throw new IllegalArgumentException("Unknown option " + opt);
- }
- }
- return params;
- }
-
- private int doCreateSession(SessionParams params, String installerPackageName, int userId)
- throws RemoteException {
- userId = translateUserId(userId, "runInstallCreate");
- if (userId == UserHandle.USER_ALL) {
- userId = UserHandle.USER_SYSTEM;
- params.installFlags |= PackageManager.INSTALL_ALL_USERS;
- }
-
- final int sessionId = mInstaller.createSession(params, installerPackageName, userId);
- return sessionId;
- }
-
- private int doWriteSession(int sessionId, String inPath, long sizeBytes, String splitName,
- boolean logSuccess) throws RemoteException {
- if (STDIN_PATH.equals(inPath)) {
- inPath = null;
- } else if (inPath != null) {
- final File file = new File(inPath);
- if (file.isFile()) {
- sizeBytes = file.length();
- }
- }
-
- final SessionInfo info = mInstaller.getSessionInfo(sessionId);
-
- PackageInstaller.Session session = null;
- InputStream in = null;
- OutputStream out = null;
- try {
- session = new PackageInstaller.Session(
- mInstaller.openSession(sessionId));
-
- if (inPath != null) {
- in = new FileInputStream(inPath);
- } else {
- in = new SizedInputStream(System.in, sizeBytes);
- }
- out = session.openWrite(splitName, 0, sizeBytes);
-
- int total = 0;
- byte[] buffer = new byte[1024 * 1024];
- int c;
- while ((c = in.read(buffer)) != -1) {
- total += c;
- out.write(buffer, 0, c);
-
- if (info.sizeBytes > 0) {
- final float fraction = ((float) c / (float) info.sizeBytes);
- session.addProgress(fraction);
- }
- }
- session.fsync(out);
-
- if (logSuccess) {
- System.out.println("Success: streamed " + total + " bytes");
- }
- return PackageInstaller.STATUS_SUCCESS;
- } catch (IOException e) {
- System.err.println("Error: failed to write; " + e.getMessage());
- return PackageInstaller.STATUS_FAILURE;
- } finally {
- IoUtils.closeQuietly(out);
- IoUtils.closeQuietly(in);
- IoUtils.closeQuietly(session);
- }
- }
-
- private Pair<String, Integer> doCommitSession(int sessionId, boolean logSuccess)
- throws RemoteException {
- PackageInstaller.Session session = null;
- try {
- session = new PackageInstaller.Session(
- mInstaller.openSession(sessionId));
-
- final LocalIntentReceiver receiver = new LocalIntentReceiver();
- session.commit(receiver.getIntentSender());
-
- final Intent result = receiver.getResult();
- final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
- PackageInstaller.STATUS_FAILURE);
- if (status == PackageInstaller.STATUS_SUCCESS) {
- if (logSuccess) {
- System.out.println("Success");
- }
- } else {
- System.err.println("Failure ["
- + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
- }
- return new Pair<>(result.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME), status);
- } finally {
- IoUtils.closeQuietly(session);
- }
- }
-
- private int doAbandonSession(int sessionId, boolean logSuccess) throws RemoteException {
- PackageInstaller.Session session = null;
- try {
- session = new PackageInstaller.Session(mInstaller.openSession(sessionId));
- session.abandon();
- if (logSuccess) {
- System.out.println("Success");
- }
- return PackageInstaller.STATUS_SUCCESS;
- } finally {
- IoUtils.closeQuietly(session);
- }
- }
-
- class LocalPackageInstallObserver extends PackageInstallObserver {
- boolean finished;
- int result;
- String extraPermission;
- String extraPackage;
-
- @Override
- public void onPackageInstalled(String name, int status, String msg, Bundle extras) {
- synchronized (this) {
- finished = true;
- result = status;
- if (status == PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION) {
- extraPermission = extras.getString(
- PackageManager.EXTRA_FAILURE_EXISTING_PERMISSION);
- extraPackage = extras.getString(
- PackageManager.EXTRA_FAILURE_EXISTING_PACKAGE);
- }
- notifyAll();
- }
- }
- }
-
- private static boolean isNumber(String s) {
- try {
- Integer.parseInt(s);
- } catch (NumberFormatException nfe) {
- return false;
- }
- return true;
- }
-
- static class ClearCacheObserver extends IPackageDataObserver.Stub {
- boolean finished;
- boolean result;
-
- @Override
- public void onRemoveCompleted(String packageName, boolean succeeded) throws RemoteException {
- synchronized (this) {
- finished = true;
- result = succeeded;
- notifyAll();
- }
- }
-
- }
-
- static class ClearDataObserver extends IPackageDataObserver.Stub {
- boolean finished;
- boolean result;
-
- @Override
- public void onRemoveCompleted(String packageName, boolean succeeded) throws RemoteException {
- synchronized (this) {
- finished = true;
- result = succeeded;
- notifyAll();
- }
- }
- }
-
- /**
- * Displays the package file for a package.
- * @param pckg
- */
- private int displayPackageFilePath(String pckg, int userId) {
- try {
- PackageInfo info = mPm.getPackageInfo(pckg, 0, userId);
- if (info != null && info.applicationInfo != null) {
- System.out.print("package:");
- System.out.println(info.applicationInfo.sourceDir);
- if (!ArrayUtils.isEmpty(info.applicationInfo.splitSourceDirs)) {
- for (String splitSourceDir : info.applicationInfo.splitSourceDirs) {
- System.out.print("package:");
- System.out.println(splitSourceDir);
- }
- }
- return 0;
- }
- } catch (RemoteException e) {
- System.err.println(e.toString());
- System.err.println(PM_NOT_RUNNING_ERR);
- }
- return 1;
- }
-
- private String nextOption() {
- if (mNextArg >= mArgs.length) {
- return null;
- }
- String arg = mArgs[mNextArg];
- if (!arg.startsWith("-")) {
- return null;
- }
- mNextArg++;
- if (arg.equals("--")) {
- return null;
- }
- if (arg.length() > 1 && arg.charAt(1) != '-') {
- if (arg.length() > 2) {
- mCurArgData = arg.substring(2);
- return arg.substring(0, 2);
- } else {
- mCurArgData = null;
- return arg;
- }
- }
- mCurArgData = null;
- return arg;
- }
-
- private String nextOptionData() {
- if (mCurArgData != null) {
- return mCurArgData;
- }
- if (mNextArg >= mArgs.length) {
- return null;
- }
- String data = mArgs[mNextArg];
- mNextArg++;
- return data;
- }
-
- private String nextArg() {
- if (mNextArg >= mArgs.length) {
- return null;
- }
- String arg = mArgs[mNextArg];
- mNextArg++;
- return arg;
- }
-
- private static int showUsage() {
- System.err.println("usage: pm path [--user USER_ID] PACKAGE");
- System.err.println(" pm dump PACKAGE");
- System.err.println(" pm install [-lrtsfdg] [-i PACKAGE] [--user USER_ID]");
- System.err.println(" [-p INHERIT_PACKAGE] [--install-location 0/1/2]");
- System.err.println(" [--originating-uri URI] [---referrer URI]");
- System.err.println(" [--abi ABI_NAME] [--force-sdk]");
- System.err.println(" [--preload] [--instantapp] [--full] [--dont-kill]");
- System.err.println(" [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES] [PATH|-]");
- System.err.println(" pm install-create [-lrtsfdg] [-i PACKAGE] [--user USER_ID]");
- System.err.println(" [-p INHERIT_PACKAGE] [--install-location 0/1/2]");
- System.err.println(" [--originating-uri URI] [---referrer URI]");
- System.err.println(" [--abi ABI_NAME] [--force-sdk]");
- System.err.println(" [--preload] [--instantapp] [--full] [--dont-kill]");
- System.err.println(" [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES]");
- System.err.println(" pm install-write [-S BYTES] SESSION_ID SPLIT_NAME [PATH|-]");
- System.err.println(" pm install-commit SESSION_ID");
- System.err.println(" pm install-abandon SESSION_ID");
- System.err.println(" pm uninstall [-k] [--user USER_ID] [--versionCode VERSION_CODE] PACKAGE");
- System.err.println(" pm set-installer PACKAGE INSTALLER");
- System.err.println(" pm move-package PACKAGE [internal|UUID]");
- System.err.println(" pm move-primary-storage [internal|UUID]");
- System.err.println(" pm clear [--user USER_ID] PACKAGE");
- System.err.println(" pm enable [--user USER_ID] PACKAGE_OR_COMPONENT");
- System.err.println(" pm disable [--user USER_ID] PACKAGE_OR_COMPONENT");
- System.err.println(" pm disable-user [--user USER_ID] PACKAGE_OR_COMPONENT");
- System.err.println(" pm disable-until-used [--user USER_ID] PACKAGE_OR_COMPONENT");
- System.err.println(" pm default-state [--user USER_ID] PACKAGE_OR_COMPONENT");
- System.err.println(" pm set-user-restriction [--user USER_ID] RESTRICTION VALUE");
- System.err.println(" pm hide [--user USER_ID] PACKAGE_OR_COMPONENT");
- System.err.println(" pm unhide [--user USER_ID] PACKAGE_OR_COMPONENT");
- System.err.println(" pm grant [--user USER_ID] PACKAGE PERMISSION");
- System.err.println(" pm revoke [--user USER_ID] PACKAGE PERMISSION");
- System.err.println(" pm reset-permissions");
- System.err.println(" pm set-app-link [--user USER_ID] PACKAGE {always|ask|never|undefined}");
- System.err.println(" pm get-app-link [--user USER_ID] PACKAGE");
- System.err.println(" pm set-install-location [0/auto] [1/internal] [2/external]");
- System.err.println(" pm get-install-location");
- System.err.println(" pm set-permission-enforced PERMISSION [true|false]");
- System.err.println(" pm trim-caches DESIRED_FREE_SPACE [internal|UUID]");
- System.err.println(" pm create-user [--profileOf USER_ID] [--managed] [--restricted] [--ephemeral] [--guest] USER_NAME");
- System.err.println(" pm remove-user USER_ID");
- System.err.println(" pm get-max-users");
- System.err.println("");
- System.err.println("NOTE: 'pm list' commands have moved! Run 'adb shell cmd package'");
- System.err.println(" to display the new commands.");
- System.err.println("");
- System.err.println("pm path: print the path to the .apk of the given PACKAGE.");
- System.err.println("");
- System.err.println("pm dump: print system state associated with the given PACKAGE.");
- System.err.println("");
- System.err.println("pm install: install a single legacy package");
- System.err.println("pm install-create: create an install session");
- System.err.println(" -l: forward lock application");
- System.err.println(" -r: allow replacement of existing application");
- System.err.println(" -t: allow test packages");
- System.err.println(" -i: specify package name of installer owning the app");
- System.err.println(" -s: install application on sdcard");
- System.err.println(" -f: install application on internal flash");
- System.err.println(" -d: allow version code downgrade (debuggable packages only)");
- System.err.println(" -p: partial application install (new split on top of existing pkg)");
- System.err.println(" -g: grant all runtime permissions");
- System.err.println(" -S: size in bytes of entire session");
- System.err.println(" --dont-kill: installing a new feature split, don't kill running app");
- System.err.println(" --originating-uri: set URI where app was downloaded from");
- System.err.println(" --referrer: set URI that instigated the install of the app");
- System.err.println(" --pkg: specify expected package name of app being installed");
- System.err.println(" --abi: override the default ABI of the platform");
- System.err.println(" --instantapp: cause the app to be installed as an ephemeral install app");
- System.err.println(" --full: cause the app to be installed as a non-ephemeral full app");
- System.err.println(" --install-location: force the install location:");
- System.err.println(" 0=auto, 1=internal only, 2=prefer external");
- System.err.println(" --force-uuid: force install on to disk volume with given UUID");
- System.err.println(" --force-sdk: allow install even when existing app targets platform");
- System.err.println(" codename but new one targets a final API level");
- System.err.println("");
- System.err.println("pm install-write: write a package into existing session; path may");
- System.err.println(" be '-' to read from stdin");
- System.err.println(" -S: size in bytes of package, required for stdin");
- System.err.println("");
- System.err.println("pm install-commit: perform install of fully staged session");
- System.err.println("pm install-abandon: abandon session");
- System.err.println("");
- System.err.println("pm set-installer: set installer package name");
- System.err.println("");
- System.err.println("pm uninstall: removes a package from the system. Options:");
- System.err.println(" -k: keep the data and cache directories around after package removal.");
- System.err.println("");
- System.err.println("pm clear: deletes all data associated with a package.");
- System.err.println("");
- System.err.println("pm enable, disable, disable-user, disable-until-used, default-state:");
- System.err.println(" these commands change the enabled state of a given package or");
- System.err.println(" component (written as \"package/class\").");
- System.err.println("");
- System.err.println("pm grant, revoke: these commands either grant or revoke permissions");
- System.err.println(" to apps. The permissions must be declared as used in the app's");
- System.err.println(" manifest, be runtime permissions (protection level dangerous),");
- System.err.println(" and the app targeting SDK greater than Lollipop MR1.");
- System.err.println("");
- System.err.println("pm reset-permissions: revert all runtime permissions to their default state.");
- System.err.println("");
- System.err.println("pm get-install-location: returns the current install location.");
- System.err.println(" 0 [auto]: Let system decide the best location");
- System.err.println(" 1 [internal]: Install on internal device storage");
- System.err.println(" 2 [external]: Install on external media");
- System.err.println("");
- System.err.println("pm set-install-location: changes the default install location.");
- System.err.println(" NOTE: this is only intended for debugging; using this can cause");
- System.err.println(" applications to break and other undersireable behavior.");
- System.err.println(" 0 [auto]: Let system decide the best location");
- System.err.println(" 1 [internal]: Install on internal device storage");
- System.err.println(" 2 [external]: Install on external media");
- System.err.println("");
- System.err.println("pm trim-caches: trim cache files to reach the given free space.");
- System.err.println("");
- System.err.println("pm create-user: create a new user with the given USER_NAME,");
- System.err.println(" printing the new user identifier of the user.");
- System.err.println("");
- System.err.println("pm remove-user: remove the user with the given USER_IDENTIFIER,");
- System.err.println(" deleting all data associated with that user");
- System.err.println("");
- return 1;
- }
-}
diff --git a/com/android/datetimepicker/AccessibleLinearLayout.java b/com/android/datetimepicker/AccessibleLinearLayout.java
index 629f856..a67d809 100644
--- a/com/android/datetimepicker/AccessibleLinearLayout.java
+++ b/com/android/datetimepicker/AccessibleLinearLayout.java
@@ -25,7 +25,10 @@
/**
* Fake Button class, used so TextViews can announce themselves as Buttons, for accessibility.
+ *
+ * @deprecated This module is deprecated. Do not use this class.
*/
+@Deprecated
public class AccessibleLinearLayout extends LinearLayout {
public AccessibleLinearLayout(Context context, AttributeSet attrs) {
diff --git a/com/android/datetimepicker/AccessibleTextView.java b/com/android/datetimepicker/AccessibleTextView.java
index 98fa744..9a2ecbb 100644
--- a/com/android/datetimepicker/AccessibleTextView.java
+++ b/com/android/datetimepicker/AccessibleTextView.java
@@ -25,7 +25,10 @@
/**
* Fake Button class, used so TextViews can announce themselves as Buttons, for accessibility.
+ *
+ * @deprecated This module is deprecated. Do not use this class.
*/
+@Deprecated
public class AccessibleTextView extends TextView {
public AccessibleTextView(Context context, AttributeSet attrs) {
diff --git a/com/android/datetimepicker/HapticFeedbackController.java b/com/android/datetimepicker/HapticFeedbackController.java
index b9be63f..96d3a24 100644
--- a/com/android/datetimepicker/HapticFeedbackController.java
+++ b/com/android/datetimepicker/HapticFeedbackController.java
@@ -10,7 +10,10 @@
/**
* A simple utility class to handle haptic feedback.
+ *
+ * @deprecated This module is deprecated. Do not use this class.
*/
+@Deprecated
public class HapticFeedbackController {
private static final int VIBRATE_DELAY_MS = 125;
private static final int VIBRATE_LENGTH_MS = 5;
diff --git a/com/android/datetimepicker/Utils.java b/com/android/datetimepicker/Utils.java
index 4a3110c..6c0adbe 100644
--- a/com/android/datetimepicker/Utils.java
+++ b/com/android/datetimepicker/Utils.java
@@ -28,7 +28,10 @@
/**
* Utility helper functions for time and date pickers.
+ *
+ * @deprecated This module is deprecated. Do not use this class.
*/
+@Deprecated
public class Utils {
public static final int MONDAY_BEFORE_JULIAN_EPOCH = Time.EPOCH_JULIAN_DAY - 3;
diff --git a/com/android/datetimepicker/date/AccessibleDateAnimator.java b/com/android/datetimepicker/date/AccessibleDateAnimator.java
index fc022cd..4e65aea 100644
--- a/com/android/datetimepicker/date/AccessibleDateAnimator.java
+++ b/com/android/datetimepicker/date/AccessibleDateAnimator.java
@@ -22,7 +22,7 @@
import android.view.accessibility.AccessibilityEvent;
import android.widget.ViewAnimator;
-public class AccessibleDateAnimator extends ViewAnimator {
+class AccessibleDateAnimator extends ViewAnimator {
private long mDateMillis;
public AccessibleDateAnimator(Context context, AttributeSet attrs) {
diff --git a/com/android/datetimepicker/date/DatePickerController.java b/com/android/datetimepicker/date/DatePickerController.java
index 42989dd..b027d33 100644
--- a/com/android/datetimepicker/date/DatePickerController.java
+++ b/com/android/datetimepicker/date/DatePickerController.java
@@ -24,7 +24,7 @@
/**
* Controller class to communicate among the various components of the date picker dialog.
*/
-public interface DatePickerController {
+interface DatePickerController {
void onYearSelected(int year);
diff --git a/com/android/datetimepicker/date/DatePickerDialog.java b/com/android/datetimepicker/date/DatePickerDialog.java
index 994fdf2..9b26b48 100644
--- a/com/android/datetimepicker/date/DatePickerDialog.java
+++ b/com/android/datetimepicker/date/DatePickerDialog.java
@@ -48,7 +48,10 @@
/**
* Dialog allowing users to select a date.
+ *
+ * @deprecated Use {@link android.app.DatePickerDialog}.
*/
+@Deprecated
public class DatePickerDialog extends DialogFragment implements
OnClickListener, DatePickerController {
diff --git a/com/android/datetimepicker/date/DayPickerView.java b/com/android/datetimepicker/date/DayPickerView.java
index 47a2aa7..9f81537 100644
--- a/com/android/datetimepicker/date/DayPickerView.java
+++ b/com/android/datetimepicker/date/DayPickerView.java
@@ -42,7 +42,7 @@
/**
* This displays a list of months in a calendar format with selectable days.
*/
-public abstract class DayPickerView extends ListView implements OnScrollListener,
+abstract class DayPickerView extends ListView implements OnScrollListener,
OnDateChangedListener {
private static final String TAG = "MonthFragment";
diff --git a/com/android/datetimepicker/date/MonthAdapter.java b/com/android/datetimepicker/date/MonthAdapter.java
index 3ed88b0..0f95e55 100644
--- a/com/android/datetimepicker/date/MonthAdapter.java
+++ b/com/android/datetimepicker/date/MonthAdapter.java
@@ -32,7 +32,7 @@
/**
* An adapter for a list of {@link MonthView} items.
*/
-public abstract class MonthAdapter extends BaseAdapter implements OnDayClickListener {
+abstract class MonthAdapter extends BaseAdapter implements OnDayClickListener {
private static final String TAG = "SimpleMonthAdapter";
diff --git a/com/android/datetimepicker/date/MonthView.java b/com/android/datetimepicker/date/MonthView.java
index 00711f3..63d073c 100644
--- a/com/android/datetimepicker/date/MonthView.java
+++ b/com/android/datetimepicker/date/MonthView.java
@@ -52,7 +52,7 @@
* A calendar-like view displaying a specified month and the appropriate selectable day numbers
* within the specified month.
*/
-public abstract class MonthView extends View {
+abstract class MonthView extends View {
private static final String TAG = "MonthView";
/**
diff --git a/com/android/datetimepicker/date/SimpleDayPickerView.java b/com/android/datetimepicker/date/SimpleDayPickerView.java
index 658c8a2..c20c754 100644
--- a/com/android/datetimepicker/date/SimpleDayPickerView.java
+++ b/com/android/datetimepicker/date/SimpleDayPickerView.java
@@ -22,7 +22,7 @@
/**
* A DayPickerView customized for {@link SimpleMonthAdapter}
*/
-public class SimpleDayPickerView extends DayPickerView {
+class SimpleDayPickerView extends DayPickerView {
public SimpleDayPickerView(Context context, AttributeSet attrs) {
super(context, attrs);
diff --git a/com/android/datetimepicker/date/SimpleMonthAdapter.java b/com/android/datetimepicker/date/SimpleMonthAdapter.java
index 0c939fe..394abc2 100644
--- a/com/android/datetimepicker/date/SimpleMonthAdapter.java
+++ b/com/android/datetimepicker/date/SimpleMonthAdapter.java
@@ -21,7 +21,7 @@
/**
* An adapter for a list of {@link SimpleMonthView} items.
*/
-public class SimpleMonthAdapter extends MonthAdapter {
+class SimpleMonthAdapter extends MonthAdapter {
public SimpleMonthAdapter(Context context, DatePickerController controller) {
super(context, controller);
diff --git a/com/android/datetimepicker/date/SimpleMonthView.java b/com/android/datetimepicker/date/SimpleMonthView.java
index b416a45..bb392eb 100644
--- a/com/android/datetimepicker/date/SimpleMonthView.java
+++ b/com/android/datetimepicker/date/SimpleMonthView.java
@@ -21,7 +21,7 @@
import java.util.Calendar;
-public class SimpleMonthView extends MonthView {
+class SimpleMonthView extends MonthView {
public SimpleMonthView(Context context) {
super(context);
diff --git a/com/android/datetimepicker/date/TextViewWithCircularIndicator.java b/com/android/datetimepicker/date/TextViewWithCircularIndicator.java
index ad78746..e4d69e1 100644
--- a/com/android/datetimepicker/date/TextViewWithCircularIndicator.java
+++ b/com/android/datetimepicker/date/TextViewWithCircularIndicator.java
@@ -29,7 +29,10 @@
/**
* A text view which, when pressed or activated, displays a blue circle around the text.
+ *
+ * @deprecated This module is deprecated. Do not use this class.
*/
+@Deprecated
public class TextViewWithCircularIndicator extends TextView {
private static final int SELECTED_CIRCLE_ALPHA = 60;
diff --git a/com/android/datetimepicker/date/YearPickerView.java b/com/android/datetimepicker/date/YearPickerView.java
index ae14eb5..d058b36 100644
--- a/com/android/datetimepicker/date/YearPickerView.java
+++ b/com/android/datetimepicker/date/YearPickerView.java
@@ -37,7 +37,7 @@
/**
* Displays a selectable list of years.
*/
-public class YearPickerView extends ListView implements OnItemClickListener, OnDateChangedListener {
+class YearPickerView extends ListView implements OnItemClickListener, OnDateChangedListener {
private static final String TAG = "YearPickerView";
private final DatePickerController mController;
diff --git a/com/android/datetimepicker/time/AmPmCirclesView.java b/com/android/datetimepicker/time/AmPmCirclesView.java
index 902abd9..bafa51c 100644
--- a/com/android/datetimepicker/time/AmPmCirclesView.java
+++ b/com/android/datetimepicker/time/AmPmCirclesView.java
@@ -33,7 +33,7 @@
/**
* Draw the two smaller AM and PM circles next to where the larger circle will be.
*/
-public class AmPmCirclesView extends View {
+class AmPmCirclesView extends View {
private static final String TAG = "AmPmCirclesView";
// Alpha level for selected circle.
diff --git a/com/android/datetimepicker/time/CircleView.java b/com/android/datetimepicker/time/CircleView.java
index 1dd4eea..0d6766f 100644
--- a/com/android/datetimepicker/time/CircleView.java
+++ b/com/android/datetimepicker/time/CircleView.java
@@ -28,7 +28,7 @@
/**
* Draws a simple white circle on which the numbers will be drawn.
*/
-public class CircleView extends View {
+class CircleView extends View {
private static final String TAG = "CircleView";
private final Paint mPaint = new Paint();
diff --git a/com/android/datetimepicker/time/RadialPickerLayout.java b/com/android/datetimepicker/time/RadialPickerLayout.java
index 1d44907..6a4adea 100644
--- a/com/android/datetimepicker/time/RadialPickerLayout.java
+++ b/com/android/datetimepicker/time/RadialPickerLayout.java
@@ -44,6 +44,8 @@
* The primary layout to hold the circular picker, and the am/pm buttons. This view well measure
* itself to end up as a square. It also handles touches to be passed in to views that need to know
* when they'd been touched.
+ *
+ * @deprecated This module is deprecated. Do not use this class.
*/
public class RadialPickerLayout extends FrameLayout implements OnTouchListener {
private static final String TAG = "RadialPickerLayout";
diff --git a/com/android/datetimepicker/time/RadialSelectorView.java b/com/android/datetimepicker/time/RadialSelectorView.java
index 0339dcd..4906991 100644
--- a/com/android/datetimepicker/time/RadialSelectorView.java
+++ b/com/android/datetimepicker/time/RadialSelectorView.java
@@ -35,7 +35,7 @@
* View to show what number is selected. This will draw a blue circle over the number, with a blue
* line coming from the center of the main circle to the edge of the blue selection.
*/
-public class RadialSelectorView extends View {
+class RadialSelectorView extends View {
private static final String TAG = "RadialSelectorView";
// Alpha level for selected circle.
diff --git a/com/android/datetimepicker/time/RadialTextsView.java b/com/android/datetimepicker/time/RadialTextsView.java
index 684e8f5..0b69d06 100644
--- a/com/android/datetimepicker/time/RadialTextsView.java
+++ b/com/android/datetimepicker/time/RadialTextsView.java
@@ -35,7 +35,7 @@
/**
* A view to show a series of numbers in a circular pattern.
*/
-public class RadialTextsView extends View {
+class RadialTextsView extends View {
private final static String TAG = "RadialTextsView";
private final Paint mPaint = new Paint();
diff --git a/com/android/datetimepicker/time/TimePickerDialog.java b/com/android/datetimepicker/time/TimePickerDialog.java
index c7661ad..0dd13c9 100644
--- a/com/android/datetimepicker/time/TimePickerDialog.java
+++ b/com/android/datetimepicker/time/TimePickerDialog.java
@@ -46,7 +46,10 @@
/**
* Dialog to set a time.
+ *
+ * @deprecated Use {@link android.app.TimePickerDialog}.
*/
+@Deprecated
public class TimePickerDialog extends DialogFragment implements OnValueSelectedListener{
private static final String TAG = "TimePickerDialog";
diff --git a/com/android/ex/photo/ActionBarWrapper.java b/com/android/ex/photo/ActionBarWrapper.java
index ae62197..6d4d4d2 100644
--- a/com/android/ex/photo/ActionBarWrapper.java
+++ b/com/android/ex/photo/ActionBarWrapper.java
@@ -1,8 +1,7 @@
package com.android.ex.photo;
-
+import android.app.ActionBar;
import android.graphics.drawable.Drawable;
-import android.support.v7.app.ActionBar;
/**
* Wrapper around {@link ActionBar}.
diff --git a/com/android/ex/photo/PhotoViewActivity.java b/com/android/ex/photo/PhotoViewActivity.java
index a5c4a43..7b53918 100644
--- a/com/android/ex/photo/PhotoViewActivity.java
+++ b/com/android/ex/photo/PhotoViewActivity.java
@@ -21,14 +21,14 @@
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
-import android.support.v7.app.AppCompatActivity;
+import android.support.v4.app.FragmentActivity;
import android.view.Menu;
import android.view.MenuItem;
/**
* Activity to view the contents of an album.
*/
-public class PhotoViewActivity extends AppCompatActivity
+public class PhotoViewActivity extends FragmentActivity
implements PhotoViewController.ActivityInterface {
private PhotoViewController mController;
@@ -41,7 +41,7 @@
mController.onCreate(savedInstanceState);
}
- protected PhotoViewController createController() {
+ public PhotoViewController createController() {
return new PhotoViewController(this);
}
@@ -122,7 +122,7 @@
@Override
public ActionBarInterface getActionBarInterface() {
if (mActionBar == null) {
- mActionBar = new ActionBarWrapper(getSupportActionBar());
+ mActionBar = new ActionBarWrapper(getActionBar());
}
return mActionBar;
}
diff --git a/com/android/ims/ImsManager.java b/com/android/ims/ImsManager.java
index 813118b..e0a966a 100644
--- a/com/android/ims/ImsManager.java
+++ b/com/android/ims/ImsManager.java
@@ -43,6 +43,7 @@
import com.android.ims.internal.IImsCallSession;
import com.android.ims.internal.IImsConfig;
import com.android.ims.internal.IImsEcbm;
+import com.android.ims.internal.IImsMMTelFeature;
import com.android.ims.internal.IImsMultiEndpoint;
import com.android.ims.internal.IImsRegistrationListener;
import com.android.ims.internal.IImsServiceController;
@@ -281,22 +282,28 @@
}
/**
- * Returns the user configuration of Enhanced 4G LTE Mode setting for slot. If not set, it
- * returns true as default value.
+ * Returns the user configuration of Enhanced 4G LTE Mode setting for slot. If the option is
+ * not editable ({@link CarrierConfigManager#KEY_EDITABLE_ENHANCED_4G_LTE_BOOL} is false), or
+ * the setting is not initialized, this method will return default value specified by
+ * {@link CarrierConfigManager#KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL}.
+ *
+ * Note that even if the setting was set, it may no longer be editable. If this is the case we
+ * return the default value.
*/
public boolean isEnhanced4gLteModeSettingEnabledByUser() {
- // If user can't edit Enhanced 4G LTE Mode, it assumes Enhanced 4G LTE Mode is always true.
- // If user changes SIM from editable mode to uneditable mode, need to return true.
- if (!getBooleanCarrierConfig(CarrierConfigManager.KEY_EDITABLE_ENHANCED_4G_LTE_BOOL)) {
- return true;
- }
-
int setting = SubscriptionManager.getIntegerSubscriptionProperty(
getSubId(), SubscriptionManager.ENHANCED_4G_MODE_ENABLED,
SUB_PROPERTY_NOT_INITIALIZED, mContext);
+ boolean onByDefault = getBooleanCarrierConfig(
+ CarrierConfigManager.KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL);
- // If it's never set, by default we return true.
- return (setting == SUB_PROPERTY_NOT_INITIALIZED || setting == 1);
+ // If Enhanced 4G LTE Mode is uneditable or not initialized, we use the default value
+ if (!getBooleanCarrierConfig(CarrierConfigManager.KEY_EDITABLE_ENHANCED_4G_LTE_BOOL)
+ || setting == SUB_PROPERTY_NOT_INITIALIZED) {
+ return onByDefault;
+ } else {
+ return (setting == ImsConfig.FeatureValueConstants.ON);
+ }
}
/**
@@ -315,21 +322,26 @@
}
/**
- * Change persistent Enhanced 4G LTE Mode setting. If the the option is not editable
+ * Change persistent Enhanced 4G LTE Mode setting. If the option is not editable
* ({@link CarrierConfigManager#KEY_EDITABLE_ENHANCED_4G_LTE_BOOL} is false), this method will
- * always set the setting to true.
+ * set the setting to the default value specified by
+ * {@link CarrierConfigManager#KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL}.
*
*/
public void setEnhanced4gLteModeSetting(boolean enabled) {
- // If false, we must always keep advanced 4G mode set to true.
- enabled = getBooleanCarrierConfig(CarrierConfigManager.KEY_EDITABLE_ENHANCED_4G_LTE_BOOL)
- ? enabled : true;
+ // If editable=false, we must keep default advanced 4G mode.
+ if (!getBooleanCarrierConfig(CarrierConfigManager.KEY_EDITABLE_ENHANCED_4G_LTE_BOOL)) {
+ enabled = getBooleanCarrierConfig(
+ CarrierConfigManager.KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL);
+ }
int prevSetting = SubscriptionManager.getIntegerSubscriptionProperty(
getSubId(), SubscriptionManager.ENHANCED_4G_MODE_ENABLED,
SUB_PROPERTY_NOT_INITIALIZED, mContext);
- if (prevSetting != (enabled ? 1 : 0)) {
+ if (prevSetting != (enabled ?
+ ImsConfig.FeatureValueConstants.ON :
+ ImsConfig.FeatureValueConstants.OFF)) {
SubscriptionManager.setSubscriptionProperty(getSubId(),
SubscriptionManager.ENHANCED_4G_MODE_ENABLED, booleanToPropertyString(enabled));
if (isNonTtyOrTtyOnVolteEnabled()) {
@@ -566,7 +578,8 @@
SUB_PROPERTY_NOT_INITIALIZED, mContext);
// If it's never set, by default we return true.
- return (setting == SUB_PROPERTY_NOT_INITIALIZED || setting == 1);
+ return (setting == SUB_PROPERTY_NOT_INITIALIZED
+ || setting == ImsConfig.FeatureValueConstants.ON);
}
/**
@@ -672,7 +685,7 @@
return getBooleanCarrierConfig(
CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ENABLED_BOOL);
} else {
- return setting == 1;
+ return setting == ImsConfig.FeatureValueConstants.ON;
}
}
@@ -957,7 +970,7 @@
return getBooleanCarrierConfig(
CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_ENABLED_BOOL);
} else {
- return (setting == 1);
+ return setting == ImsConfig.FeatureValueConstants.ON;
}
}
@@ -1982,8 +1995,8 @@
serviceProxy.setStatusCallback(() -> mStatusCallbacks.forEach(
ImsServiceProxy.INotifyStatusChanged::notifyStatusChanged));
// Returns null if the service is not available.
- IImsServiceController b = tm.getImsServiceControllerAndListen(mPhoneId,
- ImsFeature.MMTEL, serviceProxy.getListener());
+ IImsMMTelFeature b = tm.getImsMMTelFeatureAndListen(mPhoneId,
+ serviceProxy.getListener());
if (b != null) {
serviceProxy.setBinder(b.asBinder());
// Trigger the cache to be updated for feature status.
@@ -2405,7 +2418,9 @@
public void factoryReset() {
// Set VoLTE to default
SubscriptionManager.setSubscriptionProperty(getSubId(),
- SubscriptionManager.ENHANCED_4G_MODE_ENABLED, booleanToPropertyString(true));
+ SubscriptionManager.ENHANCED_4G_MODE_ENABLED,
+ booleanToPropertyString(getBooleanCarrierConfig(
+ CarrierConfigManager.KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL)));
// Set VoWiFi to default
SubscriptionManager.setSubscriptionProperty(getSubId(),
diff --git a/com/android/ims/ImsServiceProxy.java b/com/android/ims/ImsServiceProxy.java
index 8c51202..f348919 100644
--- a/com/android/ims/ImsServiceProxy.java
+++ b/com/android/ims/ImsServiceProxy.java
@@ -27,10 +27,10 @@
import com.android.ims.internal.IImsCallSessionListener;
import com.android.ims.internal.IImsConfig;
import com.android.ims.internal.IImsEcbm;
+import com.android.ims.internal.IImsMMTelFeature;
import com.android.ims.internal.IImsMultiEndpoint;
import com.android.ims.internal.IImsRegistrationListener;
-import com.android.ims.internal.IImsServiceController;
-import com.android.ims.internal.IImsServiceFeatureListener;
+import com.android.ims.internal.IImsServiceFeatureCallback;
import com.android.ims.internal.IImsUt;
/**
@@ -57,8 +57,8 @@
void notifyStatusChanged();
}
- private final IImsServiceFeatureListener mListenerBinder =
- new IImsServiceFeatureListener.Stub() {
+ private final IImsServiceFeatureCallback mListenerBinder =
+ new IImsServiceFeatureCallback.Stub() {
@Override
public void imsFeatureCreated(int slotId, int feature) throws RemoteException {
@@ -108,7 +108,7 @@
this(slotId, null, featureType);
}
- public IImsServiceFeatureListener getListener() {
+ public IImsServiceFeatureCallback getListener() {
return mListenerBinder;
}
@@ -120,8 +120,7 @@
throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
- return getServiceInterface(mBinder).startSession(mSlotId, mSupportedFeature,
- incomingCallIntent, listener);
+ return getServiceInterface(mBinder).startSession(incomingCallIntent, listener);
}
}
@@ -130,7 +129,7 @@
// Only check to make sure the binder connection still exists. This method should
// still be able to be called when the state is STATE_NOT_AVAILABLE.
checkBinderConnection();
- getServiceInterface(mBinder).endSession(mSlotId, mSupportedFeature, sessionId);
+ getServiceInterface(mBinder).endSession(sessionId);
}
}
@@ -138,15 +137,14 @@
throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
- return getServiceInterface(mBinder).isConnected(mSlotId, mSupportedFeature,
- callServiceType, callType);
+ return getServiceInterface(mBinder).isConnected(callServiceType, callType);
}
}
public boolean isOpened() throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
- return getServiceInterface(mBinder).isOpened(mSlotId, mSupportedFeature);
+ return getServiceInterface(mBinder).isOpened();
}
}
@@ -154,8 +152,7 @@
throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
- getServiceInterface(mBinder).addRegistrationListener(mSlotId, mSupportedFeature,
- listener);
+ getServiceInterface(mBinder).addRegistrationListener(listener);
}
}
@@ -163,8 +160,7 @@
throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
- getServiceInterface(mBinder).removeRegistrationListener(mSlotId, mSupportedFeature,
- listener);
+ getServiceInterface(mBinder).removeRegistrationListener(listener);
}
}
@@ -172,8 +168,8 @@
throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
- return getServiceInterface(mBinder).createCallProfile(mSlotId, mSupportedFeature,
- sessionId, callServiceType, callType);
+ return getServiceInterface(mBinder).createCallProfile(sessionId, callServiceType,
+ callType);
}
}
@@ -181,8 +177,7 @@
IImsCallSessionListener listener) throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
- return getServiceInterface(mBinder).createCallSession(mSlotId, mSupportedFeature,
- sessionId, profile, listener);
+ return getServiceInterface(mBinder).createCallSession(sessionId, profile, listener);
}
}
@@ -190,43 +185,42 @@
throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
- return getServiceInterface(mBinder).getPendingCallSession(mSlotId, mSupportedFeature,
- sessionId, callId);
+ return getServiceInterface(mBinder).getPendingCallSession(sessionId, callId);
}
}
public IImsUt getUtInterface() throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
- return getServiceInterface(mBinder).getUtInterface(mSlotId, mSupportedFeature);
+ return getServiceInterface(mBinder).getUtInterface();
}
}
public IImsConfig getConfigInterface() throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
- return getServiceInterface(mBinder).getConfigInterface(mSlotId, mSupportedFeature);
+ return getServiceInterface(mBinder).getConfigInterface();
}
}
public void turnOnIms() throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
- getServiceInterface(mBinder).turnOnIms(mSlotId, mSupportedFeature);
+ getServiceInterface(mBinder).turnOnIms();
}
}
public void turnOffIms() throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
- getServiceInterface(mBinder).turnOffIms(mSlotId, mSupportedFeature);
+ getServiceInterface(mBinder).turnOffIms();
}
}
public IImsEcbm getEcbmInterface() throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
- return getServiceInterface(mBinder).getEcbmInterface(mSlotId, mSupportedFeature);
+ return getServiceInterface(mBinder).getEcbmInterface();
}
}
@@ -234,16 +228,14 @@
throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
- getServiceInterface(mBinder).setUiTTYMode(mSlotId, mSupportedFeature, uiTtyMode,
- onComplete);
+ getServiceInterface(mBinder).setUiTTYMode(uiTtyMode, onComplete);
}
}
public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
- return getServiceInterface(mBinder).getMultiEndpointInterface(mSlotId,
- mSupportedFeature);
+ return getServiceInterface(mBinder).getMultiEndpointInterface();
}
}
@@ -277,7 +269,7 @@
private Integer retrieveFeatureStatus() {
if (mBinder != null) {
try {
- return getServiceInterface(mBinder).getFeatureStatus(mSlotId, mSupportedFeature);
+ return getServiceInterface(mBinder).getFeatureStatus();
} catch (RemoteException e) {
// Status check failed, don't update cache
}
@@ -318,8 +310,8 @@
}
}
- private IImsServiceController getServiceInterface(IBinder b) {
- return IImsServiceController.Stub.asInterface(b);
+ private IImsMMTelFeature getServiceInterface(IBinder b) {
+ return IImsMMTelFeature.Stub.asInterface(b);
}
protected void checkBinderConnection() throws RemoteException {
diff --git a/com/android/internal/app/procstats/ProcessState.java b/com/android/internal/app/procstats/ProcessState.java
index 7519fce..fbdf17d 100644
--- a/com/android/internal/app/procstats/ProcessState.java
+++ b/com/android/internal/app/procstats/ProcessState.java
@@ -102,6 +102,7 @@
STATE_LAST_ACTIVITY, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
STATE_CACHED_ACTIVITY, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
STATE_CACHED_ACTIVITY_CLIENT, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+ STATE_CACHED_ACTIVITY, // ActivityManager.PROCESS_STATE_CACHED_RECENT
STATE_CACHED_EMPTY, // ActivityManager.PROCESS_STATE_CACHED_EMPTY
};
diff --git a/com/android/internal/content/NativeLibraryHelper.java b/com/android/internal/content/NativeLibraryHelper.java
index 83b7d2f..a1e6fd8 100644
--- a/com/android/internal/content/NativeLibraryHelper.java
+++ b/com/android/internal/content/NativeLibraryHelper.java
@@ -43,6 +43,7 @@
import java.io.Closeable;
import java.io.File;
+import java.io.FileDescriptor;
import java.io.IOException;
import java.util.List;
@@ -118,6 +119,17 @@
return new Handle(apkHandles, multiArch, extractNativeLibs, debuggable);
}
+ public static Handle createFd(PackageLite lite, FileDescriptor fd) throws IOException {
+ final long[] apkHandles = new long[1];
+ final String path = lite.baseCodePath;
+ apkHandles[0] = nativeOpenApkFd(fd, path);
+ if (apkHandles[0] == 0) {
+ throw new IOException("Unable to open APK " + path + " from fd " + fd);
+ }
+
+ return new Handle(apkHandles, lite.multiArch, lite.extractNativeLibs, lite.debuggable);
+ }
+
Handle(long[] apkHandles, boolean multiArch, boolean extractNativeLibs,
boolean debuggable) {
this.apkHandles = apkHandles;
@@ -152,6 +164,7 @@
}
private static native long nativeOpenApk(String path);
+ private static native long nativeOpenApkFd(FileDescriptor fd, String debugPath);
private static native void nativeClose(long handle);
private static native long nativeSumNativeBinaries(long handle, String cpuAbi,
diff --git a/com/android/internal/content/PackageHelper.java b/com/android/internal/content/PackageHelper.java
index 59a7995..e765ab1 100644
--- a/com/android/internal/content/PackageHelper.java
+++ b/com/android/internal/content/PackageHelper.java
@@ -42,6 +42,7 @@
import libcore.io.IoUtils;
import java.io.File;
+import java.io.FileDescriptor;
import java.io.IOException;
import java.util.Objects;
import java.util.UUID;
@@ -383,9 +384,15 @@
public static long calculateInstalledSize(PackageLite pkg, String abiOverride)
throws IOException {
+ return calculateInstalledSize(pkg, abiOverride, null);
+ }
+
+ public static long calculateInstalledSize(PackageLite pkg, String abiOverride,
+ FileDescriptor fd) throws IOException {
NativeLibraryHelper.Handle handle = null;
try {
- handle = NativeLibraryHelper.Handle.create(pkg);
+ handle = fd != null ? NativeLibraryHelper.Handle.createFd(pkg, fd)
+ : NativeLibraryHelper.Handle.create(pkg);
return calculateInstalledSize(pkg, handle, abiOverride);
} finally {
IoUtils.closeQuietly(handle);
diff --git a/com/android/internal/content/ReferrerIntent.java b/com/android/internal/content/ReferrerIntent.java
index 8d9a1cf..76dcc9b 100644
--- a/com/android/internal/content/ReferrerIntent.java
+++ b/com/android/internal/content/ReferrerIntent.java
@@ -19,6 +19,8 @@
import android.content.Intent;
import android.os.Parcel;
+import java.util.Objects;
+
/**
* Subclass of Intent that also contains referrer (as a package name) information.
*/
@@ -48,4 +50,21 @@
return new ReferrerIntent[size];
}
};
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null || !(obj instanceof ReferrerIntent)) {
+ return false;
+ }
+ final ReferrerIntent other = (ReferrerIntent) obj;
+ return filterEquals(other) && Objects.equals(mReferrer, other.mReferrer);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + filterHashCode();
+ result = 31 * result + Objects.hashCode(mReferrer);
+ return result;
+ }
}
diff --git a/com/android/internal/os/BatteryStatsImpl.java b/com/android/internal/os/BatteryStatsImpl.java
index f2483c0..56d0bb2 100644
--- a/com/android/internal/os/BatteryStatsImpl.java
+++ b/com/android/internal/os/BatteryStatsImpl.java
@@ -120,7 +120,7 @@
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- private static final int VERSION = 168 + (USE_OLD_HISTORY ? 1000 : 0);
+ private static final int VERSION = 169 + (USE_OLD_HISTORY ? 1000 : 0);
// Maximum number of items we will record in the history.
private static final int MAX_HISTORY_ITEMS;
@@ -599,6 +599,8 @@
private LongSamplingCounter mDischargeScreenOffCounter;
private LongSamplingCounter mDischargeScreenDozeCounter;
private LongSamplingCounter mDischargeCounter;
+ private LongSamplingCounter mDischargeLightDozeCounter;
+ private LongSamplingCounter mDischargeDeepDozeCounter;
static final int MAX_LEVEL_STEPS = 200;
@@ -697,6 +699,16 @@
}
@Override
+ public long getUahDischargeLightDoze(int which) {
+ return mDischargeLightDozeCounter.getCountLocked(which);
+ }
+
+ @Override
+ public long getUahDischargeDeepDoze(int which) {
+ return mDischargeDeepDozeCounter.getCountLocked(which);
+ }
+
+ @Override
public int getEstimatedBatteryCapacity() {
return mEstimatedBatteryCapacity;
}
@@ -6008,6 +6020,11 @@
}
@Override
+ public Timer getMulticastWakelockStats() {
+ return mWifiMulticastTimer;
+ }
+
+ @Override
public ArrayMap<String, ? extends BatteryStats.Timer> getSyncStats() {
return mSyncStats.getMap();
}
@@ -9085,6 +9102,8 @@
mBluetoothScanTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase);
mDischargeScreenOffCounter = new LongSamplingCounter(mOnBatteryScreenOffTimeBase);
mDischargeScreenDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase);
+ mDischargeLightDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase);
+ mDischargeDeepDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase);
mDischargeCounter = new LongSamplingCounter(mOnBatteryTimeBase);
mOnBattery = mOnBatteryInternal = false;
long uptime = mClocks.uptimeMillis() * 1000;
@@ -9664,6 +9683,8 @@
mChargeStepTracker.init();
mDischargeScreenOffCounter.reset(false);
mDischargeScreenDozeCounter.reset(false);
+ mDischargeLightDozeCounter.reset(false);
+ mDischargeDeepDozeCounter.reset(false);
mDischargeCounter.reset(false);
}
@@ -11263,6 +11284,11 @@
if (isScreenDoze(mScreenState)) {
mDischargeScreenDozeCounter.addCountLocked(chargeDiff);
}
+ if (mDeviceIdleMode == DEVICE_IDLE_MODE_LIGHT) {
+ mDischargeLightDozeCounter.addCountLocked(chargeDiff);
+ } else if (mDeviceIdleMode == DEVICE_IDLE_MODE_DEEP) {
+ mDischargeDeepDozeCounter.addCountLocked(chargeDiff);
+ }
}
mHistoryCur.batteryChargeUAh = chargeUAh;
setOnBatteryLocked(elapsedRealtime, uptime, onBattery, oldStatus, level, chargeUAh);
@@ -11308,6 +11334,11 @@
if (isScreenDoze(mScreenState)) {
mDischargeScreenDozeCounter.addCountLocked(chargeDiff);
}
+ if (mDeviceIdleMode == DEVICE_IDLE_MODE_LIGHT) {
+ mDischargeLightDozeCounter.addCountLocked(chargeDiff);
+ } else if (mDeviceIdleMode == DEVICE_IDLE_MODE_DEEP) {
+ mDischargeDeepDozeCounter.addCountLocked(chargeDiff);
+ }
}
mHistoryCur.batteryChargeUAh = chargeUAh;
changed = true;
@@ -12069,6 +12100,8 @@
mDischargeCounter.readSummaryFromParcelLocked(in);
mDischargeScreenOffCounter.readSummaryFromParcelLocked(in);
mDischargeScreenDozeCounter.readSummaryFromParcelLocked(in);
+ mDischargeLightDozeCounter.readSummaryFromParcelLocked(in);
+ mDischargeDeepDozeCounter.readSummaryFromParcelLocked(in);
int NPKG = in.readInt();
if (NPKG > 0) {
mDailyPackageChanges = new ArrayList<>(NPKG);
@@ -12493,6 +12526,8 @@
mDischargeCounter.writeSummaryFromParcelLocked(out);
mDischargeScreenOffCounter.writeSummaryFromParcelLocked(out);
mDischargeScreenDozeCounter.writeSummaryFromParcelLocked(out);
+ mDischargeLightDozeCounter.writeSummaryFromParcelLocked(out);
+ mDischargeDeepDozeCounter.writeSummaryFromParcelLocked(out);
if (mDailyPackageChanges != null) {
final int NPKG = mDailyPackageChanges.size();
out.writeInt(NPKG);
@@ -13044,6 +13079,8 @@
mDischargeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in);
mDischargeScreenOffCounter = new LongSamplingCounter(mOnBatteryScreenOffTimeBase, in);
mDischargeScreenDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in);
+ mDischargeLightDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in);
+ mDischargeDeepDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in);
mLastWriteTime = in.readLong();
mRpmStats.clear();
@@ -13230,6 +13267,8 @@
mDischargeCounter.writeToParcel(out);
mDischargeScreenOffCounter.writeToParcel(out);
mDischargeScreenDozeCounter.writeToParcel(out);
+ mDischargeLightDozeCounter.writeToParcel(out);
+ mDischargeDeepDozeCounter.writeToParcel(out);
out.writeLong(mLastWriteTime);
out.writeInt(mRpmStats.size());
diff --git a/com/android/internal/policy/DecorView.java b/com/android/internal/policy/DecorView.java
index 85251d4..5fddfba 100644
--- a/com/android/internal/policy/DecorView.java
+++ b/com/android/internal/policy/DecorView.java
@@ -431,7 +431,11 @@
}
}
- return super.dispatchKeyEvent(event);
+ if (super.dispatchKeyEvent(event)) {
+ return true;
+ }
+
+ return (getViewRootImpl() != null) && getViewRootImpl().dispatchKeyFallbackEvent(event);
}
public boolean superDispatchKeyShortcutEvent(KeyEvent event) {
diff --git a/com/android/internal/telephony/CarrierIdentifier.java b/com/android/internal/telephony/CarrierIdentifier.java
new file mode 100644
index 0000000..e8be159
--- /dev/null
+++ b/com/android/internal/telephony/CarrierIdentifier.java
@@ -0,0 +1,569 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.telephony;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Message;
+import android.preference.PreferenceManager;
+import android.provider.Telephony;
+import android.telephony.Rlog;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.LocalLog;
+import android.util.Log;
+
+import com.android.internal.telephony.uicc.IccRecords;
+import com.android.internal.telephony.uicc.UiccController;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static android.provider.Telephony.CarrierIdentification;
+
+/**
+ * CarrierIdentifier identifies the subscription carrier and returns a canonical carrier Id
+ * and a user friendly carrier name. CarrierIdentifier reads subscription info and check against
+ * all carrier matching rules stored in CarrierIdProvider. It is msim aware, each phone has a
+ * dedicated CarrierIdentifier.
+ */
+public class CarrierIdentifier extends Handler {
+ private static final String LOG_TAG = CarrierIdentifier.class.getSimpleName();
+ private static final boolean DBG = true;
+ private static final boolean VDBG = Rlog.isLoggable(LOG_TAG, Log.VERBOSE);
+
+ // events to trigger carrier identification
+ private static final int SIM_LOAD_EVENT = 1;
+ private static final int SIM_ABSENT_EVENT = 2;
+ private static final int SPN_OVERRIDE_EVENT = 3;
+ private static final int ICC_CHANGED_EVENT = 4;
+ private static final int PREFER_APN_UPDATE_EVENT = 5;
+ private static final int CARRIER_ID_DB_UPDATE_EVENT = 6;
+
+ private static final Uri CONTENT_URL_PREFER_APN = Uri.withAppendedPath(
+ Telephony.Carriers.CONTENT_URI, "preferapn");
+ private static final String OPERATOR_BRAND_OVERRIDE_PREFIX = "operator_branding_";
+ private static final int INVALID_CARRIER_ID = -1;
+
+ // cached matching rules based mccmnc to speed up resolution
+ private List<CarrierMatchingRule> mCarrierMatchingRulesOnMccMnc = new ArrayList<>();
+ // cached carrier Id
+ private int mCarrierId = INVALID_CARRIER_ID;
+ // cached carrier name
+ private String mCarrierName;
+ // cached preferapn name
+ private String mPreferApn;
+ // cached service provider name. telephonyManager API returns empty string as default value.
+ // some carriers need to target devices with Empty SPN. In that case, carrier matching rule
+ // should specify "" spn explicitly.
+ private String mSpn = "";
+
+ private Context mContext;
+ private Phone mPhone;
+ private IccRecords mIccRecords;
+ private final LocalLog mCarrierIdLocalLog = new LocalLog(20);
+ private final TelephonyManager mTelephonyMgr;
+ private final SubscriptionsChangedListener mOnSubscriptionsChangedListener =
+ new SubscriptionsChangedListener();
+ private final SharedPreferenceChangedListener mSharedPrefListener =
+ new SharedPreferenceChangedListener();
+
+ private final ContentObserver mContentObserver = new ContentObserver(this) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ logd("onChange URI: " + uri);
+ if (CONTENT_URL_PREFER_APN.equals(uri.getLastPathSegment())) {
+ sendEmptyMessage(PREFER_APN_UPDATE_EVENT);
+ } else {
+ sendEmptyMessage(CARRIER_ID_DB_UPDATE_EVENT);
+ }
+ }
+ };
+
+ private class SubscriptionsChangedListener
+ extends SubscriptionManager.OnSubscriptionsChangedListener {
+ final AtomicInteger mPreviousSubId =
+ new AtomicInteger(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ /**
+ * Callback invoked when there is any change to any SubscriptionInfo. Typically
+ * this method would invoke {@link SubscriptionManager#getActiveSubscriptionInfoList}
+ */
+ @Override
+ public void onSubscriptionsChanged() {
+ int subId = mPhone.getSubId();
+ if (mPreviousSubId.getAndSet(subId) != subId) {
+ if (DBG) {
+ logd("SubscriptionListener.onSubscriptionInfoChanged subId: "
+ + mPreviousSubId);
+ }
+ if (SubscriptionManager.isValidSubscriptionId(subId)) {
+ sendEmptyMessage(SIM_LOAD_EVENT);
+ } else {
+ sendEmptyMessage(SIM_ABSENT_EVENT);
+ }
+ }
+ }
+ }
+
+ private class SharedPreferenceChangedListener implements
+ SharedPreferences.OnSharedPreferenceChangeListener {
+ @Override
+ public void onSharedPreferenceChanged(
+ SharedPreferences sharedPreferences, String key) {
+ if (TextUtils.equals(key, OPERATOR_BRAND_OVERRIDE_PREFIX
+ + mPhone.getIccSerialNumber())) {
+ // SPN override from carrier privileged apps
+ logd("[onSharedPreferenceChanged]: " + key);
+ sendEmptyMessage(SPN_OVERRIDE_EVENT);
+ }
+ }
+ }
+
+ public CarrierIdentifier(Phone phone) {
+ logd("Creating CarrierIdentifier[" + phone.getPhoneId() + "]");
+ mContext = phone.getContext();
+ mPhone = phone;
+ mTelephonyMgr = TelephonyManager.from(mContext);
+
+ // register events
+ mContext.getContentResolver().registerContentObserver(CONTENT_URL_PREFER_APN, false,
+ mContentObserver);
+ mContext.getContentResolver().registerContentObserver(
+ Telephony.CarrierIdentification.CONTENT_URI, false, mContentObserver);
+ SubscriptionManager.from(mContext).addOnSubscriptionsChangedListener(
+ mOnSubscriptionsChangedListener);
+ PreferenceManager.getDefaultSharedPreferences(mContext)
+ .registerOnSharedPreferenceChangeListener(mSharedPrefListener);
+ UiccController.getInstance().registerForIccChanged(this, ICC_CHANGED_EVENT, null);
+ }
+
+ /**
+ * Entry point for the carrier identification.
+ *
+ * 1. SIM_LOAD_EVENT
+ * This indicates that all SIM records has been loaded and its first entry point for the
+ * carrier identification. Note, there are other attributes could be changed on the fly
+ * like APN and SPN. We cached all carrier matching rules based on MCCMNC to speed
+ * up carrier resolution on following trigger events.
+ *
+ * 2. PREFER_APN_UPDATE_EVENT
+ * This indicates prefer apn has been changed. It could be triggered when user modified
+ * APN settings or when default data connection first establishes on the current carrier.
+ * We follow up on this by querying prefer apn sqlite and re-issue carrier identification
+ * with the updated prefer apn name.
+ *
+ * 3. SPN_OVERRIDE_EVENT
+ * This indicates that SPN value as been changed. It could be triggered from EF_SPN
+ * record loading, carrier config override
+ * {@link android.telephony.CarrierConfigManager#KEY_CARRIER_NAME_STRING}
+ * or carrier app override {@link TelephonyManager#setOperatorBrandOverride(String)}.
+ * we follow up this by checking the cached mSPN against the latest value and issue
+ * carrier identification only if spn changes.
+ *
+ * 4. CARRIER_ID_DB_UPDATE_EVENT
+ * This indicates that carrierIdentification database which stores all matching rules
+ * has been updated. It could be triggered from OTA or assets update.
+ */
+ @Override
+ public void handleMessage(Message msg) {
+ if (VDBG) logd("handleMessage: " + msg.what);
+ switch (msg.what) {
+ case SIM_LOAD_EVENT:
+ case CARRIER_ID_DB_UPDATE_EVENT:
+ mSpn = mTelephonyMgr.getSimOperatorNameForPhone(mPhone.getPhoneId());
+ mPreferApn = getPreferApn();
+ loadCarrierMatchingRulesOnMccMnc();
+ break;
+ case SIM_ABSENT_EVENT:
+ mCarrierMatchingRulesOnMccMnc.clear();
+ mSpn = null;
+ mPreferApn = null;
+ updateCarrierIdAndName(INVALID_CARRIER_ID, null);
+ break;
+ case PREFER_APN_UPDATE_EVENT:
+ String preferApn = getPreferApn();
+ if (!equals(mPreferApn, preferApn, true)) {
+ logd("[updatePreferApn] from:" + mPreferApn + " to:" + preferApn);
+ mPreferApn = preferApn;
+ matchCarrier();
+ }
+ break;
+ case SPN_OVERRIDE_EVENT:
+ String spn = mTelephonyMgr.getSimOperatorNameForPhone(mPhone.getPhoneId());
+ if (!equals(mSpn, spn, true)) {
+ logd("[updateSpn] from:" + mSpn + " to:" + spn);
+ mSpn = spn;
+ matchCarrier();
+ }
+ break;
+ case ICC_CHANGED_EVENT:
+ IccRecords newIccRecords = UiccController.getInstance().getIccRecords(
+ mPhone.getPhoneId(), UiccController.APP_FAM_3GPP);
+ if (mIccRecords != newIccRecords) {
+ if (mIccRecords != null) {
+ logd("Removing stale icc objects.");
+ mIccRecords.unregisterForSpnUpdate(this);
+ mIccRecords = null;
+ }
+ if (newIccRecords != null) {
+ logd("new Icc object");
+ newIccRecords.registerForSpnUpdate(this, SPN_OVERRIDE_EVENT, null);
+ mIccRecords = newIccRecords;
+ }
+ }
+ break;
+ default:
+ loge("invalid msg: " + msg.what);
+ break;
+ }
+ }
+
+ private void loadCarrierMatchingRulesOnMccMnc() {
+ try {
+ String mccmnc = mTelephonyMgr.getSimOperatorNumericForPhone(mPhone.getPhoneId());
+ Cursor cursor = mContext.getContentResolver().query(CarrierIdentification.CONTENT_URI,
+ /* projection */ null,
+ /* selection */ CarrierIdentification.MCCMNC + "=?",
+ /* selectionArgs */ new String[]{mccmnc}, null);
+ try {
+ if (cursor != null) {
+ if (VDBG) {
+ logd("[loadCarrierMatchingRules]- " + cursor.getCount()
+ + " Records(s) in DB" + " mccmnc: " + mccmnc);
+ }
+ mCarrierMatchingRulesOnMccMnc.clear();
+ while (cursor.moveToNext()) {
+ mCarrierMatchingRulesOnMccMnc.add(makeCarrierMatchingRule(cursor));
+ }
+ matchCarrier();
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ } catch (Exception ex) {
+ loge("[loadCarrierMatchingRules]- ex: " + ex);
+ }
+ }
+
+ private String getPreferApn() {
+ Cursor cursor = mContext.getContentResolver().query(
+ Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI, "preferapn/subId/"
+ + mPhone.getSubId()), /* projection */ new String[]{Telephony.Carriers.APN},
+ /* selection */ null, /* selectionArgs */ null, /* sortOrder */ null);
+ try {
+ if (cursor != null) {
+ if (VDBG) {
+ logd("[getPreferApn]- " + cursor.getCount() + " Records(s) in DB");
+ }
+ while (cursor.moveToNext()) {
+ String apn = cursor.getString(cursor.getColumnIndexOrThrow(
+ Telephony.Carriers.APN));
+ logd("[getPreferApn]- " + apn);
+ return apn;
+ }
+ }
+ } catch (Exception ex) {
+ loge("[getPreferApn]- exception: " + ex);
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ return null;
+ }
+
+ private void updateCarrierIdAndName(int cid, String name) {
+ boolean update = false;
+ if (!equals(name, mCarrierName, true)) {
+ logd("[updateCarrierName] from:" + mCarrierName + " to:" + name);
+ mCarrierName = name;
+ update = true;
+ }
+ if (cid != mCarrierId) {
+ logd("[updateCarrierId] from:" + mCarrierId + " to:" + cid);
+ mCarrierId = cid;
+ update = true;
+ }
+ if (update) {
+ // TODO new public intent CARRIER_ID_CHANGED
+ mCarrierIdLocalLog.log("[updateCarrierIdAndName] cid:" + mCarrierId + " name:"
+ + mCarrierName);
+ }
+ }
+
+ private CarrierMatchingRule makeCarrierMatchingRule(Cursor cursor) {
+ return new CarrierMatchingRule(
+ cursor.getString(cursor.getColumnIndexOrThrow(CarrierIdentification.MCCMNC)),
+ cursor.getString(cursor.getColumnIndexOrThrow(
+ CarrierIdentification.IMSI_PREFIX_XPATTERN)),
+ cursor.getString(cursor.getColumnIndexOrThrow(CarrierIdentification.GID1)),
+ cursor.getString(cursor.getColumnIndexOrThrow(CarrierIdentification.GID2)),
+ cursor.getString(cursor.getColumnIndexOrThrow(CarrierIdentification.PLMN)),
+ cursor.getString(cursor.getColumnIndexOrThrow(CarrierIdentification.SPN)),
+ cursor.getString(cursor.getColumnIndexOrThrow(CarrierIdentification.APN)),
+ cursor.getInt(cursor.getColumnIndexOrThrow(CarrierIdentification.CID)),
+ cursor.getString(cursor.getColumnIndexOrThrow(CarrierIdentification.NAME)));
+ }
+
+ /**
+ * carrier matching attributes with corresponding cid
+ */
+ private static class CarrierMatchingRule {
+ /**
+ * These scores provide the hierarchical relationship between the attributes, intended to
+ * resolve conflicts in a deterministic way. The scores are constructed such that a match
+ * from a higher tier will beat any subsequent match which does not match at that tier,
+ * so MCCMNC beats everything else. This avoids problems when two (or more) carriers rule
+ * matches as the score helps to find the best match uniquely. e.g.,
+ * rule 1 {mccmnc, imsi} rule 2 {mccmnc, imsi, gid1} and rule 3 {mccmnc, imsi, gid2} all
+ * matches with subscription data. rule 2 wins with the highest matching score.
+ */
+ private static final int SCORE_MCCMNC = 1 << 6;
+ private static final int SCORE_IMSI_PREFIX = 1 << 5;
+ private static final int SCORE_GID1 = 1 << 4;
+ private static final int SCORE_GID2 = 1 << 3;
+ private static final int SCORE_PLMN = 1 << 2;
+ private static final int SCORE_SPN = 1 << 1;
+ private static final int SCORE_APN = 1 << 0;
+
+ private static final int SCORE_INVALID = -1;
+
+ // carrier matching attributes
+ private String mMccMnc;
+ private String mImsiPrefixPattern;
+ private String mGid1;
+ private String mGid2;
+ private String mPlmn;
+ private String mSpn;
+ private String mApn;
+
+ // user-facing carrier name
+ private String mName;
+ // unique carrier id
+ private int mCid;
+
+ private int mScore = 0;
+
+ CarrierMatchingRule(String mccmnc, String imsiPrefixPattern, String gid1, String gid2,
+ String plmn, String spn, String apn, int cid, String name) {
+ mMccMnc = mccmnc;
+ mImsiPrefixPattern = imsiPrefixPattern;
+ mGid1 = gid1;
+ mGid2 = gid2;
+ mPlmn = plmn;
+ mSpn = spn;
+ mApn = apn;
+ mCid = cid;
+ mName = name;
+ }
+
+ // Calculate matching score. Values which aren't set in the rule are considered "wild".
+ // All values in the rule must match in order for the subscription to be considered part of
+ // the carrier. otherwise, a invalid score -1 will be assigned. A match from a higher tier
+ // will beat any subsequent match which does not match at that tier. When there are multiple
+ // matches at the same tier, the longest, best match will be used.
+ public void match(CarrierMatchingRule subscriptionRule) {
+ mScore = 0;
+ if (mMccMnc != null) {
+ if (!CarrierIdentifier.equals(subscriptionRule.mMccMnc, mMccMnc, false)) {
+ mScore = SCORE_INVALID;
+ return;
+ }
+ mScore += SCORE_MCCMNC;
+ }
+ if (mImsiPrefixPattern != null) {
+ if (!imsiPrefixMatch(subscriptionRule.mImsiPrefixPattern, mImsiPrefixPattern)) {
+ mScore = SCORE_INVALID;
+ return;
+ }
+ mScore += SCORE_IMSI_PREFIX;
+ }
+ if (mGid1 != null) {
+ // full string match. carrier matching should cover the corner case that gid1
+ // with garbage tail due to SIM manufacture issues.
+ if (!CarrierIdentifier.equals(subscriptionRule.mGid1, mGid1, true)) {
+ mScore = SCORE_INVALID;
+ return;
+ }
+ mScore += SCORE_GID1;
+ }
+ if (mGid2 != null) {
+ // full string match. carrier matching should cover the corner case that gid2
+ // with garbage tail due to SIM manufacture issues.
+ if (!CarrierIdentifier.equals(subscriptionRule.mGid2, mGid2, true)) {
+ mScore = SCORE_INVALID;
+ return;
+ }
+ mScore += SCORE_GID2;
+ }
+ if (mPlmn != null) {
+ if (!CarrierIdentifier.equals(subscriptionRule.mPlmn, mPlmn, true)) {
+ mScore = SCORE_INVALID;
+ return;
+ }
+ mScore += SCORE_PLMN;
+ }
+ if (mSpn != null) {
+ if (!CarrierIdentifier.equals(subscriptionRule.mSpn, mSpn, true)) {
+ mScore = SCORE_INVALID;
+ return;
+ }
+ mScore += SCORE_SPN;
+ }
+ if (mApn != null) {
+ if (!CarrierIdentifier.equals(subscriptionRule.mApn, mApn, true)) {
+ mScore = SCORE_INVALID;
+ return;
+ }
+ mScore += SCORE_APN;
+ }
+ }
+
+ private boolean imsiPrefixMatch(String imsi, String prefixXPattern) {
+ if (TextUtils.isEmpty(prefixXPattern)) return true;
+ if (TextUtils.isEmpty(imsi)) return false;
+ if (imsi.length() < prefixXPattern.length()) {
+ return false;
+ }
+ for (int i = 0; i < prefixXPattern.length(); i++) {
+ if ((prefixXPattern.charAt(i) != 'x') && (prefixXPattern.charAt(i) != 'X')
+ && (prefixXPattern.charAt(i) != imsi.charAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public String toString() {
+ return "[CarrierMatchingRule] -"
+ + " mccmnc: " + mMccMnc
+ + " gid1: " + mGid1
+ + " gid2: " + mGid2
+ + " plmn: " + mPlmn
+ + " imsi_prefix: " + mImsiPrefixPattern
+ + " spn: " + mSpn
+ + " apn: " + mApn
+ + " name: " + mName
+ + " cid: " + mCid
+ + " score: " + mScore;
+ }
+ }
+
+ /**
+ * find the best matching carrier from candidates with matched MCCMNC and notify
+ * all interested parties on carrier id change.
+ */
+ private void matchCarrier() {
+ if (!SubscriptionManager.isValidSubscriptionId(mPhone.getSubId())) {
+ logd("[matchCarrier]" + "skip before sim records loaded");
+ return;
+ }
+ final String mccmnc = mTelephonyMgr.getSimOperatorNumericForPhone(mPhone.getPhoneId());
+ final String gid1 = mPhone.getGroupIdLevel1();
+ final String gid2 = mPhone.getGroupIdLevel2();
+ final String imsi = mPhone.getSubscriberId();
+ final String plmn = mPhone.getPlmn();
+ final String spn = mSpn;
+ final String apn = mPreferApn;
+
+ if (VDBG) {
+ logd("[matchCarrier]"
+ + " gid1: " + gid1
+ + " gid2: " + gid2
+ + " imsi: " + Rlog.pii(LOG_TAG, imsi)
+ + " plmn: " + plmn
+ + " spn: " + spn
+ + " apn: " + apn);
+ }
+
+ CarrierMatchingRule subscriptionRule = new CarrierMatchingRule(
+ mccmnc, imsi, gid1, gid2, plmn, spn, apn, INVALID_CARRIER_ID, null);
+
+ int maxScore = CarrierMatchingRule.SCORE_INVALID;
+ CarrierMatchingRule maxRule = null;
+
+ for (CarrierMatchingRule rule : mCarrierMatchingRulesOnMccMnc) {
+ rule.match(subscriptionRule);
+ if (rule.mScore > maxScore) {
+ maxScore = rule.mScore;
+ maxRule = rule;
+ }
+ }
+ if (maxScore == CarrierMatchingRule.SCORE_INVALID) {
+ logd("[matchCarrier - no match] cid: " + INVALID_CARRIER_ID + " name: " + null);
+ updateCarrierIdAndName(INVALID_CARRIER_ID, null);
+ } else {
+ logd("[matchCarrier] cid: " + maxRule.mCid + " name: " + maxRule.mName);
+ updateCarrierIdAndName(maxRule.mCid, maxRule.mName);
+ }
+ }
+
+ public int getCarrierId() {
+ return mCarrierId;
+ }
+
+ public String getCarrierName() {
+ return mCarrierName;
+ }
+
+ private static boolean equals(String a, String b, boolean ignoreCase) {
+ if (a == null && b == null) return true;
+ if (a != null && b != null) {
+ return (ignoreCase) ? a.equalsIgnoreCase(b) : a.equals(b);
+ }
+ return false;
+ }
+
+ private static void logd(String str) {
+ Rlog.d(LOG_TAG, str);
+ }
+ private static void loge(String str) {
+ Rlog.e(LOG_TAG, str);
+ }
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+ ipw.println("mCarrierIdLocalLogs:");
+ ipw.increaseIndent();
+ mCarrierIdLocalLog.dump(fd, pw, args);
+ ipw.decreaseIndent();
+
+ ipw.println("mCarrierId: " + mCarrierId);
+ ipw.println("mCarrierName: " + mCarrierName);
+
+ ipw.println("mCarrierMatchingRules on mccmnc: "
+ + mTelephonyMgr.getSimOperatorNumericForPhone(mPhone.getPhoneId()));
+ ipw.increaseIndent();
+ for (CarrierMatchingRule rule : mCarrierMatchingRulesOnMccMnc) {
+ ipw.println(rule.toString());
+ }
+ ipw.decreaseIndent();
+
+ ipw.println("mSpn: " + mSpn);
+ ipw.println("mPreferApn: " + mPreferApn);
+ ipw.flush();
+ }
+}
diff --git a/com/android/internal/telephony/CarrierInfoManager.java b/com/android/internal/telephony/CarrierInfoManager.java
index ebf04e8..d224c7d 100644
--- a/com/android/internal/telephony/CarrierInfoManager.java
+++ b/com/android/internal/telephony/CarrierInfoManager.java
@@ -38,30 +38,30 @@
/**
* Returns Carrier specific information that will be used to encrypt the IMSI and IMPI.
* @param keyType whether the key is being used for WLAN or ePDG.
- * @param mContext
+ * @param context
* @return ImsiEncryptionInfo which contains the information, including the public key, to be
* used for encryption.
*/
public static ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int keyType,
- Context mContext) {
+ Context context) {
String mcc = "";
String mnc = "";
final TelephonyManager telephonyManager =
- (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
- String networkOperator = telephonyManager.getNetworkOperator();
- if (!TextUtils.isEmpty(networkOperator)) {
- mcc = networkOperator.substring(0, 3);
- mnc = networkOperator.substring(3);
+ (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ String simOperator = telephonyManager.getSimOperator();
+ if (!TextUtils.isEmpty(simOperator)) {
+ mcc = simOperator.substring(0, 3);
+ mnc = simOperator.substring(3);
Log.i(LOG_TAG, "using values for mnc, mcc: " + mnc + "," + mcc);
} else {
- Log.e(LOG_TAG, "Invalid networkOperator: " + networkOperator);
+ Log.e(LOG_TAG, "Invalid networkOperator: " + simOperator);
return null;
}
Cursor findCursor = null;
try {
// In the current design, MVNOs are not supported. If we decide to support them,
// we'll need to add to this CL.
- ContentResolver mContentResolver = mContext.getContentResolver();
+ ContentResolver mContentResolver = context.getContentResolver();
String[] columns = {Telephony.CarrierColumns.PUBLIC_KEY,
Telephony.CarrierColumns.EXPIRATION_TIME,
Telephony.CarrierColumns.KEY_IDENTIFIER};
@@ -95,12 +95,12 @@
/**
* Inserts or update the Carrier Key in the database
* @param imsiEncryptionInfo ImsiEncryptionInfo object.
- * @param mContext Context.
+ * @param context Context.
*/
public static void updateOrInsertCarrierKey(ImsiEncryptionInfo imsiEncryptionInfo,
- Context mContext) {
+ Context context) {
byte[] keyBytes = imsiEncryptionInfo.getPublicKey().getEncoded();
- ContentResolver mContentResolver = mContext.getContentResolver();
+ ContentResolver mContentResolver = context.getContentResolver();
// In the current design, MVNOs are not supported. If we decide to support them,
// we'll need to add to this CL.
ContentValues contentValues = new ContentValues();
@@ -150,12 +150,54 @@
* {@link java.security.PublicKey} and the Key Identifier.
* The keyIdentifier Attribute value pair that helps a server locate
* the private key to decrypt the permanent identity.
- * @param mContext Context.
+ * @param context Context.
*/
public static void setCarrierInfoForImsiEncryption(ImsiEncryptionInfo imsiEncryptionInfo,
- Context mContext) {
+ Context context) {
Log.i(LOG_TAG, "inserting carrier key: " + imsiEncryptionInfo);
- updateOrInsertCarrierKey(imsiEncryptionInfo, mContext);
+ updateOrInsertCarrierKey(imsiEncryptionInfo, context);
//todo send key to modem. Will be done in a subsequent CL.
}
+
+ /**
+ * Deletes all the keys for a given Carrier from the device keystore.
+ * @param context Context
+ */
+ public static void deleteCarrierInfoForImsiEncryption(Context context) {
+ Log.i(LOG_TAG, "deleting carrier key from db");
+ String mcc = "";
+ String mnc = "";
+ final TelephonyManager telephonyManager =
+ (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ String simOperator = telephonyManager.getSimOperator();
+ if (!TextUtils.isEmpty(simOperator)) {
+ mcc = simOperator.substring(0, 3);
+ mnc = simOperator.substring(3);
+ } else {
+ Log.e(LOG_TAG, "Invalid networkOperator: " + simOperator);
+ return;
+ }
+ ContentResolver mContentResolver = context.getContentResolver();
+ try {
+ String whereClause = "mcc=? and mnc=?";
+ String[] whereArgs = new String[] { mcc, mnc };
+ mContentResolver.delete(Telephony.CarrierColumns.CONTENT_URI, whereClause, whereArgs);
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Delete failed" + e);
+ }
+ }
+
+ /**
+ * Deletes all the keys from the device keystore.
+ * @param context Context
+ */
+ public static void deleteAllCarrierKeysForImsiEncryption(Context context) {
+ Log.i(LOG_TAG, "deleting ALL carrier keys from db");
+ ContentResolver mContentResolver = context.getContentResolver();
+ try {
+ mContentResolver.delete(Telephony.CarrierColumns.CONTENT_URI, null, null);
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Delete failed" + e);
+ }
+ }
}
\ No newline at end of file
diff --git a/com/android/internal/telephony/CommandsInterface.java b/com/android/internal/telephony/CommandsInterface.java
index 7026ff2..d5eb546 100644
--- a/com/android/internal/telephony/CommandsInterface.java
+++ b/com/android/internal/telephony/CommandsInterface.java
@@ -23,9 +23,9 @@
import android.telephony.ClientRequestStats;
import android.telephony.ImsiEncryptionInfo;
import android.telephony.NetworkScanRequest;
+import android.telephony.data.DataProfile;
import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
-import com.android.internal.telephony.dataconnection.DataProfile;
import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
import com.android.internal.telephony.uicc.IccCardStatus;
diff --git a/com/android/internal/telephony/GsmCdmaPhone.java b/com/android/internal/telephony/GsmCdmaPhone.java
index 47289e5..6480417 100644
--- a/com/android/internal/telephony/GsmCdmaPhone.java
+++ b/com/android/internal/telephony/GsmCdmaPhone.java
@@ -157,6 +157,8 @@
private ArrayList <MmiCode> mPendingMMIs = new ArrayList<MmiCode>();
private IccPhoneBookInterfaceManager mIccPhoneBookIntManager;
private DeviceStateMonitor mDeviceStateMonitor;
+ // Used for identify the carrier of current subscription
+ private CarrierIdentifier mCarrerIdentifier;
private int mPrecisePhoneType;
@@ -212,6 +214,8 @@
mSST = mTelephonyComponentFactory.makeServiceStateTracker(this, this.mCi);
// DcTracker uses SST so needs to be created after it is instantiated
mDcTracker = mTelephonyComponentFactory.makeDcTracker(this);
+ mCarrerIdentifier = mTelephonyComponentFactory.makeCarrierIdentifier(this);
+
mSST.registerForNetworkAttached(this, EVENT_REGISTERED_TO_NETWORK, null);
mDeviceStateMonitor = mTelephonyComponentFactory.makeDeviceStateMonitor(this);
logd("GsmCdmaPhone: constructor: sub = " + mPhoneId);
@@ -1537,6 +1541,16 @@
}
@Override
+ public int getCarrierId() {
+ return mCarrerIdentifier.getCarrierId();
+ }
+
+ @Override
+ public String getCarrierName() {
+ return mCarrerIdentifier.getCarrierName();
+ }
+
+ @Override
public String getGroupIdLevel1() {
if (isPhoneTypeGsm()) {
IccRecords r = mIccRecords.get();
@@ -1573,6 +1587,19 @@
}
@Override
+ public String getPlmn() {
+ if (isPhoneTypeGsm()) {
+ IccRecords r = mIccRecords.get();
+ return (r != null) ? r.getPnnHomeName() : null;
+ } else if (isPhoneTypeCdma()) {
+ loge("Plmn is not available in CDMA");
+ return null;
+ } else { //isPhoneTypeCdmaLte()
+ return (mSimRecords != null) ? mSimRecords.getPnnHomeName() : null;
+ }
+ }
+
+ @Override
public String getCdmaPrlVersion() {
return mSST.getPrlVersion();
}
diff --git a/com/android/internal/telephony/Phone.java b/com/android/internal/telephony/Phone.java
index c51a5fe..d702c09 100644
--- a/com/android/internal/telephony/Phone.java
+++ b/com/android/internal/telephony/Phone.java
@@ -2881,6 +2881,13 @@
}
/**
+ * Retrieves the EF_PNN from the UICC For GSM/UMTS phones.
+ */
+ public String getPlmn() {
+ return null;
+ }
+
+ /**
* Get the current for the default apn DataState. No change notification
* exists at this interface -- use
* {@link android.telephony.PhoneStateListener} instead.
@@ -2985,6 +2992,15 @@
return;
}
+ public int getCarrierId() {
+ // TODO remove hardcoding and expose a public API for INVALID CARRIER ID
+ return -1;
+ }
+
+ public String getCarrierName() {
+ return null;
+ }
+
/**
* Return if UT capability of ImsPhone is enabled or not
*/
diff --git a/com/android/internal/telephony/RIL.java b/com/android/internal/telephony/RIL.java
index 3ddbf12..2d33498 100644
--- a/com/android/internal/telephony/RIL.java
+++ b/com/android/internal/telephony/RIL.java
@@ -54,6 +54,7 @@
import android.hardware.radio.V1_0.UusInfo;
import android.net.ConnectivityManager;
import android.os.AsyncResult;
+import android.os.Build;
import android.os.Handler;
import android.os.HwBinder;
import android.os.Message;
@@ -80,15 +81,17 @@
import android.telephony.SmsManager;
import android.telephony.TelephonyHistogram;
import android.telephony.TelephonyManager;
+import android.telephony.data.DataProfile;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.cat.ComprehensionTlv;
+import com.android.internal.telephony.cat.ComprehensionTlvTag;
import com.android.internal.telephony.cdma.CdmaInformationRecords;
import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
import com.android.internal.telephony.dataconnection.DataCallResponse;
-import com.android.internal.telephony.dataconnection.DataProfile;
import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
import com.android.internal.telephony.metrics.TelephonyMetrics;
import com.android.internal.telephony.nano.TelephonyProto.SmsSession;
@@ -189,6 +192,9 @@
static final int IRADIO_GET_SERVICE_DELAY_MILLIS = 4 * 1000;
+ static final String EMPTY_ALPHA_LONG = "";
+ static final String EMPTY_ALPHA_SHORT = "";
+
public static List<TelephonyHistogram> getTelephonyRILTimingHistograms() {
List<TelephonyHistogram> list;
synchronized (mRilTimeHistograms) {
@@ -1061,23 +1067,23 @@
private static DataProfileInfo convertToHalDataProfile(DataProfile dp) {
DataProfileInfo dpi = new DataProfileInfo();
- dpi.profileId = dp.profileId;
- dpi.apn = dp.apn;
- dpi.protocol = dp.protocol;
- dpi.roamingProtocol = dp.roamingProtocol;
- dpi.authType = dp.authType;
- dpi.user = dp.user;
- dpi.password = dp.password;
- dpi.type = dp.type;
- dpi.maxConnsTime = dp.maxConnsTime;
- dpi.maxConns = dp.maxConns;
- dpi.waitTime = dp.waitTime;
- dpi.enabled = dp.enabled;
- dpi.supportedApnTypesBitmap = dp.supportedApnTypesBitmap;
- dpi.bearerBitmap = dp.bearerBitmap;
- dpi.mtu = dp.mtu;
- dpi.mvnoType = convertToHalMvnoType(dp.mvnoType);
- dpi.mvnoMatchData = dp.mvnoMatchData;
+ dpi.profileId = dp.getProfileId();
+ dpi.apn = dp.getApn();
+ dpi.protocol = dp.getProtocol();
+ dpi.roamingProtocol = dp.getRoamingProtocol();
+ dpi.authType = dp.getAuthType();
+ dpi.user = dp.getUserName();
+ dpi.password = dp.getPassword();
+ dpi.type = dp.getType();
+ dpi.maxConnsTime = dp.getMaxConnsTime();
+ dpi.maxConns = dp.getMaxConns();
+ dpi.waitTime = dp.getWaitTime();
+ dpi.enabled = dp.isEnabled();
+ dpi.supportedApnTypesBitmap = dp.getSupportedApnTypesBitmap();
+ dpi.bearerBitmap = dp.getBearerBitmap();
+ dpi.mtu = dp.getMtu();
+ dpi.mvnoType = convertToHalMvnoType(dp.getMvnoType());
+ dpi.mvnoMatchData = dp.getMvnoMatchData();
return dpi;
}
@@ -1143,7 +1149,7 @@
try {
radioProxy.setupDataCall(rr.mSerial, radioTechnology, dpi,
- dataProfile.modemCognitive, allowRoaming, isRoaming);
+ dataProfile.isModemCognitive(), allowRoaming, isRoaming);
mMetrics.writeRilSetupDataCall(mPhoneId, rr.mSerial, radioTechnology, dpi.profileId,
dpi.apn, dpi.authType, dpi.protocol);
} catch (RemoteException | RuntimeException e) {
@@ -1167,12 +1173,16 @@
mRILDefaultWorkSource);
if (RILJ_LOGD) {
- riljLog(rr.serialString() + "> iccIO: "
- + requestToString(rr.mRequest) + " command = 0x"
- + Integer.toHexString(command) + " fileId = 0x"
- + Integer.toHexString(fileId) + " path = " + path + " p1 = "
- + p1 + " p2 = " + p2 + " p3 = " + " data = " + data
- + " aid = " + aid);
+ if (Build.IS_DEBUGGABLE) {
+ riljLog(rr.serialString() + "> iccIO: "
+ + requestToString(rr.mRequest) + " command = 0x"
+ + Integer.toHexString(command) + " fileId = 0x"
+ + Integer.toHexString(fileId) + " path = " + path + " p1 = "
+ + p1 + " p2 = " + p2 + " p3 = " + " data = " + data
+ + " aid = " + aid);
+ } else {
+ riljLog(rr.serialString() + "> iccIO: " + requestToString(rr.mRequest));
+ }
}
IccIo iccIo = new IccIo();
@@ -2027,7 +2037,7 @@
if (RILJ_LOGD) {
riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " contents = "
- + contents);
+ + (Build.IS_DEBUGGABLE ? contents : censoredTerminalResponse(contents)));
}
try {
@@ -2039,6 +2049,33 @@
}
}
+ private String censoredTerminalResponse(String terminalResponse) {
+ try {
+ byte[] bytes = IccUtils.hexStringToBytes(terminalResponse);
+ if (bytes != null) {
+ List<ComprehensionTlv> ctlvs = ComprehensionTlv.decodeMany(bytes, 0);
+ int from = 0;
+ for (ComprehensionTlv ctlv : ctlvs) {
+ // Find text strings which might be personal information input by user,
+ // then replace it with "********".
+ if (ComprehensionTlvTag.TEXT_STRING.value() == ctlv.getTag()) {
+ byte[] target = Arrays.copyOfRange(ctlv.getRawValue(), from,
+ ctlv.getValueIndex() + ctlv.getLength());
+ terminalResponse = terminalResponse.toLowerCase().replace(
+ IccUtils.bytesToHexString(target), "********");
+ }
+ // The text string tag and the length field should also be hidden.
+ from = ctlv.getValueIndex() + ctlv.getLength();
+ }
+ }
+ } catch (Exception e) {
+ Rlog.e(RILJ_LOG_TAG, "Could not censor the terminal response: " + e);
+ terminalResponse = null;
+ }
+
+ return terminalResponse;
+ }
+
@Override
public void sendEnvelopeWithStatus(String contents, Message result) {
IRadio radioProxy = getRadioProxy(result);
@@ -2864,7 +2901,7 @@
try {
radioProxy.setInitialAttachApn(rr.mSerial, convertToHalDataProfile(dataProfile),
- dataProfile.modemCognitive, isRoaming);
+ dataProfile.isModemCognitive(), isRoaming);
} catch (RemoteException | RuntimeException e) {
handleRadioProxyExceptionForRR(rr, "setInitialAttachApn", e);
}
@@ -2969,9 +3006,13 @@
mRILDefaultWorkSource);
if (RILJ_LOGD) {
- riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
- + " cla = " + cla + " instruction = " + instruction
- + " p1 = " + p1 + " p2 = " + " p3 = " + p3 + " data = " + data);
+ if (Build.IS_DEBUGGABLE) {
+ riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+ + " cla = " + cla + " instruction = " + instruction
+ + " p1 = " + p1 + " p2 = " + " p3 = " + p3 + " data = " + data);
+ } else {
+ riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+ }
}
SimApdu msg = createSimApdu(0, cla, instruction, p1, p2, p3, data);
@@ -2991,8 +3032,12 @@
mRILDefaultWorkSource);
if (RILJ_LOGD) {
- riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " aid = " + aid
- + " p2 = " + p2);
+ if (Build.IS_DEBUGGABLE) {
+ riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " aid = " + aid
+ + " p2 = " + p2);
+ } else {
+ riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+ }
}
try {
@@ -3038,9 +3083,13 @@
mRILDefaultWorkSource);
if (RILJ_LOGD) {
- riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " channel = "
- + channel + " cla = " + cla + " instruction = " + instruction
- + " p1 = " + p1 + " p2 = " + " p3 = " + p3 + " data = " + data);
+ if (Build.IS_DEBUGGABLE) {
+ riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " channel = "
+ + channel + " cla = " + cla + " instruction = " + instruction
+ + " p1 = " + p1 + " p2 = " + " p3 = " + p3 + " data = " + data);
+ } else {
+ riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+ }
}
SimApdu msg = createSimApdu(channel, cla, instruction, p1, p2, p3, data);
@@ -4813,7 +4862,80 @@
return capacityResponse;
}
- static ArrayList<CellInfo> convertHalCellInfoList(
+ private static void writeToParcelForGsm(
+ Parcel p, int lac, int cid, int arfcn, int bsic, String mcc, String mnc,
+ String al, String as, int ss, int ber, int ta) {
+ p.writeInt(lac);
+ p.writeInt(cid);
+ p.writeInt(arfcn);
+ p.writeInt(bsic);
+ p.writeString(mcc);
+ p.writeString(mnc);
+ p.writeString(al);
+ p.writeString(as);
+ p.writeInt(ss);
+ p.writeInt(ber);
+ p.writeInt(ta);
+ }
+
+ private static void writeToParcelForCdma(
+ Parcel p, int ni, int si, int bsi, int lon, int lat, String al, String as,
+ int dbm, int ecio, int eDbm, int eEcio, int eSnr) {
+ p.writeInt(ni);
+ p.writeInt(si);
+ p.writeInt(bsi);
+ p.writeInt(lon);
+ p.writeInt(lat);
+ p.writeString(al);
+ p.writeString(as);
+ p.writeInt(dbm);
+ p.writeInt(ecio);
+ p.writeInt(eDbm);
+ p.writeInt(eEcio);
+ p.writeInt(eSnr);
+ }
+
+ private static void writeToParcelForLte(
+ Parcel p, int ci, int pci, int tac, int earfcn, String mcc, String mnc, String al,
+ String as, int ss, int rsrp, int rsrq, int rssnr, int cqi, int ta) {
+ p.writeInt(ci);
+ p.writeInt(pci);
+ p.writeInt(tac);
+ p.writeInt(earfcn);
+ p.writeString(mcc);
+ p.writeString(mnc);
+ p.writeString(al);
+ p.writeString(as);
+ p.writeInt(ss);
+ p.writeInt(rsrp);
+ p.writeInt(rsrq);
+ p.writeInt(rssnr);
+ p.writeInt(cqi);
+ p.writeInt(ta);
+ }
+
+ private static void writeToParcelForWcdma(
+ Parcel p, int lac, int cid, int psc, int uarfcn, String mcc, String mnc,
+ String al, String as, int ss, int ber) {
+ p.writeInt(lac);
+ p.writeInt(cid);
+ p.writeInt(psc);
+ p.writeInt(uarfcn);
+ p.writeString(mcc);
+ p.writeString(mnc);
+ p.writeString(al);
+ p.writeString(as);
+ p.writeInt(ss);
+ p.writeInt(ber);
+ }
+
+ /**
+ * Convert CellInfo defined in 1.0/types.hal to CellInfo type.
+ * @param records List of CellInfo defined in 1.0/types.hal
+ * @return List of converted CellInfo object
+ */
+ @VisibleForTesting
+ public static ArrayList<CellInfo> convertHalCellInfoList(
ArrayList<android.hardware.radio.V1_0.CellInfo> records) {
ArrayList<CellInfo> response = new ArrayList<CellInfo>(records.size());
@@ -4827,60 +4949,182 @@
switch (record.cellInfoType) {
case CellInfoType.GSM: {
CellInfoGsm cellInfoGsm = record.gsm.get(0);
- p.writeInt(Integer.parseInt(cellInfoGsm.cellIdentityGsm.mcc));
- p.writeInt(Integer.parseInt(cellInfoGsm.cellIdentityGsm.mnc));
- p.writeInt(cellInfoGsm.cellIdentityGsm.lac);
- p.writeInt(cellInfoGsm.cellIdentityGsm.cid);
- p.writeInt(cellInfoGsm.cellIdentityGsm.arfcn);
- p.writeInt(Byte.toUnsignedInt(cellInfoGsm.cellIdentityGsm.bsic));
- p.writeInt(cellInfoGsm.signalStrengthGsm.signalStrength);
- p.writeInt(cellInfoGsm.signalStrengthGsm.bitErrorRate);
- p.writeInt(cellInfoGsm.signalStrengthGsm.timingAdvance);
+ writeToParcelForGsm(
+ p,
+ cellInfoGsm.cellIdentityGsm.lac,
+ cellInfoGsm.cellIdentityGsm.cid,
+ cellInfoGsm.cellIdentityGsm.arfcn,
+ Byte.toUnsignedInt(cellInfoGsm.cellIdentityGsm.bsic),
+ cellInfoGsm.cellIdentityGsm.mcc,
+ cellInfoGsm.cellIdentityGsm.mnc,
+ EMPTY_ALPHA_LONG,
+ EMPTY_ALPHA_SHORT,
+ cellInfoGsm.signalStrengthGsm.signalStrength,
+ cellInfoGsm.signalStrengthGsm.bitErrorRate,
+ cellInfoGsm.signalStrengthGsm.timingAdvance);
break;
}
case CellInfoType.CDMA: {
CellInfoCdma cellInfoCdma = record.cdma.get(0);
- p.writeInt(cellInfoCdma.cellIdentityCdma.networkId);
- p.writeInt(cellInfoCdma.cellIdentityCdma.systemId);
- p.writeInt(cellInfoCdma.cellIdentityCdma.baseStationId);
- p.writeInt(cellInfoCdma.cellIdentityCdma.longitude);
- p.writeInt(cellInfoCdma.cellIdentityCdma.latitude);
- p.writeInt(cellInfoCdma.signalStrengthCdma.dbm);
- p.writeInt(cellInfoCdma.signalStrengthCdma.ecio);
- p.writeInt(cellInfoCdma.signalStrengthEvdo.dbm);
- p.writeInt(cellInfoCdma.signalStrengthEvdo.ecio);
- p.writeInt(cellInfoCdma.signalStrengthEvdo.signalNoiseRatio);
+ writeToParcelForCdma(
+ p,
+ cellInfoCdma.cellIdentityCdma.networkId,
+ cellInfoCdma.cellIdentityCdma.systemId,
+ cellInfoCdma.cellIdentityCdma.baseStationId,
+ cellInfoCdma.cellIdentityCdma.longitude,
+ cellInfoCdma.cellIdentityCdma.latitude,
+ EMPTY_ALPHA_LONG,
+ EMPTY_ALPHA_SHORT,
+ cellInfoCdma.signalStrengthCdma.dbm,
+ cellInfoCdma.signalStrengthCdma.ecio,
+ cellInfoCdma.signalStrengthEvdo.dbm,
+ cellInfoCdma.signalStrengthEvdo.ecio,
+ cellInfoCdma.signalStrengthEvdo.signalNoiseRatio);
break;
}
case CellInfoType.LTE: {
CellInfoLte cellInfoLte = record.lte.get(0);
- p.writeInt(Integer.parseInt(cellInfoLte.cellIdentityLte.mcc));
- p.writeInt(Integer.parseInt(cellInfoLte.cellIdentityLte.mnc));
- p.writeInt(cellInfoLte.cellIdentityLte.ci);
- p.writeInt(cellInfoLte.cellIdentityLte.pci);
- p.writeInt(cellInfoLte.cellIdentityLte.tac);
- p.writeInt(cellInfoLte.cellIdentityLte.earfcn);
- p.writeInt(cellInfoLte.signalStrengthLte.signalStrength);
- p.writeInt(cellInfoLte.signalStrengthLte.rsrp);
- p.writeInt(cellInfoLte.signalStrengthLte.rsrq);
- p.writeInt(cellInfoLte.signalStrengthLte.rssnr);
- p.writeInt(cellInfoLte.signalStrengthLte.cqi);
- p.writeInt(cellInfoLte.signalStrengthLte.timingAdvance);
+ writeToParcelForLte(
+ p,
+ cellInfoLte.cellIdentityLte.ci,
+ cellInfoLte.cellIdentityLte.pci,
+ cellInfoLte.cellIdentityLte.tac,
+ cellInfoLte.cellIdentityLte.earfcn,
+ cellInfoLte.cellIdentityLte.mcc,
+ cellInfoLte.cellIdentityLte.mnc,
+ EMPTY_ALPHA_LONG,
+ EMPTY_ALPHA_SHORT,
+ cellInfoLte.signalStrengthLte.signalStrength,
+ cellInfoLte.signalStrengthLte.rsrp,
+ cellInfoLte.signalStrengthLte.rsrq,
+ cellInfoLte.signalStrengthLte.rssnr,
+ cellInfoLte.signalStrengthLte.cqi,
+ cellInfoLte.signalStrengthLte.timingAdvance);
break;
}
case CellInfoType.WCDMA: {
CellInfoWcdma cellInfoWcdma = record.wcdma.get(0);
- p.writeInt(Integer.parseInt(cellInfoWcdma.cellIdentityWcdma.mcc));
- p.writeInt(Integer.parseInt(cellInfoWcdma.cellIdentityWcdma.mnc));
- p.writeInt(cellInfoWcdma.cellIdentityWcdma.lac);
- p.writeInt(cellInfoWcdma.cellIdentityWcdma.cid);
- p.writeInt(cellInfoWcdma.cellIdentityWcdma.psc);
- p.writeInt(cellInfoWcdma.cellIdentityWcdma.uarfcn);
- p.writeInt(cellInfoWcdma.signalStrengthWcdma.signalStrength);
- p.writeInt(cellInfoWcdma.signalStrengthWcdma.bitErrorRate);
+ writeToParcelForWcdma(
+ p,
+ cellInfoWcdma.cellIdentityWcdma.lac,
+ cellInfoWcdma.cellIdentityWcdma.cid,
+ cellInfoWcdma.cellIdentityWcdma.psc,
+ cellInfoWcdma.cellIdentityWcdma.uarfcn,
+ cellInfoWcdma.cellIdentityWcdma.mcc,
+ cellInfoWcdma.cellIdentityWcdma.mnc,
+ EMPTY_ALPHA_LONG,
+ EMPTY_ALPHA_SHORT,
+ cellInfoWcdma.signalStrengthWcdma.signalStrength,
+ cellInfoWcdma.signalStrengthWcdma.bitErrorRate);
+ break;
+ }
+
+ default:
+ throw new RuntimeException("unexpected cellinfotype: " + record.cellInfoType);
+ }
+
+ p.setDataPosition(0);
+ CellInfo InfoRec = CellInfo.CREATOR.createFromParcel(p);
+ p.recycle();
+ response.add(InfoRec);
+ }
+
+ return response;
+ }
+
+ /**
+ * Convert CellInfo defined in 1.2/types.hal to CellInfo type.
+ * @param records List of CellInfo defined in 1.2/types.hal
+ * @return List of converted CellInfo object
+ */
+ @VisibleForTesting
+ public static ArrayList<CellInfo> convertHalCellInfoList_1_2(
+ ArrayList<android.hardware.radio.V1_2.CellInfo> records) {
+ ArrayList<CellInfo> response = new ArrayList<CellInfo>(records.size());
+
+ for (android.hardware.radio.V1_2.CellInfo record : records) {
+ // first convert RIL CellInfo to Parcel
+ Parcel p = Parcel.obtain();
+ p.writeInt(record.cellInfoType);
+ p.writeInt(record.registered ? 1 : 0);
+ p.writeInt(record.timeStampType);
+ p.writeLong(record.timeStamp);
+ switch (record.cellInfoType) {
+ case CellInfoType.GSM: {
+ android.hardware.radio.V1_2.CellInfoGsm cellInfoGsm = record.gsm.get(0);
+ writeToParcelForGsm(
+ p,
+ cellInfoGsm.cellIdentityGsm.base.lac,
+ cellInfoGsm.cellIdentityGsm.base.cid,
+ cellInfoGsm.cellIdentityGsm.base.arfcn,
+ Byte.toUnsignedInt(cellInfoGsm.cellIdentityGsm.base.bsic),
+ cellInfoGsm.cellIdentityGsm.base.mcc,
+ cellInfoGsm.cellIdentityGsm.base.mnc,
+ cellInfoGsm.cellIdentityGsm.operatorNames.alphaLong,
+ cellInfoGsm.cellIdentityGsm.operatorNames.alphaShort,
+ cellInfoGsm.signalStrengthGsm.signalStrength,
+ cellInfoGsm.signalStrengthGsm.bitErrorRate,
+ cellInfoGsm.signalStrengthGsm.timingAdvance);
+ break;
+ }
+
+ case CellInfoType.CDMA: {
+ android.hardware.radio.V1_2.CellInfoCdma cellInfoCdma = record.cdma.get(0);
+ writeToParcelForCdma(
+ p,
+ cellInfoCdma.cellIdentityCdma.base.networkId,
+ cellInfoCdma.cellIdentityCdma.base.systemId,
+ cellInfoCdma.cellIdentityCdma.base.baseStationId,
+ cellInfoCdma.cellIdentityCdma.base.longitude,
+ cellInfoCdma.cellIdentityCdma.base.latitude,
+ cellInfoCdma.cellIdentityCdma.operatorNames.alphaLong,
+ cellInfoCdma.cellIdentityCdma.operatorNames.alphaShort,
+ cellInfoCdma.signalStrengthCdma.dbm,
+ cellInfoCdma.signalStrengthCdma.ecio,
+ cellInfoCdma.signalStrengthEvdo.dbm,
+ cellInfoCdma.signalStrengthEvdo.ecio,
+ cellInfoCdma.signalStrengthEvdo.signalNoiseRatio);
+ break;
+ }
+
+ case CellInfoType.LTE: {
+ android.hardware.radio.V1_2.CellInfoLte cellInfoLte = record.lte.get(0);
+ writeToParcelForLte(
+ p,
+ cellInfoLte.cellIdentityLte.base.ci,
+ cellInfoLte.cellIdentityLte.base.pci,
+ cellInfoLte.cellIdentityLte.base.tac,
+ cellInfoLte.cellIdentityLte.base.earfcn,
+ cellInfoLte.cellIdentityLte.base.mcc,
+ cellInfoLte.cellIdentityLte.base.mnc,
+ cellInfoLte.cellIdentityLte.operatorNames.alphaLong,
+ cellInfoLte.cellIdentityLte.operatorNames.alphaShort,
+ cellInfoLte.signalStrengthLte.signalStrength,
+ cellInfoLte.signalStrengthLte.rsrp,
+ cellInfoLte.signalStrengthLte.rsrq,
+ cellInfoLte.signalStrengthLte.rssnr,
+ cellInfoLte.signalStrengthLte.cqi,
+ cellInfoLte.signalStrengthLte.timingAdvance);
+ break;
+ }
+
+ case CellInfoType.WCDMA: {
+ android.hardware.radio.V1_2.CellInfoWcdma cellInfoWcdma = record.wcdma.get(0);
+ writeToParcelForWcdma(
+ p,
+ cellInfoWcdma.cellIdentityWcdma.base.lac,
+ cellInfoWcdma.cellIdentityWcdma.base.cid,
+ cellInfoWcdma.cellIdentityWcdma.base.psc,
+ cellInfoWcdma.cellIdentityWcdma.base.uarfcn,
+ cellInfoWcdma.cellIdentityWcdma.base.mcc,
+ cellInfoWcdma.cellIdentityWcdma.base.mnc,
+ cellInfoWcdma.cellIdentityWcdma.operatorNames.alphaLong,
+ cellInfoWcdma.cellIdentityWcdma.operatorNames.alphaShort,
+ cellInfoWcdma.signalStrengthWcdma.signalStrength,
+ cellInfoWcdma.signalStrengthWcdma.bitErrorRate);
break;
}
diff --git a/com/android/internal/telephony/RadioIndication.java b/com/android/internal/telephony/RadioIndication.java
index a7d2418..bcae450 100644
--- a/com/android/internal/telephony/RadioIndication.java
+++ b/com/android/internal/telephony/RadioIndication.java
@@ -640,6 +640,12 @@
responseCellInfos(indicationType, result);
}
+ /** Incremental network scan results with HAL V1_2 */
+ public void networkScanResult_1_2(int indicationType,
+ android.hardware.radio.V1_2.NetworkScanResult result) {
+ responseCellInfos_1_2(indicationType, result);
+ }
+
public void imsNetworkStateChanged(int indicationType) {
mRil.processIndication(indicationType);
@@ -842,4 +848,15 @@
if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_NETWORK_SCAN_RESULT, nsr);
mRil.mRilNetworkScanResultRegistrants.notifyRegistrants(new AsyncResult(null, nsr, null));
}
+
+ private void responseCellInfos_1_2(int indicationType,
+ android.hardware.radio.V1_2.NetworkScanResult result) {
+ mRil.processIndication(indicationType);
+
+ NetworkScanResult nsr = null;
+ ArrayList<CellInfo> infos = RIL.convertHalCellInfoList_1_2(result.networkInfos);
+ nsr = new NetworkScanResult(result.status, result.error, infos);
+ if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_NETWORK_SCAN_RESULT, nsr);
+ mRil.mRilNetworkScanResultRegistrants.notifyRegistrants(new AsyncResult(null, nsr, null));
+ }
}
diff --git a/com/android/internal/telephony/RadioResponse.java b/com/android/internal/telephony/RadioResponse.java
index 3990d24..84c1d57 100644
--- a/com/android/internal/telephony/RadioResponse.java
+++ b/com/android/internal/telephony/RadioResponse.java
@@ -946,6 +946,16 @@
/**
* @param responseInfo Response info struct containing response type, serial no. and error
+ * @param cellInfo List of current cell information known to radio
+ */
+ public void getCellInfoListResponse_1_2(
+ RadioResponseInfo responseInfo,
+ ArrayList<android.hardware.radio.V1_2.CellInfo> cellInfo) {
+ responseCellInfoList_1_2(responseInfo, cellInfo);
+ }
+
+ /**
+ * @param responseInfo Response info struct containing response type, serial no. and error
*/
public void setCellInfoListRateResponse(RadioResponseInfo responseInfo) {
responseVoid(responseInfo);
@@ -1681,6 +1691,20 @@
}
}
+ private void responseCellInfoList_1_2(
+ RadioResponseInfo responseInfo,
+ ArrayList<android.hardware.radio.V1_2.CellInfo> cellInfo) {
+ RILRequest rr = mRil.processResponse(responseInfo);
+
+ if (rr != null) {
+ ArrayList<CellInfo> ret = RIL.convertHalCellInfoList_1_2(cellInfo);
+ if (responseInfo.error == RadioError.NONE) {
+ sendMessageResponse(rr.mResult, ret);
+ }
+ mRil.processResponseDone(rr, responseInfo, ret);
+ }
+ }
+
private void responseActivityData(RadioResponseInfo responseInfo,
ActivityStatsInfo activityInfo) {
RILRequest rr = mRil.processResponse(responseInfo);
diff --git a/com/android/internal/telephony/ServiceStateTracker.java b/com/android/internal/telephony/ServiceStateTracker.java
index 0f4c9e4..a9523bb 100644
--- a/com/android/internal/telephony/ServiceStateTracker.java
+++ b/com/android/internal/telephony/ServiceStateTracker.java
@@ -210,6 +210,7 @@
protected static final int EVENT_RADIO_POWER_FROM_CARRIER = 51;
protected static final int EVENT_SIM_NOT_INSERTED = 52;
protected static final int EVENT_IMS_SERVICE_STATE_CHANGED = 53;
+ protected static final int EVENT_RADIO_POWER_OFF_DONE = 54;
protected static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
@@ -1130,6 +1131,16 @@
}
break;
+ case EVENT_RADIO_POWER_OFF_DONE:
+ if (DBG) log("EVENT_RADIO_POWER_OFF_DONE");
+ if (mDeviceShuttingDown && mCi.getRadioState().isAvailable()) {
+ // during shutdown the modem may not send radio state changed event
+ // as a result of radio power request
+ // Hence, issuing shut down regardless of radio power response
+ mCi.requestShutdown(null);
+ }
+ break;
+
// GSM
case EVENT_SIM_READY:
// Reset the mPreviousSubId so we treat a SIM power bounce
@@ -4403,7 +4414,7 @@
mPhone.mCT.mForegroundCall.hangupIfAlive();
}
- mCi.setRadioPower(false, null);
+ mCi.setRadioPower(false, obtainMessage(EVENT_RADIO_POWER_OFF_DONE));
}
diff --git a/com/android/internal/telephony/SubscriptionInfoUpdater.java b/com/android/internal/telephony/SubscriptionInfoUpdater.java
index 1a2e09c..983c436 100644
--- a/com/android/internal/telephony/SubscriptionInfoUpdater.java
+++ b/com/android/internal/telephony/SubscriptionInfoUpdater.java
@@ -273,7 +273,8 @@
if (ar.exception == null) {
if (ar.result != null) {
byte[] data = (byte[])ar.result;
- mIccId[slotId] = IccUtils.bchToString(data, 0, data.length);
+ mIccId[slotId] = stripIccIdSuffix(
+ IccUtils.bchToString(data, 0, data.length));
} else {
logd("Null ar");
mIccId[slotId] = ICCID_STRING_FOR_NO_SIM;
@@ -399,11 +400,11 @@
logd("handleSimLoaded: IccRecords null");
return;
}
- if (records.getFullIccId() == null) {
+ if (stripIccIdSuffix(records.getFullIccId()) == null) {
logd("onRecieve: IccID null");
return;
}
- mIccId[slotId] = records.getFullIccId();
+ mIccId[slotId] = stripIccIdSuffix(records.getFullIccId());
if (isAllIccIdQueryDone()) {
updateSubscriptionInfoByIccId();
@@ -820,6 +821,15 @@
IntentBroadcaster.getInstance().broadcastStickyIntent(i, slotId);
}
+ // Remove trailing F's from full hexadecimal IccId, as they should be considered padding
+ private String stripIccIdSuffix(String hexIccId) {
+ if (hexIccId == null) {
+ return null;
+ } else {
+ return hexIccId.replaceAll("(?i)f*$", "");
+ }
+ }
+
public void dispose() {
logd("[dispose]");
mContext.unregisterReceiver(sReceiver);
diff --git a/com/android/internal/telephony/TelephonyComponentFactory.java b/com/android/internal/telephony/TelephonyComponentFactory.java
index 193d29e..82a0823 100644
--- a/com/android/internal/telephony/TelephonyComponentFactory.java
+++ b/com/android/internal/telephony/TelephonyComponentFactory.java
@@ -77,6 +77,10 @@
return new CarrierActionAgent(phone);
}
+ public CarrierIdentifier makeCarrierIdentifier(Phone phone) {
+ return new CarrierIdentifier(phone);
+ }
+
public IccPhoneBookInterfaceManager makeIccPhoneBookInterfaceManager(Phone phone) {
return new IccPhoneBookInterfaceManager(phone);
}
diff --git a/com/android/internal/telephony/cat/ComprehensionTlv.java b/com/android/internal/telephony/cat/ComprehensionTlv.java
index e2522a4..d4ad532 100644
--- a/com/android/internal/telephony/cat/ComprehensionTlv.java
+++ b/com/android/internal/telephony/cat/ComprehensionTlv.java
@@ -29,7 +29,7 @@
*
* {@hide}
*/
-class ComprehensionTlv {
+public class ComprehensionTlv {
private static final String LOG_TAG = "ComprehensionTlv";
private int mTag;
private boolean mCr;
diff --git a/com/android/internal/telephony/cdma/sms/CdmaSmsAddress.java b/com/android/internal/telephony/cdma/sms/CdmaSmsAddress.java
index 5f2e561..d27a758 100644
--- a/com/android/internal/telephony/cdma/sms/CdmaSmsAddress.java
+++ b/com/android/internal/telephony/cdma/sms/CdmaSmsAddress.java
@@ -18,6 +18,7 @@
import android.util.SparseBooleanArray;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.SmsAddress;
import com.android.internal.telephony.cdma.sms.UserData;
import com.android.internal.util.HexDump;
@@ -113,8 +114,8 @@
* share code and logic with GSM. Also, gather all DTMF/BCD
* processing code in one place.
*/
-
- private static byte[] parseToDtmf(String address) {
+ @VisibleForTesting
+ public static byte[] parseToDtmf(String address) {
int digits = address.length();
byte[] result = new byte[digits];
for (int i = 0; i < digits; i++) {
@@ -196,33 +197,46 @@
public static CdmaSmsAddress parse(String address) {
CdmaSmsAddress addr = new CdmaSmsAddress();
addr.address = address;
- addr.ton = CdmaSmsAddress.TON_UNKNOWN;
- byte[] origBytes = null;
+ addr.ton = TON_UNKNOWN;
+ addr.digitMode = DIGIT_MODE_4BIT_DTMF;
+ addr.numberPlan = NUMBERING_PLAN_UNKNOWN;
+ addr.numberMode = NUMBER_MODE_NOT_DATA_NETWORK;
+
+ byte[] origBytes;
String filteredAddr = filterNumericSugar(address);
- if (filteredAddr != null) {
- origBytes = parseToDtmf(filteredAddr);
- }
- if (origBytes != null) {
- addr.digitMode = DIGIT_MODE_4BIT_DTMF;
- addr.numberMode = NUMBER_MODE_NOT_DATA_NETWORK;
- if (address.indexOf('+') != -1) {
- addr.ton = TON_INTERNATIONAL_OR_IP;
- }
- } else {
- filteredAddr = filterWhitespace(address);
- origBytes = UserData.stringToAscii(filteredAddr);
- if (origBytes == null) {
- return null;
- }
+ if (address.contains("+") || filteredAddr == null) {
+ // 3GPP2 C.S0015-B section 3.4.3.3 Address Parameters
+ // NUMBER_MODE should set to 1 for network address and email address.
addr.digitMode = DIGIT_MODE_8BIT_CHAR;
addr.numberMode = NUMBER_MODE_DATA_NETWORK;
- if (address.indexOf('@') != -1) {
+ filteredAddr = filterWhitespace(address);
+
+ if (address.contains("@")) {
+ // This is an email address
addr.ton = TON_NATIONAL_OR_EMAIL;
+ } else if (address.contains("+") && filterNumericSugar(address) != null) {
+ // This is an international number
+ // 3GPP2 C.S0015-B section 3.4.3.3 Address Parameters
+ // digit mode is set to 1 and number mode is set to 0, type of number should set
+ // to the value correspond to the value in 3GPP2 C.S005-D, table2.7.1.3.2.4-2
+ addr.ton = TON_INTERNATIONAL_OR_IP;
+ addr.numberPlan = NUMBERING_PLAN_ISDN_TELEPHONY;
+ addr.numberMode = NUMBER_MODE_NOT_DATA_NETWORK;
+ filteredAddr = filterNumericSugar(address);
}
+
+ origBytes = UserData.stringToAscii(filteredAddr);
+ } else {
+ // The address is not an international number and it only contains digit and *#
+ origBytes = parseToDtmf(filteredAddr);
}
+
+ if (origBytes == null) {
+ return null;
+ }
+
addr.origBytes = origBytes;
addr.numberOfDigits = origBytes.length;
return addr;
}
-
}
diff --git a/com/android/internal/telephony/dataconnection/ApnSetting.java b/com/android/internal/telephony/dataconnection/ApnSetting.java
index ce8318d..0eeed6a 100644
--- a/com/android/internal/telephony/dataconnection/ApnSetting.java
+++ b/com/android/internal/telephony/dataconnection/ApnSetting.java
@@ -23,6 +23,7 @@
import android.telephony.Rlog;
import android.telephony.ServiceState;
import android.text.TextUtils;
+import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.Phone;
@@ -48,6 +49,7 @@
static final String V2_FORMAT_REGEX = "^\\[ApnSettingV2\\]\\s*";
static final String V3_FORMAT_REGEX = "^\\[ApnSettingV3\\]\\s*";
+ static final String TAG = "ApnSetting";
public final String carrier;
public final String apn;
@@ -100,6 +102,7 @@
* "spn": Service provider name.
* "imsi": IMSI.
* "gid": Group identifier level 1.
+ * "iccid": ICCID
*/
public final String mvnoType;
/**
@@ -107,6 +110,7 @@
* "spn": A MOBILE, BEN NL
* "imsi": 302720x94, 2060188
* "gid": 4E, 33
+ * "iccid": 898603 etc.
*/
public final String mvnoMatchData;
@@ -362,6 +366,17 @@
return false;
}
+ private static boolean iccidMatches(String mvnoData, String iccId) {
+ String[] mvnoIccidList = mvnoData.split(",");
+ for (String mvnoIccid : mvnoIccidList) {
+ if (iccId.startsWith(mvnoIccid)) {
+ Log.d(TAG, "mvno icc id match found");
+ return true;
+ }
+ }
+ return false;
+ }
+
private static boolean imsiMatches(String imsiDB, String imsiSIM) {
// Note: imsiDB value has digit number or 'x' character for seperating USIM information
// for MVNO operator. And then digit number is matched at same order and 'x' character
@@ -404,7 +419,13 @@
gid1.substring(0, mvno_match_data_length).equalsIgnoreCase(mvnoMatchData)) {
return true;
}
+ } else if (mvnoType.equalsIgnoreCase("iccid")) {
+ String iccId = r.getIccId();
+ if ((iccId != null) && iccidMatches(mvnoMatchData, iccId)) {
+ return true;
+ }
}
+
return false;
}
diff --git a/com/android/internal/telephony/dataconnection/DataCallResponse.java b/com/android/internal/telephony/dataconnection/DataCallResponse.java
index bc02b97..5196bce 100644
--- a/com/android/internal/telephony/dataconnection/DataCallResponse.java
+++ b/com/android/internal/telephony/dataconnection/DataCallResponse.java
@@ -17,18 +17,9 @@
package com.android.internal.telephony.dataconnection;
-import android.net.LinkAddress;
-import android.net.LinkProperties;
-import android.net.NetworkUtils;
-import android.net.RouteInfo;
import android.os.SystemProperties;
-import android.telephony.Rlog;
import android.text.TextUtils;
-import java.net.Inet4Address;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-
/**
* This is RIL_Data_Call_Response_v5 from ril.h
*/
@@ -44,34 +35,10 @@
public final String ifname;
public final String [] addresses;
public final String [] dnses;
- // TODO: Change this to final if possible.
- public String[] gateways;
+ public final String[] gateways;
public final String [] pcscf;
public final int mtu;
- /**
- * Class returned by onSetupConnectionCompleted.
- */
- public enum SetupResult {
- SUCCESS,
- ERR_BadCommand,
- ERR_UnacceptableParameter,
- ERR_GetLastErrorFromRil,
- ERR_Stale,
- ERR_RilError;
-
- public DcFailCause mFailCause;
-
- SetupResult() {
- mFailCause = DcFailCause.fromInt(0);
- }
-
- @Override
- public String toString() {
- return name() + " SetupResult.mFailCause=" + mFailCause;
- }
- }
-
public DataCallResponse(int status, int suggestedRetryTime, int cid, int active, String type,
String ifname, String addresses, String dnses, String gateways,
String pcscf, int mtu) {
@@ -86,7 +53,22 @@
}
this.addresses = TextUtils.isEmpty(addresses) ? new String[0] : addresses.split(" ");
this.dnses = TextUtils.isEmpty(dnses) ? new String[0] : dnses.split(" ");
- this.gateways = TextUtils.isEmpty(gateways) ? new String[0] : gateways.split(" ");
+
+ String[] myGateways = TextUtils.isEmpty(gateways)
+ ? new String[0] : gateways.split(" ");
+
+ // set gateways
+ if (myGateways.length == 0) {
+ String propertyPrefix = "net." + this.ifname + ".";
+ String sysGateways = SystemProperties.get(propertyPrefix + "gw");
+ if (sysGateways != null) {
+ myGateways = sysGateways.split(" ");
+ } else {
+ myGateways = new String[0];
+ }
+ }
+ this.gateways = myGateways;
+
this.pcscf = TextUtils.isEmpty(pcscf) ? new String[0] : pcscf.split(" ");
this.mtu = mtu;
}
@@ -129,147 +111,4 @@
sb.append("]}");
return sb.toString();
}
-
- public SetupResult setLinkProperties(LinkProperties linkProperties,
- boolean okToUseSystemPropertyDns) {
- SetupResult result;
-
- // Start with clean network properties and if we have
- // a failure we'll clear again at the bottom of this code.
- if (linkProperties == null)
- linkProperties = new LinkProperties();
- else
- linkProperties.clear();
-
- if (status == DcFailCause.NONE.getErrorCode()) {
- String propertyPrefix = "net." + ifname + ".";
-
- try {
- // set interface name
- linkProperties.setInterfaceName(ifname);
-
- // set link addresses
- if (addresses != null && addresses.length > 0) {
- for (String addr : addresses) {
- addr = addr.trim();
- if (addr.isEmpty()) continue;
- LinkAddress la;
- int addrPrefixLen;
-
- String [] ap = addr.split("/");
- if (ap.length == 2) {
- addr = ap[0];
- addrPrefixLen = Integer.parseInt(ap[1]);
- } else {
- addrPrefixLen = 0;
- }
- InetAddress ia;
- try {
- ia = NetworkUtils.numericToInetAddress(addr);
- } catch (IllegalArgumentException e) {
- throw new UnknownHostException("Non-numeric ip addr=" + addr);
- }
- if (! ia.isAnyLocalAddress()) {
- if (addrPrefixLen == 0) {
- // Assume point to point
- addrPrefixLen = (ia instanceof Inet4Address) ? 32 : 128;
- }
- if (DBG) Rlog.d(LOG_TAG, "addr/pl=" + addr + "/" + addrPrefixLen);
- try {
- la = new LinkAddress(ia, addrPrefixLen);
- } catch (IllegalArgumentException e) {
- throw new UnknownHostException("Bad parameter for LinkAddress, ia="
- + ia.getHostAddress() + "/" + addrPrefixLen);
- }
-
- linkProperties.addLinkAddress(la);
- }
- }
- } else {
- throw new UnknownHostException("no address for ifname=" + ifname);
- }
-
- // set dns servers
- if (dnses != null && dnses.length > 0) {
- for (String addr : dnses) {
- addr = addr.trim();
- if (addr.isEmpty()) continue;
- InetAddress ia;
- try {
- ia = NetworkUtils.numericToInetAddress(addr);
- } catch (IllegalArgumentException e) {
- throw new UnknownHostException("Non-numeric dns addr=" + addr);
- }
- if (! ia.isAnyLocalAddress()) {
- linkProperties.addDnsServer(ia);
- }
- }
- } else if (okToUseSystemPropertyDns){
- String dnsServers[] = new String[2];
- dnsServers[0] = SystemProperties.get(propertyPrefix + "dns1");
- dnsServers[1] = SystemProperties.get(propertyPrefix + "dns2");
- for (String dnsAddr : dnsServers) {
- dnsAddr = dnsAddr.trim();
- if (dnsAddr.isEmpty()) continue;
- InetAddress ia;
- try {
- ia = NetworkUtils.numericToInetAddress(dnsAddr);
- } catch (IllegalArgumentException e) {
- throw new UnknownHostException("Non-numeric dns addr=" + dnsAddr);
- }
- if (! ia.isAnyLocalAddress()) {
- linkProperties.addDnsServer(ia);
- }
- }
- } else {
- throw new UnknownHostException("Empty dns response and no system default dns");
- }
-
- // set gateways
- if ((gateways == null) || (gateways.length == 0)) {
- String sysGateways = SystemProperties.get(propertyPrefix + "gw");
- if (sysGateways != null) {
- gateways = sysGateways.split(" ");
- } else {
- gateways = new String[0];
- }
- }
- for (String addr : gateways) {
- addr = addr.trim();
- if (addr.isEmpty()) continue;
- InetAddress ia;
- try {
- ia = NetworkUtils.numericToInetAddress(addr);
- } catch (IllegalArgumentException e) {
- throw new UnknownHostException("Non-numeric gateway addr=" + addr);
- }
- // Allow 0.0.0.0 or :: as a gateway; this indicates a point-to-point interface.
- linkProperties.addRoute(new RouteInfo(ia));
- }
-
- // set interface MTU
- // this may clobber the setting read from the APN db, but that's ok
- linkProperties.setMtu(mtu);
-
- result = SetupResult.SUCCESS;
- } catch (UnknownHostException e) {
- Rlog.d(LOG_TAG, "setLinkProperties: UnknownHostException " + e);
- e.printStackTrace();
- result = SetupResult.ERR_UnacceptableParameter;
- }
- } else {
- result = SetupResult.ERR_RilError;
- }
-
- // An error occurred so clear properties
- if (result != SetupResult.SUCCESS) {
- if(DBG) {
- Rlog.d(LOG_TAG, "setLinkProperties: error clearing LinkProperties " +
- "status=" + status + " result=" + result);
- }
- linkProperties.clear();
- }
-
- return result;
- }
}
diff --git a/com/android/internal/telephony/dataconnection/DataConnection.java b/com/android/internal/telephony/dataconnection/DataConnection.java
index 9e4b29c..3dbcc3d 100644
--- a/com/android/internal/telephony/dataconnection/DataConnection.java
+++ b/com/android/internal/telephony/dataconnection/DataConnection.java
@@ -19,12 +19,15 @@
import android.app.PendingIntent;
import android.content.Context;
import android.net.ConnectivityManager;
+import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.NetworkAgent;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkMisc;
+import android.net.NetworkUtils;
import android.net.ProxyInfo;
+import android.net.RouteInfo;
import android.net.StringNetworkSpecifier;
import android.os.AsyncResult;
import android.os.Looper;
@@ -34,6 +37,7 @@
import android.telephony.Rlog;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
+import android.telephony.data.DataProfile;
import android.text.TextUtils;
import android.util.LocalLog;
import android.util.Pair;
@@ -59,7 +63,9 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.io.StringWriter;
+import java.net.Inet4Address;
import java.net.InetAddress;
+import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@@ -283,7 +289,7 @@
}
public static class UpdateLinkPropertyResult {
- public DataCallResponse.SetupResult setupResult = DataCallResponse.SetupResult.SUCCESS;
+ public SetupResult setupResult = SetupResult.SUCCESS;
public LinkProperties oldLp;
public LinkProperties newLp;
public UpdateLinkPropertyResult(LinkProperties curLp) {
@@ -292,6 +298,29 @@
}
}
+ /**
+ * Class returned by onSetupConnectionCompleted.
+ */
+ public enum SetupResult {
+ SUCCESS,
+ ERR_BadCommand,
+ ERR_UnacceptableParameter,
+ ERR_GetLastErrorFromRil,
+ ERR_Stale,
+ ERR_RilError;
+
+ public DcFailCause mFailCause;
+
+ SetupResult() {
+ mFailCause = DcFailCause.fromInt(0);
+ }
+
+ @Override
+ public String toString() {
+ return name() + " SetupResult.mFailCause=" + mFailCause;
+ }
+ }
+
public boolean isIpv4Connected() {
boolean ret = false;
Collection <InetAddress> addresses = mLinkProperties.getAddresses();
@@ -331,12 +360,12 @@
if (newState == null) return result;
- DataCallResponse.SetupResult setupResult;
+ SetupResult setupResult;
result.newLp = new LinkProperties();
// set link properties based on data call response
result.setupResult = setLinkProperties(newState, result.newLp);
- if (result.setupResult != DataCallResponse.SetupResult.SUCCESS) {
+ if (result.setupResult != SetupResult.SUCCESS) {
if (DBG) log("updateLinkProperty failed : " + result.setupResult);
return result;
}
@@ -465,7 +494,7 @@
Message msg = obtainMessage(EVENT_SETUP_DATA_CONNECTION_DONE, cp);
msg.obj = cp;
- DataProfile dp = new DataProfile(mApnSetting, cp.mProfileId);
+ DataProfile dp = DcTracker.createDataProfile(mApnSetting, cp.mProfileId);
// We need to use the actual modem roaming state instead of the framework roaming state
// here. This flag is only passed down to ril_service for picking the correct protocol (for
@@ -665,16 +694,16 @@
* @param ar is the result
* @return SetupResult.
*/
- private DataCallResponse.SetupResult onSetupConnectionCompleted(AsyncResult ar) {
+ private SetupResult onSetupConnectionCompleted(AsyncResult ar) {
DataCallResponse response = (DataCallResponse) ar.result;
ConnectionParams cp = (ConnectionParams) ar.userObj;
- DataCallResponse.SetupResult result;
+ SetupResult result;
if (cp.mTag != mTag) {
if (DBG) {
log("onSetupConnectionCompleted stale cp.tag=" + cp.mTag + ", mtag=" + mTag);
}
- result = DataCallResponse.SetupResult.ERR_Stale;
+ result = SetupResult.ERR_Stale;
} else if (ar.exception != null) {
if (DBG) {
log("onSetupConnectionCompleted failed, ar.exception=" + ar.exception +
@@ -684,14 +713,14 @@
if (ar.exception instanceof CommandException
&& ((CommandException) (ar.exception)).getCommandError()
== CommandException.Error.RADIO_NOT_AVAILABLE) {
- result = DataCallResponse.SetupResult.ERR_BadCommand;
+ result = SetupResult.ERR_BadCommand;
result.mFailCause = DcFailCause.RADIO_NOT_AVAILABLE;
} else {
- result = DataCallResponse.SetupResult.ERR_RilError;
+ result = SetupResult.ERR_RilError;
result.mFailCause = DcFailCause.fromInt(response.status);
}
} else if (response.status != 0) {
- result = DataCallResponse.SetupResult.ERR_RilError;
+ result = SetupResult.ERR_RilError;
result.mFailCause = DcFailCause.fromInt(response.status);
} else {
if (DBG) log("onSetupConnectionCompleted received successful DataCallResponse");
@@ -995,18 +1024,139 @@
return InetAddress.isNumeric(address);
}
- private DataCallResponse.SetupResult setLinkProperties(DataCallResponse response,
- LinkProperties lp) {
+ private SetupResult setLinkProperties(DataCallResponse response,
+ LinkProperties linkProperties) {
// Check if system property dns usable
- boolean okToUseSystemPropertyDns = false;
String propertyPrefix = "net." + response.ifname + ".";
String dnsServers[] = new String[2];
dnsServers[0] = SystemProperties.get(propertyPrefix + "dns1");
dnsServers[1] = SystemProperties.get(propertyPrefix + "dns2");
- okToUseSystemPropertyDns = isDnsOk(dnsServers);
+ boolean okToUseSystemPropertyDns = isDnsOk(dnsServers);
- // set link properties based on data call response
- return response.setLinkProperties(lp, okToUseSystemPropertyDns);
+ SetupResult result;
+
+ // Start with clean network properties and if we have
+ // a failure we'll clear again at the bottom of this code.
+ linkProperties.clear();
+
+ if (response.status == DcFailCause.NONE.getErrorCode()) {
+ try {
+ // set interface name
+ linkProperties.setInterfaceName(response.ifname);
+
+ // set link addresses
+ if (response.addresses != null && response.addresses.length > 0) {
+ for (String addr : response.addresses) {
+ addr = addr.trim();
+ if (addr.isEmpty()) continue;
+ LinkAddress la;
+ int addrPrefixLen;
+
+ String [] ap = addr.split("/");
+ if (ap.length == 2) {
+ addr = ap[0];
+ addrPrefixLen = Integer.parseInt(ap[1]);
+ } else {
+ addrPrefixLen = 0;
+ }
+ InetAddress ia;
+ try {
+ ia = NetworkUtils.numericToInetAddress(addr);
+ } catch (IllegalArgumentException e) {
+ throw new UnknownHostException("Non-numeric ip addr=" + addr);
+ }
+ if (!ia.isAnyLocalAddress()) {
+ if (addrPrefixLen == 0) {
+ // Assume point to point
+ addrPrefixLen = (ia instanceof Inet4Address) ? 32 : 128;
+ }
+ if (DBG) log("addr/pl=" + addr + "/" + addrPrefixLen);
+ try {
+ la = new LinkAddress(ia, addrPrefixLen);
+ } catch (IllegalArgumentException e) {
+ throw new UnknownHostException("Bad parameter for LinkAddress, ia="
+ + ia.getHostAddress() + "/" + addrPrefixLen);
+ }
+
+ linkProperties.addLinkAddress(la);
+ }
+ }
+ } else {
+ throw new UnknownHostException("no address for ifname=" + response.ifname);
+ }
+
+ // set dns servers
+ if (response.dnses != null && response.dnses.length > 0) {
+ for (String addr : response.dnses) {
+ addr = addr.trim();
+ if (addr.isEmpty()) continue;
+ InetAddress ia;
+ try {
+ ia = NetworkUtils.numericToInetAddress(addr);
+ } catch (IllegalArgumentException e) {
+ throw new UnknownHostException("Non-numeric dns addr=" + addr);
+ }
+ if (!ia.isAnyLocalAddress()) {
+ linkProperties.addDnsServer(ia);
+ }
+ }
+ } else if (okToUseSystemPropertyDns) {
+ dnsServers[0] = SystemProperties.get(propertyPrefix + "dns1");
+ dnsServers[1] = SystemProperties.get(propertyPrefix + "dns2");
+ for (String dnsAddr : dnsServers) {
+ dnsAddr = dnsAddr.trim();
+ if (dnsAddr.isEmpty()) continue;
+ InetAddress ia;
+ try {
+ ia = NetworkUtils.numericToInetAddress(dnsAddr);
+ } catch (IllegalArgumentException e) {
+ throw new UnknownHostException("Non-numeric dns addr=" + dnsAddr);
+ }
+ if (!ia.isAnyLocalAddress()) {
+ linkProperties.addDnsServer(ia);
+ }
+ }
+ } else {
+ throw new UnknownHostException("Empty dns response and no system default dns");
+ }
+
+ for (String addr : response.gateways) {
+ addr = addr.trim();
+ if (addr.isEmpty()) continue;
+ InetAddress ia;
+ try {
+ ia = NetworkUtils.numericToInetAddress(addr);
+ } catch (IllegalArgumentException e) {
+ throw new UnknownHostException("Non-numeric gateway addr=" + addr);
+ }
+ // Allow 0.0.0.0 or :: as a gateway; this indicates a point-to-point interface.
+ linkProperties.addRoute(new RouteInfo(ia));
+ }
+
+ // set interface MTU
+ // this may clobber the setting read from the APN db, but that's ok
+ linkProperties.setMtu(response.mtu);
+
+ result = SetupResult.SUCCESS;
+ } catch (UnknownHostException e) {
+ log("setLinkProperties: UnknownHostException " + e);
+ e.printStackTrace();
+ result = SetupResult.ERR_UnacceptableParameter;
+ }
+ } else {
+ result = SetupResult.ERR_RilError;
+ }
+
+ // An error occurred so clear properties
+ if (result != SetupResult.SUCCESS) {
+ if (DBG) {
+ log("setLinkProperties: error clearing LinkProperties status=" + response.status
+ + " result=" + result);
+ }
+ linkProperties.clear();
+ }
+
+ return result;
}
/**
@@ -1421,8 +1571,8 @@
ar = (AsyncResult) msg.obj;
cp = (ConnectionParams) ar.userObj;
- DataCallResponse.SetupResult result = onSetupConnectionCompleted(ar);
- if (result != DataCallResponse.SetupResult.ERR_Stale) {
+ SetupResult result = onSetupConnectionCompleted(ar);
+ if (result != SetupResult.ERR_Stale) {
if (mConnectionParams != cp) {
loge("DcActivatingState: WEIRD mConnectionsParams:"+ mConnectionParams
+ " != cp:" + cp);
diff --git a/com/android/internal/telephony/dataconnection/DataProfile.java b/com/android/internal/telephony/dataconnection/DataProfile.java
deleted file mode 100644
index 48a8107..0000000
--- a/com/android/internal/telephony/dataconnection/DataProfile.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.telephony.dataconnection;
-
-import android.telephony.ServiceState;
-import android.text.TextUtils;
-
-import com.android.internal.telephony.RILConstants;
-
-public class DataProfile {
-
- static final int TYPE_COMMON = 0;
- static final int TYPE_3GPP = 1;
- static final int TYPE_3GPP2 = 2;
-
- //id of the data profile
- public final int profileId;
- //the APN to connect to
- public final String apn;
- //one of the PDP_type values in TS 27.007 section 10.1.1.
- //For example, "IP", "IPV6", "IPV4V6", or "PPP".
- public final String protocol;
- //authentication protocol used for this PDP context
- //(None: 0, PAP: 1, CHAP: 2, PAP&CHAP: 3)
- public final int authType;
- //the username for APN, or NULL
- public final String user;
- //the password for APN, or NULL
- public final String password;
- //the profile type, TYPE_COMMON, TYPE_3GPP, TYPE_3GPP2
- public final int type;
- //the period in seconds to limit the maximum connections
- public final int maxConnsTime;
- //the maximum connections during maxConnsTime
- public final int maxConns;
- //the required wait time in seconds after a successful UE initiated
- //disconnect of a given PDN connection before the device can send
- //a new PDN connection request for that given PDN
- public final int waitTime;
- //true to enable the profile, false to disable
- public final boolean enabled;
- //supported APN types bitmap. See RIL_ApnTypes for the value of each bit.
- public final int supportedApnTypesBitmap;
- //one of the PDP_type values in TS 27.007 section 10.1.1 used on roaming network.
- //For example, "IP", "IPV6", "IPV4V6", or "PPP".
- public final String roamingProtocol;
- //The bearer bitmap. See RIL_RadioAccessFamily for the value of each bit.
- public final int bearerBitmap;
- //maximum transmission unit (MTU) size in bytes
- public final int mtu;
- //the MVNO type: possible values are "imsi", "gid", "spn"
- public final String mvnoType;
- //MVNO match data. For example, SPN: A MOBILE, BEN NL, ...
- //IMSI: 302720x94, 2060188, ...
- //GID: 4E, 33, ...
- public final String mvnoMatchData;
- //indicating the data profile was sent to the modem through setDataProfile earlier.
- public final boolean modemCognitive;
-
- DataProfile(int profileId, String apn, String protocol, int authType,
- String user, String password, int type, int maxConnsTime, int maxConns,
- int waitTime, boolean enabled, int supportedApnTypesBitmap, String roamingProtocol,
- int bearerBitmap, int mtu, String mvnoType, String mvnoMatchData,
- boolean modemCognitive) {
-
- this.profileId = profileId;
- this.apn = apn;
- this.protocol = protocol;
- if (authType == -1) {
- authType = TextUtils.isEmpty(user) ? RILConstants.SETUP_DATA_AUTH_NONE
- : RILConstants.SETUP_DATA_AUTH_PAP_CHAP;
- }
- this.authType = authType;
- this.user = user;
- this.password = password;
- this.type = type;
- this.maxConnsTime = maxConnsTime;
- this.maxConns = maxConns;
- this.waitTime = waitTime;
- this.enabled = enabled;
-
- this.supportedApnTypesBitmap = supportedApnTypesBitmap;
- this.roamingProtocol = roamingProtocol;
- this.bearerBitmap = bearerBitmap;
- this.mtu = mtu;
- this.mvnoType = mvnoType;
- this.mvnoMatchData = mvnoMatchData;
- this.modemCognitive = modemCognitive;
- }
-
- public DataProfile(ApnSetting apn) {
- this(apn, apn.profileId);
- }
-
- public DataProfile(ApnSetting apn, int profileId) {
- this(profileId, apn.apn, apn.protocol,
- apn.authType, apn.user, apn.password, apn.bearerBitmask == 0
- ? TYPE_COMMON : (ServiceState.bearerBitmapHasCdma(apn.bearerBitmask)
- ? TYPE_3GPP2 : TYPE_3GPP),
- apn.maxConnsTime, apn.maxConns, apn.waitTime, apn.carrierEnabled, apn.typesBitmap,
- apn.roamingProtocol, apn.bearerBitmask, apn.mtu, apn.mvnoType, apn.mvnoMatchData,
- apn.modemCognitive);
- }
-
- @Override
- public String toString() {
- return "DataProfile=" + profileId + "/" + apn + "/" + protocol + "/" + authType
- + "/" + user + "/" + password + "/" + type + "/" + maxConnsTime
- + "/" + maxConns + "/" + waitTime + "/" + enabled + "/" + supportedApnTypesBitmap
- + "/" + roamingProtocol + "/" + bearerBitmap + "/" + mtu + "/" + mvnoType + "/"
- + mvnoMatchData + "/" + modemCognitive;
- }
-
- @Override
- public boolean equals(Object o) {
- if (o instanceof DataProfile == false) return false;
- return (o == this || toString().equals(o.toString()));
- }
-}
diff --git a/com/android/internal/telephony/dataconnection/DcTracker.java b/com/android/internal/telephony/dataconnection/DcTracker.java
index fb756cd..540ec54 100644
--- a/com/android/internal/telephony/dataconnection/DcTracker.java
+++ b/com/android/internal/telephony/dataconnection/DcTracker.java
@@ -65,6 +65,7 @@
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
import android.telephony.TelephonyManager;
import android.telephony.cdma.CdmaCellLocation;
+import android.telephony.data.DataProfile;
import android.telephony.gsm.GsmCellLocation;
import android.text.TextUtils;
import android.util.EventLog;
@@ -2093,7 +2094,7 @@
} else {
if (DBG) log("setInitialAttachApn: X selected Apn=" + initialAttachApnSetting);
- mPhone.mCi.setInitialAttachApn(new DataProfile(initialAttachApnSetting),
+ mPhone.mCi.setInitialAttachApn(createDataProfile(initialAttachApnSetting),
mPhone.getServiceState().getDataRoamingFromRegistration(), null);
}
}
@@ -3281,7 +3282,7 @@
ArrayList<DataProfile> dps = new ArrayList<DataProfile>();
for (ApnSetting apn : mAllApnSettings) {
if (apn.modemCognitive) {
- DataProfile dp = new DataProfile(apn);
+ DataProfile dp = createDataProfile(apn);
if (!dps.contains(dp)) {
dps.add(dp);
}
@@ -4800,4 +4801,25 @@
}
}
+ private static DataProfile createDataProfile(ApnSetting apn) {
+ return createDataProfile(apn, apn.profileId);
+ }
+
+ @VisibleForTesting
+ public static DataProfile createDataProfile(ApnSetting apn, int profileId) {
+ int profileType;
+ if (apn.bearerBitmask == 0) {
+ profileType = DataProfile.TYPE_COMMON;
+ } else if (ServiceState.bearerBitmapHasCdma(apn.bearerBitmask)) {
+ profileType = DataProfile.TYPE_3GPP2;
+ } else {
+ profileType = DataProfile.TYPE_3GPP;
+ }
+
+ return new DataProfile(profileId, apn.apn, apn.protocol,
+ apn.authType, apn.user, apn.password, profileType,
+ apn.maxConnsTime, apn.maxConns, apn.waitTime, apn.carrierEnabled, apn.typesBitmap,
+ apn.roamingProtocol, apn.bearerBitmask, apn.mtu, apn.mvnoType, apn.mvnoMatchData,
+ apn.modemCognitive);
+ }
}
diff --git a/com/android/internal/telephony/euicc/EuiccController.java b/com/android/internal/telephony/euicc/EuiccController.java
index ac2a039..dc84718 100644
--- a/com/android/internal/telephony/euicc/EuiccController.java
+++ b/com/android/internal/telephony/euicc/EuiccController.java
@@ -38,6 +38,7 @@
import android.telephony.euicc.DownloadableSubscription;
import android.telephony.euicc.EuiccInfo;
import android.telephony.euicc.EuiccManager;
+import android.text.TextUtils;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -226,6 +227,7 @@
addResolutionIntent(extrasIntent,
EuiccService.ACTION_RESOLVE_DEACTIVATE_SIM,
mCallingPackage,
+ false /* confirmationCodeRetried */,
getOperationForDeactivateSim());
break;
default:
@@ -306,6 +308,7 @@
Intent extrasIntent = new Intent();
addResolutionIntent(extrasIntent, EuiccService.ACTION_RESOLVE_NO_PRIVILEGES,
mCallingPackage,
+ false /* confirmationCodeRetried */,
EuiccOperation.forDownloadNoPrivileges(
mCallingToken, mSubscription, mSwitchAfterDownload,
mCallingPackage));
@@ -354,6 +357,7 @@
Intent extrasIntent = new Intent();
addResolutionIntent(extrasIntent, EuiccService.ACTION_RESOLVE_NO_PRIVILEGES,
mCallingPackage,
+ false /* confirmationCodeRetried */,
EuiccOperation.forDownloadNoPrivileges(
mCallingToken, subscription, mSwitchAfterDownload,
mCallingPackage));
@@ -407,15 +411,21 @@
addResolutionIntent(extrasIntent,
EuiccService.ACTION_RESOLVE_DEACTIVATE_SIM,
callingPackage,
+ false /* confirmationCodeRetried */,
EuiccOperation.forDownloadDeactivateSim(
callingToken, subscription, switchAfterDownload,
callingPackage));
break;
case EuiccService.RESULT_NEED_CONFIRMATION_CODE:
resultCode = RESOLVABLE_ERROR;
+ boolean retried = false;
+ if (!TextUtils.isEmpty(subscription.getConfirmationCode())) {
+ retried = true;
+ }
addResolutionIntent(extrasIntent,
EuiccService.ACTION_RESOLVE_CONFIRMATION_CODE,
callingPackage,
+ retried /* confirmationCodeRetried */,
EuiccOperation.forDownloadConfirmationCode(
callingToken, subscription, switchAfterDownload,
callingPackage));
@@ -520,6 +530,7 @@
addResolutionIntent(extrasIntent,
EuiccService.ACTION_RESOLVE_DEACTIVATE_SIM,
mCallingPackage,
+ false /* confirmationCodeRetried */,
EuiccOperation.forGetDefaultListDeactivateSim(
mCallingToken, mCallingPackage));
break;
@@ -671,6 +682,7 @@
addResolutionIntent(extrasIntent,
EuiccService.ACTION_RESOLVE_NO_PRIVILEGES,
callingPackage,
+ false /* confirmationCodeRetried */,
EuiccOperation.forSwitchNoPrivileges(
token, subscriptionId, callingPackage));
sendResult(callbackIntent, RESOLVABLE_ERROR, extrasIntent);
@@ -716,6 +728,7 @@
addResolutionIntent(extrasIntent,
EuiccService.ACTION_RESOLVE_DEACTIVATE_SIM,
callingPackage,
+ false /* confirmationCodeRetried */,
EuiccOperation.forSwitchDeactivateSim(
callingToken, subscriptionId, callingPackage));
break;
@@ -883,11 +896,13 @@
/** Add a resolution intent to the given extras intent. */
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
public void addResolutionIntent(Intent extrasIntent, String resolutionAction,
- String callingPackage, EuiccOperation op) {
+ String callingPackage, boolean confirmationCodeRetried, EuiccOperation op) {
Intent intent = new Intent(EuiccManager.ACTION_RESOLVE_ERROR);
intent.putExtra(EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_ACTION,
resolutionAction);
intent.putExtra(EuiccService.EXTRA_RESOLUTION_CALLING_PACKAGE, callingPackage);
+ intent.putExtra(EuiccService.EXTRA_RESOLUTION_CONFIRMATION_CODE_RETRIED,
+ confirmationCodeRetried);
intent.putExtra(EXTRA_OPERATION, op);
PendingIntent resolutionIntent = PendingIntent.getActivity(
mContext, 0 /* requestCode */, intent, PendingIntent.FLAG_ONE_SHOT);
diff --git a/com/android/internal/telephony/euicc/EuiccOperation.java b/com/android/internal/telephony/euicc/EuiccOperation.java
index 84df52e..148d9dc 100644
--- a/com/android/internal/telephony/euicc/EuiccOperation.java
+++ b/com/android/internal/telephony/euicc/EuiccOperation.java
@@ -309,9 +309,7 @@
if (TextUtils.isEmpty(confirmationCode)) {
fail(callbackIntent);
} else {
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
- mDownloadableSubscription.setConfirmationCode(confirmationCode);
- }
+ mDownloadableSubscription.setConfirmationCode(confirmationCode);
EuiccController.get()
.downloadSubscription(
mDownloadableSubscription,
diff --git a/com/android/internal/telephony/gsm/GsmMmiCode.java b/com/android/internal/telephony/gsm/GsmMmiCode.java
index b9a07f9..3376e2b 100644
--- a/com/android/internal/telephony/gsm/GsmMmiCode.java
+++ b/com/android/internal/telephony/gsm/GsmMmiCode.java
@@ -863,6 +863,13 @@
return mIsSsInfo;
}
+ public static boolean isVoiceUnconditionalForwarding(int reason, int serviceClass) {
+ return (((reason == CommandsInterface.CF_REASON_UNCONDITIONAL)
+ || (reason == CommandsInterface.CF_REASON_ALL))
+ && (((serviceClass & CommandsInterface.SERVICE_CLASS_VOICE) != 0)
+ || (serviceClass == CommandsInterface.SERVICE_CLASS_NONE)));
+ }
+
/** Process a MMI code or short code...anything that isn't a dialing number */
public void
processCode() throws CallStateException {
@@ -933,12 +940,6 @@
throw new RuntimeException ("invalid action");
}
- int isSettingUnconditionalVoice =
- (((reason == CommandsInterface.CF_REASON_UNCONDITIONAL) ||
- (reason == CommandsInterface.CF_REASON_ALL)) &&
- (((serviceClass & CommandsInterface.SERVICE_CLASS_VOICE) != 0) ||
- (serviceClass == CommandsInterface.SERVICE_CLASS_NONE))) ? 1 : 0;
-
int isEnableDesired =
((cfAction == CommandsInterface.CF_ACTION_ENABLE) ||
(cfAction == CommandsInterface.CF_ACTION_REGISTRATION)) ? 1 : 0;
@@ -947,7 +948,7 @@
mPhone.mCi.setCallForward(cfAction, reason, serviceClass,
dialingNumber, time, obtainMessage(
EVENT_SET_CFF_COMPLETE,
- isSettingUnconditionalVoice,
+ isVoiceUnconditionalForwarding(reason, serviceClass) ? 1 : 0,
isEnableDesired, this));
}
} else if (isServiceCodeCallBarring(mSc)) {
diff --git a/com/android/internal/telephony/ims/ImsResolver.java b/com/android/internal/telephony/ims/ImsResolver.java
index 2f790b7..4a64984 100644
--- a/com/android/internal/telephony/ims/ImsResolver.java
+++ b/com/android/internal/telephony/ims/ImsResolver.java
@@ -38,8 +38,9 @@
import android.util.Pair;
import android.util.SparseArray;
-import com.android.ims.internal.IImsServiceController;
-import com.android.ims.internal.IImsServiceFeatureListener;
+import com.android.ims.internal.IImsMMTelFeature;
+import com.android.ims.internal.IImsRcsFeature;
+import com.android.ims.internal.IImsServiceFeatureCallback;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.PhoneConstants;
@@ -295,21 +296,58 @@
}
/**
- * Returns the {@link IImsServiceController} that corresponds to the given slot Id and IMS
- * feature or {@link null} if the service is not available. If an ImsServiceController is
- * available, the {@link IImsServiceFeatureListener} callback is registered as a listener for
- * feature updates.
- * @param slotId The SIM slot that we are requesting the {@link IImsServiceController} for.
- * @param feature The IMS Feature we are requesting.
+ * Returns the {@link IImsMMTelFeature} that corresponds to the given slot Id or {@link null} if
+ * the service is not available. If an IImsMMTelFeature is available, the
+ * {@link IImsServiceFeatureCallback} callback is registered as a listener for feature updates.
+ * @param slotId The SIM slot that we are requesting the {@link IImsMMTelFeature} for.
* @param callback Listener that will send updates to ImsManager when there are updates to
- * ImsServiceController.
- * @return {@link IImsServiceController} interface for the feature specified or {@link null} if
- * it is unavailable.
+ * the feature.
+ * @return {@link IImsMMTelFeature} interface or {@link null} if it is unavailable.
*/
- public IImsServiceController getImsServiceControllerAndListen(int slotId, int feature,
- IImsServiceFeatureListener callback) {
- if (slotId < 0 || slotId >= mNumSlots || feature <= ImsFeature.INVALID
- || feature >= ImsFeature.MAX) {
+ public IImsMMTelFeature getMMTelFeatureAndListen(int slotId,
+ IImsServiceFeatureCallback callback) {
+ ImsServiceController controller = getImsServiceControllerAndListen(slotId, ImsFeature.MMTEL,
+ callback);
+ return (controller != null) ? controller.getMMTelFeature(slotId) : null;
+ }
+
+ /**
+ * Returns the {@link IImsMMTelFeature} that corresponds to the given slot Id for emergency
+ * calling or {@link null} if the service is not available. If an IImsMMTelFeature is
+ * available, the {@link IImsServiceFeatureCallback} callback is registered as a listener for
+ * feature updates.
+ * @param slotId The SIM slot that we are requesting the {@link IImsMMTelFeature} for.
+ * @param callback listener that will send updates to ImsManager when there are updates to
+ * the feature.
+ * @return {@link IImsMMTelFeature} interface or {@link null} if it is unavailable.
+ */
+ public IImsMMTelFeature getEmergencyMMTelFeatureAndListen(int slotId,
+ IImsServiceFeatureCallback callback) {
+ ImsServiceController controller = getImsServiceControllerAndListen(slotId,
+ ImsFeature.EMERGENCY_MMTEL, callback);
+ return (controller != null) ? controller.getEmergencyMMTelFeature(slotId) : null;
+ }
+
+ /**
+ * Returns the {@link IImsMMTelFeature} that corresponds to the given slot Id for emergency
+ * calling or {@link null} if the service is not available. If an IImsMMTelFeature is
+ * available, the {@link IImsServiceFeatureCallback} callback is registered as a listener for
+ * feature updates.
+ * @param slotId The SIM slot that we are requesting the {@link IImsMMTelFeature} for.
+ * @param callback listener that will send updates to ImsManager when there are updates to
+ * the feature.
+ * @return {@link IImsMMTelFeature} interface or {@link null} if it is unavailable.
+ */
+ public IImsRcsFeature getRcsFeatureAndListen(int slotId, IImsServiceFeatureCallback callback) {
+ ImsServiceController controller = getImsServiceControllerAndListen(slotId, ImsFeature.RCS,
+ callback);
+ return (controller != null) ? controller.getRcsFeature(slotId) : null;
+ }
+
+ @VisibleForTesting
+ public ImsServiceController getImsServiceControllerAndListen(int slotId, int feature,
+ IImsServiceFeatureCallback callback) {
+ if (slotId < 0 || slotId >= mNumSlots) {
return null;
}
ImsServiceController controller;
@@ -322,7 +360,7 @@
}
if (controller != null) {
controller.addImsServiceFeatureListener(callback);
- return controller.getImsServiceController();
+ return controller;
}
return null;
}
diff --git a/com/android/internal/telephony/ims/ImsServiceController.java b/com/android/internal/telephony/ims/ImsServiceController.java
index 6fcefbd..6f31b50 100644
--- a/com/android/internal/telephony/ims/ImsServiceController.java
+++ b/com/android/internal/telephony/ims/ImsServiceController.java
@@ -24,14 +24,18 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.IInterface;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.telephony.ims.feature.ImsFeature;
import android.util.Log;
import android.util.Pair;
import com.android.ims.internal.IImsFeatureStatusCallback;
+import com.android.ims.internal.IImsMMTelFeature;
+import com.android.ims.internal.IImsRcsFeature;
import com.android.ims.internal.IImsServiceController;
-import com.android.ims.internal.IImsServiceFeatureListener;
+import com.android.ims.internal.IImsServiceFeatureCallback;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.ExponentialBackoff;
@@ -171,15 +175,54 @@
private boolean mIsBinding = false;
// Set of a pair of slotId->feature
private HashSet<Pair<Integer, Integer>> mImsFeatures;
+ // Binder interfaces to the features set in mImsFeatures;
+ private HashSet<ImsFeatureContainer> mImsFeatureBinders = new HashSet<>();
private IImsServiceController mIImsServiceController;
// Easier for testing.
private IBinder mImsServiceControllerBinder;
private ImsServiceConnection mImsServiceConnection;
private ImsDeathRecipient mImsDeathRecipient;
- private Set<IImsServiceFeatureListener> mImsStatusCallbacks = new HashSet<>();
+ private Set<IImsServiceFeatureCallback> mImsStatusCallbacks = new HashSet<>();
// Only added or removed, never accessed on purpose.
private Set<ImsFeatureStatusCallback> mFeatureStatusCallbacks = new HashSet<>();
+ private class ImsFeatureContainer {
+ public int slotId;
+ public int featureType;
+ private IInterface mBinder;
+
+ ImsFeatureContainer(int slotId, int featureType, IInterface binder) {
+ this.slotId = slotId;
+ this.featureType = featureType;
+ this.mBinder = binder;
+ }
+
+ // Casts the IInterface into the binder class we are looking for.
+ public <T extends IInterface> T resolve(Class<T> className) {
+ return className.cast(mBinder);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ ImsFeatureContainer that = (ImsFeatureContainer) o;
+
+ if (slotId != that.slotId) return false;
+ if (featureType != that.featureType) return false;
+ return mBinder != null ? mBinder.equals(that.mBinder) : that.mBinder == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = slotId;
+ result = 31 * result + featureType;
+ result = 31 * result + (mBinder != null ? mBinder.hashCode() : 0);
+ return result;
+ }
+ }
+
/**
* Container class for the IImsFeatureStatusCallback callback implementation. This class is
* never used directly, but we need to keep track of the IImsFeatureStatusCallback
@@ -373,12 +416,56 @@
/**
* Add a callback to ImsManager that signals a new feature that the ImsServiceProxy can handle.
*/
- public void addImsServiceFeatureListener(IImsServiceFeatureListener callback) {
+ public void addImsServiceFeatureListener(IImsServiceFeatureCallback callback) {
synchronized (mLock) {
mImsStatusCallbacks.add(callback);
}
}
+ /**
+ * Return the {@Link MMTelFeature} binder on the slot associated with the slotId.
+ * Used for normal calling.
+ */
+ public IImsMMTelFeature getMMTelFeature(int slotId) {
+ synchronized (mLock) {
+ ImsFeatureContainer f = getImsFeatureContainer(slotId, ImsFeature.MMTEL);
+ if (f == null) {
+ Log.w(LOG_TAG, "Requested null MMTelFeature on slot " + slotId);
+ return null;
+ }
+ return f.resolve(IImsMMTelFeature.class);
+ }
+ }
+
+ /**
+ * Return the {@Link MMTelFeature} binder on the slot associated with the slotId.
+ * Used for emergency calling only.
+ */
+ public IImsMMTelFeature getEmergencyMMTelFeature(int slotId) {
+ synchronized (mLock) {
+ ImsFeatureContainer f = getImsFeatureContainer(slotId, ImsFeature.EMERGENCY_MMTEL);
+ if (f == null) {
+ Log.w(LOG_TAG, "Requested null Emergency MMTelFeature on slot " + slotId);
+ return null;
+ }
+ return f.resolve(IImsMMTelFeature.class);
+ }
+ }
+
+ /**
+ * Return the {@Link RcsFeature} binder on the slot associated with the slotId.
+ */
+ public IImsRcsFeature getRcsFeature(int slotId) {
+ synchronized (mLock) {
+ ImsFeatureContainer f = getImsFeatureContainer(slotId, ImsFeature.RCS);
+ if (f == null) {
+ Log.w(LOG_TAG, "Requested null RcsFeature on slot " + slotId);
+ return null;
+ }
+ return f.resolve(IImsRcsFeature.class);
+ }
+ }
+
private void removeImsServiceFeatureListener() {
synchronized (mLock) {
mImsStatusCallbacks.clear();
@@ -407,9 +494,9 @@
private void sendImsFeatureCreatedCallback(int slot, int feature) {
synchronized (mLock) {
- for (Iterator<IImsServiceFeatureListener> i = mImsStatusCallbacks.iterator();
+ for (Iterator<IImsServiceFeatureCallback> i = mImsStatusCallbacks.iterator();
i.hasNext(); ) {
- IImsServiceFeatureListener callbacks = i.next();
+ IImsServiceFeatureCallback callbacks = i.next();
try {
callbacks.imsFeatureCreated(slot, feature);
} catch (RemoteException e) {
@@ -424,9 +511,9 @@
private void sendImsFeatureRemovedCallback(int slot, int feature) {
synchronized (mLock) {
- for (Iterator<IImsServiceFeatureListener> i = mImsStatusCallbacks.iterator();
+ for (Iterator<IImsServiceFeatureCallback> i = mImsStatusCallbacks.iterator();
i.hasNext(); ) {
- IImsServiceFeatureListener callbacks = i.next();
+ IImsServiceFeatureCallback callbacks = i.next();
try {
callbacks.imsFeatureRemoved(slot, feature);
} catch (RemoteException e) {
@@ -441,9 +528,9 @@
private void sendImsFeatureStatusChanged(int slot, int feature, int status) {
synchronized (mLock) {
- for (Iterator<IImsServiceFeatureListener> i = mImsStatusCallbacks.iterator();
+ for (Iterator<IImsServiceFeatureCallback> i = mImsStatusCallbacks.iterator();
i.hasNext(); ) {
- IImsServiceFeatureListener callbacks = i.next();
+ IImsServiceFeatureCallback callbacks = i.next();
try {
callbacks.imsStatusChanged(slot, feature, status);
} catch (RemoteException e) {
@@ -465,8 +552,8 @@
ImsFeatureStatusCallback c = new ImsFeatureStatusCallback(featurePair.first,
featurePair.second);
mFeatureStatusCallbacks.add(c);
- mIImsServiceController.createImsFeature(featurePair.first, featurePair.second,
- c.getCallback());
+ IInterface f = createImsFeature(featurePair.first, featurePair.second, c.getCallback());
+ addImsFeatureBinder(featurePair.first, featurePair.second, f);
// Signal ImsResolver to change supported ImsFeatures for this ImsServiceController
mCallbacks.imsServiceFeatureCreated(featurePair.first, featurePair.second, this);
// Send callback to ImsServiceProxy to change supported ImsFeatures
@@ -489,6 +576,7 @@
}
mIImsServiceController.removeImsFeature(featurePair.first, featurePair.second,
(callbackToRemove != null ? callbackToRemove.getCallback() : null));
+ removeImsFeatureBinder(featurePair.first, featurePair.second);
// Signal ImsResolver to change supported ImsFeatures for this ImsServiceController
mCallbacks.imsServiceFeatureRemoved(featurePair.first, featurePair.second, this);
// Send callback to ImsServiceProxy to change supported ImsFeatures
@@ -498,6 +586,45 @@
sendImsFeatureRemovedCallback(featurePair.first, featurePair.second);
}
+ // This method should only be called when already synchronized on mLock;
+ private IInterface createImsFeature(int slotId, int featureType, IImsFeatureStatusCallback c)
+ throws RemoteException {
+ switch (featureType) {
+ case ImsFeature.EMERGENCY_MMTEL: {
+ return mIImsServiceController.createEmergencyMMTelFeature(slotId, c);
+ }
+ case ImsFeature.MMTEL: {
+ return mIImsServiceController.createMMTelFeature(slotId, c);
+ }
+ case ImsFeature.RCS: {
+ return mIImsServiceController.createRcsFeature(slotId, c);
+ }
+ default:
+ return null;
+ }
+ }
+
+ // This method should only be called when synchronized on mLock
+ private void addImsFeatureBinder(int slotId, int featureType, IInterface b) {
+ mImsFeatureBinders.add(new ImsFeatureContainer(slotId, featureType, b));
+ }
+
+ // This method should only be called when synchronized on mLock
+ private void removeImsFeatureBinder(int slotId, int featureType) {
+ ImsFeatureContainer container = mImsFeatureBinders.stream()
+ .filter(f-> (f.slotId == slotId && f.featureType == featureType))
+ .findFirst().orElse(null);
+ if (container != null) {
+ mImsFeatureBinders.remove(container);
+ }
+ }
+
+ private ImsFeatureContainer getImsFeatureContainer(int slotId, int featureType) {
+ return mImsFeatureBinders.stream()
+ .filter(f-> (f.slotId == slotId && f.featureType == featureType))
+ .findFirst().orElse(null);
+ }
+
private void notifyAllFeaturesRemoved() {
if (mCallbacks == null) {
Log.w(LOG_TAG, "notifyAllFeaturesRemoved called with invalid callbacks.");
diff --git a/com/android/internal/telephony/imsphone/ImsPhone.java b/com/android/internal/telephony/imsphone/ImsPhone.java
index bb2c34f..e54d7bc 100644
--- a/com/android/internal/telephony/imsphone/ImsPhone.java
+++ b/com/android/internal/telephony/imsphone/ImsPhone.java
@@ -95,6 +95,7 @@
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.telephony.TelephonyProperties;
import com.android.internal.telephony.UUSInfo;
+import com.android.internal.telephony.gsm.GsmMmiCode;
import com.android.internal.telephony.gsm.SuppServiceNotification;
import com.android.internal.telephony.uicc.IccRecords;
import com.android.internal.telephony.util.NotificationChannelController;
@@ -876,9 +877,8 @@
if ((isValidCommandInterfaceCFAction(commandInterfaceCFAction)) &&
(isValidCommandInterfaceCFReason(commandInterfaceCFReason))) {
Message resp;
- Cf cf = new Cf(dialingNumber,
- (commandInterfaceCFReason == CF_REASON_UNCONDITIONAL ? true : false),
- onComplete);
+ Cf cf = new Cf(dialingNumber, GsmMmiCode.isVoiceUnconditionalForwarding(
+ commandInterfaceCFReason, serviceClass), onComplete);
resp = obtainMessage(EVENT_SET_CALL_FORWARD_DONE,
isCfEnable(commandInterfaceCFAction) ? 1 : 0, 0, cf);
diff --git a/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java b/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java
index cea8b21..c2711e8 100644
--- a/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java
+++ b/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java
@@ -22,13 +22,13 @@
import android.service.carrier.CarrierIdentifier;
import android.telephony.ImsiEncryptionInfo;
import android.telephony.NetworkScanRequest;
+import android.telephony.data.DataProfile;
import com.android.internal.telephony.BaseCommands;
import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.RadioCapability;
import com.android.internal.telephony.UUSInfo;
import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
-import com.android.internal.telephony.dataconnection.DataProfile;
import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
import java.util.List;
diff --git a/com/android/internal/telephony/sip/SipCommandInterface.java b/com/android/internal/telephony/sip/SipCommandInterface.java
index 1264053..59af1bb 100644
--- a/com/android/internal/telephony/sip/SipCommandInterface.java
+++ b/com/android/internal/telephony/sip/SipCommandInterface.java
@@ -22,12 +22,12 @@
import android.service.carrier.CarrierIdentifier;
import android.telephony.ImsiEncryptionInfo;
import android.telephony.NetworkScanRequest;
+import android.telephony.data.DataProfile;
import com.android.internal.telephony.BaseCommands;
import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.UUSInfo;
import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
-import com.android.internal.telephony.dataconnection.DataProfile;
import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
import java.util.List;
diff --git a/com/android/internal/telephony/test/SimulatedCommands.java b/com/android/internal/telephony/test/SimulatedCommands.java
index 7553b61..b90a292 100644
--- a/com/android/internal/telephony/test/SimulatedCommands.java
+++ b/com/android/internal/telephony/test/SimulatedCommands.java
@@ -35,6 +35,7 @@
import android.telephony.Rlog;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
+import android.telephony.data.DataProfile;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.BaseCommands;
@@ -49,7 +50,6 @@
import com.android.internal.telephony.UUSInfo;
import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
import com.android.internal.telephony.dataconnection.DataCallResponse;
-import com.android.internal.telephony.dataconnection.DataProfile;
import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
import com.android.internal.telephony.gsm.SuppServiceNotification;
import com.android.internal.telephony.uicc.IccCardStatus;
@@ -132,6 +132,7 @@
private boolean mDcSuccess = true;
private DataCallResponse mDcResponse;
+ private boolean mIsRadioPowerFailResponse = false;
//***** Constructor
public
@@ -1188,11 +1189,17 @@
@Override
public void setRadioPower(boolean on, Message result) {
+ if (mIsRadioPowerFailResponse) {
+ resultFail(result, null, new RuntimeException("setRadioPower failed!"));
+ return;
+ }
+
if(on) {
setRadioState(RadioState.RADIO_ON);
} else {
setRadioState(RadioState.RADIO_OFF);
}
+ resultSuccess(result, null);
}
@@ -2151,4 +2158,8 @@
super.setOnRestrictedStateChanged(h, what, obj);
SimulatedCommandsVerifier.getInstance().setOnRestrictedStateChanged(h, what, obj);
}
+
+ public void setRadioPowerFailResponse(boolean fail) {
+ mIsRadioPowerFailResponse = fail;
+ }
}
diff --git a/com/android/internal/telephony/test/SimulatedCommandsVerifier.java b/com/android/internal/telephony/test/SimulatedCommandsVerifier.java
index b03e70b..c51b6ad 100644
--- a/com/android/internal/telephony/test/SimulatedCommandsVerifier.java
+++ b/com/android/internal/telephony/test/SimulatedCommandsVerifier.java
@@ -21,12 +21,12 @@
import android.service.carrier.CarrierIdentifier;
import android.telephony.ImsiEncryptionInfo;
import android.telephony.NetworkScanRequest;
+import android.telephony.data.DataProfile;
import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.RadioCapability;
import com.android.internal.telephony.UUSInfo;
import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
-import com.android.internal.telephony.dataconnection.DataProfile;
import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
import java.util.List;
diff --git a/com/android/internal/telephony/uicc/SIMRecords.java b/com/android/internal/telephony/uicc/SIMRecords.java
index 8594f80..3af04a9 100644
--- a/com/android/internal/telephony/uicc/SIMRecords.java
+++ b/com/android/internal/telephony/uicc/SIMRecords.java
@@ -60,9 +60,6 @@
VoiceMailConstants mVmConfig;
-
- SpnOverride mSpnOverride;
-
// ***** Cached SIM State; cleared on channel close
private int mCallForwardingStatus;
@@ -99,7 +96,6 @@
public String toString() {
return "SimRecords: " + super.toString()
+ " mVmConfig" + mVmConfig
- + " mSpnOverride=" + mSpnOverride
+ " callForwardingEnabled=" + mCallForwardingStatus
+ " spnState=" + mSpnState
+ " mCphsInfo=" + mCphsInfo
@@ -214,7 +210,6 @@
mAdnCache = new AdnRecordCache(mFh);
mVmConfig = new VoiceMailConstants();
- mSpnOverride = new SpnOverride();
mRecordsRequested = false; // No load request is made till SIM ready
@@ -1640,57 +1635,63 @@
//***** Private methods
+ /**
+ * Override the carrier name with either carrier config or SPN
+ * if an override is provided.
+ */
private void handleCarrierNameOverride() {
- CarrierConfigManager configLoader = (CarrierConfigManager)
- mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
- if (configLoader != null && configLoader.getConfig().getBoolean(
- CarrierConfigManager.KEY_CARRIER_NAME_OVERRIDE_BOOL)) {
- String carrierName = configLoader.getConfig().getString(
- CarrierConfigManager.KEY_CARRIER_NAME_STRING);
- setServiceProviderName(carrierName);
- mTelephonyManager.setSimOperatorNameForPhone(mParentApp.getPhoneId(),
- carrierName);
- } else {
- setSpnFromConfig(getOperatorNumeric());
- }
-
- /* update display name with carrier override */
- setDisplayName();
- }
-
- private void setDisplayName() {
- SubscriptionManager subManager = SubscriptionManager.from(mContext);
- int[] subId = subManager.getSubId(mParentApp.getPhoneId());
-
- if ((subId == null) || subId.length <= 0) {
- log("subId not valid for Phone " + mParentApp.getPhoneId());
+ final int phoneId = mParentApp.getPhoneId();
+ SubscriptionController subCon = SubscriptionController.getInstance();
+ final int subId = subCon.getSubIdUsingPhoneId(phoneId);
+ if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ loge("subId not valid for Phone " + phoneId);
return;
}
- SubscriptionInfo subInfo = subManager.getActiveSubscriptionInfo(subId[0]);
- if (subInfo != null && subInfo.getNameSource()
- != SubscriptionManager.NAME_SOURCE_USER_INPUT) {
- CharSequence oldSubName = subInfo.getDisplayName();
- String newCarrierName = mTelephonyManager.getSimOperatorName(subId[0]);
-
- if (!TextUtils.isEmpty(newCarrierName) && !newCarrierName.equals(oldSubName)) {
- log("sim name[" + mParentApp.getPhoneId() + "] = " + newCarrierName);
- SubscriptionController.getInstance().setDisplayName(newCarrierName, subId[0]);
- }
- } else {
- log("SUB[" + mParentApp.getPhoneId() + "] " + subId[0] + " SubInfo not created yet");
+ CarrierConfigManager configLoader = (CarrierConfigManager)
+ mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+ if (configLoader == null) {
+ loge("Failed to load a Carrier Config");
+ return;
}
+
+ PersistableBundle config = configLoader.getConfigForSubId(subId);
+ boolean preferCcName = config.getBoolean(
+ CarrierConfigManager.KEY_CARRIER_NAME_OVERRIDE_BOOL, false);
+ String ccName = config.getString(CarrierConfigManager.KEY_CARRIER_NAME_STRING);
+ // If carrier config is priority, use it regardless - the preference
+ // and the name were both set by the carrier, so this is safe;
+ // otherwise, if the SPN is priority but we don't have one *and* we have
+ // a name in carrier config, use the carrier config name as a backup.
+ if (preferCcName || (TextUtils.isEmpty(getServiceProviderName())
+ && !TextUtils.isEmpty(ccName))) {
+ setServiceProviderName(ccName);
+ mTelephonyManager.setSimOperatorNameForPhone(phoneId, ccName);
+ }
+
+ updateCarrierNameForSubscription(subCon, subId);
}
- private void setSpnFromConfig(String carrier) {
- if (mSpnOverride.containsCarrier(carrier)) {
- setServiceProviderName(mSpnOverride.getSpn(carrier));
- mTelephonyManager.setSimOperatorNameForPhone(
- mParentApp.getPhoneId(), getServiceProviderName());
+ private void updateCarrierNameForSubscription(SubscriptionController subCon, int subId) {
+ /* update display name with carrier override */
+ SubscriptionInfo subInfo = subCon.getActiveSubscriptionInfo(
+ subId, mContext.getOpPackageName());
+
+ if (subInfo == null || subInfo.getNameSource()
+ == SubscriptionManager.NAME_SOURCE_USER_INPUT) {
+ // either way, there is no subinfo to update
+ return;
+ }
+
+ CharSequence oldSubName = subInfo.getDisplayName();
+ String newCarrierName = mTelephonyManager.getSimOperatorName(subId);
+
+ if (!TextUtils.isEmpty(newCarrierName) && !newCarrierName.equals(oldSubName)) {
+ log("sim name[" + mParentApp.getPhoneId() + "] = " + newCarrierName);
+ subCon.setDisplayName(newCarrierName, subId);
}
}
-
private void setVoiceMailByCountry (String spn) {
if (mVmConfig.containsCarrier(spn)) {
mIsVoiceMailFixed = true;
@@ -2223,7 +2224,6 @@
pw.println(" extends:");
super.dump(fd, pw, args);
pw.println(" mVmConfig=" + mVmConfig);
- pw.println(" mSpnOverride=" + mSpnOverride);
pw.println(" mCallForwardingStatus=" + mCallForwardingStatus);
pw.println(" mSpnState=" + mSpnState);
pw.println(" mCphsInfo=" + mCphsInfo);
diff --git a/com/android/internal/telephony/uicc/SpnOverride.java b/com/android/internal/telephony/uicc/SpnOverride.java
deleted file mode 100644
index 3a01af6..0000000
--- a/com/android/internal/telephony/uicc/SpnOverride.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.telephony.uicc;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileReader;
-import java.io.IOException;
-import java.util.HashMap;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import android.os.Environment;
-import android.telephony.Rlog;
-import android.util.Xml;
-
-import com.android.internal.util.XmlUtils;
-
-public class SpnOverride {
- private HashMap<String, String> mCarrierSpnMap;
-
-
- static final String LOG_TAG = "SpnOverride";
- static final String PARTNER_SPN_OVERRIDE_PATH ="etc/spn-conf.xml";
- static final String OEM_SPN_OVERRIDE_PATH = "telephony/spn-conf.xml";
-
- SpnOverride () {
- mCarrierSpnMap = new HashMap<String, String>();
- loadSpnOverrides();
- }
-
- boolean containsCarrier(String carrier) {
- return mCarrierSpnMap.containsKey(carrier);
- }
-
- String getSpn(String carrier) {
- return mCarrierSpnMap.get(carrier);
- }
-
- private void loadSpnOverrides() {
- FileReader spnReader;
-
- File spnFile = new File(Environment.getRootDirectory(),
- PARTNER_SPN_OVERRIDE_PATH);
- File oemSpnFile = new File(Environment.getOemDirectory(),
- OEM_SPN_OVERRIDE_PATH);
-
- if (oemSpnFile.exists()) {
- // OEM image exist SPN xml, get the timestamp from OEM & System image for comparison.
- long oemSpnTime = oemSpnFile.lastModified();
- long sysSpnTime = spnFile.lastModified();
- Rlog.d(LOG_TAG, "SPN Timestamp: oemTime = " + oemSpnTime + " sysTime = " + sysSpnTime);
-
- // To get the newer version of SPN from OEM image
- if (oemSpnTime > sysSpnTime) {
- Rlog.d(LOG_TAG, "SPN in OEM image is newer than System image");
- spnFile = oemSpnFile;
- }
- } else {
- // No SPN in OEM image, so load it from system image.
- Rlog.d(LOG_TAG, "No SPN in OEM image = " + oemSpnFile.getPath() +
- " Load SPN from system image");
- }
-
- try {
- spnReader = new FileReader(spnFile);
- } catch (FileNotFoundException e) {
- Rlog.w(LOG_TAG, "Can not open " + spnFile.getAbsolutePath());
- return;
- }
-
- try {
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(spnReader);
-
- XmlUtils.beginDocument(parser, "spnOverrides");
-
- while (true) {
- XmlUtils.nextElement(parser);
-
- String name = parser.getName();
- if (!"spnOverride".equals(name)) {
- break;
- }
-
- String numeric = parser.getAttributeValue(null, "numeric");
- String data = parser.getAttributeValue(null, "spn");
-
- mCarrierSpnMap.put(numeric, data);
- }
- spnReader.close();
- } catch (XmlPullParserException e) {
- Rlog.w(LOG_TAG, "Exception in spn-conf parser " + e);
- } catch (IOException e) {
- Rlog.w(LOG_TAG, "Exception in spn-conf parser " + e);
- }
- }
-
-}
diff --git a/com/android/internal/util/ArrayUtils.java b/com/android/internal/util/ArrayUtils.java
index 91bc681..22bfcc3 100644
--- a/com/android/internal/util/ArrayUtils.java
+++ b/com/android/internal/util/ArrayUtils.java
@@ -30,6 +30,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
/**
@@ -134,6 +135,13 @@
}
/**
+ * Checks if given map is null or has zero elements.
+ */
+ public static boolean isEmpty(@Nullable Map<?, ?> map) {
+ return map == null || map.isEmpty();
+ }
+
+ /**
* Checks if given array is null or has zero elements.
*/
public static <T> boolean isEmpty(@Nullable T[] array) {
diff --git a/com/android/internal/util/RingBuffer.java b/com/android/internal/util/RingBuffer.java
index c22be2c..9a6e542 100644
--- a/com/android/internal/util/RingBuffer.java
+++ b/com/android/internal/util/RingBuffer.java
@@ -60,6 +60,25 @@
mBuffer[indexOf(mCursor++)] = t;
}
+ /**
+ * Returns object of type <T> at the next writable slot, creating one if it is not already
+ * available. In case of any errors while creating the object, <code>null</code> will
+ * be returned.
+ */
+ public T getNextSlot() {
+ final int nextSlotIdx = indexOf(mCursor++);
+ T item = mBuffer[nextSlotIdx];
+ if (item == null) {
+ try {
+ item = (T) mBuffer.getClass().getComponentType().newInstance();
+ } catch (IllegalAccessException | InstantiationException e) {
+ return null;
+ }
+ mBuffer[nextSlotIdx] = item;
+ }
+ return item;
+ }
+
public T[] toArray() {
// Only generic way to create a T[] from another T[]
T[] out = Arrays.copyOf(mBuffer, size(), (Class<T[]>) mBuffer.getClass());
diff --git a/com/android/internal/util/UserIcons.java b/com/android/internal/util/UserIcons.java
index daf745f..bfe4323 100644
--- a/com/android/internal/util/UserIcons.java
+++ b/com/android/internal/util/UserIcons.java
@@ -61,17 +61,19 @@
* Returns a default user icon for the given user.
*
* Note that for guest users, you should pass in {@code UserHandle.USER_NULL}.
+ *
+ * @param resources resources object to fetch user icon / color.
* @param userId the user id or {@code UserHandle.USER_NULL} for a non-user specific icon
* @param light whether we want a light icon (suitable for a dark background)
*/
- public static Drawable getDefaultUserIcon(int userId, boolean light) {
+ public static Drawable getDefaultUserIcon(Resources resources, int userId, boolean light) {
int colorResId = light ? R.color.user_icon_default_white : R.color.user_icon_default_gray;
if (userId != UserHandle.USER_NULL) {
// Return colored icon instead
colorResId = USER_ICON_COLORS[userId % USER_ICON_COLORS.length];
}
- Drawable icon = Resources.getSystem().getDrawable(R.drawable.ic_account_circle, null).mutate();
- icon.setColorFilter(Resources.getSystem().getColor(colorResId, null), Mode.SRC_IN);
+ Drawable icon = resources.getDrawable(R.drawable.ic_account_circle, null).mutate();
+ icon.setColorFilter(resources.getColor(colorResId, null), Mode.SRC_IN);
icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
return icon;
}
diff --git a/com/android/internal/view/TooltipPopup.java b/com/android/internal/view/TooltipPopup.java
index d38ea2c..24f0b0c 100644
--- a/com/android/internal/view/TooltipPopup.java
+++ b/com/android/internal/view/TooltipPopup.java
@@ -142,7 +142,7 @@
mTmpAnchorPos[1] -= mTmpAppPos[1];
// mTmpAnchorPos is now relative to the main app window.
- outParams.x = mTmpAnchorPos[0] + offsetX - mTmpDisplayFrame.width() / 2;
+ outParams.x = mTmpAnchorPos[0] + offsetX - appView.getWidth() / 2;
final int spec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
mContentView.measure(spec, spec);
@@ -157,6 +157,9 @@
outParams.y = yBelow;
}
} else {
+ // Use mTmpDisplayFrame.height() as the lower boundary instead of appView.getHeight(),
+ // as the latter includes the navigation bar, and tooltips do not look good over
+ // the navigation bar.
if (yBelow + tooltipHeight <= mTmpDisplayFrame.height()) {
outParams.y = yBelow;
} else {
diff --git a/com/android/internal/widget/ExploreByTouchHelper.java b/com/android/internal/widget/ExploreByTouchHelper.java
index 50ad547..759a41a 100644
--- a/com/android/internal/widget/ExploreByTouchHelper.java
+++ b/com/android/internal/widget/ExploreByTouchHelper.java
@@ -186,6 +186,9 @@
}
final AccessibilityEvent event = createEvent(virtualViewId, eventType);
+ if (event == null) {
+ return false;
+ }
return parent.requestSendAccessibilityEvent(mView, event);
}
@@ -240,6 +243,9 @@
if (parent != null) {
final AccessibilityEvent event = createEvent(virtualViewId,
AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+ if (event == null) {
+ return;
+ }
event.setContentChangeTypes(changeTypes);
parent.requestSendAccessibilityEvent(mView, event);
}
@@ -305,6 +311,9 @@
* the specified item.
*/
private AccessibilityEvent createEventForHost(int eventType) {
+ if (!AccessibilityManager.getInstance(mContext).isObservedEventType(eventType)) {
+ return null;
+ }
final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
mView.onInitializeAccessibilityEvent(event);
@@ -325,6 +334,9 @@
* the specified item.
*/
private AccessibilityEvent createEventForChild(int virtualViewId, int eventType) {
+ if (!AccessibilityManager.getInstance(mContext).isObservedEventType(eventType)) {
+ return null;
+ }
final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
event.setEnabled(true);
event.setClassName(DEFAULT_CLASS_NAME);
diff --git a/com/android/internal/widget/Magnifier.java b/com/android/internal/widget/Magnifier.java
deleted file mode 100644
index f6741c3..0000000
--- a/com/android/internal/widget/Magnifier.java
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.internal.widget;
-
-import android.annotation.FloatRange;
-import android.annotation.NonNull;
-import android.annotation.UiThread;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.os.Handler;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.PixelCopy;
-import android.view.View;
-import android.view.ViewRootImpl;
-import android.widget.ImageView;
-import android.widget.PopupWindow;
-
-import com.android.internal.R;
-import com.android.internal.util.Preconditions;
-
-import java.util.Timer;
-import java.util.TimerTask;
-
-/**
- * Android magnifier widget. Can be used by any view which is attached to window.
- */
-public final class Magnifier {
- private static final String LOG_TAG = "magnifier";
- private static final int MAGNIFIER_REFRESH_RATE_MS = 33; // ~30fps
- // The view for which this magnifier is attached.
- private final View mView;
- // The window containing the magnifier.
- private final PopupWindow mWindow;
- // The center coordinates of the window containing the magnifier.
- private final Point mWindowCoords = new Point();
- // The width of the window containing the magnifier.
- private final int mWindowWidth;
- // The height of the window containing the magnifier.
- private final int mWindowHeight;
- // The bitmap used to display the contents of the magnifier.
- private final Bitmap mBitmap;
- // The center coordinates of the content that is to be magnified.
- private final Point mCenterZoomCoords = new Point();
- // The callback of the pixel copy request will be invoked on this Handler when
- // the copy is finished.
- private final Handler mPixelCopyHandler = Handler.getMain();
- // Current magnification scale.
- private final float mZoomScale;
- // Timer used to schedule the copy task.
- private Timer mTimer;
-
- /**
- * Initializes a magnifier.
- *
- * @param view the view for which this magnifier is attached
- */
- @UiThread
- public Magnifier(@NonNull View view) {
- mView = Preconditions.checkNotNull(view);
- final Context context = mView.getContext();
- final float elevation = context.getResources().getDimension(R.dimen.magnifier_elevation);
- final View content = LayoutInflater.from(context).inflate(R.layout.magnifier, null);
- content.findViewById(R.id.magnifier_inner).setClipToOutline(true);
- mWindowWidth = context.getResources().getDimensionPixelSize(R.dimen.magnifier_width);
- mWindowHeight = context.getResources().getDimensionPixelSize(R.dimen.magnifier_height);
- mZoomScale = context.getResources().getFloat(R.dimen.magnifier_zoom_scale);
-
- mWindow = new PopupWindow(context);
- mWindow.setContentView(content);
- mWindow.setWidth(mWindowWidth);
- mWindow.setHeight(mWindowHeight);
- mWindow.setElevation(elevation);
- mWindow.setTouchable(false);
- mWindow.setBackgroundDrawable(null);
-
- final int bitmapWidth = (int) (mWindowWidth / mZoomScale);
- final int bitmapHeight = (int) (mWindowHeight / mZoomScale);
- mBitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
- getImageView().setImageBitmap(mBitmap);
- }
-
- /**
- * Shows the magnifier on the screen.
- *
- * @param xPosInView horizontal coordinate of the center point of the magnifier source relative
- * to the view. The lower end is clamped to 0
- * @param yPosInView vertical coordinate of the center point of the magnifier source
- * relative to the view. The lower end is clamped to 0
- */
- public void show(@FloatRange(from=0) float xPosInView, @FloatRange(from=0) float yPosInView) {
- if (xPosInView < 0) {
- xPosInView = 0;
- }
-
- if (yPosInView < 0) {
- yPosInView = 0;
- }
-
- configureCoordinates(xPosInView, yPosInView);
-
- if (mTimer == null) {
- mTimer = new Timer();
- mTimer.schedule(new TimerTask() {
- @Override
- public void run() {
- performPixelCopy();
- }
- }, 0 /* delay */, MAGNIFIER_REFRESH_RATE_MS);
- }
-
- if (mWindow.isShowing()) {
- mWindow.update(mWindowCoords.x, mWindowCoords.y, mWindow.getWidth(),
- mWindow.getHeight());
- } else {
- mWindow.showAtLocation(mView.getRootView(), Gravity.NO_GRAVITY,
- mWindowCoords.x, mWindowCoords.y);
- }
- }
-
- /**
- * Dismisses the magnifier from the screen.
- */
- public void dismiss() {
- mWindow.dismiss();
-
- if (mTimer != null) {
- mTimer.cancel();
- mTimer.purge();
- mTimer = null;
- }
- }
-
- /**
- * @return the height of the magnifier window.
- */
- public int getHeight() {
- return mWindowHeight;
- }
-
- /**
- * @return the width of the magnifier window.
- */
- public int getWidth() {
- return mWindowWidth;
- }
-
- /**
- * @return the zoom scale of the magnifier.
- */
- public float getZoomScale() {
- return mZoomScale;
- }
-
- private void configureCoordinates(float xPosInView, float yPosInView) {
- final int[] coordinatesOnScreen = new int[2];
- mView.getLocationOnScreen(coordinatesOnScreen);
- final float posXOnScreen = xPosInView + coordinatesOnScreen[0];
- final float posYOnScreen = yPosInView + coordinatesOnScreen[1];
-
- mCenterZoomCoords.x = (int) posXOnScreen;
- mCenterZoomCoords.y = (int) posYOnScreen;
-
- final int verticalMagnifierOffset = mView.getContext().getResources().getDimensionPixelSize(
- R.dimen.magnifier_offset);
- mWindowCoords.x = mCenterZoomCoords.x - mWindowWidth / 2;
- mWindowCoords.y = mCenterZoomCoords.y - mWindowHeight / 2 - verticalMagnifierOffset;
- }
-
- private void performPixelCopy() {
- final int startY = mCenterZoomCoords.y - mBitmap.getHeight() / 2;
- int rawStartX = mCenterZoomCoords.x - mBitmap.getWidth() / 2;
-
- // Clamp startX value to avoid distorting the rendering of the magnifier content.
- if (rawStartX < 0) {
- rawStartX = 0;
- } else if (rawStartX + mBitmap.getWidth() > mView.getWidth()) {
- rawStartX = mView.getWidth() - mBitmap.getWidth();
- }
-
- final int startX = rawStartX;
- final ViewRootImpl viewRootImpl = mView.getViewRootImpl();
-
- if (viewRootImpl != null && viewRootImpl.mSurface != null
- && viewRootImpl.mSurface.isValid()) {
- PixelCopy.request(
- viewRootImpl.mSurface,
- new Rect(startX, startY, startX + mBitmap.getWidth(),
- startY + mBitmap.getHeight()),
- mBitmap,
- result -> getImageView().invalidate(),
- mPixelCopyHandler);
- } else {
- Log.d(LOG_TAG, "Could not perform PixelCopy request");
- }
- }
-
- private ImageView getImageView() {
- return mWindow.getContentView().findViewById(R.id.magnifier_image);
- }
-}
diff --git a/com/android/internal/widget/PointerLocationView.java b/com/android/internal/widget/PointerLocationView.java
index e53162c..5847033 100644
--- a/com/android/internal/widget/PointerLocationView.java
+++ b/com/android/internal/widget/PointerLocationView.java
@@ -32,7 +32,7 @@
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
-import android.view.WindowManagerPolicy.PointerEventListener;
+import android.view.WindowManagerPolicyConstants.PointerEventListener;
import android.view.MotionEvent.PointerCoords;
import java.util.ArrayList;
diff --git a/com/android/internal/widget/RecyclerView.java b/com/android/internal/widget/RecyclerView.java
index 7abc76a..408a4e9 100644
--- a/com/android/internal/widget/RecyclerView.java
+++ b/com/android/internal/widget/RecyclerView.java
@@ -9556,7 +9556,7 @@
if (vScroll == 0 && hScroll == 0) {
return false;
}
- mRecyclerView.scrollBy(hScroll, vScroll);
+ mRecyclerView.smoothScrollBy(hScroll, vScroll);
return true;
}
diff --git a/com/android/keyguard/KeyguardSliceView.java b/com/android/keyguard/KeyguardSliceView.java
index c18f9b6..cb3d59c 100644
--- a/com/android/keyguard/KeyguardSliceView.java
+++ b/com/android/keyguard/KeyguardSliceView.java
@@ -33,6 +33,8 @@
import com.android.systemui.R;
import com.android.systemui.keyguard.KeyguardSliceProvider;
+import java.util.Collections;
+
/**
* View visible under the clock on the lock screen and AoD.
*/
@@ -75,7 +77,8 @@
super.onAttachedToWindow();
// Set initial content
- showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri));
+ showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri,
+ Collections.emptyList()));
// Make sure we always have the most current slice
getContext().getContentResolver().registerContentObserver(mKeyguardSliceUri,
@@ -91,7 +94,7 @@
private void showSlice(Slice slice) {
// Items will be wrapped into an action when they have tap targets.
- SliceItem actionSlice = SliceQuery.find(slice, SliceItem.TYPE_ACTION);
+ SliceItem actionSlice = SliceQuery.find(slice, SliceItem.FORMAT_ACTION);
if (actionSlice != null) {
mSlice = actionSlice.getSlice();
mSliceAction = actionSlice.getAction();
@@ -105,7 +108,7 @@
return;
}
- SliceItem title = SliceQuery.find(mSlice, SliceItem.TYPE_TEXT, Slice.HINT_TITLE, null);
+ SliceItem title = SliceQuery.find(mSlice, SliceItem.FORMAT_TEXT, Slice.HINT_TITLE, null);
if (title == null) {
mTitle.setVisibility(GONE);
} else {
@@ -113,7 +116,7 @@
mTitle.setText(title.getText());
}
- SliceItem text = SliceQuery.find(mSlice, SliceItem.TYPE_TEXT, null, Slice.HINT_TITLE);
+ SliceItem text = SliceQuery.find(mSlice, SliceItem.FORMAT_TEXT, null, Slice.HINT_TITLE);
if (text == null) {
mText.setVisibility(GONE);
} else {
@@ -154,7 +157,8 @@
@Override
public void onChange(boolean selfChange, Uri uri) {
- showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri));
+ showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri,
+ Collections.emptyList()));
}
}
}
diff --git a/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 5a02178..8d55eea 100644
--- a/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -21,7 +21,7 @@
import android.os.SystemClock;
import android.hardware.fingerprint.FingerprintManager;
import android.telephony.TelephonyManager;
-import android.view.WindowManagerPolicy;
+import android.view.WindowManagerPolicyConstants;
import com.android.internal.telephony.IccCardConstants;
@@ -171,9 +171,9 @@
/**
* Called when the device has finished going to sleep.
- * @param why either {@link WindowManagerPolicy#OFF_BECAUSE_OF_ADMIN},
- * {@link WindowManagerPolicy#OFF_BECAUSE_OF_USER}, or
- * {@link WindowManagerPolicy#OFF_BECAUSE_OF_TIMEOUT}.
+ * @param why either {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_ADMIN},
+ * {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_USER}, or
+ * {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_TIMEOUT}.
*
* @deprecated use {@link com.android.systemui.keyguard.WakefulnessLifecycle}.
*/
diff --git a/com/android/keyguard/PasswordTextView.java b/com/android/keyguard/PasswordTextView.java
index 12f75bb..0219db3 100644
--- a/com/android/keyguard/PasswordTextView.java
+++ b/com/android/keyguard/PasswordTextView.java
@@ -307,8 +307,9 @@
void sendAccessibilityEventTypeViewTextChanged(String beforeText, int fromIndex,
int removedCount, int addedCount) {
- if (AccessibilityManager.getInstance(mContext).isEnabled() &&
- (isFocused() || isSelected() && isShown())) {
+ if (AccessibilityManager.getInstance(mContext).isObservedEventType(
+ AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED)
+ && (isFocused() || isSelected() && isShown())) {
AccessibilityEvent event =
AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
event.setFromIndex(fromIndex);
diff --git a/com/android/layoutlib/bridge/Bridge.java b/com/android/layoutlib/bridge/Bridge.java
index 0cfc181..5dca8e7 100644
--- a/com/android/layoutlib/bridge/Bridge.java
+++ b/com/android/layoutlib/bridge/Bridge.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2008 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.
@@ -14,62 +14,659 @@
* limitations under the License.
*/
-package com.android.layoutlib.bridge;import com.android.ide.common.rendering.api.RenderSession;
+package com.android.layoutlib.bridge;
+
+import com.android.ide.common.rendering.api.Capability;
+import com.android.ide.common.rendering.api.DrawableParams;
+import com.android.ide.common.rendering.api.Features;
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.RenderSession;
import com.android.ide.common.rendering.api.Result;
import com.android.ide.common.rendering.api.Result.Status;
import com.android.ide.common.rendering.api.SessionParams;
+import com.android.layoutlib.bridge.android.RenderParamsFlags;
+import com.android.layoutlib.bridge.impl.RenderDrawable;
+import com.android.layoutlib.bridge.impl.RenderSessionImpl;
+import com.android.layoutlib.bridge.util.DynamicIdMap;
+import com.android.ninepatch.NinePatchChunk;
+import com.android.resources.ResourceType;
+import com.android.tools.layoutlib.create.MethodAdapter;
+import com.android.tools.layoutlib.create.OverrideMethod;
+import com.android.util.Pair;
-import java.awt.Graphics2D;
-import java.awt.image.BufferedImage;
+import android.annotation.NonNull;
+import android.content.res.BridgeAssetManager;
+import android.graphics.Bitmap;
+import android.graphics.FontFamily_Delegate;
+import android.graphics.Typeface;
+import android.graphics.Typeface_Delegate;
+import android.icu.util.ULocale;
+import android.os.Looper;
+import android.os.Looper_Accessor;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+
+import java.io.File;
+import java.lang.ref.SoftReference;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.EnumMap;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.WeakHashMap;
+import java.util.concurrent.locks.ReentrantLock;
+
+import libcore.io.MemoryMappedFile_Delegate;
+
+import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN;
/**
- * Legacy Bridge used in the SDK version of layoutlib
+ * Main entry point of the LayoutLib Bridge.
+ * <p/>To use this bridge, simply instantiate an object of type {@link Bridge} and call
+ * {@link #createSession(SessionParams)}
*/
public final class Bridge extends com.android.ide.common.rendering.api.Bridge {
- private static final String SDK_NOT_SUPPORTED = "The SDK layoutlib version is not supported";
- private static final Result NOT_SUPPORTED_RESULT =
- Status.NOT_IMPLEMENTED.createResult(SDK_NOT_SUPPORTED);
- private static BufferedImage sImage;
- private static class BridgeRenderSession extends RenderSession {
+ private static final String ICU_LOCALE_DIRECTION_RTL = "right-to-left";
- @Override
- public synchronized BufferedImage getImage() {
- if (sImage == null) {
- sImage = new BufferedImage(500, 500, BufferedImage.TYPE_INT_ARGB);
- Graphics2D g = sImage.createGraphics();
- g.clearRect(0, 0, 500, 500);
- g.drawString(SDK_NOT_SUPPORTED, 20, 20);
- g.dispose();
- }
+ public static class StaticMethodNotImplementedException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
- return sImage;
- }
-
- @Override
- public Result render(long timeout, boolean forceMeasure) {
- return NOT_SUPPORTED_RESULT;
- }
-
- @Override
- public Result measure(long timeout) {
- return NOT_SUPPORTED_RESULT;
- }
-
- @Override
- public Result getResult() {
- return NOT_SUPPORTED_RESULT;
+ public StaticMethodNotImplementedException(String msg) {
+ super(msg);
}
}
+ /**
+ * Lock to ensure only one rendering/inflating happens at a time.
+ * This is due to some singleton in the Android framework.
+ */
+ private final static ReentrantLock sLock = new ReentrantLock();
- @Override
- public RenderSession createSession(SessionParams params) {
- return new BridgeRenderSession();
- }
+ /**
+ * Maps from id to resource type/name. This is for com.android.internal.R
+ */
+ @SuppressWarnings("deprecation")
+ private final static Map<Integer, Pair<ResourceType, String>> sRMap = new HashMap<>();
+
+ /**
+ * Reverse map compared to sRMap, resource type -> (resource name -> id).
+ * This is for com.android.internal.R.
+ */
+ private final static Map<ResourceType, Map<String, Integer>> sRevRMap = new EnumMap<>(ResourceType.class);
+
+ // framework resources are defined as 0x01XX#### where XX is the resource type (layout,
+ // drawable, etc...). Using FF as the type allows for 255 resource types before we get a
+ // collision which should be fine.
+ private final static int DYNAMIC_ID_SEED_START = 0x01ff0000;
+ private final static DynamicIdMap sDynamicIds = new DynamicIdMap(DYNAMIC_ID_SEED_START);
+
+ private final static Map<Object, Map<String, SoftReference<Bitmap>>> sProjectBitmapCache =
+ new WeakHashMap<>();
+ private final static Map<Object, Map<String, SoftReference<NinePatchChunk>>> sProject9PatchCache =
+ new WeakHashMap<>();
+
+ private final static Map<String, SoftReference<Bitmap>> sFrameworkBitmapCache = new HashMap<>();
+ private final static Map<String, SoftReference<NinePatchChunk>> sFramework9PatchCache =
+ new HashMap<>();
+
+ private static Map<String, Map<String, Integer>> sEnumValueMap;
+ private static Map<String, String> sPlatformProperties;
+
+ /**
+ * A default log than prints to stdout/stderr.
+ */
+ private final static LayoutLog sDefaultLog = new LayoutLog() {
+ @Override
+ public void error(String tag, String message, Object data) {
+ System.err.println(message);
+ }
+
+ @Override
+ public void error(String tag, String message, Throwable throwable, Object data) {
+ System.err.println(message);
+ }
+
+ @Override
+ public void warning(String tag, String message, Object data) {
+ System.out.println(message);
+ }
+ };
+
+ /**
+ * Current log.
+ */
+ private static LayoutLog sCurrentLog = sDefaultLog;
+
+ private static final int LAST_SUPPORTED_FEATURE = Features.THEME_PREVIEW_NAVIGATION_BAR;
@Override
public int getApiLevel() {
- return 0;
+ return com.android.ide.common.rendering.api.Bridge.API_CURRENT;
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ @Deprecated
+ public EnumSet<Capability> getCapabilities() {
+ // The Capability class is deprecated and frozen. All Capabilities enumerated there are
+ // supported by this version of LayoutLibrary. So, it's safe to use EnumSet.allOf()
+ return EnumSet.allOf(Capability.class);
+ }
+
+ @Override
+ public boolean supports(int feature) {
+ return feature <= LAST_SUPPORTED_FEATURE;
+ }
+
+ @Override
+ public boolean init(Map<String,String> platformProperties,
+ File fontLocation,
+ Map<String, Map<String, Integer>> enumValueMap,
+ LayoutLog log) {
+ sPlatformProperties = platformProperties;
+ sEnumValueMap = enumValueMap;
+
+ BridgeAssetManager.initSystem();
+
+ // When DEBUG_LAYOUT is set and is not 0 or false, setup a default listener
+ // on static (native) methods which prints the signature on the console and
+ // throws an exception.
+ // This is useful when testing the rendering in ADT to identify static native
+ // methods that are ignored -- layoutlib_create makes them returns 0/false/null
+ // which is generally OK yet might be a problem, so this is how you'd find out.
+ //
+ // Currently layoutlib_create only overrides static native method.
+ // Static non-natives are not overridden and thus do not get here.
+ final String debug = System.getenv("DEBUG_LAYOUT");
+ if (debug != null && !debug.equals("0") && !debug.equals("false")) {
+
+ OverrideMethod.setDefaultListener(new MethodAdapter() {
+ @Override
+ public void onInvokeV(String signature, boolean isNative, Object caller) {
+ sDefaultLog.error(null, "Missing Stub: " + signature +
+ (isNative ? " (native)" : ""), null /*data*/);
+
+ if (debug.equalsIgnoreCase("throw")) {
+ // Throwing this exception doesn't seem that useful. It breaks
+ // the layout editor yet doesn't display anything meaningful to the
+ // user. Having the error in the console is just as useful. We'll
+ // throw it only if the environment variable is "throw" or "THROW".
+ throw new StaticMethodNotImplementedException(signature);
+ }
+ }
+ });
+ }
+
+ // load the fonts.
+ FontFamily_Delegate.setFontLocation(fontLocation.getAbsolutePath());
+ MemoryMappedFile_Delegate.setDataDir(fontLocation.getAbsoluteFile().getParentFile());
+
+ // now parse com.android.internal.R (and only this one as android.R is a subset of
+ // the internal version), and put the content in the maps.
+ try {
+ Class<?> r = com.android.internal.R.class;
+ // Parse the styleable class first, since it may contribute to attr values.
+ parseStyleable();
+
+ for (Class<?> inner : r.getDeclaredClasses()) {
+ if (inner == com.android.internal.R.styleable.class) {
+ // Already handled the styleable case. Not skipping attr, as there may be attrs
+ // that are not referenced from styleables.
+ continue;
+ }
+ String resTypeName = inner.getSimpleName();
+ ResourceType resType = ResourceType.getEnum(resTypeName);
+ if (resType != null) {
+ Map<String, Integer> fullMap = null;
+ switch (resType) {
+ case ATTR:
+ fullMap = sRevRMap.get(ResourceType.ATTR);
+ break;
+ case STRING:
+ case STYLE:
+ // Slightly less than thousand entries in each.
+ fullMap = new HashMap<>(1280);
+ // no break.
+ default:
+ if (fullMap == null) {
+ fullMap = new HashMap<>();
+ }
+ sRevRMap.put(resType, fullMap);
+ }
+
+ for (Field f : inner.getDeclaredFields()) {
+ // only process static final fields. Since the final attribute may have
+ // been altered by layoutlib_create, we only check static
+ if (!isValidRField(f)) {
+ continue;
+ }
+ Class<?> type = f.getType();
+ if (!type.isArray()) {
+ Integer value = (Integer) f.get(null);
+ //noinspection deprecation
+ sRMap.put(value, Pair.of(resType, f.getName()));
+ fullMap.put(f.getName(), value);
+ }
+ }
+ }
+ }
+ } catch (Exception throwable) {
+ if (log != null) {
+ log.error(LayoutLog.TAG_BROKEN,
+ "Failed to load com.android.internal.R from the layout library jar",
+ throwable, null);
+ }
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Tests if the field is pubic, static and one of int or int[].
+ */
+ private static boolean isValidRField(Field field) {
+ int modifiers = field.getModifiers();
+ boolean isAcceptable = Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers);
+ Class<?> type = field.getType();
+ return isAcceptable && type == int.class ||
+ (type.isArray() && type.getComponentType() == int.class);
+
+ }
+
+ private static void parseStyleable() throws Exception {
+ // R.attr doesn't contain all the needed values. There are too many resources in the
+ // framework for all to be in the R class. Only the ones specified manually in
+ // res/values/symbols.xml are put in R class. Since, we need to create a map of all attr
+ // values, we try and find them from the styleables.
+
+ // There were 1500 elements in this map at M timeframe.
+ Map<String, Integer> revRAttrMap = new HashMap<>(2048);
+ sRevRMap.put(ResourceType.ATTR, revRAttrMap);
+ // There were 2000 elements in this map at M timeframe.
+ Map<String, Integer> revRStyleableMap = new HashMap<>(3072);
+ sRevRMap.put(ResourceType.STYLEABLE, revRStyleableMap);
+ Class<?> c = com.android.internal.R.styleable.class;
+ Field[] fields = c.getDeclaredFields();
+ // Sort the fields to bring all arrays to the beginning, so that indices into the array are
+ // able to refer back to the arrays (i.e. no forward references).
+ Arrays.sort(fields, (o1, o2) -> {
+ if (o1 == o2) {
+ return 0;
+ }
+ Class<?> t1 = o1.getType();
+ Class<?> t2 = o2.getType();
+ if (t1.isArray() && !t2.isArray()) {
+ return -1;
+ } else if (t2.isArray() && !t1.isArray()) {
+ return 1;
+ }
+ return o1.getName().compareTo(o2.getName());
+ });
+ Map<String, int[]> styleables = new HashMap<>();
+ for (Field field : fields) {
+ if (!isValidRField(field)) {
+ // Only consider public static fields that are int or int[].
+ // Don't check the final flag as it may have been modified by layoutlib_create.
+ continue;
+ }
+ String name = field.getName();
+ if (field.getType().isArray()) {
+ int[] styleableValue = (int[]) field.get(null);
+ styleables.put(name, styleableValue);
+ continue;
+ }
+ // Not an array.
+ String arrayName = name;
+ int[] arrayValue = null;
+ int index;
+ while ((index = arrayName.lastIndexOf('_')) >= 0) {
+ // Find the name of the corresponding styleable.
+ // Search in reverse order so that attrs like LinearLayout_Layout_layout_gravity
+ // are mapped to LinearLayout_Layout and not to LinearLayout.
+ arrayName = arrayName.substring(0, index);
+ arrayValue = styleables.get(arrayName);
+ if (arrayValue != null) {
+ break;
+ }
+ }
+ index = (Integer) field.get(null);
+ if (arrayValue != null) {
+ String attrName = name.substring(arrayName.length() + 1);
+ int attrValue = arrayValue[index];
+ //noinspection deprecation
+ sRMap.put(attrValue, Pair.of(ResourceType.ATTR, attrName));
+ revRAttrMap.put(attrName, attrValue);
+ }
+ //noinspection deprecation
+ sRMap.put(index, Pair.of(ResourceType.STYLEABLE, name));
+ revRStyleableMap.put(name, index);
+ }
+ }
+
+ @Override
+ public boolean dispose() {
+ BridgeAssetManager.clearSystem();
+
+ // dispose of the default typeface.
+ Typeface_Delegate.resetDefaults();
+ Typeface.sDynamicTypefaceCache.evictAll();
+ sProject9PatchCache.clear();
+ sProjectBitmapCache.clear();
+
+ return true;
+ }
+
+ /**
+ * Starts a layout session by inflating and rendering it. The method returns a
+ * {@link RenderSession} on which further actions can be taken.
+ * <p/>
+ * If {@link SessionParams} includes the {@link RenderParamsFlags#FLAG_DO_NOT_RENDER_ON_CREATE},
+ * this method will only inflate the layout but will NOT render it.
+ * @param params the {@link SessionParams} object with all the information necessary to create
+ * the scene.
+ * @return a new {@link RenderSession} object that contains the result of the layout.
+ * @since 5
+ */
+ @Override
+ public RenderSession createSession(SessionParams params) {
+ try {
+ Result lastResult;
+ RenderSessionImpl scene = new RenderSessionImpl(params);
+ try {
+ prepareThread();
+ lastResult = scene.init(params.getTimeout());
+ if (lastResult.isSuccess()) {
+ lastResult = scene.inflate();
+
+ boolean doNotRenderOnCreate = Boolean.TRUE.equals(
+ params.getFlag(RenderParamsFlags.FLAG_DO_NOT_RENDER_ON_CREATE));
+ if (lastResult.isSuccess() && !doNotRenderOnCreate) {
+ lastResult = scene.render(true /*freshRender*/);
+ }
+ }
+ } finally {
+ scene.release();
+ cleanupThread();
+ }
+
+ return new BridgeRenderSession(scene, lastResult);
+ } catch (Throwable t) {
+ // get the real cause of the exception.
+ Throwable t2 = t;
+ while (t2.getCause() != null) {
+ t2 = t2.getCause();
+ }
+ return new BridgeRenderSession(null,
+ ERROR_UNKNOWN.createResult(t2.getMessage(), t));
+ }
+ }
+
+ @Override
+ public Result renderDrawable(DrawableParams params) {
+ try {
+ Result lastResult;
+ RenderDrawable action = new RenderDrawable(params);
+ try {
+ prepareThread();
+ lastResult = action.init(params.getTimeout());
+ if (lastResult.isSuccess()) {
+ lastResult = action.render();
+ }
+ } finally {
+ action.release();
+ cleanupThread();
+ }
+
+ return lastResult;
+ } catch (Throwable t) {
+ // get the real cause of the exception.
+ Throwable t2 = t;
+ while (t2.getCause() != null) {
+ t2 = t.getCause();
+ }
+ return ERROR_UNKNOWN.createResult(t2.getMessage(), t);
+ }
+ }
+
+ @Override
+ public void clearCaches(Object projectKey) {
+ if (projectKey != null) {
+ sProjectBitmapCache.remove(projectKey);
+ sProject9PatchCache.remove(projectKey);
+ }
+ }
+
+ @Override
+ public Result getViewParent(Object viewObject) {
+ if (viewObject instanceof View) {
+ return Status.SUCCESS.createResult(((View)viewObject).getParent());
+ }
+
+ throw new IllegalArgumentException("viewObject is not a View");
+ }
+
+ @Override
+ public Result getViewIndex(Object viewObject) {
+ if (viewObject instanceof View) {
+ View view = (View) viewObject;
+ ViewParent parentView = view.getParent();
+
+ if (parentView instanceof ViewGroup) {
+ Status.SUCCESS.createResult(((ViewGroup) parentView).indexOfChild(view));
+ }
+
+ return Status.SUCCESS.createResult();
+ }
+
+ throw new IllegalArgumentException("viewObject is not a View");
+ }
+
+ @Override
+ public boolean isRtl(String locale) {
+ return isLocaleRtl(locale);
+ }
+
+ public static boolean isLocaleRtl(String locale) {
+ if (locale == null) {
+ locale = "";
+ }
+ ULocale uLocale = new ULocale(locale);
+ return uLocale.getCharacterOrientation().equals(ICU_LOCALE_DIRECTION_RTL);
+ }
+
+ /**
+ * Returns the lock for the bridge
+ */
+ public static ReentrantLock getLock() {
+ return sLock;
+ }
+
+ /**
+ * Prepares the current thread for rendering.
+ *
+ * Note that while this can be called several time, the first call to {@link #cleanupThread()}
+ * will do the clean-up, and make the thread unable to do further scene actions.
+ */
+ public synchronized static void prepareThread() {
+ // we need to make sure the Looper has been initialized for this thread.
+ // this is required for View that creates Handler objects.
+ if (Looper.myLooper() == null) {
+ Looper.prepareMainLooper();
+ }
+ }
+
+ /**
+ * Cleans up thread-specific data. After this, the thread cannot be used for scene actions.
+ * <p>
+ * Note that it doesn't matter how many times {@link #prepareThread()} was called, a single
+ * call to this will prevent the thread from doing further scene actions
+ */
+ public synchronized static void cleanupThread() {
+ // clean up the looper
+ Looper_Accessor.cleanupThread();
+ }
+
+ public static LayoutLog getLog() {
+ return sCurrentLog;
+ }
+
+ public static void setLog(LayoutLog log) {
+ // check only the thread currently owning the lock can do this.
+ if (!sLock.isHeldByCurrentThread()) {
+ throw new IllegalStateException("scene must be acquired first. see #acquire(long)");
+ }
+
+ if (log != null) {
+ sCurrentLog = log;
+ } else {
+ sCurrentLog = sDefaultLog;
+ }
+ }
+
+ /**
+ * Returns details of a framework resource from its integer value.
+ * @param value the integer value
+ * @return a Pair containing the resource type and name, or null if the id
+ * does not match any resource.
+ */
+ @SuppressWarnings("deprecation")
+ public static Pair<ResourceType, String> resolveResourceId(int value) {
+ Pair<ResourceType, String> pair = sRMap.get(value);
+ if (pair == null) {
+ pair = sDynamicIds.resolveId(value);
+ }
+ return pair;
+ }
+
+ /**
+ * Returns the integer id of a framework resource, from a given resource type and resource name.
+ * <p/>
+ * If no resource is found, it creates a dynamic id for the resource.
+ *
+ * @param type the type of the resource
+ * @param name the name of the resource.
+ *
+ * @return an {@link Integer} containing the resource id.
+ */
+ @NonNull
+ public static Integer getResourceId(ResourceType type, String name) {
+ Map<String, Integer> map = sRevRMap.get(type);
+ Integer value = null;
+ if (map != null) {
+ value = map.get(name);
+ }
+
+ return value == null ? sDynamicIds.getId(type, name) : value;
+
+ }
+
+ /**
+ * Returns the list of possible enums for a given attribute name.
+ */
+ public static Map<String, Integer> getEnumValues(String attributeName) {
+ if (sEnumValueMap != null) {
+ return sEnumValueMap.get(attributeName);
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the platform build properties.
+ */
+ public static Map<String, String> getPlatformProperties() {
+ return sPlatformProperties;
+ }
+
+ /**
+ * Returns the bitmap for a specific path, from a specific project cache, or from the
+ * framework cache.
+ * @param value the path of the bitmap
+ * @param projectKey the key of the project, or null to query the framework cache.
+ * @return the cached Bitmap or null if not found.
+ */
+ public static Bitmap getCachedBitmap(String value, Object projectKey) {
+ if (projectKey != null) {
+ Map<String, SoftReference<Bitmap>> map = sProjectBitmapCache.get(projectKey);
+ if (map != null) {
+ SoftReference<Bitmap> ref = map.get(value);
+ if (ref != null) {
+ return ref.get();
+ }
+ }
+ } else {
+ SoftReference<Bitmap> ref = sFrameworkBitmapCache.get(value);
+ if (ref != null) {
+ return ref.get();
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Sets a bitmap in a project cache or in the framework cache.
+ * @param value the path of the bitmap
+ * @param bmp the Bitmap object
+ * @param projectKey the key of the project, or null to put the bitmap in the framework cache.
+ */
+ public static void setCachedBitmap(String value, Bitmap bmp, Object projectKey) {
+ if (projectKey != null) {
+ Map<String, SoftReference<Bitmap>> map =
+ sProjectBitmapCache.computeIfAbsent(projectKey, k -> new HashMap<>());
+
+ map.put(value, new SoftReference<>(bmp));
+ } else {
+ sFrameworkBitmapCache.put(value, new SoftReference<>(bmp));
+ }
+ }
+
+ /**
+ * Returns the 9 patch chunk for a specific path, from a specific project cache, or from the
+ * framework cache.
+ * @param value the path of the 9 patch
+ * @param projectKey the key of the project, or null to query the framework cache.
+ * @return the cached 9 patch or null if not found.
+ */
+ public static NinePatchChunk getCached9Patch(String value, Object projectKey) {
+ if (projectKey != null) {
+ Map<String, SoftReference<NinePatchChunk>> map = sProject9PatchCache.get(projectKey);
+
+ if (map != null) {
+ SoftReference<NinePatchChunk> ref = map.get(value);
+ if (ref != null) {
+ return ref.get();
+ }
+ }
+ } else {
+ SoftReference<NinePatchChunk> ref = sFramework9PatchCache.get(value);
+ if (ref != null) {
+ return ref.get();
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Sets a 9 patch chunk in a project cache or in the framework cache.
+ * @param value the path of the 9 patch
+ * @param ninePatch the 9 patch object
+ * @param projectKey the key of the project, or null to put the bitmap in the framework cache.
+ */
+ public static void setCached9Patch(String value, NinePatchChunk ninePatch, Object projectKey) {
+ if (projectKey != null) {
+ Map<String, SoftReference<NinePatchChunk>> map =
+ sProject9PatchCache.computeIfAbsent(projectKey, k -> new HashMap<>());
+
+ map.put(value, new SoftReference<>(ninePatch));
+ } else {
+ sFramework9PatchCache.put(value, new SoftReference<>(ninePatch));
+ }
}
}
diff --git a/com/android/layoutlib/bridge/android/BridgePackageManager.java b/com/android/layoutlib/bridge/android/BridgePackageManager.java
index 98937ef..37dc166 100644
--- a/com/android/layoutlib/bridge/android/BridgePackageManager.java
+++ b/com/android/layoutlib/bridge/android/BridgePackageManager.java
@@ -26,12 +26,11 @@
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ChangedPackages;
-import android.content.pm.InstantAppInfo;
import android.content.pm.FeatureInfo;
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageDeleteObserver;
-import android.content.pm.IPackageInstallObserver;
import android.content.pm.IPackageStatsObserver;
+import android.content.pm.InstantAppInfo;
import android.content.pm.InstrumentationInfo;
import android.content.pm.IntentFilterVerificationInfo;
import android.content.pm.KeySet;
@@ -56,6 +55,7 @@
import android.os.Handler;
import android.os.UserHandle;
import android.os.storage.VolumeInfo;
+
import java.util.List;
/**
@@ -611,11 +611,6 @@
}
@Override
- public void installPackage(Uri packageURI, IPackageInstallObserver observer, int flags,
- String installerPackageName) {
- }
-
- @Override
public void installPackage(Uri packageURI, PackageInstallObserver observer, int flags,
String installerPackageName) {
}
diff --git a/com/android/providers/settings/SettingsProtoDumpUtil.java b/com/android/providers/settings/SettingsProtoDumpUtil.java
index 41b205b..52b4f4d 100644
--- a/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -801,6 +801,9 @@
Settings.Global.BLUETOOTH_PAN_PRIORITY_PREFIX,
GlobalSettingsProto.BLUETOOTH_PAN_PRIORITY_PREFIX);
dumpSetting(s, p,
+ Settings.Global.BLUETOOTH_HEARING_AID_PRIORITY_PREFIX,
+ GlobalSettingsProto.BLUETOOTH_HEARING_AID_PRIORITY_PREFIX);
+ dumpSetting(s, p,
Settings.Global.ACTIVITY_MANAGER_CONSTANTS,
GlobalSettingsProto.ACTIVITY_MANAGER_CONSTANTS);
dumpSetting(s, p,
@@ -869,6 +872,15 @@
dumpSetting(s, p,
Settings.Global.WAIT_FOR_DEBUGGER,
GlobalSettingsProto.WAIT_FOR_DEBUGGER);
+ dumpSetting(s, p,
+ Settings.Global.ENABLE_GPU_DEBUG_LAYERS,
+ GlobalSettingsProto.ENABLE_GPU_DEBUG_LAYERS);
+ dumpSetting(s, p,
+ Settings.Global.GPU_DEBUG_APP,
+ GlobalSettingsProto.GPU_DEBUG_APP);
+ dumpSetting(s, p,
+ Settings.Global.GPU_DEBUG_LAYERS,
+ GlobalSettingsProto.GPU_DEBUG_LAYERS);
// Settings.Global.SHOW_PROCESSES intentionally excluded since it's deprecated.
dumpSetting(s, p,
Settings.Global.LOW_POWER_MODE,
diff --git a/com/android/providers/settings/SettingsProvider.java b/com/android/providers/settings/SettingsProvider.java
index 258c96c..7fb6ede 100644
--- a/com/android/providers/settings/SettingsProvider.java
+++ b/com/android/providers/settings/SettingsProvider.java
@@ -61,6 +61,7 @@
import android.os.UserManagerInternal;
import android.provider.Settings;
import android.provider.Settings.Global;
+import android.provider.Settings.Secure;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -343,7 +344,8 @@
}
case Settings.CALL_METHOD_GET_SECURE: {
- Setting setting = getSecureSetting(name, requestingUserId);
+ Setting setting = getSecureSetting(name, requestingUserId,
+ /*enableOverride=*/ true);
return packageValueForCallResult(setting, isTrackingGeneration(args));
}
@@ -1073,6 +1075,10 @@
}
private Setting getSecureSetting(String name, int requestingUserId) {
+ return getSecureSetting(name, requestingUserId, /*enableOverride=*/ false);
+ }
+
+ private Setting getSecureSetting(String name, int requestingUserId, boolean enableOverride) {
if (DEBUG) {
Slog.v(LOG_TAG, "getSecureSetting(" + name + ", " + requestingUserId + ")");
}
@@ -1102,6 +1108,14 @@
return getSsaidSettingLocked(callingPkg, owningUserId);
}
}
+ if (enableOverride) {
+ if (Secure.LOCATION_PROVIDERS_ALLOWED.equals(name)) {
+ final Setting overridden = getLocationProvidersAllowedSetting(owningUserId);
+ if (overridden != null) {
+ return overridden;
+ }
+ }
+ }
// Not the SSAID; do a straight lookup
synchronized (mLock) {
@@ -1190,6 +1204,35 @@
return null;
}
+ private Setting getLocationProvidersAllowedSetting(int owningUserId) {
+ synchronized (mLock) {
+ final Setting setting = getGlobalSetting(
+ Global.LOCATION_GLOBAL_KILL_SWITCH);
+ if (!"1".equals(setting.getValue())) {
+ return null;
+ }
+ // Global kill-switch is enabled. Return an empty value.
+ final SettingsState settingsState = mSettingsRegistry.getSettingsLocked(
+ SETTINGS_TYPE_SECURE, owningUserId);
+ return settingsState.new Setting(
+ Secure.LOCATION_PROVIDERS_ALLOWED,
+ "", // value
+ "", // tag
+ "", // default value
+ "", // package name
+ false, // from system
+ "0" // id
+ ) {
+ @Override
+ public boolean update(String value, boolean setDefault, String packageName,
+ String tag, boolean forceNonSystemPackage) {
+ Slog.wtf(LOG_TAG, "update shoudln't be called on this instance.");
+ return false;
+ }
+ };
+ }
+ }
+
private boolean insertSecureSetting(String name, String value, String tag,
boolean makeDefault, int requestingUserId, boolean forceNotify) {
if (DEBUG) {
@@ -2780,6 +2823,12 @@
}
mHandler.obtainMessage(MyHandler.MSG_NOTIFY_DATA_CHANGED).sendToTarget();
+
+ // When the global kill switch is updated, send the change notification for
+ // the location setting.
+ if (isGlobalSettingsKey(key) && Global.LOCATION_GLOBAL_KILL_SWITCH.equals(name)) {
+ notifyLocationChangeForRunningUsers();
+ }
}
private void maybeNotifyProfiles(int type, int userId, Uri uri, String name,
@@ -2799,6 +2848,24 @@
}
}
+ private void notifyLocationChangeForRunningUsers() {
+ final List<UserInfo> users = mUserManager.getUsers(/*excludeDying=*/ true);
+
+ for (int i = 0; i < users.size(); i++) {
+ final int userId = users.get(i).id;
+
+ if (!mUserManager.isUserRunning(UserHandle.of(userId))) {
+ continue;
+ }
+
+ final int key = makeKey(SETTINGS_TYPE_GLOBAL, userId);
+ final Uri uri = getNotificationUriFor(key, Secure.LOCATION_PROVIDERS_ALLOWED);
+
+ mHandler.obtainMessage(MyHandler.MSG_NOTIFY_URI_CHANGED,
+ userId, 0, uri).sendToTarget();
+ }
+ }
+
private boolean isGlobalSettingsKey(int key) {
return getTypeFromKey(key) == SETTINGS_TYPE_GLOBAL;
}
@@ -2885,7 +2952,7 @@
} catch (SecurityException e) {
Slog.w(LOG_TAG, "Failed to notify for " + userId + ": " + uri, e);
}
- if (DEBUG) {
+ if (DEBUG || true) {
Slog.v(LOG_TAG, "Notifying for " + userId + ": " + uri);
}
} break;
diff --git a/com/android/server/AlarmManagerService.java b/com/android/server/AlarmManagerService.java
index 3904fc9..ca15249 100644
--- a/com/android/server/AlarmManagerService.java
+++ b/com/android/server/AlarmManagerService.java
@@ -46,16 +46,15 @@
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.os.Trace;
import android.os.UserHandle;
import android.os.WorkSource;
import android.provider.Settings;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.KeyValueListParser;
import android.util.Log;
import android.util.Slog;
@@ -81,6 +80,7 @@
import java.util.Random;
import java.util.TimeZone;
import java.util.TreeSet;
+import java.util.function.Predicate;
import static android.app.AlarmManager.RTC_WAKEUP;
import static android.app.AlarmManager.RTC;
@@ -88,11 +88,17 @@
import static android.app.AlarmManager.ELAPSED_REALTIME;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.app.IAppOpsCallback;
-import com.android.internal.app.IAppOpsService;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.LocalLog;
+import com.android.server.ForceAppStandbyTracker.Listener;
+/**
+ * Alarm manager implementaion.
+ *
+ * Unit test:
+ atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/AlarmManagerServiceTest.java
+ */
class AlarmManagerService extends SystemService {
private static final int RTC_WAKEUP_MASK = 1 << RTC_WAKEUP;
private static final int RTC_MASK = 1 << RTC;
@@ -131,13 +137,10 @@
final LocalLog mLog = new LocalLog(TAG);
AppOpsManager mAppOps;
- IAppOpsService mAppOpsService;
DeviceIdleController.LocalService mLocalDeviceIdleController;
final Object mLock = new Object();
- ArraySet<String> mForcedAppStandbyPackages = new ArraySet<>();
- SparseBooleanArray mForegroundUids = new SparseBooleanArray();
// List of alarms per uid deferred due to user applied background restrictions on the source app
SparseArray<ArrayList<Alarm>> mPendingBackgroundAlarms = new SparseArray<>();
long mNativeData;
@@ -184,12 +187,6 @@
int mSystemUiUid;
/**
- * The current set of user whitelisted apps for device idle mode, meaning these are allowed
- * to freely schedule alarms.
- */
- int[] mDeviceIdleUserWhitelist = new int[0];
-
- /**
* For each uid, this is the last time we dispatched an "allow while idle" alarm,
* used to determine the earliest we can dispatch the next such alarm. Times are in the
* 'elapsed' timebase.
@@ -223,6 +220,8 @@
private final SparseArray<AlarmManager.AlarmClockInfo> mHandlerSparseAlarmClockArray =
new SparseArray<>();
+ private final ForceAppStandbyTracker mForceAppStandbyTracker;
+
/**
* All times are in milliseconds. These constants are kept synchronized with the system
* global Settings. Any access to this class or its fields should be done while
@@ -757,6 +756,9 @@
public AlarmManagerService(Context context) {
super(context);
mConstants = new Constants(mHandler);
+
+ mForceAppStandbyTracker = ForceAppStandbyTracker.getInstance(context);
+ mForceAppStandbyTracker.addListener(mForceAppStandbyListener);
}
static long convertToElapsed(long when, int type) {
@@ -894,17 +896,48 @@
deliverPendingBackgroundAlarmsLocked(alarmsToDeliver, SystemClock.elapsedRealtime());
}
- void sendPendingBackgroundAlarmsForAppIdLocked(int appId) {
+ /**
+ * Check all alarms in {@link #mPendingBackgroundAlarms} and send the ones that are not
+ * restricted.
+ *
+ * This is only called when the global "force all apps-standby" flag changes or when the
+ * power save whitelist changes, so it's okay to be slow.
+ */
+ void sendAllUnrestrictedPendingBackgroundAlarmsLocked() {
final ArrayList<Alarm> alarmsToDeliver = new ArrayList<>();
- for (int i = mPendingBackgroundAlarms.size() - 1; i >= 0; i--) {
- final int uid = mPendingBackgroundAlarms.keyAt(i);
- final ArrayList<Alarm> alarmsForUid = mPendingBackgroundAlarms.valueAt(i);
- if (UserHandle.getAppId(uid) == appId) {
- alarmsToDeliver.addAll(alarmsForUid);
- mPendingBackgroundAlarms.removeAt(i);
+
+ findAllUnrestrictedPendingBackgroundAlarmsLockedInner(
+ mPendingBackgroundAlarms, alarmsToDeliver, this::isBackgroundRestricted);
+
+ if (alarmsToDeliver.size() > 0) {
+ deliverPendingBackgroundAlarmsLocked(alarmsToDeliver, SystemClock.elapsedRealtime());
+ }
+ }
+
+ @VisibleForTesting
+ static void findAllUnrestrictedPendingBackgroundAlarmsLockedInner(
+ SparseArray<ArrayList<Alarm>> pendingAlarms, ArrayList<Alarm> unrestrictedAlarms,
+ Predicate<Alarm> isBackgroundRestricted) {
+
+ for (int uidIndex = pendingAlarms.size() - 1; uidIndex >= 0; uidIndex--) {
+ final int uid = pendingAlarms.keyAt(uidIndex);
+ final ArrayList<Alarm> alarmsForUid = pendingAlarms.valueAt(uidIndex);
+
+ for (int alarmIndex = alarmsForUid.size() - 1; alarmIndex >= 0; alarmIndex--) {
+ final Alarm alarm = alarmsForUid.get(alarmIndex);
+
+ if (isBackgroundRestricted.test(alarm)) {
+ continue;
+ }
+
+ unrestrictedAlarms.add(alarm);
+ alarmsForUid.remove(alarmIndex);
+ }
+
+ if (alarmsForUid.size() == 0) {
+ pendingAlarms.removeAt(uidIndex);
}
}
- deliverPendingBackgroundAlarmsLocked(alarmsToDeliver, SystemClock.elapsedRealtime());
}
private void deliverPendingBackgroundAlarmsLocked(ArrayList<Alarm> alarms, long nowELAPSED) {
@@ -1234,10 +1267,8 @@
} catch (RemoteException e) {
// ignored; both services live in system_server
}
- mAppOpsService = IAppOpsService.Stub.asInterface(
- ServiceManager.getService(Context.APP_OPS_SERVICE));
publishBinderService(Context.ALARM_SERVICE, mService);
- publishLocalService(LocalService.class, new LocalService());
+ mForceAppStandbyTracker.start();
}
@Override
@@ -1247,13 +1278,6 @@
mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
mLocalDeviceIdleController
= LocalServices.getService(DeviceIdleController.LocalService.class);
- try {
- mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, null,
- new AppOpsWatcher());
- } catch (RemoteException rexc) {
- // Shouldn't happen as they are in the same process.
- Slog.e(TAG, "AppOps service not reachable", rexc);
- }
}
}
@@ -1582,8 +1606,7 @@
// timing restrictions.
} else if (workSource == null && (callingUid < Process.FIRST_APPLICATION_UID
|| callingUid == mSystemUiUid
- || Arrays.binarySearch(mDeviceIdleUserWhitelist,
- UserHandle.getAppId(callingUid)) >= 0)) {
+ || mForceAppStandbyTracker.isUidPowerSaveWhitelisted(callingUid))) {
flags |= AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
flags &= ~AlarmManager.FLAG_ALLOW_WHILE_IDLE;
}
@@ -1660,24 +1683,14 @@
}
};
- public final class LocalService {
- public void setDeviceIdleUserWhitelist(int[] appids) {
- setDeviceIdleUserWhitelistImpl(appids);
- }
- }
-
void dumpImpl(PrintWriter pw) {
synchronized (mLock) {
pw.println("Current Alarm Manager state:");
mConstants.dump(pw);
pw.println();
- pw.print(" Foreground uids: [");
- for (int i = 0; i < mForegroundUids.size(); i++) {
- if (mForegroundUids.valueAt(i)) pw.print(mForegroundUids.keyAt(i) + " ");
- }
- pw.println("]");
- pw.println(" Forced app standby packages: " + mForcedAppStandbyPackages);
+ mForceAppStandbyTracker.dump(pw, " ");
+
final long nowRTC = System.currentTimeMillis();
final long nowELAPSED = SystemClock.elapsedRealtime();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@@ -1717,7 +1730,6 @@
pw.print(" set at "); TimeUtils.formatDuration(mLastWakeupSet, nowELAPSED, pw);
pw.println();
pw.print(" Num time change events: "); pw.println(mNumTimeChanged);
- pw.println(" mDeviceIdleUserWhitelist=" + Arrays.toString(mDeviceIdleUserWhitelist));
pw.println();
pw.println(" Next alarm clock information: ");
@@ -1990,15 +2002,8 @@
mConstants.dumpProto(proto, AlarmManagerServiceProto.SETTINGS);
- final int foregroundUidsSize = mForegroundUids.size();
- for (int i = 0; i < foregroundUidsSize; i++) {
- if (mForegroundUids.valueAt(i)) {
- proto.write(AlarmManagerServiceProto.FOREGROUND_UIDS, mForegroundUids.keyAt(i));
- }
- }
- for (String pkg : mForcedAppStandbyPackages) {
- proto.write(AlarmManagerServiceProto.FORCED_APP_STANDBY_PACKAGES, pkg);
- }
+ mForceAppStandbyTracker.dumpProto(proto,
+ AlarmManagerServiceProto.FORCE_APP_STANDBY_TRACKER);
proto.write(AlarmManagerServiceProto.IS_INTERACTIVE, mInteractive);
if (!mInteractive) {
@@ -2022,9 +2027,6 @@
proto.write(AlarmManagerServiceProto.TIME_SINCE_LAST_WAKEUP_SET_MS,
nowElapsed - mLastWakeupSet);
proto.write(AlarmManagerServiceProto.TIME_CHANGE_EVENT_COUNT, mNumTimeChanged);
- for (int i : mDeviceIdleUserWhitelist) {
- proto.write(AlarmManagerServiceProto.DEVICE_IDLE_USER_WHITELIST_APP_IDS, i);
- }
final TreeSet<Integer> users = new TreeSet<>();
final int nextAlarmClockForUserSize = mNextAlarmClockForUser.size();
@@ -2266,28 +2268,6 @@
}
}
- void setDeviceIdleUserWhitelistImpl(int[] appids) {
- synchronized (mLock) {
- // appids are sorted, just send pending alarms for any new appids added to the whitelist
- int i = 0, j = 0;
- while (i < appids.length) {
- while (j < mDeviceIdleUserWhitelist.length
- && mDeviceIdleUserWhitelist[j] < appids[i]) {
- j++;
- }
- if (j < mDeviceIdleUserWhitelist.length
- && appids[i] != mDeviceIdleUserWhitelist[j]) {
- if (DEBUG_BG_LIMIT) {
- Slog.d(TAG, "Sending blocked alarms for whitelisted appid " + appids[j]);
- }
- sendPendingBackgroundAlarmsForAppIdLocked(appids[j]);
- }
- i++;
- }
- mDeviceIdleUserWhitelist = appids;
- }
- }
-
AlarmManager.AlarmClockInfo getNextAlarmClockImpl(int userId) {
synchronized (mLock) {
return mNextAlarmClockForUser.get(userId);
@@ -2710,9 +2690,7 @@
final String sourcePackage =
(alarm.operation != null) ? alarm.operation.getCreatorPackage() : alarm.packageName;
final int sourceUid = alarm.creatorUid;
- return mForcedAppStandbyPackages.contains(sourcePackage) && !mForegroundUids.get(sourceUid)
- && Arrays.binarySearch(mDeviceIdleUserWhitelist, UserHandle.getAppId(sourceUid))
- < 0;
+ return mForceAppStandbyTracker.areAlarmsRestricted(sourceUid, sourcePackage);
}
private native long init();
@@ -2859,7 +2837,8 @@
}
}
- private static class Alarm {
+ @VisibleForTesting
+ static class Alarm {
public final int type;
public final long origWhen;
public final boolean wakeup;
@@ -3073,6 +3052,11 @@
for (int i=0; i<triggerList.size(); i++) {
Alarm alarm = triggerList.get(i);
final boolean allowWhileIdle = (alarm.flags&AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0;
+ if (alarm.wakeup) {
+ Trace.traceBegin(Trace.TRACE_TAG_POWER, "Dispatch wakeup alarm to " + alarm.packageName);
+ } else {
+ Trace.traceBegin(Trace.TRACE_TAG_POWER, "Dispatch non-wakeup alarm to " + alarm.packageName);
+ }
try {
if (localLOGV) {
Slog.v(TAG, "sending alarm " + alarm);
@@ -3092,6 +3076,7 @@
} catch (RuntimeException e) {
Slog.w(TAG, "Failure sending alarm.", e);
}
+ Trace.traceEnd(Trace.TRACE_TAG_POWER);
}
}
@@ -3476,17 +3461,10 @@
if (disabled) {
removeForStoppedLocked(uid);
}
- mForegroundUids.delete(uid);
}
}
@Override public void onUidActive(int uid) {
- synchronized (mLock) {
- if (!mForegroundUids.get(uid)) {
- mForegroundUids.put(uid, true);
- sendPendingBackgroundAlarmsLocked(uid, null);
- }
- }
}
@Override public void onUidIdle(int uid, boolean disabled) {
@@ -3494,7 +3472,6 @@
if (disabled) {
removeForStoppedLocked(uid);
}
- mForegroundUids.delete(uid);
}
}
@@ -3502,27 +3479,29 @@
}
};
- private final class AppOpsWatcher extends IAppOpsCallback.Stub {
+
+ private final Listener mForceAppStandbyListener = new Listener() {
@Override
- public void opChanged(int op, int uid, String packageName) throws RemoteException {
+ public void unblockAllUnrestrictedAlarms() {
synchronized (mLock) {
- final int mode = mAppOpsService.checkOperation(op, uid, packageName);
- if (DEBUG_BG_LIMIT) {
- Slog.d(TAG,
- "Appop changed for " + uid + ", " + packageName + " to " + mode);
- }
- final boolean changed;
- if (mode != AppOpsManager.MODE_ALLOWED) {
- changed = mForcedAppStandbyPackages.add(packageName);
- } else {
- changed = mForcedAppStandbyPackages.remove(packageName);
- }
- if (changed && mode == AppOpsManager.MODE_ALLOWED) {
- sendPendingBackgroundAlarmsLocked(uid, packageName);
- }
+ sendAllUnrestrictedPendingBackgroundAlarmsLocked();
}
}
- }
+
+ @Override
+ public void unblockAlarmsForUid(int uid) {
+ synchronized (mLock) {
+ sendPendingBackgroundAlarmsLocked(uid, null);
+ }
+ }
+
+ @Override
+ public void unblockAlarmsForUidPackage(int uid, String packageName) {
+ synchronized (mLock) {
+ sendPendingBackgroundAlarmsLocked(uid, packageName);
+ }
+ }
+ };
private final BroadcastStats getStatsLocked(PendingIntent pi) {
String pkg = pi.getCreatorPackage();
diff --git a/com/android/server/BatteryService.java b/com/android/server/BatteryService.java
index ea0ed27..924e736 100644
--- a/com/android/server/BatteryService.java
+++ b/com/android/server/BatteryService.java
@@ -619,6 +619,7 @@
intent.putExtra(BatteryManager.EXTRA_HEALTH, mHealthInfo.batteryHealth);
intent.putExtra(BatteryManager.EXTRA_PRESENT, mHealthInfo.batteryPresent);
intent.putExtra(BatteryManager.EXTRA_LEVEL, mHealthInfo.batteryLevel);
+ intent.putExtra(BatteryManager.EXTRA_BATTERY_LOW, mSentLowBatteryBroadcast);
intent.putExtra(BatteryManager.EXTRA_SCALE, BATTERY_SCALE);
intent.putExtra(BatteryManager.EXTRA_ICON_SMALL, icon);
intent.putExtra(BatteryManager.EXTRA_PLUGGED, mPlugType);
diff --git a/com/android/server/BluetoothManagerService.java b/com/android/server/BluetoothManagerService.java
index c34c30c..04279a3 100644
--- a/com/android/server/BluetoothManagerService.java
+++ b/com/android/server/BluetoothManagerService.java
@@ -2150,31 +2150,26 @@
(int)((onDuration / (1000 * 60)) % 60),
(int)((onDuration / 1000) % 60),
(int)(onDuration % 1000));
- writer.println(" time since enabled: " + onDurationString + "\n");
+ writer.println(" time since enabled: " + onDurationString);
}
if (mActiveLogs.size() == 0) {
- writer.println("Bluetooth never enabled!");
+ writer.println("\nBluetooth never enabled!");
} else {
- writer.println("Enable log:");
+ writer.println("\nEnable log:");
for (ActiveLog log : mActiveLogs) {
writer.println(" " + log);
}
}
- writer.println("Bluetooth crashed " + mCrashes + " time" + (mCrashes == 1 ? "" : "s"));
+ writer.println("\nBluetooth crashed " + mCrashes + " time" + (mCrashes == 1 ? "" : "s"));
if (mCrashes == CRASH_LOG_MAX_SIZE) writer.println("(last " + CRASH_LOG_MAX_SIZE + ")");
for (Long time : mCrashTimestamps) {
writer.println(" " + timeToLog(time.longValue()));
}
- String bleAppString = "No BLE Apps registered.";
- if (mBleApps.size() == 1) {
- bleAppString = "1 BLE App registered:";
- } else if (mBleApps.size() > 1) {
- bleAppString = mBleApps.size() + " BLE Apps registered:";
- }
- writer.println("\n" + bleAppString);
+ writer.println("\n" + mBleApps.size() + " BLE app" +
+ (mBleApps.size() == 1 ? "" : "s") + "registered");
for (ClientDeathRecipient app : mBleApps.values()) {
writer.println(" " + app.getPackageName());
}
diff --git a/com/android/server/DeviceIdleController.java b/com/android/server/DeviceIdleController.java
index 0921a00..d7aeb8c 100644
--- a/com/android/server/DeviceIdleController.java
+++ b/com/android/server/DeviceIdleController.java
@@ -119,7 +119,6 @@
private PowerManagerInternal mLocalPowerManager;
private PowerManager mPowerManager;
private ConnectivityService mConnectivityService;
- private AlarmManagerService.LocalService mLocalAlarmManager;
private INetworkPolicyManager mNetworkPolicyManager;
private SensorManager mSensorManager;
private Sensor mMotionSensor;
@@ -1435,7 +1434,6 @@
mGoingIdleWakeLock.setReferenceCounted(true);
mConnectivityService = (ConnectivityService)ServiceManager.getService(
Context.CONNECTIVITY_SERVICE);
- mLocalAlarmManager = getLocalService(AlarmManagerService.LocalService.class);
mNetworkPolicyManager = INetworkPolicyManager.Stub.asInterface(
ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
mNetworkPolicyManagerInternal = getLocalService(NetworkPolicyManagerInternal.class);
@@ -1500,8 +1498,8 @@
mLocalActivityManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
- mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray);
+ passWhiteListToForceAppStandbyTrackerLocked();
updateInteractivityLocked();
}
updateConnectivityState(null);
@@ -2477,13 +2475,7 @@
}
mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
}
- if (mLocalAlarmManager != null) {
- if (DEBUG) {
- Slog.d(TAG, "Setting alarm whitelist to "
- + Arrays.toString(mPowerSaveWhitelistUserAppIdArray));
- }
- mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray);
- }
+ passWhiteListToForceAppStandbyTrackerLocked();
}
private void updateTempWhitelistAppIdsLocked(int appId, boolean adding) {
@@ -2509,6 +2501,7 @@
}
mLocalPowerManager.setDeviceIdleTempWhitelist(mTempWhitelistAppIdArray);
}
+ passWhiteListToForceAppStandbyTrackerLocked();
}
private void reportPowerSaveWhitelistChangedLocked() {
@@ -2523,6 +2516,12 @@
getContext().sendBroadcastAsUser(intent, UserHandle.SYSTEM);
}
+ private void passWhiteListToForceAppStandbyTrackerLocked() {
+ ForceAppStandbyTracker.getInstance(getContext()).setPowerSaveWhitelistAppIds(
+ mPowerSaveWhitelistAllAppIdArray,
+ mTempWhitelistAppIdArray);
+ }
+
void readConfigFileLocked() {
if (DEBUG) Slog.d(TAG, "Reading config from " + mConfigFile.getBaseFile());
mPowerSaveWhitelistUserApps.clear();
diff --git a/com/android/server/ForceAppStandbyTracker.java b/com/android/server/ForceAppStandbyTracker.java
new file mode 100644
index 0000000..61d3833
--- /dev/null
+++ b/com/android/server/ForceAppStandbyTracker.java
@@ -0,0 +1,838 @@
+/*
+ * Copyright (C) 2017 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.server;
+
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.app.AppOpsManager.PackageOps;
+import android.app.IActivityManager;
+import android.app.IUidObserver;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager.ServiceType;
+import android.os.PowerManagerInternal;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.Pair;
+import android.util.SparseBooleanArray;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IAppOpsCallback;
+import com.android.internal.app.IAppOpsService;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
+import com.android.server.ForceAppStandbyTrackerProto.RunAnyInBackgroundRestrictedPackages;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Class to keep track of the information related to "force app standby", which includes:
+ * - OP_RUN_ANY_IN_BACKGROUND for each package
+ * - UID foreground state
+ * - User+system power save whitelist
+ * - Temporary power save whitelist
+ * - Global "force all apps standby" mode enforced by battery saver.
+ *
+ * TODO: In general, we can reduce the number of callbacks by checking all signals before sending
+ * each callback. For example, even when an UID comes into the foreground, if it wasn't
+ * originally restricted, then there's no need to send an event.
+ * Doing this would be error-prone, so we punt it for now, but we should revisit it later.
+ *
+ * Test:
+ atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
+ */
+public class ForceAppStandbyTracker {
+ private static final String TAG = "ForceAppStandbyTracker";
+
+ @GuardedBy("ForceAppStandbyTracker.class")
+ private static ForceAppStandbyTracker sInstance;
+
+ private final Object mLock = new Object();
+ private final Context mContext;
+
+ @VisibleForTesting
+ static final int TARGET_OP = AppOpsManager.OP_RUN_ANY_IN_BACKGROUND;
+
+ IActivityManager mIActivityManager;
+ AppOpsManager mAppOpsManager;
+ IAppOpsService mAppOpsService;
+ PowerManagerInternal mPowerManagerInternal;
+
+ private final MyHandler mHandler;
+
+ /**
+ * Pair of (uid (not user-id), packageName) with OP_RUN_ANY_IN_BACKGROUND *not* allowed.
+ */
+ @GuardedBy("mLock")
+ final ArraySet<Pair<Integer, String>> mRunAnyRestrictedPackages = new ArraySet<>();
+
+ @GuardedBy("mLock")
+ final SparseBooleanArray mForegroundUids = new SparseBooleanArray();
+
+ @GuardedBy("mLock")
+ private int[] mPowerWhitelistedAllAppIds = new int[0];
+
+ @GuardedBy("mLock")
+ private int[] mTempWhitelistedAppIds = mPowerWhitelistedAllAppIds;
+
+ @GuardedBy("mLock")
+ final ArraySet<Listener> mListeners = new ArraySet<>();
+
+ @GuardedBy("mLock")
+ boolean mStarted;
+
+ @GuardedBy("mLock")
+ boolean mForceAllAppsStandby;
+
+ public static abstract class Listener {
+ /**
+ * This is called when the OP_RUN_ANY_IN_BACKGROUND appops changed for a package.
+ */
+ private void onRunAnyAppOpsChanged(ForceAppStandbyTracker sender,
+ int uid, @NonNull String packageName) {
+ updateJobsForUidPackage(uid, packageName);
+
+ if (!sender.areAlarmsRestricted(uid, packageName)) {
+ unblockAlarmsForUidPackage(uid, packageName);
+ }
+ }
+
+ /**
+ * This is called when the foreground state changed for a UID.
+ */
+ private void onUidForegroundStateChanged(ForceAppStandbyTracker sender, int uid) {
+ updateJobsForUid(uid);
+
+ if (sender.isInForeground(uid)) {
+ unblockAlarmsForUid(uid);
+ }
+ }
+
+ /**
+ * This is called when an app-id(s) is removed from the power save whitelist.
+ */
+ private void onPowerSaveUnwhitelisted(ForceAppStandbyTracker sender) {
+ updateAllJobs();
+ unblockAllUnrestrictedAlarms();
+ }
+
+ /**
+ * This is called when the power save whitelist changes, excluding the
+ * {@link #onPowerSaveUnwhitelisted} case.
+ */
+ private void onPowerSaveWhitelistedChanged(ForceAppStandbyTracker sender) {
+ updateAllJobs();
+ }
+
+ /**
+ * This is called when the temp whitelist changes.
+ */
+ private void onTempPowerSaveWhitelistChanged(ForceAppStandbyTracker sender) {
+
+ // TODO This case happens rather frequently; consider optimizing and update jobs
+ // only for affected app-ids.
+
+ updateAllJobs();
+ }
+
+ /**
+ * This is called when the global "force all apps standby" flag changes.
+ */
+ private void onForceAllAppsStandbyChanged(ForceAppStandbyTracker sender) {
+ updateAllJobs();
+
+ if (!sender.isForceAllAppsStandbyEnabled()) {
+ unblockAllUnrestrictedAlarms();
+ }
+ }
+
+ /**
+ * Called when the job restrictions for multiple UIDs might have changed, so the job
+ * scheduler should re-evaluate all restrictions for all jobs.
+ */
+ public void updateAllJobs() {
+ }
+
+ /**
+ * Called when the job restrictions for a UID might have changed, so the job
+ * scheduler should re-evaluate all restrictions for all jobs.
+ */
+ public void updateJobsForUid(int uid) {
+ }
+
+ /**
+ * Called when the job restrictions for a UID - package might have changed, so the job
+ * scheduler should re-evaluate all restrictions for all jobs.
+ */
+ public void updateJobsForUidPackage(int uid, String packageName) {
+ }
+
+ /**
+ * Called when the job restrictions for multiple UIDs might have changed, so the alarm
+ * manager should re-evaluate all restrictions for all blocked jobs.
+ */
+ public void unblockAllUnrestrictedAlarms() {
+ }
+
+ /**
+ * Called when all jobs for a specific UID are unblocked.
+ */
+ public void unblockAlarmsForUid(int uid) {
+ }
+
+ /**
+ * Called when all alarms for a specific UID - package are unblocked.
+ */
+ public void unblockAlarmsForUidPackage(int uid, String packageName) {
+ }
+ }
+
+ @VisibleForTesting
+ ForceAppStandbyTracker(Context context, Looper looper) {
+ mContext = context;
+ mHandler = new MyHandler(looper);
+ }
+
+ private ForceAppStandbyTracker(Context context) {
+ this(context, FgThread.get().getLooper());
+ }
+
+ /**
+ * Get the singleton instance.
+ */
+ public static synchronized ForceAppStandbyTracker getInstance(Context context) {
+ if (sInstance == null) {
+ sInstance = new ForceAppStandbyTracker(context);
+ }
+ return sInstance;
+ }
+
+ /**
+ * Call it when the system is ready.
+ */
+ public void start() {
+ synchronized (mLock) {
+ if (mStarted) {
+ return;
+ }
+ mStarted = true;
+
+ mIActivityManager = Preconditions.checkNotNull(injectIActivityManager());
+ mAppOpsManager = Preconditions.checkNotNull(injectAppOpsManager());
+ mAppOpsService = Preconditions.checkNotNull(injectIAppOpsService());
+ mPowerManagerInternal = Preconditions.checkNotNull(injectPowerManagerInternal());
+
+ try {
+ mIActivityManager.registerUidObserver(new UidObserver(),
+ ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_IDLE
+ | ActivityManager.UID_OBSERVER_ACTIVE,
+ ActivityManager.PROCESS_STATE_UNKNOWN, null);
+ mAppOpsService.startWatchingMode(TARGET_OP, null,
+ new AppOpsWatcher());
+ } catch (RemoteException e) {
+ // shouldn't happen.
+ }
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_USER_REMOVED);
+ mContext.registerReceiver(new MyReceiver(), filter);
+
+ refreshForcedAppStandbyUidPackagesLocked();
+
+ mPowerManagerInternal.registerLowPowerModeObserver(
+ ServiceType.FORCE_ALL_APPS_STANDBY,
+ (state) -> updateForceAllAppsStandby(state.batterySaverEnabled));
+
+ updateForceAllAppsStandby(mPowerManagerInternal.getLowPowerState(
+ ServiceType.FORCE_ALL_APPS_STANDBY).batterySaverEnabled);
+ }
+ }
+
+ @VisibleForTesting
+ AppOpsManager injectAppOpsManager() {
+ return mContext.getSystemService(AppOpsManager.class);
+ }
+
+ @VisibleForTesting
+ IAppOpsService injectIAppOpsService() {
+ return IAppOpsService.Stub.asInterface(
+ ServiceManager.getService(Context.APP_OPS_SERVICE));
+ }
+
+ @VisibleForTesting
+ IActivityManager injectIActivityManager() {
+ return ActivityManager.getService();
+ }
+
+ @VisibleForTesting
+ PowerManagerInternal injectPowerManagerInternal() {
+ return LocalServices.getService(PowerManagerInternal.class);
+ }
+
+ /**
+ * Update {@link #mRunAnyRestrictedPackages} with the current app ops state.
+ */
+ private void refreshForcedAppStandbyUidPackagesLocked() {
+ mRunAnyRestrictedPackages.clear();
+ final List<PackageOps> ops = mAppOpsManager.getPackagesForOps(
+ new int[] {TARGET_OP});
+
+ if (ops == null) {
+ return;
+ }
+ final int size = ops.size();
+ for (int i = 0; i < size; i++) {
+ final AppOpsManager.PackageOps pkg = ops.get(i);
+ final List<AppOpsManager.OpEntry> entries = ops.get(i).getOps();
+
+ for (int j = 0; j < entries.size(); j++) {
+ AppOpsManager.OpEntry ent = entries.get(j);
+ if (ent.getOp() != TARGET_OP) {
+ continue;
+ }
+ if (ent.getMode() != AppOpsManager.MODE_ALLOWED) {
+ mRunAnyRestrictedPackages.add(Pair.create(
+ pkg.getUid(), pkg.getPackageName()));
+ }
+ }
+ }
+ }
+
+ /**
+ * Update {@link #mForceAllAppsStandby} and notifies the listeners.
+ */
+ void updateForceAllAppsStandby(boolean enable) {
+ synchronized (mLock) {
+ if (enable == mForceAllAppsStandby) {
+ return;
+ }
+ mForceAllAppsStandby = enable;
+
+ mHandler.notifyForceAllAppsStandbyChanged();
+ }
+ }
+
+ private int findForcedAppStandbyUidPackageIndexLocked(int uid, @NonNull String packageName) {
+ final int size = mRunAnyRestrictedPackages.size();
+ if (size > 8) {
+ return mRunAnyRestrictedPackages.indexOf(Pair.create(uid, packageName));
+ }
+ for (int i = 0; i < size; i++) {
+ final Pair<Integer, String> pair = mRunAnyRestrictedPackages.valueAt(i);
+
+ if ((pair.first == uid) && packageName.equals(pair.second)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * @return whether a uid package-name pair is in mRunAnyRestrictedPackages.
+ */
+ boolean isRunAnyRestrictedLocked(int uid, @NonNull String packageName) {
+ return findForcedAppStandbyUidPackageIndexLocked(uid, packageName) >= 0;
+ }
+
+ /**
+ * Add to / remove from {@link #mRunAnyRestrictedPackages}.
+ */
+ boolean updateForcedAppStandbyUidPackageLocked(int uid, @NonNull String packageName,
+ boolean restricted) {
+ final int index = findForcedAppStandbyUidPackageIndexLocked(uid, packageName);
+ final boolean wasRestricted = index >= 0;
+ if (wasRestricted == restricted) {
+ return false;
+ }
+ if (restricted) {
+ mRunAnyRestrictedPackages.add(Pair.create(uid, packageName));
+ } else {
+ mRunAnyRestrictedPackages.removeAt(index);
+ }
+ return true;
+ }
+
+ /**
+ * Puts a UID to {@link #mForegroundUids}.
+ */
+ void uidToForeground(int uid) {
+ synchronized (mLock) {
+ if (!UserHandle.isApp(uid)) {
+ return;
+ }
+ // TODO This can be optimized by calling indexOfKey and sharing the index for get and
+ // put.
+ if (mForegroundUids.get(uid)) {
+ return;
+ }
+ mForegroundUids.put(uid, true);
+ mHandler.notifyUidForegroundStateChanged(uid);
+ }
+ }
+
+ /**
+ * Sets false for a UID {@link #mForegroundUids}, or remove it when {@code remove} is true.
+ */
+ void uidToBackground(int uid, boolean remove) {
+ synchronized (mLock) {
+ if (!UserHandle.isApp(uid)) {
+ return;
+ }
+ // TODO This can be optimized by calling indexOfKey and sharing the index for get and
+ // put.
+ if (!mForegroundUids.get(uid)) {
+ return;
+ }
+ if (remove) {
+ mForegroundUids.delete(uid);
+ } else {
+ mForegroundUids.put(uid, false);
+ }
+ mHandler.notifyUidForegroundStateChanged(uid);
+ }
+ }
+
+ private final class UidObserver extends IUidObserver.Stub {
+ @Override public void onUidStateChanged(int uid, int procState, long procStateSeq) {
+ }
+
+ @Override public void onUidGone(int uid, boolean disabled) {
+ uidToBackground(uid, /*remove=*/ true);
+ }
+
+ @Override public void onUidActive(int uid) {
+ uidToForeground(uid);
+ }
+
+ @Override public void onUidIdle(int uid, boolean disabled) {
+ // Just to avoid excessive memcpy, don't remove from the array in this case.
+ uidToBackground(uid, /*remove=*/ false);
+ }
+
+ @Override public void onUidCachedChanged(int uid, boolean cached) {
+ }
+ };
+
+ private final class AppOpsWatcher extends IAppOpsCallback.Stub {
+ @Override
+ public void opChanged(int op, int uid, String packageName) throws RemoteException {
+ boolean restricted = false;
+ try {
+ restricted = mAppOpsService.checkOperation(TARGET_OP,
+ uid, packageName) != AppOpsManager.MODE_ALLOWED;
+ } catch (RemoteException e) {
+ // Shouldn't happen
+ }
+ synchronized (mLock) {
+ if (updateForcedAppStandbyUidPackageLocked(uid, packageName, restricted)) {
+ mHandler.notifyRunAnyAppOpsChanged(uid, packageName);
+ }
+ }
+ }
+ }
+
+ private final class MyReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
+ final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+ if (userId > 0) {
+ mHandler.doUserRemoved(userId);
+ }
+ }
+ }
+ }
+
+ private Listener[] cloneListeners() {
+ synchronized (mLock) {
+ return mListeners.toArray(new Listener[mListeners.size()]);
+ }
+ }
+
+ private class MyHandler extends Handler {
+ private static final int MSG_UID_STATE_CHANGED = 1;
+ private static final int MSG_RUN_ANY_CHANGED = 2;
+ private static final int MSG_ALL_UNWHITELISTED = 3;
+ private static final int MSG_ALL_WHITELIST_CHANGED = 4;
+ private static final int MSG_TEMP_WHITELIST_CHANGED = 5;
+ private static final int MSG_FORCE_ALL_CHANGED = 6;
+
+ private static final int MSG_USER_REMOVED = 7;
+
+ public MyHandler(Looper looper) {
+ super(looper);
+ }
+
+ public void notifyUidForegroundStateChanged(int uid) {
+ obtainMessage(MSG_UID_STATE_CHANGED, uid, 0).sendToTarget();
+ }
+ public void notifyRunAnyAppOpsChanged(int uid, @NonNull String packageName) {
+ obtainMessage(MSG_RUN_ANY_CHANGED, uid, 0, packageName).sendToTarget();
+ }
+
+ public void notifyAllUnwhitelisted() {
+ obtainMessage(MSG_ALL_UNWHITELISTED).sendToTarget();
+ }
+
+ public void notifyAllWhitelistChanged() {
+ obtainMessage(MSG_ALL_WHITELIST_CHANGED).sendToTarget();
+ }
+
+ public void notifyTempWhitelistChanged() {
+ obtainMessage(MSG_TEMP_WHITELIST_CHANGED).sendToTarget();
+ }
+
+ public void notifyForceAllAppsStandbyChanged() {
+ obtainMessage(MSG_FORCE_ALL_CHANGED).sendToTarget();
+ }
+
+ public void doUserRemoved(int userId) {
+ obtainMessage(MSG_USER_REMOVED, userId, 0).sendToTarget();
+ }
+
+ @Override
+ public void dispatchMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_USER_REMOVED:
+ handleUserRemoved(msg.arg1);
+ return;
+ }
+
+ // Only notify the listeners when started.
+ synchronized (mLock) {
+ if (!mStarted) {
+ return;
+ }
+ }
+ final ForceAppStandbyTracker sender = ForceAppStandbyTracker.this;
+
+ switch (msg.what) {
+ case MSG_UID_STATE_CHANGED:
+ for (Listener l : cloneListeners()) {
+ l.onUidForegroundStateChanged(sender, msg.arg1);
+ }
+ return;
+ case MSG_RUN_ANY_CHANGED:
+ for (Listener l : cloneListeners()) {
+ l.onRunAnyAppOpsChanged(sender, msg.arg1, (String) msg.obj);
+ }
+ return;
+ case MSG_ALL_UNWHITELISTED:
+ for (Listener l : cloneListeners()) {
+ l.onPowerSaveUnwhitelisted(sender);
+ }
+ return;
+ case MSG_ALL_WHITELIST_CHANGED:
+ for (Listener l : cloneListeners()) {
+ l.onPowerSaveWhitelistedChanged(sender);
+ }
+ return;
+ case MSG_TEMP_WHITELIST_CHANGED:
+ for (Listener l : cloneListeners()) {
+ l.onTempPowerSaveWhitelistChanged(sender);
+ }
+ return;
+ case MSG_FORCE_ALL_CHANGED:
+ for (Listener l : cloneListeners()) {
+ l.onForceAllAppsStandbyChanged(sender);
+ }
+ return;
+ case MSG_USER_REMOVED:
+ handleUserRemoved(msg.arg1);
+ return;
+ }
+ }
+ }
+
+ void handleUserRemoved(int removedUserId) {
+ synchronized (mLock) {
+ for (int i = mRunAnyRestrictedPackages.size() - 1; i >= 0; i--) {
+ final Pair<Integer, String> pair = mRunAnyRestrictedPackages.valueAt(i);
+ final int uid = pair.first;
+ final int userId = UserHandle.getUserId(uid);
+
+ if (userId == removedUserId) {
+ mRunAnyRestrictedPackages.removeAt(i);
+ }
+ }
+ for (int i = mForegroundUids.size() - 1; i >= 0; i--) {
+ final int uid = mForegroundUids.keyAt(i);
+ final int userId = UserHandle.getUserId(uid);
+
+ if (userId == removedUserId) {
+ mForegroundUids.removeAt(i);
+ }
+ }
+ }
+ }
+
+ /**
+ * Called by device idle controller to update the power save whitelists.
+ */
+ public void setPowerSaveWhitelistAppIds(
+ int[] powerSaveWhitelistAllAppIdArray, int[] tempWhitelistAppIdArray) {
+ synchronized (mLock) {
+ final int[] previousWhitelist = mPowerWhitelistedAllAppIds;
+ final int[] previousTempWhitelist = mTempWhitelistedAppIds;
+
+ mPowerWhitelistedAllAppIds = powerSaveWhitelistAllAppIdArray;
+ mTempWhitelistedAppIds = tempWhitelistAppIdArray;
+
+ if (isAnyAppIdUnwhitelisted(previousWhitelist, mPowerWhitelistedAllAppIds)) {
+ mHandler.notifyAllUnwhitelisted();
+ } else if (!Arrays.equals(previousWhitelist, mPowerWhitelistedAllAppIds)) {
+ mHandler.notifyAllWhitelistChanged();
+ }
+
+ if (!Arrays.equals(previousTempWhitelist, mTempWhitelistedAppIds)) {
+ mHandler.notifyTempWhitelistChanged();
+ }
+
+ }
+ }
+
+ /**
+ * @retunr true if a sorted app-id array {@code prevArray} has at least one element
+ * that's not in a sorted app-id array {@code newArray}.
+ */
+ @VisibleForTesting
+ static boolean isAnyAppIdUnwhitelisted(int[] prevArray, int[] newArray) {
+ int i1 = 0;
+ int i2 = 0;
+ boolean prevFinished;
+ boolean newFinished;
+
+ for (;;) {
+ prevFinished = i1 >= prevArray.length;
+ newFinished = i2 >= newArray.length;
+ if (prevFinished || newFinished) {
+ break;
+ }
+ int a1 = prevArray[i1];
+ int a2 = newArray[i2];
+
+ if (a1 == a2) {
+ i1++;
+ i2++;
+ continue;
+ }
+ if (a1 < a2) {
+ // prevArray has an element that's not in a2.
+ return true;
+ }
+ i2++;
+ }
+ if (prevFinished) {
+ return false;
+ }
+ return newFinished;
+ }
+
+ // Public interface.
+
+ /**
+ * Register a new listener.
+ */
+ public void addListener(@NonNull Listener listener) {
+ synchronized (mLock) {
+ mListeners.add(listener);
+ }
+ }
+
+ /**
+ * @return whether alarms should be restricted for a UID package-name.
+ */
+ public boolean areAlarmsRestricted(int uid, @NonNull String packageName) {
+ return isRestricted(uid, packageName, /*useTempWhitelistToo=*/ false);
+ }
+
+ /**
+ * @return whether jobs should be restricted for a UID package-name.
+ */
+ public boolean areJobsRestricted(int uid, @NonNull String packageName) {
+ return isRestricted(uid, packageName, /*useTempWhitelistToo=*/ true);
+ }
+
+ /**
+ * @return whether force-app-standby is effective for a UID package-name.
+ */
+ private boolean isRestricted(int uid, @NonNull String packageName,
+ boolean useTempWhitelistToo) {
+ if (isInForeground(uid)) {
+ return false;
+ }
+ synchronized (mLock) {
+ // Whitelisted?
+ final int appId = UserHandle.getAppId(uid);
+ if (ArrayUtils.contains(mPowerWhitelistedAllAppIds, appId)) {
+ return false;
+ }
+ if (useTempWhitelistToo &&
+ ArrayUtils.contains(mTempWhitelistedAppIds, appId)) {
+ return false;
+ }
+
+ if (mForceAllAppsStandby) {
+ return true;
+ }
+
+ return isRunAnyRestrictedLocked(uid, packageName);
+ }
+ }
+
+ /**
+ * @return whether a UID is in the foreground or not.
+ *
+ * Note clients normally shouldn't need to access it. It's only for dumpsys.
+ */
+ public boolean isInForeground(int uid) {
+ if (!UserHandle.isApp(uid)) {
+ return true;
+ }
+ synchronized (mLock) {
+ return mForegroundUids.get(uid);
+ }
+ }
+
+ /**
+ * @return whether force all apps standby is enabled or not.
+ *
+ * Note clients normally shouldn't need to access it.
+ */
+ boolean isForceAllAppsStandbyEnabled() {
+ synchronized (mLock) {
+ return mForceAllAppsStandby;
+ }
+ }
+
+ /**
+ * @return whether a UID/package has {@code OP_RUN_ANY_IN_BACKGROUND} allowed or not.
+ *
+ * Note clients normally shouldn't need to access it. It's only for dumpsys.
+ */
+ public boolean isRunAnyInBackgroundAppOpsAllowed(int uid, @NonNull String packageName) {
+ synchronized (mLock) {
+ return !isRunAnyRestrictedLocked(uid, packageName);
+ }
+ }
+
+ /**
+ * @return whether a UID is in the user / system defined power-save whitelist or not.
+ *
+ * Note clients normally shouldn't need to access it. It's only for dumpsys.
+ */
+ public boolean isUidPowerSaveWhitelisted(int uid) {
+ synchronized (mLock) {
+ return ArrayUtils.contains(mPowerWhitelistedAllAppIds, UserHandle.getAppId(uid));
+ }
+ }
+
+ /**
+ * @return whether a UID is in the temp power-save whitelist or not.
+ *
+ * Note clients normally shouldn't need to access it. It's only for dumpsys.
+ */
+ public boolean isUidTempPowerSaveWhitelisted(int uid) {
+ synchronized (mLock) {
+ return ArrayUtils.contains(mTempWhitelistedAppIds, UserHandle.getAppId(uid));
+ }
+ }
+
+ public void dump(PrintWriter pw, String indent) {
+ synchronized (mLock) {
+ pw.print(indent);
+ pw.print("Force all apps standby: ");
+ pw.println(isForceAllAppsStandbyEnabled());
+
+ pw.print(indent);
+ pw.print("Foreground uids: [");
+
+ String sep = "";
+ for (int i = 0; i < mForegroundUids.size(); i++) {
+ if (mForegroundUids.valueAt(i)) {
+ pw.print(sep);
+ pw.print(UserHandle.formatUid(mForegroundUids.keyAt(i)));
+ sep = " ";
+ }
+ }
+ pw.println("]");
+
+ pw.print(indent);
+ pw.print("Whitelist appids: ");
+ pw.println(Arrays.toString(mPowerWhitelistedAllAppIds));
+
+ pw.print(indent);
+ pw.print("Temp whitelist appids: ");
+ pw.println(Arrays.toString(mTempWhitelistedAppIds));
+
+ pw.print(indent);
+ pw.println("Restricted packages:");
+ for (Pair<Integer, String> uidAndPackage : mRunAnyRestrictedPackages) {
+ pw.print(indent);
+ pw.print(" ");
+ pw.print(UserHandle.formatUid(uidAndPackage.first));
+ pw.print(" ");
+ pw.print(uidAndPackage.second);
+ pw.println();
+ }
+ }
+ }
+
+ public void dumpProto(ProtoOutputStream proto, long fieldId) {
+ synchronized (mLock) {
+ final long token = proto.start(fieldId);
+
+ proto.write(ForceAppStandbyTrackerProto.FORCE_ALL_APPS_STANDBY, mForceAllAppsStandby);
+
+ for (int i = 0; i < mForegroundUids.size(); i++) {
+ if (mForegroundUids.valueAt(i)) {
+ proto.write(ForceAppStandbyTrackerProto.FOREGROUND_UIDS,
+ mForegroundUids.keyAt(i));
+ }
+ }
+
+ for (int appId : mPowerWhitelistedAllAppIds) {
+ proto.write(ForceAppStandbyTrackerProto.POWER_SAVE_WHITELIST_APP_IDS, appId);
+ }
+
+ for (int appId : mTempWhitelistedAppIds) {
+ proto.write(ForceAppStandbyTrackerProto.TEMP_POWER_SAVE_WHITELIST_APP_IDS, appId);
+ }
+
+ for (Pair<Integer, String> uidAndPackage : mRunAnyRestrictedPackages) {
+ final long token2 = proto.start(
+ ForceAppStandbyTrackerProto.RUN_ANY_IN_BACKGROUND_RESTRICTED_PACKAGES);
+ proto.write(RunAnyInBackgroundRestrictedPackages.UID, uidAndPackage.first);
+ proto.write(RunAnyInBackgroundRestrictedPackages.PACKAGE_NAME,
+ uidAndPackage.second);
+ proto.end(token2);
+ }
+ proto.end(token);
+ }
+ }
+}
diff --git a/com/android/server/GestureLauncherService.java b/com/android/server/GestureLauncherService.java
index a903f3d..b7b5bd9 100644
--- a/com/android/server/GestureLauncherService.java
+++ b/com/android/server/GestureLauncherService.java
@@ -40,13 +40,13 @@
import android.util.MutableBoolean;
import android.util.Slog;
import android.view.KeyEvent;
-import android.view.WindowManagerInternal;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.server.LocalServices;
import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.wm.WindowManagerInternal;
/**
* The service that listens for gestures detected in sensor firmware and starts the intent
diff --git a/com/android/server/InputMethodManagerService.java b/com/android/server/InputMethodManagerService.java
index f007bcc..fc57a0d 100644
--- a/com/android/server/InputMethodManagerService.java
+++ b/com/android/server/InputMethodManagerService.java
@@ -128,7 +128,6 @@
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
-import android.view.WindowManagerInternal;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputConnection;
@@ -147,6 +146,8 @@
import android.widget.TextView;
import android.widget.Toast;
+import com.android.server.wm.WindowManagerInternal;
+
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
diff --git a/com/android/server/LocationManagerService.java b/com/android/server/LocationManagerService.java
index 0fd59ea..bdfccd6 100644
--- a/com/android/server/LocationManagerService.java
+++ b/com/android/server/LocationManagerService.java
@@ -118,7 +118,7 @@
private static final String TAG = "LocationManagerService";
public static final boolean D = Log.isLoggable(TAG, Log.DEBUG);
- private static final String WAKELOCK_KEY = TAG;
+ private static final String WAKELOCK_KEY = "*location*";
// Location resolution level: no location data whatsoever
private static final int RESOLUTION_LEVEL_NONE = 0;
diff --git a/com/android/server/NetworkTimeUpdateService.java b/com/android/server/NetworkTimeUpdateService.java
index 1ad1404..2c24798 100644
--- a/com/android/server/NetworkTimeUpdateService.java
+++ b/com/android/server/NetworkTimeUpdateService.java
@@ -69,13 +69,10 @@
private static final String ACTION_POLL =
"com.android.server.NetworkTimeUpdateService.action.POLL";
- private static final int NETWORK_CHANGE_EVENT_DELAY_MS = 1000;
- private static int POLL_REQUEST = 0;
+ private static final int POLL_REQUEST = 0;
private static final long NOT_SET = -1;
private long mNitzTimeSetTime = NOT_SET;
- // TODO: Have a way to look up the timezone we are in
- private long mNitzZoneSetTime = NOT_SET;
private Network mDefaultNetwork = null;
private Context mContext;
@@ -144,7 +141,6 @@
private void registerForTelephonyIntents() {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(TelephonyIntents.ACTION_NETWORK_SET_TIME);
- intentFilter.addAction(TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE);
mContext.registerReceiver(mNitzReceiver, intentFilter);
}
@@ -257,8 +253,6 @@
if (DBG) Log.d(TAG, "Received " + action);
if (TelephonyIntents.ACTION_NETWORK_SET_TIME.equals(action)) {
mNitzTimeSetTime = SystemClock.elapsedRealtime();
- } else if (TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE.equals(action)) {
- mNitzZoneSetTime = SystemClock.elapsedRealtime();
}
}
};
diff --git a/com/android/server/StorageManagerService.java b/com/android/server/StorageManagerService.java
index 3c955eb..66b3adb 100644
--- a/com/android/server/StorageManagerService.java
+++ b/com/android/server/StorageManagerService.java
@@ -2192,6 +2192,11 @@
mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
"no permission to access the crypt keeper");
+ if (StorageManager.isFileEncryptedNativeOnly()) {
+ // Not supported on FBE devices
+ return -1;
+ }
+
if (type == StorageManager.CRYPT_TYPE_DEFAULT) {
password = "";
} else if (TextUtils.isEmpty(password)) {
@@ -2268,6 +2273,11 @@
mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
"no permission to access the crypt keeper");
+ if (StorageManager.isFileEncryptedNativeOnly()) {
+ // Not supported on FBE devices
+ return;
+ }
+
try {
mVold.fdeSetField(field, contents);
return;
@@ -2287,6 +2297,11 @@
mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
"no permission to access the crypt keeper");
+ if (StorageManager.isFileEncryptedNativeOnly()) {
+ // Not supported on FBE devices
+ return null;
+ }
+
try {
return mVold.fdeGetField(field);
} catch (Exception e) {
@@ -2568,7 +2583,7 @@
}
@Override
- public int mkdirs(String callingPkg, String appPath) {
+ public void mkdirs(String callingPkg, String appPath) {
final int userId = UserHandle.getUserId(Binder.getCallingUid());
final UserEnvironment userEnv = new UserEnvironment(userId);
@@ -2581,8 +2596,7 @@
try {
appFile = new File(appPath).getCanonicalFile();
} catch (IOException e) {
- Slog.e(TAG, "Failed to resolve " + appPath + ": " + e);
- return -1;
+ throw new IllegalStateException("Failed to resolve " + appPath + ": " + e);
}
// Try translating the app path into a vold path, but require that it
@@ -2597,9 +2611,8 @@
try {
mVold.mkdirs(appPath);
- return 0;
} catch (Exception e) {
- Slog.wtf(TAG, e);
+ throw new IllegalStateException("Failed to prepare " + appPath + ": " + e);
}
}
diff --git a/com/android/server/SystemServer.java b/com/android/server/SystemServer.java
index 74a7bd4..33f4e34 100644
--- a/com/android/server/SystemServer.java
+++ b/com/android/server/SystemServer.java
@@ -35,6 +35,7 @@
import android.os.IIncidentManager;
import android.os.Looper;
import android.os.Message;
+import android.os.Parcel;
import android.os.PowerManager;
import android.os.Process;
import android.os.ServiceManager;
@@ -360,6 +361,9 @@
// to avoid throwing BadParcelableException.
BaseBundle.setShouldDefuse(true);
+ // Within the system server, when parceling exceptions, include the stack trace
+ Parcel.setStackTraceParceling(true);
+
// Ensure binder calls into the system always run at foreground priority.
BinderInternal.disableBackgroundScheduling(true);
diff --git a/com/android/server/accessibility/AccessibilityClientConnection.java b/com/android/server/accessibility/AccessibilityClientConnection.java
index df4c8ed..22d922b 100644
--- a/com/android/server/accessibility/AccessibilityClientConnection.java
+++ b/com/android/server/accessibility/AccessibilityClientConnection.java
@@ -20,9 +20,7 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
-import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
-import android.accessibilityservice.GestureDescription;
import android.accessibilityservice.IAccessibilityServiceClient;
import android.accessibilityservice.IAccessibilityServiceConnection;
import android.annotation.NonNull;
@@ -47,10 +45,8 @@
import android.view.KeyEvent;
import android.view.MagnificationSpec;
import android.view.View;
-import android.view.WindowManagerInternal;
import android.view.accessibility.AccessibilityCache;
import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityInteractionClient;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo;
import android.view.accessibility.IAccessibilityInteractionConnection;
@@ -60,11 +56,13 @@
import com.android.internal.os.SomeArgs;
import com.android.internal.util.DumpUtils;
import com.android.server.accessibility.AccessibilityManagerService.SecurityPolicy;
+import com.android.server.wm.WindowManagerInternal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -106,7 +104,7 @@
int mFeedbackType;
- Set<String> mPackageNames = new HashSet<>();
+ final Set<String> mPackageNames = new HashSet<>();
boolean mIsDefault;
@@ -284,40 +282,98 @@
return true;
}
- public void setDynamicallyConfigurableProperties(AccessibilityServiceInfo info) {
- mEventTypes = info.eventTypes;
- mFeedbackType = info.feedbackType;
- String[] packageNames = info.packageNames;
- if (packageNames != null) {
- mPackageNames.addAll(Arrays.asList(packageNames));
+ boolean setDynamicallyConfigurableProperties(AccessibilityServiceInfo info) {
+ boolean somethingChanged = false;
+
+ if (mEventTypes != info.eventTypes) {
+ mEventTypes = info.eventTypes;
+ somethingChanged = true;
}
- mNotificationTimeout = info.notificationTimeout;
- mIsDefault = (info.flags & DEFAULT) != 0;
+
+ if (mFeedbackType != info.feedbackType) {
+ mFeedbackType = info.feedbackType;
+ somethingChanged = true;
+ }
+
+ final String[] oldPackageNames = mPackageNames.toArray(new String[mPackageNames.size()]);
+ if (!Arrays.equals(oldPackageNames, info.packageNames)) {
+ mPackageNames.clear();
+ if (info.packageNames != null) {
+ Collections.addAll(mPackageNames, info.packageNames);
+ }
+ somethingChanged = true;
+ }
+
+ if (mNotificationTimeout != info.notificationTimeout) {
+ mNotificationTimeout = info.notificationTimeout;
+ somethingChanged = true;
+ }
+
+ final boolean newIsDefault = (info.flags & DEFAULT) != 0;
+ if (mIsDefault != newIsDefault) {
+ mIsDefault = newIsDefault;
+ somethingChanged = true;
+ }
if (supportsFlagForNotImportantViews(info)) {
- if ((info.flags & AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0) {
- mFetchFlags |= AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
- } else {
- mFetchFlags &= ~AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
+ somethingChanged |= updateFetchFlag(info.flags,
+ AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS);
+ }
+
+ somethingChanged |= updateFetchFlag(info.flags,
+ AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS);
+
+ final boolean newRequestTouchExplorationMode = (info.flags
+ & AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0;
+ if (mRequestTouchExplorationMode != newRequestTouchExplorationMode) {
+ mRequestTouchExplorationMode = newRequestTouchExplorationMode;
+ somethingChanged = true;
+ }
+
+ final boolean newRequestFilterKeyEvents = (info.flags
+ & AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS) != 0;
+ if (mRequestFilterKeyEvents != newRequestFilterKeyEvents) {
+ mRequestFilterKeyEvents = newRequestFilterKeyEvents;
+ somethingChanged = true;
+ }
+
+ final boolean newRetrieveInteractiveWindows = (info.flags
+ & AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS) != 0;
+ if (mRetrieveInteractiveWindows != newRetrieveInteractiveWindows) {
+ mRetrieveInteractiveWindows = newRetrieveInteractiveWindows;
+ somethingChanged = true;
+ }
+
+ final boolean newCaptureFingerprintGestures = (info.flags
+ & AccessibilityServiceInfo.FLAG_REQUEST_FINGERPRINT_GESTURES) != 0;
+ if (mCaptureFingerprintGestures != newCaptureFingerprintGestures) {
+ mCaptureFingerprintGestures = newCaptureFingerprintGestures;
+ somethingChanged = true;
+ }
+
+ final boolean newRequestAccessibilityButton = (info.flags
+ & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
+ if (mRequestAccessibilityButton != newRequestAccessibilityButton) {
+ mRequestAccessibilityButton = newRequestAccessibilityButton;
+ somethingChanged = true;
+ }
+
+ return somethingChanged;
+ }
+
+ private boolean updateFetchFlag(int allFlags, int flagToUpdate) {
+ if ((allFlags & flagToUpdate) != 0) {
+ if ((mFetchFlags & flagToUpdate) == 0) {
+ mFetchFlags |= flagToUpdate;
+ return true;
+ }
+ } else {
+ if ((mFetchFlags & flagToUpdate) != 0) {
+ mFetchFlags &= ~flagToUpdate;
+ return true;
}
}
-
- if ((info.flags & AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS) != 0) {
- mFetchFlags |= AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS;
- } else {
- mFetchFlags &= ~AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS;
- }
-
- mRequestTouchExplorationMode = (info.flags
- & AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0;
- mRequestFilterKeyEvents = (info.flags
- & AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS) != 0;
- mRetrieveInteractiveWindows = (info.flags
- & AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS) != 0;
- mCaptureFingerprintGestures = (info.flags
- & AccessibilityServiceInfo.FLAG_REQUEST_FINGERPRINT_GESTURES) != 0;
- mRequestAccessibilityButton = (info.flags
- & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
+ return false;
}
protected boolean supportsFlagForNotImportantViews(AccessibilityServiceInfo info) {
@@ -349,14 +405,15 @@
// If the XML manifest had data to configure the service its info
// should be already set. In such a case update only the dynamically
// configurable properties.
+ final boolean serviceInfoChanged;
AccessibilityServiceInfo oldInfo = mAccessibilityServiceInfo;
if (oldInfo != null) {
oldInfo.updateDynamicallyConfigurableProperties(info);
- setDynamicallyConfigurableProperties(oldInfo);
+ serviceInfoChanged = setDynamicallyConfigurableProperties(oldInfo);
} else {
- setDynamicallyConfigurableProperties(info);
+ serviceInfoChanged = setDynamicallyConfigurableProperties(info);
}
- mSystemSupport.onClientChange(true);
+ mSystemSupport.onClientChange(serviceInfoChanged);
}
} finally {
Binder.restoreCallingIdentity(identity);
diff --git a/com/android/server/accessibility/AccessibilityInputFilter.java b/com/android/server/accessibility/AccessibilityInputFilter.java
index f6fcaae..11b2343 100644
--- a/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -31,11 +31,11 @@
import android.view.InputFilter;
import android.view.KeyEvent;
import android.view.MotionEvent;
-import android.view.WindowManagerPolicy;
import android.view.accessibility.AccessibilityEvent;
import com.android.internal.util.BitUtils;
import com.android.server.LocalServices;
+import com.android.server.policy.WindowManagerPolicy;
/**
* This class is an input filter for implementing accessibility features such
diff --git a/com/android/server/accessibility/AccessibilityManagerService.java b/com/android/server/accessibility/AccessibilityManagerService.java
index d661754..8b5c85a 100644
--- a/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/com/android/server/accessibility/AccessibilityManagerService.java
@@ -82,7 +82,6 @@
import android.view.View;
import android.view.WindowInfo;
import android.view.WindowManager;
-import android.view.WindowManagerInternal;
import android.view.accessibility.AccessibilityCache;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityInteractionClient;
@@ -102,6 +101,7 @@
import com.android.internal.util.IntPair;
import com.android.server.LocalServices;
import com.android.server.policy.AccessibilityShortcutController;
+import com.android.server.wm.WindowManagerInternal;
import org.xmlpull.v1.XmlPullParserException;
@@ -2400,7 +2400,8 @@
private void announceNewUserIfNeeded() {
synchronized (mLock) {
UserState userState = getCurrentUserStateLocked();
- if (userState.isHandlingAccessibilityEvents()) {
+ if (userState.isHandlingAccessibilityEvents()
+ && userState.isObservedEventType(AccessibilityEvent.TYPE_ANNOUNCEMENT)) {
UserManager userManager = (UserManager) mContext.getSystemService(
Context.USER_SERVICE);
String message = mContext.getString(R.string.user_switched,
@@ -3157,13 +3158,21 @@
if (mWindowsForAccessibilityCallback == null) {
return;
}
+ final int userId;
+ synchronized (mLock) {
+ userId = mCurrentUserId;
+ final UserState userState = getUserStateLocked(userId);
+ if (!userState.isObservedEventType(AccessibilityEvent.TYPE_WINDOWS_CHANGED)) {
+ return;
+ }
+ }
final long identity = Binder.clearCallingIdentity();
try {
// Let the client know the windows changed.
AccessibilityEvent event = AccessibilityEvent.obtain(
AccessibilityEvent.TYPE_WINDOWS_CHANGED);
event.setEventTime(SystemClock.uptimeMillis());
- sendAccessibilityEvent(event, mCurrentUserId);
+ sendAccessibilityEvent(event, userId);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -3368,6 +3377,10 @@
mUserId = userId;
}
+ public boolean isObservedEventType(@AccessibilityEvent.EventType int type) {
+ return (mLastSentRelevantEventTypes & type) != 0;
+ }
+
public int getClientState() {
int clientState = 0;
final boolean a11yEnabled = (mUiAutomationManager.isUiAutomationRunningLocked()
diff --git a/com/android/server/accessibility/AccessibilityServiceConnection.java b/com/android/server/accessibility/AccessibilityServiceConnection.java
index eb26752..9cafa1e 100644
--- a/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -33,10 +33,10 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Slog;
-import android.view.WindowManagerInternal;
import com.android.server.accessibility.AccessibilityManagerService.SecurityPolicy;
import com.android.server.accessibility.AccessibilityManagerService.UserState;
+import com.android.server.wm.WindowManagerInternal;
import java.lang.ref.WeakReference;
import java.util.List;
diff --git a/com/android/server/accessibility/GlobalActionPerformer.java b/com/android/server/accessibility/GlobalActionPerformer.java
index 5db6f7d..3b8d4bc 100644
--- a/com/android/server/accessibility/GlobalActionPerformer.java
+++ b/com/android/server/accessibility/GlobalActionPerformer.java
@@ -21,14 +21,18 @@
import android.content.Context;
import android.hardware.input.InputManager;
import android.os.Binder;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemClock;
+import android.view.IWindowManager;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
-import android.view.WindowManagerInternal;
import com.android.server.LocalServices;
import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.wm.WindowManagerInternal;
/**
* Handle the back-end of AccessibilityService#performGlobalAction
@@ -72,6 +76,9 @@
case AccessibilityService.GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN: {
return toggleSplitScreen();
}
+ case AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN: {
+ return lockScreen();
+ }
}
return false;
} finally {
@@ -153,4 +160,11 @@
}
return true;
}
+
+ private boolean lockScreen() {
+ mContext.getSystemService(PowerManager.class).goToSleep(SystemClock.uptimeMillis(),
+ PowerManager.GO_TO_SLEEP_REASON_ACCESSIBILITY, 0);
+ mWindowManagerService.lockNow();
+ return true;
+ }
}
diff --git a/com/android/server/accessibility/KeyEventDispatcher.java b/com/android/server/accessibility/KeyEventDispatcher.java
index 3358432..b144e1c 100644
--- a/com/android/server/accessibility/KeyEventDispatcher.java
+++ b/com/android/server/accessibility/KeyEventDispatcher.java
@@ -26,7 +26,8 @@
import android.util.Slog;
import android.view.InputEventConsistencyVerifier;
import android.view.KeyEvent;
-import android.view.WindowManagerPolicy;
+
+import com.android.server.policy.WindowManagerPolicy;
import java.util.ArrayList;
import java.util.List;
diff --git a/com/android/server/accessibility/KeyboardInterceptor.java b/com/android/server/accessibility/KeyboardInterceptor.java
index 7724945..bc379c2 100644
--- a/com/android/server/accessibility/KeyboardInterceptor.java
+++ b/com/android/server/accessibility/KeyboardInterceptor.java
@@ -22,7 +22,8 @@
import android.util.Pools;
import android.util.Slog;
import android.view.KeyEvent;
-import android.view.WindowManagerPolicy;
+
+import com.android.server.policy.WindowManagerPolicy;
/**
* Intercepts key events and forwards them to accessibility manager service.
diff --git a/com/android/server/accessibility/MagnificationController.java b/com/android/server/accessibility/MagnificationController.java
index a10b7a2..a70b88e 100644
--- a/com/android/server/accessibility/MagnificationController.java
+++ b/com/android/server/accessibility/MagnificationController.java
@@ -34,7 +34,6 @@
import android.util.Slog;
import android.view.MagnificationSpec;
import android.view.View;
-import android.view.WindowManagerInternal;
import android.view.animation.DecelerateInterpolator;
import com.android.internal.R;
@@ -42,6 +41,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
import com.android.server.LocalServices;
+import com.android.server.wm.WindowManagerInternal;
import java.util.Locale;
diff --git a/com/android/server/accessibility/MotionEventInjector.java b/com/android/server/accessibility/MotionEventInjector.java
index b6b7812..46e3226 100644
--- a/com/android/server/accessibility/MotionEventInjector.java
+++ b/com/android/server/accessibility/MotionEventInjector.java
@@ -31,9 +31,9 @@
import android.util.SparseIntArray;
import android.view.InputDevice;
import android.view.MotionEvent;
-import android.view.WindowManagerPolicy;
import com.android.internal.os.SomeArgs;
+import com.android.server.policy.WindowManagerPolicy;
import java.util.ArrayList;
import java.util.Arrays;
diff --git a/com/android/server/accessibility/TouchExplorer.java b/com/android/server/accessibility/TouchExplorer.java
index a32686d..62017e8 100644
--- a/com/android/server/accessibility/TouchExplorer.java
+++ b/com/android/server/accessibility/TouchExplorer.java
@@ -26,11 +26,12 @@
import android.view.MotionEvent.PointerCoords;
import android.view.MotionEvent.PointerProperties;
import android.view.ViewConfiguration;
-import android.view.WindowManagerPolicy;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
+import com.android.server.policy.WindowManagerPolicy;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -790,7 +791,7 @@
*/
private void sendAccessibilityEvent(int type) {
AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mContext);
- if (accessibilityManager.isEnabled()) {
+ if (accessibilityManager.isObservedEventType(type)) {
AccessibilityEvent event = AccessibilityEvent.obtain(type);
event.setWindowId(mAms.getActiveWindowId());
accessibilityManager.sendAccessibilityEvent(event);
diff --git a/com/android/server/accessibility/UiAutomationManager.java b/com/android/server/accessibility/UiAutomationManager.java
index a6b81ff..f057112 100644
--- a/com/android/server/accessibility/UiAutomationManager.java
+++ b/com/android/server/accessibility/UiAutomationManager.java
@@ -26,9 +26,10 @@
import android.os.IBinder.DeathRecipient;
import android.os.RemoteException;
import android.util.Slog;
-import android.view.WindowManagerInternal;
import android.view.accessibility.AccessibilityEvent;
+import com.android.server.wm.WindowManagerInternal;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
diff --git a/com/android/server/am/ActiveServices.java b/com/android/server/am/ActiveServices.java
index 2131731..3cd2f6a 100644
--- a/com/android/server/am/ActiveServices.java
+++ b/com/android/server/am/ActiveServices.java
@@ -58,6 +58,7 @@
import com.android.internal.util.FastPrintWriter;
import com.android.server.am.ActivityManagerService.ItemMatcher;
import com.android.server.am.ActivityManagerService.NeededUriGrants;
+import com.android.server.am.proto.ActiveServicesProto;
import android.app.ActivityManager;
import android.app.AppGlobals;
@@ -85,6 +86,7 @@
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
import android.webkit.WebViewZygote;
public final class ActiveServices {
@@ -633,7 +635,7 @@
sb.append("Stopping service due to app idle: ");
UserHandle.formatUid(sb, service.appInfo.uid);
sb.append(" ");
- TimeUtils.formatDuration(service.createTime
+ TimeUtils.formatDuration(service.createRealTime
- SystemClock.elapsedRealtime(), sb);
sb.append(" ");
sb.append(compName);
@@ -1043,8 +1045,8 @@
try {
if (AppGlobals.getPackageManager().checkPermission(
android.Manifest.permission.INSTANT_APP_FOREGROUND_SERVICE,
- r.appInfo.packageName,
- r.appInfo.uid) != PackageManager.PERMISSION_GRANTED) {
+ r.appInfo.packageName, UserHandle.getUserId(r.appInfo.uid))
+ != PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Instant app " + r.appInfo.packageName
+ " does not have permission to create foreground"
+ "services");
@@ -3220,7 +3222,7 @@
info.uid = r.appInfo.uid;
info.process = r.processName;
info.foreground = r.isForeground;
- info.activeSince = r.createTime;
+ info.activeSince = r.createRealTime;
info.started = r.startRequested;
info.clientCount = r.connections.size();
info.crashCount = r.crashCount;
@@ -3574,7 +3576,7 @@
pw.print(" app=");
pw.println(r.app);
pw.print(" created=");
- TimeUtils.formatDuration(r.createTime, nowReal, pw);
+ TimeUtils.formatDuration(r.createRealTime, nowReal, pw);
pw.print(" started=");
pw.print(r.startRequested);
pw.print(" connections=");
@@ -3840,6 +3842,26 @@
return new ServiceDumper(fd, pw, args, opti, dumpAll, dumpPackage);
}
+ protected void writeToProto(ProtoOutputStream proto) {
+ synchronized (mAm) {
+ int[] users = mAm.mUserController.getUsers();
+ for (int user : users) {
+ ServiceMap smap = mServiceMap.get(user);
+ if (smap == null) {
+ continue;
+ }
+ long token = proto.start(ActiveServicesProto.SERVICES_BY_USERS);
+ proto.write(ActiveServicesProto.ServicesByUser.USER_ID, user);
+ ArrayMap<ComponentName, ServiceRecord> alls = smap.mServicesByName;
+ for (int i=0; i<alls.size(); i++) {
+ alls.valueAt(i).writeToProto(proto,
+ ActiveServicesProto.ServicesByUser.SERVICE_RECORDS);
+ }
+ proto.end(token);
+ }
+ }
+ }
+
/**
* There are three ways to call this:
* - no service specified: dump all the services
diff --git a/com/android/server/am/ActivityDisplay.java b/com/android/server/am/ActivityDisplay.java
index 2289f85..b11b16e 100644
--- a/com/android/server/am/ActivityDisplay.java
+++ b/com/android/server/am/ActivityDisplay.java
@@ -42,6 +42,8 @@
import android.app.ActivityManagerInternal;
import android.app.ActivityOptions;
import android.app.WindowConfiguration;
+import android.graphics.Point;
+import android.graphics.Rect;
import android.util.IntArray;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
@@ -89,6 +91,9 @@
private ActivityStack mPinnedStack = null;
private ActivityStack mSplitScreenPrimaryStack = null;
+ // Used in updating the display size
+ private Point mTmpDisplaySize = new Point();
+
ActivityDisplay(ActivityStackSupervisor supervisor, int displayId) {
mSupervisor = supervisor;
mDisplayId = displayId;
@@ -97,6 +102,13 @@
throw new IllegalStateException("Display does not exist displayId=" + displayId);
}
mDisplay = display;
+
+ updateBounds();
+ }
+
+ void updateBounds() {
+ mDisplay.getSize(mTmpDisplaySize);
+ setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y);
}
void addChild(ActivityStack stack, int position) {
@@ -387,6 +399,16 @@
otherStack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
}
} finally {
+ if (mHomeStack != null && !isTopStack(mHomeStack)) {
+ // Whenever split-screen is dismissed we want the home stack directly behind the
+ // currently top stack so it shows up when the top stack is finished.
+ final ActivityStack topStack = getTopStack();
+ // TODO: Would be better to use ActivityDisplay.positionChildAt() for this, however
+ // ActivityDisplay doesn't have a direct controller to WM side yet. We can switch
+ // once we have that.
+ mHomeStack.moveToFront("onSplitScreenModeDismissed");
+ topStack.moveToFront("onSplitScreenModeDismissed");
+ }
mSupervisor.mWindowManager.continueSurfaceLayout();
}
}
diff --git a/com/android/server/am/ActivityManagerService.java b/com/android/server/am/ActivityManagerService.java
index 4361856..fe992da 100644
--- a/com/android/server/am/ActivityManagerService.java
+++ b/com/android/server/am/ActivityManagerService.java
@@ -246,6 +246,7 @@
import android.app.assist.AssistContent;
import android.app.assist.AssistStructure;
import android.app.backup.IBackupManager;
+import android.app.servertransaction.ConfigurationChangeItem;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManagerInternal;
import android.appwidget.AppWidgetManager;
@@ -317,6 +318,7 @@
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.PowerManager;
+import android.os.PowerManager.ServiceType;
import android.os.PowerManagerInternal;
import android.os.Process;
import android.os.RemoteCallbackList;
@@ -351,7 +353,6 @@
import android.util.StatsLog;
import android.util.TimingsTraceLog;
import android.util.DebugUtils;
-import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
import android.util.Pair;
@@ -415,6 +416,8 @@
import com.android.server.am.ActivityStack.ActivityState;
import com.android.server.am.proto.ActivityManagerServiceProto;
import com.android.server.am.proto.BroadcastProto;
+import com.android.server.am.proto.GrantUriProto;
+import com.android.server.am.proto.NeededUriGrantsProto;
import com.android.server.am.proto.StickyBroadcastProto;
import com.android.server.firewall.IntentFirewall;
import com.android.server.job.JobSchedulerInternal;
@@ -630,6 +633,8 @@
final ActivityStarter mActivityStarter;
+ final ClientLifecycleManager mLifecycleManager;
+
final TaskChangeNotificationController mTaskChangeNotificationController;
final InstrumentationReporter mInstrumentationReporter = new InstrumentationReporter();
@@ -712,8 +717,16 @@
final UserController mUserController;
+ /**
+ * Packages that are being allowed to perform unrestricted app switches. Mapping is
+ * User -> Type -> uid.
+ */
+ final SparseArray<ArrayMap<String, Integer>> mAllowAppSwitchUids = new SparseArray<>();
+
final AppErrors mAppErrors;
+ final AppWarnings mAppWarnings;
+
/**
* Dump of the activity state at the time of the last ANR. Cleared after
* {@link WindowManagerService#LAST_ANR_LIFETIME_DURATION_MSECS}
@@ -1191,6 +1204,13 @@
return result;
}
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ long token = proto.start(fieldId);
+ proto.write(GrantUriProto.URI, uri.toString());
+ proto.write(GrantUriProto.SOURCE_USER_ID, sourceUserId);
+ proto.end(token);
+ }
+
public static GrantUri resolve(int defaultSourceUserHandle, Uri uri) {
return new GrantUri(ContentProvider.getUserIdFromUri(uri, defaultSourceUserHandle),
ContentProvider.getUriWithoutUserId(uri), false);
@@ -1717,7 +1737,6 @@
static final int IDLE_UIDS_MSG = 58;
static final int LOG_STACK_STATE = 60;
static final int VR_MODE_CHANGE_MSG = 61;
- static final int SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG = 62;
static final int HANDLE_TRUST_STORAGE_UPDATE_MSG = 63;
static final int NOTIFY_VR_SLEEPING_MSG = 65;
static final int SERVICE_FOREGROUND_TIMEOUT_MSG = 66;
@@ -1736,7 +1755,6 @@
static KillHandler sKillHandler = null;
CompatModeDialog mCompatModeDialog;
- UnsupportedDisplaySizeDialog mUnsupportedDisplaySizeDialog;
long mLastMemUsageReportTime = 0;
/**
@@ -1764,6 +1782,11 @@
final boolean mPermissionReviewRequired;
+ /**
+ * Whether to force background check on all apps (for battery saver) or not.
+ */
+ boolean mForceBackgroundCheck;
+
private static String sTheRealBuildSerial = Build.UNKNOWN;
/**
@@ -1918,23 +1941,6 @@
}
break;
}
- case SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG: {
- synchronized (ActivityManagerService.this) {
- final ActivityRecord ar = (ActivityRecord) msg.obj;
- if (mUnsupportedDisplaySizeDialog != null) {
- mUnsupportedDisplaySizeDialog.dismiss();
- mUnsupportedDisplaySizeDialog = null;
- }
- if (ar != null && mCompatModePackages.getPackageNotifyUnsupportedZoomLocked(
- ar.packageName)) {
- // TODO(multi-display): Show dialog on appropriate display.
- mUnsupportedDisplaySizeDialog = new UnsupportedDisplaySizeDialog(
- ActivityManagerService.this, mUiContext, ar.info.applicationInfo);
- mUnsupportedDisplaySizeDialog.show();
- }
- }
- break;
- }
case DISMISS_DIALOG_UI_MSG: {
final Dialog d = (Dialog) msg.obj;
d.dismiss();
@@ -2503,7 +2509,7 @@
DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO);
ServiceManager.addService(ProcessStats.SERVICE_NAME, mProcessStats);
ServiceManager.addService("meminfo", new MemBinder(this), /* allowIsolated= */ false,
- DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL);
+ DUMP_FLAG_PRIORITY_HIGH);
ServiceManager.addService("gfxinfo", new GraphicsBinder(this));
ServiceManager.addService("dbinfo", new DbBinder(this));
if (MONITOR_CPU_USAGE) {
@@ -2561,9 +2567,15 @@
private final PriorityDump.PriorityDumper mPriorityDumper =
new PriorityDump.PriorityDumper() {
@Override
- public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args,
+ public void dumpHigh(FileDescriptor fd, PrintWriter pw, String[] args,
boolean asProto) {
if (asProto) return;
+ mActivityManagerService.dumpApplicationMemoryUsage(fd,
+ pw, " ", new String[] {"-a"}, false, null);
+ }
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
+ if (asProto) return;
mActivityManagerService.dumpApplicationMemoryUsage(fd, pw, " ", args, false, null);
}
};
@@ -2667,6 +2679,7 @@
GL_ES_VERSION = 0;
mActivityStarter = null;
mAppErrors = null;
+ mAppWarnings = null;
mAppOpsService = mInjector.getAppOpsService(null, null);
mBatteryStatsService = null;
mCompatModePackages = null;
@@ -2689,6 +2702,7 @@
mUserController = null;
mVrController = null;
mLockTaskController = null;
+ mLifecycleManager = null;
}
// Note: This method is invoked on the main thread but may need to attach various
@@ -2734,10 +2748,13 @@
mProviderMap = new ProviderMap(this);
mAppErrors = new AppErrors(mUiContext, this);
- // TODO: Move creation of battery stats service outside of activity manager service.
File dataDir = Environment.getDataDirectory();
File systemDir = new File(dataDir, "system");
systemDir.mkdirs();
+
+ mAppWarnings = new AppWarnings(this, mUiContext, mHandler, mUiHandler, systemDir);
+
+ // TODO: Move creation of battery stats service outside of activity manager service.
mBatteryStatsService = new BatteryStatsService(systemContext, systemDir, mHandler);
mBatteryStatsService.getActiveStatistics().readLocked();
mBatteryStatsService.scheduleWriteToDisk();
@@ -2784,10 +2801,11 @@
mIntentFirewall = new IntentFirewall(new IntentFirewallInterface(), mHandler);
mTaskChangeNotificationController =
new TaskChangeNotificationController(this, mStackSupervisor, mHandler);
- mActivityStarter = new ActivityStarter(this, AppGlobals.getPackageManager());
+ mActivityStarter = new ActivityStarter(this);
mRecentTasks = createRecentTasks();
mStackSupervisor.setRecentTasks(mRecentTasks);
mLockTaskController = new LockTaskController(mContext, mStackSupervisor, mHandler);
+ mLifecycleManager = new ClientLifecycleManager();
mProcessCpuThread = new Thread("CpuTracker") {
@Override
@@ -2871,6 +2889,7 @@
void onUserStoppedLocked(int userId) {
mRecentTasks.unloadUserDataFromMemoryLocked(userId);
+ mAllowAppSwitchUids.remove(userId);
}
public void initPowerManagement() {
@@ -3301,22 +3320,25 @@
mUiHandler.sendMessage(msg);
}
- final void showUnsupportedZoomDialogIfNeededLocked(ActivityRecord r) {
- final Configuration globalConfig = getGlobalConfiguration();
- if (globalConfig.densityDpi != DisplayMetrics.DENSITY_DEVICE_STABLE
- && r.appInfo.requiresSmallestWidthDp > globalConfig.smallestScreenWidthDp) {
- final Message msg = Message.obtain();
- msg.what = SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG;
- msg.obj = r;
- mUiHandler.sendMessage(msg);
- }
+ final AppWarnings getAppWarningsLocked() {
+ return mAppWarnings;
+ }
+
+ /**
+ * Shows app warning dialogs, if necessary.
+ *
+ * @param r activity record for which the warnings may be displayed
+ */
+ final void showAppWarningsIfNeededLocked(ActivityRecord r) {
+ mAppWarnings.showUnsupportedCompileSdkDialogIfNeeded(r);
+ mAppWarnings.showUnsupportedDisplaySizeDialogIfNeeded(r);
}
private int updateLruProcessInternalLocked(ProcessRecord app, long now, int index,
String what, Object obj, ProcessRecord srcApp) {
app.lastActivityTime = now;
- if (app.activities.size() > 0) {
+ if (app.activities.size() > 0 || app.recentTasks.size() > 0) {
// Don't want to touch dependent processes that are hosting activities.
return index;
}
@@ -3380,7 +3402,7 @@
final void updateLruProcessLocked(ProcessRecord app, boolean activityChange,
ProcessRecord client) {
final boolean hasActivity = app.activities.size() > 0 || app.hasClientActivities
- || app.treatLikeActivity;
+ || app.treatLikeActivity || app.recentTasks.size() > 0;
final boolean hasService = false; // not impl yet. app.services.size() > 0;
if (!activityChange && hasActivity) {
// The process has activities, so we are only allowing activity-based adjustments
@@ -3484,7 +3506,8 @@
int nextIndex;
if (hasActivity) {
final int N = mLruProcesses.size();
- if (app.activities.size() == 0 && mLruProcessActivityStart < (N - 1)) {
+ if ((app.activities.size() == 0 || app.recentTasks.size() > 0)
+ && mLruProcessActivityStart < (N - 1)) {
// Process doesn't have activities, but has clients with
// activities... move it up, but one below the top (the top
// should always have a real activity).
@@ -5116,7 +5139,8 @@
// because we don't support returning them across task boundaries. Also, to
// keep backwards compatibility we remove the task from recents when finishing
// task with root activity.
- res = mStackSupervisor.removeTaskByIdLocked(tr.taskId, false, finishWithRootActivity);
+ res = mStackSupervisor.removeTaskByIdLocked(tr.taskId, false,
+ finishWithRootActivity, "finish-activity");
if (!res) {
Slog.i(TAG, "Removing task failed to finish activity");
}
@@ -5321,6 +5345,8 @@
// Remove this application's activities from active lists.
boolean hasVisibleActivities = mStackSupervisor.handleAppDiedLocked(app);
+ app.clearRecentTasks();
+
app.activities.clear();
if (app.instr != null) {
@@ -8091,7 +8117,7 @@
return false;
}
// An activity is consider to be in multi-window mode if its task isn't fullscreen.
- return !r.getTask().mFullscreen;
+ return r.inMultiWindowMode();
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -8600,6 +8626,16 @@
}
switch (appop) {
case AppOpsManager.MODE_ALLOWED:
+ // If force-background-check is enabled, restrict all apps that aren't whitelisted.
+ if (mForceBackgroundCheck &&
+ UserHandle.isApp(uid) &&
+ !isOnDeviceIdleWhitelistLocked(uid)) {
+ if (DEBUG_BACKGROUND_CHECK) {
+ Slog.i(TAG, "Force background check: " +
+ uid + "/" + packageName + " restricted");
+ }
+ return ActivityManager.APP_START_MODE_DELAYED;
+ }
return ActivityManager.APP_START_MODE_NORMAL;
case AppOpsManager.MODE_IGNORED:
return ActivityManager.APP_START_MODE_DELAYED;
@@ -8699,6 +8735,9 @@
return ActivityManager.APP_START_MODE_NORMAL;
}
+ /**
+ * @return whether a UID is in the system, user or temp doze whitelist.
+ */
boolean isOnDeviceIdleWhitelistLocked(int uid) {
final int appId = UserHandle.getAppId(uid);
return Arrays.binarySearch(mDeviceIdleWhitelist, appId) >= 0
@@ -9044,6 +9083,19 @@
this.targetUid = targetUid;
this.flags = flags;
}
+
+ void writeToProto(ProtoOutputStream proto, long fieldId) {
+ long token = proto.start(fieldId);
+ proto.write(NeededUriGrantsProto.TARGET_PACKAGE, targetPkg);
+ proto.write(NeededUriGrantsProto.TARGET_UID, targetUid);
+ proto.write(NeededUriGrantsProto.FLAGS, flags);
+
+ final int N = this.size();
+ for (int i=0; i<N; i++) {
+ this.get(i).writeToProto(proto, NeededUriGrantsProto.GRANTS);
+ }
+ proto.end(token);
+ }
}
/**
@@ -10097,8 +10149,8 @@
} else {
// Task isn't in window manager yet since it isn't associated with a stack.
// Return the persist value from activity manager
- if (task.mBounds != null) {
- rect.set(task.mBounds);
+ if (!task.matchParentBounds()) {
+ rect.set(task.getBounds());
} else if (task.mLastNonFullscreenBounds != null) {
rect.set(task.mLastNonFullscreenBounds);
}
@@ -10279,7 +10331,8 @@
synchronized (this) {
final long ident = Binder.clearCallingIdentity();
try {
- return mStackSupervisor.removeTaskByIdLocked(taskId, true, REMOVE_FROM_RECENTS);
+ return mStackSupervisor.removeTaskByIdLocked(taskId, true, REMOVE_FROM_RECENTS,
+ "remove-task");
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -10469,12 +10522,12 @@
+ " non-standard task " + taskId + " to windowing mode="
+ windowingMode);
}
- final ActivityDisplay display = task.getStack().getDisplay();
- final ActivityStack stack = display.getOrCreateStack(windowingMode,
- task.getStack().getActivityType(), toTop);
- // TODO: Use ActivityStack.setWindowingMode instead of re-parenting.
- task.reparent(stack, toTop, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, !DEFER_RESUME,
- "moveTaskToStack");
+
+ final ActivityStack stack = task.getStack();
+ if (toTop) {
+ stack.moveToFront("setTaskWindowingMode", task);
+ }
+ stack.setWindowingMode(windowingMode);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -11693,6 +11746,15 @@
return true;
}
+ /**
+ * Returns the PackageManager. Used by classes hosted by {@link ActivityManagerService}. The
+ * PackageManager could be unavailable at construction time and therefore needs to be accessed
+ * on demand.
+ */
+ IPackageManager getPackageManager() {
+ return AppGlobals.getPackageManager();
+ }
+
PackageManagerInternal getPackageManagerInternalLocked() {
if (mPackageManagerInt == null) {
mPackageManagerInt = LocalServices.getService(PackageManagerInternal.class);
@@ -12582,6 +12644,18 @@
}
}
+ boolean checkAllowAppSwitchUid(int uid) {
+ ArrayMap<String, Integer> types = mAllowAppSwitchUids.get(UserHandle.getUserId(uid));
+ if (types != null) {
+ for (int i = types.size() - 1; i >= 0; i--) {
+ if (types.valueAt(i).intValue() == uid) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
boolean checkAppSwitchAllowedLocked(int sourcePid, int sourceUid,
int callingPid, int callingUid, String name) {
if (mAppSwitchesAllowedTime < SystemClock.uptimeMillis()) {
@@ -12594,6 +12668,9 @@
if (perm == PackageManager.PERMISSION_GRANTED) {
return true;
}
+ if (checkAllowAppSwitchUid(sourceUid)) {
+ return true;
+ }
// If the actual IPC caller is different from the logical source, then
// also see if they are allowed to control app switches.
@@ -12604,6 +12681,9 @@
if (perm == PackageManager.PERMISSION_GRANTED) {
return true;
}
+ if (checkAllowAppSwitchUid(callingUid)) {
+ return true;
+ }
}
Slog.w(TAG, name + " request from " + sourceUid + " stopped");
@@ -14115,6 +14195,16 @@
readGrantedUriPermissionsLocked();
}
+ final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class);
+ if (pmi != null) {
+ pmi.registerLowPowerModeObserver(ServiceType.FORCE_BACKGROUND_CHECK,
+ state -> updateForceBackgroundCheck(state.batterySaverEnabled));
+ updateForceBackgroundCheck(
+ pmi.getLowPowerState(ServiceType.FORCE_BACKGROUND_CHECK).batterySaverEnabled);
+ } else {
+ Slog.wtf(TAG, "PowerManagerInternal not found.");
+ }
+
if (goingCallback != null) goingCallback.run();
traceLog.traceBegin("ActivityManagerStartApps");
mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_USER_RUNNING_START,
@@ -14213,6 +14303,23 @@
}
}
+ private void updateForceBackgroundCheck(boolean enabled) {
+ synchronized (this) {
+ if (mForceBackgroundCheck != enabled) {
+ mForceBackgroundCheck = enabled;
+
+ if (DEBUG_BACKGROUND_CHECK) {
+ Slog.i(TAG, "Force background check " + (enabled ? "enabled" : "disabled"));
+ }
+
+ if (mForceBackgroundCheck) {
+ // Stop background services for idle UIDs.
+ doStopUidForIdleUidsLocked();
+ }
+ }
+ }
+ }
+
void killAppAtUsersRequest(ProcessRecord app, Dialog fromDialog) {
synchronized (this) {
mAppErrors.killAppAtUserRequestLocked(app, fromDialog);
@@ -14544,6 +14651,18 @@
final String dropboxTag = processClass(process) + "_" + eventType;
if (dbox == null || !dbox.isTagEnabled(dropboxTag)) return;
+ // Log to StatsLog before the rate-limiting.
+ // The logging below is adapated from appendDropboxProcessHeaders.
+ StatsLog.write(StatsLog.DROPBOX_ERROR_CHANGED,
+ process != null ? process.uid : -1,
+ dropboxTag,
+ processName,
+ process != null ? process.pid : -1,
+ (process != null && process.info != null) ?
+ (process.info.isInstantApp() ? 1 : 0) : -1,
+ activity != null ? activity.shortComponentName : null,
+ process != null ? (process.isInterestingToUserLocked() ? 1 : 0) : -1);
+
// Rate-limit how often we're willing to do the heavy lifting below to
// collect and record logs; currently 5 logs per 10 second period.
final long now = SystemClock.elapsedRealtime();
@@ -14868,6 +14987,7 @@
boolean dumpVisibleStacksOnly = false;
boolean dumpFocusedStackOnly = false;
String dumpPackage = null;
+ int dumpAppId = -1;
int opti = 0;
while (opti < args.length) {
@@ -14922,6 +15042,25 @@
synchronized (this) {
writeBroadcastsToProtoLocked(proto);
}
+ } else if ("provider".equals(cmd)) {
+ String[] newArgs;
+ String name;
+ if (opti >= args.length) {
+ name = null;
+ newArgs = EMPTY_STRING_ARRAY;
+ } else {
+ name = args[opti];
+ opti++;
+ newArgs = new String[args.length - opti];
+ if (args.length > 2) System.arraycopy(args, opti, newArgs, 0,
+ args.length - opti);
+ }
+ if (!dumpProviderProto(fd, pw, name, newArgs)) {
+ pw.println("No providers match: " + name);
+ pw.println("Use -h for help.");
+ }
+ } else if ("service".equals(cmd)) {
+ mServices.writeToProto(proto);
} else {
// default option, dump everything, output is ActivityManagerServiceProto
synchronized (this) {
@@ -14932,6 +15071,10 @@
long broadcastToken = proto.start(ActivityManagerServiceProto.BROADCASTS);
writeBroadcastsToProtoLocked(proto);
proto.end(broadcastToken);
+
+ long serviceToken = proto.start(ActivityManagerServiceProto.SERVICES);
+ mServices.writeToProto(proto);
+ proto.end(serviceToken);
}
}
proto.flush();
@@ -14939,6 +15082,16 @@
return;
}
+ if (dumpPackage != null) {
+ try {
+ ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(
+ dumpPackage, 0);
+ dumpAppId = UserHandle.getAppId(info.uid);
+ } catch (NameNotFoundException e) {
+ e.printStackTrace();
+ }
+ }
+
boolean more = false;
// Is the caller requesting to dump a particular piece of data?
if (opti < args.length) {
@@ -15046,7 +15199,7 @@
args.length - opti);
}
synchronized (this) {
- dumpProcessesLocked(fd, pw, args, opti, true, dumpPackage);
+ dumpProcessesLocked(fd, pw, args, opti, true, dumpPackage, dumpAppId);
}
} else if ("oom".equals(cmd) || "o".equals(cmd)) {
synchronized (this) {
@@ -15232,7 +15385,7 @@
if (dumpAll) {
pw.println("-------------------------------------------------------------------------------");
}
- dumpProcessesLocked(fd, pw, args, opti, dumpAll, dumpPackage);
+ dumpProcessesLocked(fd, pw, args, opti, dumpAll, dumpPackage, dumpAppId);
}
} else {
@@ -15309,7 +15462,7 @@
if (dumpAll) {
pw.println("-------------------------------------------------------------------------------");
}
- dumpProcessesLocked(fd, pw, args, opti, dumpAll, dumpPackage);
+ dumpProcessesLocked(fd, pw, args, opti, dumpAll, dumpPackage, dumpAppId);
}
}
Binder.restoreCallingIdentity(origId);
@@ -15464,22 +15617,12 @@
}
}
- boolean dumpUids(PrintWriter pw, String dumpPackage, SparseArray<UidRecord> uids,
+ boolean dumpUids(PrintWriter pw, String dumpPackage, int dumpAppId, SparseArray<UidRecord> uids,
String header, boolean needSep) {
boolean printed = false;
- int whichAppId = -1;
- if (dumpPackage != null) {
- try {
- ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(
- dumpPackage, 0);
- whichAppId = UserHandle.getAppId(info.uid);
- } catch (NameNotFoundException e) {
- e.printStackTrace();
- }
- }
for (int i=0; i<uids.size(); i++) {
UidRecord uidRec = uids.valueAt(i);
- if (dumpPackage != null && UserHandle.getAppId(uidRec.uid) != whichAppId) {
+ if (dumpPackage != null && UserHandle.getAppId(uidRec.uid) != dumpAppId) {
continue;
}
if (!printed) {
@@ -15526,9 +15669,8 @@
}
void dumpProcessesLocked(FileDescriptor fd, PrintWriter pw, String[] args,
- int opti, boolean dumpAll, String dumpPackage) {
+ int opti, boolean dumpAll, String dumpPackage, int dumpAppId) {
boolean needSep = false;
- boolean printedAnything = false;
int numPers = 0;
pw.println("ACTIVITY MANAGER RUNNING PROCESSES (dumpsys activity processes)");
@@ -15546,7 +15688,6 @@
if (!needSep) {
pw.println(" All known processes:");
needSep = true;
- printedAnything = true;
}
pw.print(r.persistent ? " *PERS*" : " *APP*");
pw.print(" UID "); pw.print(procs.keyAt(ia));
@@ -15571,7 +15712,6 @@
pw.println();
}
pw.println(" Isolated process list (sorted by uid):");
- printedAnything = true;
printed = true;
needSep = true;
}
@@ -15593,7 +15733,6 @@
pw.println();
}
pw.println(" Active instrumentation:");
- printedAnything = true;
printed = true;
needSep = true;
}
@@ -15604,14 +15743,15 @@
}
if (mActiveUids.size() > 0) {
- if (dumpUids(pw, dumpPackage, mActiveUids, "UID states:", needSep)) {
- printedAnything = needSep = true;
+ if (dumpUids(pw, dumpPackage, dumpAppId, mActiveUids, "UID states:", needSep)) {
+ needSep = true;
}
}
if (dumpAll) {
if (mValidateUids.size() > 0) {
- if (dumpUids(pw, dumpPackage, mValidateUids, "UID validation:", needSep)) {
- printedAnything = needSep = true;
+ if (dumpUids(pw, dumpPackage, dumpAppId, mValidateUids, "UID validation:",
+ needSep)) {
+ needSep = true;
}
}
}
@@ -15628,7 +15768,6 @@
pw.println("):");
dumpProcessOomList(pw, this, mLruProcesses, " ", "Proc", "PERS", false, dumpPackage);
needSep = true;
- printedAnything = true;
}
if (dumpAll || dumpPackage != null) {
@@ -15644,7 +15783,6 @@
needSep = true;
pw.println(" PID mappings:");
printed = true;
- printedAnything = true;
}
pw.print(" PID #"); pw.print(mPidsSelfLocked.keyAt(i));
pw.print(": "); pw.println(mPidsSelfLocked.valueAt(i));
@@ -15667,7 +15805,6 @@
needSep = true;
pw.println(" Foreground Processes:");
printed = true;
- printedAnything = true;
}
pw.print(" PID #"); pw.print(mImportantProcesses.keyAt(i));
pw.print(": "); pw.println(mImportantProcesses.valueAt(i));
@@ -15678,7 +15815,6 @@
if (mPersistentStartingProcesses.size() > 0) {
if (needSep) pw.println();
needSep = true;
- printedAnything = true;
pw.println(" Persisent processes that are starting:");
dumpProcessList(pw, this, mPersistentStartingProcesses, " ",
"Starting Norm", "Restarting PERS", dumpPackage);
@@ -15687,7 +15823,6 @@
if (mRemovedProcesses.size() > 0) {
if (needSep) pw.println();
needSep = true;
- printedAnything = true;
pw.println(" Processes that are being removed:");
dumpProcessList(pw, this, mRemovedProcesses, " ",
"Removed Norm", "Removed PERS", dumpPackage);
@@ -15696,7 +15831,6 @@
if (mProcessesOnHold.size() > 0) {
if (needSep) pw.println();
needSep = true;
- printedAnything = true;
pw.println(" Processes that are on old until the system is ready:");
dumpProcessList(pw, this, mProcessesOnHold, " ",
"OnHold Norm", "OnHold PERS", dumpPackage);
@@ -15705,9 +15839,6 @@
needSep = dumpProcessesToGc(fd, pw, args, opti, needSep, dumpAll, dumpPackage);
needSep = mAppErrors.dumpLocked(fd, pw, needSep, dumpPackage);
- if (needSep) {
- printedAnything = true;
- }
if (dumpPackage == null) {
pw.println();
@@ -15914,6 +16045,32 @@
pw.println(" mNativeDebuggingApp=" + mNativeDebuggingApp);
}
}
+ if (mAllowAppSwitchUids.size() > 0) {
+ boolean printed = false;
+ for (int i = 0; i < mAllowAppSwitchUids.size(); i++) {
+ ArrayMap<String, Integer> types = mAllowAppSwitchUids.valueAt(i);
+ for (int j = 0; j < types.size(); j++) {
+ if (dumpPackage == null ||
+ UserHandle.getAppId(types.valueAt(j).intValue()) == dumpAppId) {
+ if (needSep) {
+ pw.println();
+ needSep = false;
+ }
+ if (!printed) {
+ pw.println(" mAllowAppSwitchUids:");
+ printed = true;
+ }
+ pw.print(" User ");
+ pw.print(mAllowAppSwitchUids.keyAt(i));
+ pw.print(": Type ");
+ pw.print(types.keyAt(j));
+ pw.print(" = ");
+ UserHandle.formatUid(pw, types.valueAt(j).intValue());
+ pw.println();
+ }
+ }
+ }
+ }
if (dumpPackage == null) {
if (mAlwaysFinishActivities) {
pw.println(" mAlwaysFinishActivities=" + mAlwaysFinishActivities);
@@ -15953,10 +16110,7 @@
pw.println();
}
}
-
- if (!printedAnything) {
- pw.println(" (nothing)");
- }
+ pw.println(" mForceBackgroundCheck=" + mForceBackgroundCheck);
}
boolean dumpProcessesToGc(FileDescriptor fd, PrintWriter pw, String[] args,
@@ -16063,6 +16217,15 @@
return mProviderMap.dumpProvider(fd, pw, name, args, opti, dumpAll);
}
+ /**
+ * Similar to the dumpProvider, but only dumps the first matching provider.
+ * The provider is responsible for dumping as proto.
+ */
+ protected boolean dumpProviderProto(FileDescriptor fd, PrintWriter pw, String name,
+ String[] args) {
+ return mProviderMap.dumpProviderProto(fd, pw, name, args);
+ }
+
static class ItemMatcher {
ArrayList<ComponentName> components;
ArrayList<String> strings;
@@ -17883,7 +18046,7 @@
PrintWriter catPw = new FastPrintWriter(catSw, false, 256);
String[] emptyArgs = new String[] { };
catPw.println();
- dumpProcessesLocked(null, catPw, emptyArgs, 0, false, null);
+ dumpProcessesLocked(null, catPw, emptyArgs, 0, false, null, -1);
catPw.println();
mServices.newServiceDumperLocked(null, catPw, emptyArgs, 0,
false, null).dumpLocked();
@@ -19296,13 +19459,7 @@
mRecentTasks.removeTasksByPackageName(ssp, userId);
mServices.forceStopPackageLocked(ssp, userId);
-
- // Hide the "unsupported display" dialog if necessary.
- if (mUnsupportedDisplaySizeDialog != null && ssp.equals(
- mUnsupportedDisplaySizeDialog.getPackageName())) {
- mUnsupportedDisplaySizeDialog.dismiss();
- mUnsupportedDisplaySizeDialog = null;
- }
+ mAppWarnings.onPackageUninstalled(ssp);
mCompatModePackages.handlePackageUninstalledLocked(ssp);
mBatteryStatsService.notePackageUninstalled(ssp);
}
@@ -19381,13 +19538,8 @@
Uri data = intent.getData();
String ssp;
if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
- // Hide the "unsupported display" dialog if necessary.
- if (mUnsupportedDisplaySizeDialog != null && ssp.equals(
- mUnsupportedDisplaySizeDialog.getPackageName())) {
- mUnsupportedDisplaySizeDialog.dismiss();
- mUnsupportedDisplaySizeDialog = null;
- }
mCompatModePackages.handlePackageDataClearedLocked(ssp);
+ mAppWarnings.onPackageDataCleared(ssp);
}
break;
}
@@ -20447,9 +20599,11 @@
if (app.thread != null) {
if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Sending to proc "
+ app.processName + " new config " + configCopy);
- app.thread.scheduleConfigurationChanged(configCopy);
+ mLifecycleManager.scheduleTransaction(app.thread,
+ new ConfigurationChangeItem(configCopy));
}
} catch (Exception e) {
+ Slog.e(TAG_CONFIGURATION, "Failed to schedule configuration change", e);
}
}
@@ -20577,8 +20731,7 @@
final boolean isDensityChange = (changes & ActivityInfo.CONFIG_DENSITY) != 0;
if (isDensityChange && displayId == DEFAULT_DISPLAY) {
- // Reset the unsupported display size dialog.
- mUiHandler.sendEmptyMessage(SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG);
+ mAppWarnings.onDensityChanged();
killAllBackgroundProcessesExcept(N,
ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
@@ -20959,7 +21112,7 @@
+ " instead of expected " + app);
if (r.app == null || (r.app.uid == app.uid)) {
// Only fix things up when they look sane
- r.app = app;
+ r.setProcess(app);
} else {
continue;
}
@@ -21038,6 +21191,11 @@
adj += minLayer;
}
}
+ if (procState > ActivityManager.PROCESS_STATE_CACHED_RECENT && app.recentTasks.size() > 0) {
+ procState = ActivityManager.PROCESS_STATE_CACHED_RECENT;
+ app.adjType = "cch-rec";
+ if (DEBUG_OOM_ADJ_REASON) Slog.d(TAG, "Raise to cached recent: " + app);
+ }
if (adj > ProcessList.PERCEPTIBLE_APP_ADJ
|| procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
@@ -22598,6 +22756,7 @@
switch (app.curProcState) {
case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY:
case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
+ case ActivityManager.PROCESS_STATE_CACHED_RECENT:
// This process is a cached process holding activities...
// assign it the next cached value for that type, and then
// step that cached level.
@@ -23212,6 +23371,24 @@
}
}
+ /**
+ * Call {@link #doStopUidLocked} (which will also stop background services) for all idle UIDs.
+ */
+ void doStopUidForIdleUidsLocked() {
+ final int size = mActiveUids.size();
+ for (int i = 0; i < size; i++) {
+ final int uid = mActiveUids.keyAt(i);
+ if (!UserHandle.isApp(uid)) {
+ continue;
+ }
+ final UidRecord uidRec = mActiveUids.valueAt(i);
+ if (!uidRec.idle) {
+ continue;
+ }
+ doStopUidLocked(uidRec.uid, uidRec);
+ }
+ }
+
final void doStopUidLocked(int uid, final UidRecord uidRec) {
mServices.stopInBackgroundLocked(uid);
enqueueUidChangeLocked(uidRec, uid, UidRecord.CHANGE_IDLE);
@@ -23322,7 +23499,7 @@
// has been removed.
for (i=mRemovedProcesses.size()-1; i>=0; i--) {
final ProcessRecord app = mRemovedProcesses.get(i);
- if (app.activities.size() == 0
+ if (app.activities.size() == 0 && app.recentTasks.size() == 0
&& app.curReceivers.isEmpty() && app.services.size() == 0) {
Slog.i(
TAG, "Exiting empty application process "
@@ -24189,6 +24366,32 @@
}
}
}
+
+ @Override
+ public void setAllowAppSwitches(@NonNull String type, int uid, int userId) {
+ synchronized (ActivityManagerService.this) {
+ if (mUserController.isUserRunning(userId, ActivityManager.FLAG_OR_STOPPED)) {
+ ArrayMap<String, Integer> types = mAllowAppSwitchUids.get(userId);
+ if (types == null) {
+ if (uid < 0) {
+ return;
+ }
+ types = new ArrayMap<>();
+ mAllowAppSwitchUids.put(userId, types);
+ }
+ if (uid < 0) {
+ types.remove(type);
+ } else {
+ types.put(type, uid);
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean isRuntimeRestarted() {
+ return mSystemServiceManager.isRuntimeRestarted();
+ }
}
/**
diff --git a/com/android/server/am/ActivityRecord.java b/com/android/server/am/ActivityRecord.java
index 9a16745..a089e6c 100644
--- a/com/android/server/am/ActivityRecord.java
+++ b/com/android/server/am/ActivityRecord.java
@@ -79,7 +79,6 @@
import static android.os.Build.VERSION_CODES.O;
import static android.os.Process.SYSTEM_UID;
import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
-import static android.view.WindowManagerPolicy.NAV_BAR_LEFT;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CONFIGURATION;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SAVED_STATE;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STATES;
@@ -117,6 +116,7 @@
import static com.android.server.am.proto.ActivityRecordProto.PROC_ID;
import static com.android.server.am.proto.ActivityRecordProto.STATE;
import static com.android.server.am.proto.ActivityRecordProto.VISIBLE;
+import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_LEFT;
import static com.android.server.wm.proto.IdentifierProto.HASH_CODE;
import static com.android.server.wm.proto.IdentifierProto.TITLE;
import static com.android.server.wm.proto.IdentifierProto.USER_ID;
@@ -131,6 +131,12 @@
import android.app.PendingIntent;
import android.app.PictureInPictureParams;
import android.app.ResultInfo;
+import android.app.servertransaction.MoveToDisplayItem;
+import android.app.servertransaction.MultiWindowModeChangeItem;
+import android.app.servertransaction.NewIntentItem;
+import android.app.servertransaction.PipModeChangeItem;
+import android.app.servertransaction.WindowVisibilityItem;
+import android.app.servertransaction.ActivityConfigurationChangeItem;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -165,7 +171,6 @@
import android.view.WindowManager.LayoutParams;
import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ResolverActivity;
import com.android.internal.content.ReferrerIntent;
import com.android.internal.util.XmlUtils;
@@ -343,12 +348,6 @@
// on the window.
int mRotationAnimationHint = -1;
- // The bounds of this activity. Mainly used for aspect-ratio compatibility.
- // TODO(b/36505427): Every level on ConfigurationContainer now has bounds information, which
- // directly affects the configuration. We should probably move this into that class and have it
- // handle calculating override configuration from the bounds.
- private final Rect mBounds = new Rect();
-
private boolean mShowWhenLocked;
private boolean mTurnScreenOn;
@@ -414,8 +413,8 @@
if (!getOverrideConfiguration().equals(EMPTY)) {
pw.println(prefix + "OverrideConfiguration=" + getOverrideConfiguration());
}
- if (!mBounds.isEmpty()) {
- pw.println(prefix + "mBounds=" + mBounds);
+ if (!matchParentBounds()) {
+ pw.println(prefix + "bounds=" + getBounds());
}
if (resultTo != null || resultWho != null) {
pw.print(prefix); pw.print("resultTo="); pw.print(resultTo);
@@ -618,8 +617,8 @@
"Reporting activity moved to display" + ", activityRecord=" + this
+ ", displayId=" + displayId + ", config=" + config);
- app.thread.scheduleActivityMovedToDisplay(appToken, displayId,
- new Configuration(config));
+ service.mLifecycleManager.scheduleTransaction(app.thread, appToken,
+ new MoveToDisplayItem(displayId, config));
} catch (RemoteException e) {
// If process died, whatever.
}
@@ -636,7 +635,8 @@
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending new config to " + this + ", config: "
+ config);
- app.thread.scheduleActivityConfigurationChanged(appToken, new Configuration(config));
+ service.mLifecycleManager.scheduleTransaction(app.thread, appToken,
+ new ActivityConfigurationChangeItem(config));
} catch (RemoteException e) {
// If process died, whatever.
}
@@ -647,8 +647,13 @@
return;
}
+ if (task.getStack().deferScheduleMultiWindowModeChanged()) {
+ // Don't do anything if we are currently deferring multi-window mode change.
+ return;
+ }
+
// An activity is considered to be in multi-window mode if its task isn't fullscreen.
- final boolean inMultiWindowMode = !task.mFullscreen;
+ final boolean inMultiWindowMode = inMultiWindowMode();
if (inMultiWindowMode != mLastReportedMultiWindowMode) {
mLastReportedMultiWindowMode = inMultiWindowMode;
scheduleMultiWindowModeChanged(getConfiguration());
@@ -657,8 +662,9 @@
private void scheduleMultiWindowModeChanged(Configuration overrideConfig) {
try {
- app.thread.scheduleMultiWindowModeChanged(appToken, mLastReportedMultiWindowMode,
- overrideConfig);
+ service.mLifecycleManager.scheduleTransaction(app.thread, appToken,
+ new MultiWindowModeChangeItem(mLastReportedMultiWindowMode,
+ overrideConfig));
} catch (Exception e) {
// If process died, I don't care.
}
@@ -674,7 +680,7 @@
// Picture-in-picture mode changes also trigger a multi-window mode change as well, so
// update that here in order
mLastReportedPictureInPictureMode = inPictureInPictureMode;
- mLastReportedMultiWindowMode = inPictureInPictureMode;
+ mLastReportedMultiWindowMode = inMultiWindowMode();
final Configuration newConfig = task.computeNewOverrideConfigurationForBounds(
targetStackBounds, null);
schedulePictureInPictureModeChanged(newConfig);
@@ -684,8 +690,9 @@
private void schedulePictureInPictureModeChanged(Configuration overrideConfig) {
try {
- app.thread.schedulePictureInPictureModeChanged(appToken,
- mLastReportedPictureInPictureMode, overrideConfig);
+ service.mLifecycleManager.scheduleTransaction(app.thread, appToken,
+ new PipModeChangeItem(mLastReportedPictureInPictureMode,
+ overrideConfig));
} catch (Exception e) {
// If process died, no one cares.
}
@@ -934,6 +941,14 @@
}
}
+ void setProcess(ProcessRecord proc) {
+ app = proc;
+ final ActivityRecord root = task != null ? task.getRootActivity() : null;
+ if (root == this) {
+ task.setRootProcess(proc);
+ }
+ }
+
AppWindowContainerController getWindowContainerController() {
return mWindowContainerController;
}
@@ -958,14 +973,14 @@
(info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0, info.configChanges,
task.voiceSession != null, mLaunchTaskBehind, isAlwaysFocusable(),
appInfo.targetSdkVersion, mRotationAnimationHint,
- ActivityManagerService.getInputDispatchingTimeoutLocked(this) * 1000000L, mBounds);
+ ActivityManagerService.getInputDispatchingTimeoutLocked(this) * 1000000L);
task.addActivityToTop(this);
// When an activity is started directly into a split-screen fullscreen stack, we need to
// update the initial multi-window modes so that the callbacks are scheduled correctly when
// the user leaves that mode.
- mLastReportedMultiWindowMode = !task.mFullscreen;
+ mLastReportedMultiWindowMode = inMultiWindowMode();
mLastReportedPictureInPictureMode = inPinnedWindowingMode();
}
@@ -1364,8 +1379,8 @@
try {
ArrayList<ReferrerIntent> ar = new ArrayList<>(1);
ar.add(rintent);
- app.thread.scheduleNewIntent(
- ar, appToken, state == PAUSED /* andPause */);
+ service.mLifecycleManager.scheduleTransaction(app.thread, appToken,
+ new NewIntentItem(ar, state == PAUSED));
unsent = false;
} catch (RemoteException e) {
Slog.w(TAG, "Exception thrown sending new intent to " + this, e);
@@ -1587,7 +1602,8 @@
setVisible(true);
sleeping = false;
app.pendingUiClean = true;
- app.thread.scheduleWindowVisibility(appToken, true /* showWindow */);
+ service.mLifecycleManager.scheduleTransaction(app.thread, appToken,
+ new WindowVisibilityItem(true /* showWindow */));
// The activity may be waiting for stop, but that is no longer appropriate for it.
mStackSupervisor.mStoppingActivities.remove(this);
mStackSupervisor.mGoingToSleepActivities.remove(this);
@@ -2164,33 +2180,25 @@
mLastReportedConfiguration.setConfiguration(global, override);
}
- @Override
- public void onOverrideConfigurationChanged(Configuration newConfig) {
- final Configuration currentConfig = getOverrideConfiguration();
- if (currentConfig.equals(newConfig)) {
- return;
- }
- super.onOverrideConfigurationChanged(newConfig);
- if (mWindowContainerController == null) {
- return;
- }
- mWindowContainerController.onOverrideConfigurationChanged(newConfig, mBounds);
- }
-
// TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
private void updateOverrideConfiguration() {
mTmpConfig.unset();
computeBounds(mTmpBounds);
- if (mTmpBounds.equals(mBounds)) {
+
+ if (mTmpBounds.equals(getOverrideBounds())) {
return;
}
- mBounds.set(mTmpBounds);
+ setBounds(mTmpBounds);
+
+ final Rect updatedBounds = getOverrideBounds();
+
// Bounds changed...update configuration to match.
- if (!mBounds.isEmpty()) {
- task.computeOverrideConfiguration(mTmpConfig, mBounds, null /* insetBounds */,
+ if (!matchParentBounds()) {
+ task.computeOverrideConfiguration(mTmpConfig, updatedBounds, null /* insetBounds */,
false /* overrideWidth */, false /* overrideHeight */);
}
+
onOverrideConfigurationChanged(mTmpConfig);
}
@@ -2217,7 +2225,7 @@
outBounds.setEmpty();
final float maxAspectRatio = info.maxAspectRatio;
final ActivityStack stack = getStack();
- if (task == null || stack == null || !task.mFullscreen || maxAspectRatio == 0
+ if (task == null || stack == null || task.inMultiWindowMode() || maxAspectRatio == 0
|| isInVrUiMode(getConfiguration())) {
// We don't set override configuration if that activity task isn't fullscreen. I.e. the
// activity is in multi-window mode. Or, there isn't a max aspect ratio specified for
@@ -2248,11 +2256,11 @@
if (containingAppWidth <= maxActivityWidth && containingAppHeight <= maxActivityHeight) {
// The display matches or is less than the activity aspect ratio, so nothing else to do.
// Return the existing bounds. If this method is running for the first time,
- // {@link mBounds} will be empty (representing no override). If the method has run
- // before, then effect of {@link mBounds} will already have been applied to the
+ // {@link #getOverrideBounds()} will be empty (representing no override). If the method has run
+ // before, then effect of {@link #getOverrideBounds()} will already have been applied to the
// value returned from {@link getConfiguration}. Refer to
// {@link TaskRecord#computeOverrideConfiguration}.
- outBounds.set(mBounds);
+ outBounds.set(getOverrideBounds());
return;
}
@@ -2264,12 +2272,6 @@
outBounds.offsetTo(left, 0 /* top */);
}
- /** Get bounds of the activity. */
- @VisibleForTesting
- Rect getBounds() {
- return new Rect(mBounds);
- }
-
/**
* Make sure the given activity matches the current configuration. Returns false if the activity
* had to be destroyed. Returns true if the configuration is the same, or the activity will
@@ -2549,7 +2551,7 @@
}
results = null;
newIntents = null;
- service.showUnsupportedZoomDialogIfNeededLocked(this);
+ service.getAppWarningsLocked().onResumeActivity(this);
service.showAskCompatModeDialogLocked(this);
} else {
service.mHandler.removeMessages(PAUSE_TIMEOUT_MSG, this);
diff --git a/com/android/server/am/ActivityStack.java b/com/android/server/am/ActivityStack.java
index c086c52..bdfd82f 100644
--- a/com/android/server/am/ActivityStack.java
+++ b/com/android/server/am/ActivityStack.java
@@ -104,6 +104,13 @@
import android.app.ResultInfo;
import android.app.WindowConfiguration.ActivityType;
import android.app.WindowConfiguration.WindowingMode;
+import android.app.servertransaction.ActivityResultItem;
+import android.app.servertransaction.NewIntentItem;
+import android.app.servertransaction.WindowVisibilityItem;
+import android.app.servertransaction.DestroyActivityItem;
+import android.app.servertransaction.PauseActivityItem;
+import android.app.servertransaction.ResumeActivityItem;
+import android.app.servertransaction.StopActivityItem;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -156,7 +163,6 @@
*/
class ActivityStack<T extends StackWindowController> extends ConfigurationContainer
implements StackWindowListener {
-
private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityStack" : TAG_AM;
private static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE;
private static final String TAG_APP = TAG + POSTFIX_APP;
@@ -322,11 +328,6 @@
*/
boolean mForceHidden = false;
- // Whether or not this stack covers the entire screen; by default stacks are fullscreen
- boolean mFullscreen = true;
- // Current bounds of the stack or null if fullscreen.
- Rect mBounds = null;
-
private boolean mUpdateBoundsDeferred;
private boolean mUpdateBoundsDeferredCalled;
private final Rect mDeferredBounds = new Rect();
@@ -342,8 +343,6 @@
/** The attached Display's unique identifier, or -1 if detached */
int mDisplayId;
- /** Temp variables used during override configuration update. */
- private final SparseArray<Configuration> mTmpConfigs = new SparseArray<>();
private final SparseArray<Rect> mTmpBounds = new SparseArray<>();
private final SparseArray<Rect> mTmpInsetBounds = new SparseArray<>();
private final Rect mTmpRect2 = new Rect();
@@ -495,16 +494,18 @@
// Need to make sure windowing mode is supported.
int windowingMode = display.resolveWindowingMode(
- null /* ActivityRecord */, mTmpOptions, topTask, getActivityType());;
+ null /* ActivityRecord */, mTmpOptions, topTask, getActivityType());
if (splitScreenStack == this && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
// Resolution to split-screen secondary for the primary split-screen stack means we want
// to go fullscreen.
windowingMode = WINDOWING_MODE_FULLSCREEN;
}
+ final boolean alreadyInSplitScreenMode = display.hasSplitScreenPrimaryStack();
+
// Take any required action due to us not supporting the preferred windowing mode.
if (windowingMode != preferredWindowingMode && isActivityTypeStandardOrUndefined()) {
- if (display.hasSplitScreenPrimaryStack()
+ if (alreadyInSplitScreenMode
&& (preferredWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
|| preferredWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY)) {
// Looks like we can't launch in split screen mode, go ahead an dismiss split-screen
@@ -574,11 +575,11 @@
}
}
- if (!Objects.equals(mBounds, mTmpRect2)) {
+ if (!Objects.equals(getOverrideBounds(), mTmpRect2)) {
resize(mTmpRect2, null /* tempTaskBounds */, null /* tempTaskInsetBounds */);
}
} finally {
- if (mDisplayId == DEFAULT_DISPLAY
+ if (!alreadyInSplitScreenMode && mDisplayId == DEFAULT_DISPLAY
&& windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
// Make sure recents stack exist when creating a dock stack as it normally needs to
// be on the other side of the docked stack and we make visibility decisions based
@@ -641,9 +642,7 @@
*/
private void postAddToDisplay(ActivityDisplay activityDisplay, Rect bounds, boolean onTop) {
mDisplayId = activityDisplay.mDisplayId;
- mBounds = bounds != null ? new Rect(bounds) : null;
- mFullscreen = mBounds == null;
-
+ setBounds(bounds);
onParentChanged();
activityDisplay.addChild(this, onTop ? POSITION_TOP : POSITION_BOTTOM);
@@ -651,7 +650,7 @@
// If we created a docked stack we want to resize it so it resizes all other stacks
// in the system.
mStackSupervisor.resizeDockedStackLocked(
- mBounds, null, null, null, null, PRESERVE_WINDOWS);
+ getOverrideBounds(), null, null, null, null, PRESERVE_WINDOWS);
}
}
@@ -766,8 +765,9 @@
return false;
}
- void setBounds(Rect bounds) {
- mBounds = mFullscreen ? null : new Rect(bounds);
+ @Override
+ public int setBounds(Rect bounds) {
+ return super.setBounds(!inMultiWindowMode() ? null : bounds);
}
ActivityRecord topRunningActivityLocked() {
@@ -1428,8 +1428,10 @@
prev.userId, System.identityHashCode(prev),
prev.shortComponentName);
mService.updateUsageStats(prev, false);
- prev.app.thread.schedulePauseActivity(prev.appToken, prev.finishing,
- userLeaving, prev.configChangeFlags, pauseImmediately);
+
+ mService.mLifecycleManager.scheduleTransaction(prev.app.thread, prev.appToken,
+ new PauseActivityItem(prev.finishing, userLeaving,
+ prev.configChangeFlags, pauseImmediately));
} catch (Exception e) {
// Ignore exception, if process died other code will cleanup.
Slog.w(TAG, "Exception thrown during pause", e);
@@ -1678,12 +1680,6 @@
return true;
}
- /** Returns true if the stack is currently considered visible. */
- boolean isVisible() {
- return mWindowContainerController != null && mWindowContainerController.isVisible()
- && !mForceHidden;
- }
-
boolean isTopStackOnDisplay() {
return getDisplay().isTopStack(this);
}
@@ -2064,7 +2060,8 @@
if (r.app != null && r.app.thread != null) {
if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
"Scheduling invisibility: " + r);
- r.app.thread.scheduleWindowVisibility(r.appToken, false);
+ mService.mLifecycleManager.scheduleTransaction(r.app.thread, r.appToken,
+ new WindowVisibilityItem(false /* showWindow */));
}
// Reset the flag indicating that an app can enter picture-in-picture once the
@@ -2495,7 +2492,7 @@
// apps, maybeUpdateTransitToWallpaper() will fail to identify this as a
// TRANSIT_WALLPAPER_OPEN animation, and run some funny animation.
final boolean lastActivityTranslucent = lastStack != null
- && (!lastStack.mFullscreen
+ && (lastStack.inMultiWindowMode()
|| (lastStack.mLastPausedActivity != null
&& !lastStack.mLastPausedActivity.fullscreen));
@@ -2584,13 +2581,15 @@
if (!next.finishing && N > 0) {
if (DEBUG_RESULTS) Slog.v(TAG_RESULTS,
"Delivering results to " + next + ": " + a);
- next.app.thread.scheduleSendResult(next.appToken, a);
+ mService.mLifecycleManager.scheduleTransaction(next.app.thread,
+ next.appToken, new ActivityResultItem(a));
}
}
if (next.newIntents != null) {
- next.app.thread.scheduleNewIntent(
- next.newIntents, next.appToken, false /* andPause */);
+ mService.mLifecycleManager.scheduleTransaction(next.app.thread,
+ next.appToken, new NewIntentItem(next.newIntents,
+ false /* andPause */));
}
// Well the app will no longer be stopped.
@@ -2602,13 +2601,14 @@
next.shortComponentName);
next.sleeping = false;
- mService.showUnsupportedZoomDialogIfNeededLocked(next);
+ mService.getAppWarningsLocked().onResumeActivity(next);
mService.showAskCompatModeDialogLocked(next);
next.app.pendingUiClean = true;
next.app.forceProcessStateUpTo(mService.mTopProcessState);
next.clearOptionsLocked();
- next.app.thread.scheduleResumeActivity(next.appToken, next.app.repProcState,
- mService.isNextTransitionForward(), resumeAnimOptions);
+ mService.mLifecycleManager.scheduleTransaction(next.app.thread, next.appToken,
+ new ResumeActivityItem(next.app.repProcState,
+ mService.isNextTransitionForward()));
if (DEBUG_STATES) Slog.d(TAG_STATES, "resumeTopActivityLocked: Resumed "
+ next);
@@ -2739,8 +2739,7 @@
position = getAdjustedPositionForTask(task, position, null /* starting */);
mTaskHistory.remove(task);
mTaskHistory.add(position, task);
- mWindowContainerController.positionChildAt(task.getWindowContainerController(), position,
- task.mBounds, task.getOverrideConfiguration());
+ mWindowContainerController.positionChildAt(task.getWindowContainerController(), position);
updateTaskMovement(task, true);
}
@@ -3259,7 +3258,8 @@
ArrayList<ResultInfo> list = new ArrayList<ResultInfo>();
list.add(new ResultInfo(resultWho, requestCode,
resultCode, data));
- r.app.thread.scheduleSendResult(r.appToken, list);
+ mService.mLifecycleManager.scheduleTransaction(r.app.thread, r.appToken,
+ new ActivityResultItem(list));
return;
} catch (Exception e) {
Slog.w(TAG, "Exception thrown sending result to " + r, e);
@@ -3387,7 +3387,8 @@
}
EventLogTags.writeAmStopActivity(
r.userId, System.identityHashCode(r), r.shortComponentName);
- r.app.thread.scheduleStopActivity(r.appToken, r.visible, r.configChangeFlags);
+ mService.mLifecycleManager.scheduleTransaction(r.app.thread, r.appToken,
+ new StopActivityItem(r.visible, r.configChangeFlags));
if (shouldSleepOrShutDownActivities()) {
r.setSleeping(true);
}
@@ -4019,7 +4020,7 @@
// TODO: If the callers to removeTask() changes such that we have multiple places
// where we are destroying the task, move this back into removeTask()
mStackSupervisor.removeTaskByIdLocked(task.taskId, false /* killProcess */,
- !REMOVE_FROM_RECENTS, PAUSE_IMMEDIATELY);
+ !REMOVE_FROM_RECENTS, PAUSE_IMMEDIATELY, reason);
}
// We must keep the task around until all activities are destroyed. The following
@@ -4184,8 +4185,8 @@
try {
if (DEBUG_SWITCH) Slog.i(TAG_SWITCH, "Destroying: " + r);
- r.app.thread.scheduleDestroyActivity(r.appToken, r.finishing,
- r.configChangeFlags);
+ mService.mLifecycleManager.scheduleTransaction(r.app.thread, r.appToken,
+ new DestroyActivityItem(r.finishing, r.configChangeFlags));
} catch (Exception e) {
// We can just ignore exceptions here... if the process
// has crashed, our death notification will clean things
@@ -4602,8 +4603,6 @@
// TODO: Can only be called from special methods in ActivityStackSupervisor.
// Need to consolidate those calls points into this resize method so anyone can call directly.
void resize(Rect bounds, Rect tempTaskBounds, Rect tempTaskInsetBounds) {
- bounds = TaskRecord.validateBounds(bounds);
-
if (!updateBoundsAllowed(bounds, tempTaskBounds, tempTaskInsetBounds)) {
return;
}
@@ -4613,7 +4612,6 @@
final Rect insetBounds = tempTaskInsetBounds != null ? tempTaskInsetBounds : taskBounds;
mTmpBounds.clear();
- mTmpConfigs.clear();
mTmpInsetBounds.clear();
for (int i = mTaskHistory.size() - 1; i >= 0; i--) {
@@ -4624,7 +4622,7 @@
// For freeform stack we don't adjust the size of the tasks to match that
// of the stack, but we do try to make sure the tasks are still contained
// with the bounds of the stack.
- mTmpRect2.set(task.mBounds);
+ mTmpRect2.set(task.getOverrideBounds());
fitWithinBounds(mTmpRect2, bounds);
task.updateOverrideConfiguration(mTmpRect2);
} else {
@@ -4632,15 +4630,13 @@
}
}
- mTmpConfigs.put(task.taskId, task.getOverrideConfiguration());
- mTmpBounds.put(task.taskId, task.mBounds);
+ mTmpBounds.put(task.taskId, task.getOverrideBounds());
if (tempTaskInsetBounds != null) {
mTmpInsetBounds.put(task.taskId, tempTaskInsetBounds);
}
}
- mFullscreen = mWindowContainerController.resize(bounds, mTmpConfigs, mTmpBounds,
- mTmpInsetBounds);
+ mWindowContainerController.resize(bounds, mTmpBounds, mTmpInsetBounds);
setBounds(bounds);
}
@@ -4655,7 +4651,7 @@
* @param stackBounds Bounds within which the other bounds should remain.
*/
private static void fitWithinBounds(Rect bounds, Rect stackBounds) {
- if (stackBounds == null || stackBounds.contains(bounds)) {
+ if (stackBounds == null || stackBounds.isEmpty() || stackBounds.contains(bounds)) {
return;
}
@@ -4873,8 +4869,7 @@
pw.println("");
}
pw.println(prefix + "Task id #" + task.taskId);
- pw.println(prefix + "mFullscreen=" + task.mFullscreen);
- pw.println(prefix + "mBounds=" + task.mBounds);
+ pw.println(prefix + "mBounds=" + task.getOverrideBounds());
pw.println(prefix + "mMinWidth=" + task.mMinWidth);
pw.println(prefix + "mMinHeight=" + task.mMinHeight);
pw.println(prefix + "mLastNonFullscreenBounds=" + task.mLastNonFullscreenBounds);
@@ -4981,7 +4976,7 @@
if (isOnHomeDisplay() && mode != REMOVE_TASK_MODE_MOVING_TO_TOP
&& mStackSupervisor.isFocusedStack(this)) {
String myReason = reason + " leftTaskHistoryEmpty";
- if (mFullscreen || !adjustFocusToNextFocusableStack(myReason)) {
+ if (!inMultiWindowMode() || !adjustFocusToNextFocusableStack(myReason)) {
mStackSupervisor.moveHomeStackToFront(myReason);
}
}
@@ -5004,15 +4999,24 @@
TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
boolean toTop) {
+ return createTaskRecord(taskId, info, intent, voiceSession, voiceInteractor, toTop,
+ null /*activity*/, null /*source*/, null /*options*/);
+ }
+
+ TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent,
+ IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
+ boolean toTop, ActivityRecord activity, ActivityRecord source,
+ ActivityOptions options) {
final TaskRecord task = new TaskRecord(mService, taskId, info, intent, voiceSession,
voiceInteractor);
// add the task to stack first, mTaskPositioner might need the stack association
addTask(task, toTop, "createTaskRecord");
final boolean isLockscreenShown = mService.mStackSupervisor.getKeyguardController()
.isKeyguardShowing(mDisplayId != INVALID_DISPLAY ? mDisplayId : DEFAULT_DISPLAY);
- if (!mStackSupervisor.getLaunchingBoundsController().layoutTask(task, info.windowLayout)
- && mBounds != null && task.isResizeable() && !isLockscreenShown) {
- task.updateOverrideConfiguration(mBounds);
+ if (!mStackSupervisor.getLaunchingBoundsController()
+ .layoutTask(task, info.windowLayout, activity, source, options)
+ && !matchParentBounds() && task.isResizeable() && !isLockscreenShown) {
+ task.updateOverrideConfiguration(getOverrideBounds());
}
task.createWindowContainer(toTop, (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0);
return task;
@@ -5174,10 +5178,13 @@
mResumedActivity.writeIdentifierToProto(proto, RESUMED_ACTIVITY);
}
proto.write(DISPLAY_ID, mDisplayId);
- if (mBounds != null) {
- mBounds.writeToProto(proto, BOUNDS);
+ if (!matchParentBounds()) {
+ final Rect bounds = getOverrideBounds();
+ bounds.writeToProto(proto, BOUNDS);
}
- proto.write(FULLSCREEN, mFullscreen);
+
+ // TODO: Remove, no longer needed with windowingMode.
+ proto.write(FULLSCREEN, matchParentBounds());
proto.end(token);
}
}
diff --git a/com/android/server/am/ActivityStackSupervisor.java b/com/android/server/am/ActivityStackSupervisor.java
index 745e9fb..445bf67 100644
--- a/com/android/server/am/ActivityStackSupervisor.java
+++ b/com/android/server/am/ActivityStackSupervisor.java
@@ -114,6 +114,7 @@
import android.app.WaitResult;
import android.app.WindowConfiguration.ActivityType;
import android.app.WindowConfiguration.WindowingMode;
+import android.app.servertransaction.LaunchActivityItem;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -1270,7 +1271,7 @@
// schedule launch ticks to collect information about slow apps.
r.startLaunchTickingLocked();
- r.app = app;
+ r.setProcess(app);
if (mKeyguardController.isKeyguardLocked()) {
r.notifyUnknownVisibilityLaunched();
@@ -1358,7 +1359,7 @@
PackageManager.NOTIFY_PACKAGE_USE_ACTIVITY);
r.sleeping = false;
r.forceNewConfig = false;
- mService.showUnsupportedZoomDialogIfNeededLocked(r);
+ mService.getAppWarningsLocked().onStartActivity(r);
mService.showAskCompatModeDialogLocked(r);
r.compat = mService.compatibilityInfoForPackageLocked(r.info.applicationInfo);
ProfilerInfo profilerInfo = null;
@@ -1392,7 +1393,8 @@
r.setLastReportedConfiguration(mergedConfiguration);
logIfTransactionTooLarge(r.intent, r.icicle);
- app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
+ mService.mLifecycleManager.scheduleTransaction(app.thread, r.appToken,
+ new LaunchActivityItem(new Intent(r.intent),
System.identityHashCode(r), r.info,
// TODO: Have this take the merged configuration instead of separate global
// and override configs.
@@ -1400,7 +1402,7 @@
mergedConfiguration.getOverrideConfiguration(), r.compat,
r.launchedFromPackage, task.voiceInteractor, app.repProcState, r.icicle,
r.persistentState, results, newIntents, !andResume,
- mService.isNextTransitionForward(), profilerInfo);
+ mService.isNextTransitionForward(), profilerInfo));
if ((app.info.privateFlags & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) {
// This may be a heavy-weight process! Note that the package
@@ -2118,7 +2120,7 @@
}
if (task.isResizeable() && canUseActivityOptionsLaunchBounds(options)) {
- final Rect bounds = TaskRecord.validateBounds(options.getLaunchBounds());
+ final Rect bounds = options.getLaunchBounds();
task.updateOverrideConfiguration(bounds);
ActivityStack stack = getLaunchStack(null, options, task, ON_TOP);
@@ -2626,7 +2628,8 @@
// TODO: Checking for isAttached might not be needed as if the user passes in null
// dockedBounds then they want the docked stack to be dismissed.
- if (stack.mFullscreen || (dockedBounds == null && !stack.isAttached())) {
+ if (stack.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+ || (dockedBounds == null && !stack.isAttached())) {
// The dock stack either was dismissed or went fullscreen, which is kinda the same.
// In this case we make all other static stacks fullscreen and move all
// docked stack tasks to the fullscreen stack.
@@ -2736,7 +2739,7 @@
} else {
for (int i = tasks.size() - 1; i >= 0; i--) {
removeTaskByIdLocked(tasks.get(i).taskId, true /* killProcess */,
- REMOVE_FROM_RECENTS);
+ REMOVE_FROM_RECENTS, "remove-stack");
}
}
}
@@ -2769,8 +2772,10 @@
/**
* See {@link #removeTaskByIdLocked(int, boolean, boolean, boolean)}
*/
- boolean removeTaskByIdLocked(int taskId, boolean killProcess, boolean removeFromRecents) {
- return removeTaskByIdLocked(taskId, killProcess, removeFromRecents, !PAUSE_IMMEDIATELY);
+ boolean removeTaskByIdLocked(int taskId, boolean killProcess, boolean removeFromRecents,
+ String reason) {
+ return removeTaskByIdLocked(taskId, killProcess, removeFromRecents, !PAUSE_IMMEDIATELY,
+ reason);
}
/**
@@ -2784,10 +2789,10 @@
* @return Returns true if the given task was found and removed.
*/
boolean removeTaskByIdLocked(int taskId, boolean killProcess, boolean removeFromRecents,
- boolean pauseImmediately) {
+ boolean pauseImmediately, String reason) {
final TaskRecord tr = anyTaskForIdLocked(taskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
if (tr != null) {
- tr.removeTaskActivitiesLocked(pauseImmediately);
+ tr.removeTaskActivitiesLocked(pauseImmediately, reason);
cleanUpRemovedTaskLocked(tr, killProcess, removeFromRecents);
mService.mLockTaskController.clearLockedTask(tr);
if (tr.isPersistable) {
@@ -2921,7 +2926,12 @@
@Override
public void onRecentTaskRemoved(TaskRecord task, boolean wasTrimmed) {
- // TODO: Trim active task once b/68045330 is fixed
+ if (wasTrimmed) {
+ // Task was trimmed from the recent tasks list -- remove the active task record as well
+ // since the user won't really be able to go back to it
+ removeTaskByIdLocked(task.taskId, false /* killProcess */,
+ false /* removeFromRecents */, !PAUSE_IMMEDIATELY, "recent-task-trimmed");
+ }
task.removedFromRecents();
}
@@ -3058,7 +3068,7 @@
// Resize the pinned stack to match the current size of the task the activity we are
// going to be moving is currently contained in. We do this to have the right starting
// animation bounds for the pinned stack to the desired bounds the caller wants.
- resizeStackLocked(stack, task.mBounds, null /* tempTaskBounds */,
+ resizeStackLocked(stack, task.getOverrideBounds(), null /* tempTaskBounds */,
null /* tempTaskInsetBounds */, !PRESERVE_WINDOWS,
true /* allowResizeInDockedMode */, !DEFER_RESUME);
@@ -3782,9 +3792,8 @@
pw.println(" Stack #" + stack.mStackId
+ ": type=" + activityTypeToString(stack.getActivityType())
+ " mode=" + windowingModeToString(stack.getWindowingMode()));
- pw.println(" mFullscreen=" + stack.mFullscreen);
pw.println(" isSleeping=" + stack.shouldSleepActivities());
- pw.println(" mBounds=" + stack.mBounds);
+ pw.println(" mBounds=" + stack.getOverrideBounds());
printed |= stack.dumpActivitiesLocked(fd, pw, dumpAll, dumpClient, dumpPackage,
needSep);
@@ -4054,6 +4063,7 @@
private void handleDisplayChanged(int displayId) {
synchronized (mService) {
ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
+ // TODO: The following code block should be moved into {@link ActivityDisplay}.
if (activityDisplay != null) {
// The window policy is responsible for stopping activities on the default display
if (displayId != Display.DEFAULT_DISPLAY) {
@@ -4067,7 +4077,8 @@
activityDisplay.mOffToken = null;
}
}
- // TODO: Update the bounds.
+
+ activityDisplay.updateBounds();
}
mWindowManager.onDisplayChanged(displayId);
}
@@ -4289,7 +4300,7 @@
return;
}
- scheduleUpdatePictureInPictureModeIfNeeded(task, stack.mBounds);
+ scheduleUpdatePictureInPictureModeIfNeeded(task, stack.getOverrideBounds());
}
void scheduleUpdatePictureInPictureModeIfNeeded(TaskRecord task, Rect targetStackBounds) {
@@ -4297,6 +4308,10 @@
final ActivityRecord r = task.mActivities.get(i);
if (r.app != null && r.app.thread != null) {
mPipModeChangedActivities.add(r);
+ // If we are scheduling pip change, then remove this activity from multi-window
+ // change list as the processing of pip change will make sure multi-window changed
+ // message is processed in the right order relative to pip changed.
+ mMultiWindowModeChangedActivities.remove(r);
}
}
mPipModeChangedTargetStackBounds = targetStackBounds;
diff --git a/com/android/server/am/ActivityStarter.java b/com/android/server/am/ActivityStarter.java
index 9b8cbc1..2fc5dda 100644
--- a/com/android/server/am/ActivityStarter.java
+++ b/com/android/server/am/ActivityStarter.java
@@ -134,7 +134,6 @@
private static final int INVALID_LAUNCH_MODE = -1;
private final ActivityManagerService mService;
- private final IPackageManager mPackageManager;
private final ActivityStackSupervisor mSupervisor;
private final ActivityStartInterceptor mInterceptor;
@@ -234,9 +233,8 @@
mIntentDelivered = false;
}
- ActivityStarter(ActivityManagerService service, IPackageManager packageManager) {
+ ActivityStarter(ActivityManagerService service) {
mService = service;
- mPackageManager = packageManager;
mSupervisor = mService.mStackSupervisor;
mInterceptor = new ActivityStartInterceptor(mService, mSupervisor);
}
@@ -379,7 +377,7 @@
&& sourceRecord.info.applicationInfo.uid != aInfo.applicationInfo.uid) {
try {
intent.addCategory(Intent.CATEGORY_VOICE);
- if (!mPackageManager.activitySupportsIntent(
+ if (!mService.getPackageManager().activitySupportsIntent(
intent.getComponent(), intent, resolvedType)) {
Slog.w(TAG,
"Activity being started in current voice task does not support voice: "
@@ -397,7 +395,7 @@
// If the caller is starting a new voice session, just make sure the target
// is actually allowing it to run this way.
try {
- if (!mPackageManager.activitySupportsIntent(intent.getComponent(),
+ if (!mService.getPackageManager().activitySupportsIntent(intent.getComponent(),
intent, resolvedType)) {
Slog.w(TAG,
"Activity being started in new voice task does not support: "
@@ -608,21 +606,9 @@
return;
}
- if (startedActivityStack.inSplitScreenPrimaryWindowingMode()) {
- final ActivityStack homeStack = mSupervisor.mHomeStack;
- final boolean homeStackVisible = homeStack != null && homeStack.isVisible();
- if (homeStackVisible) {
- // We launch an activity while being in home stack, which means either launcher or
- // recents into docked stack. We don't want the launched activity to be alone in a
- // docked stack, so we want to immediately launch recents too.
- if (DEBUG_RECENTS) Slog.d(TAG, "Scheduling recents launch.");
- mService.mWindowManager.showRecentApps(true /* fromHome */);
- }
- return;
- }
-
- boolean clearedTask = (mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
- == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK) && (mReuseTask != null);
+ final int clearTaskFlags = FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK;
+ boolean clearedTask = (mLaunchFlags & clearTaskFlags) == clearTaskFlags
+ && mReuseTask != null;
if (startedActivityStack.inPinnedWindowingMode()
&& (result == START_TASK_TO_FRONT || result == START_DELIVERED_TO_TOP
|| clearedTask)) {
@@ -1758,7 +1744,8 @@
mSupervisor.getNextTaskIdForUserLocked(mStartActivity.userId),
mNewTaskInfo != null ? mNewTaskInfo : mStartActivity.info,
mNewTaskIntent != null ? mNewTaskIntent : mIntent, mVoiceSession,
- mVoiceInteractor, !mLaunchTaskBehind /* toTop */);
+ mVoiceInteractor, !mLaunchTaskBehind /* toTop */, mStartActivity, mSourceRecord,
+ mOptions);
addOrReparentStartingActivity(task, "setTaskFromReuseOrCreateNewTask - mReuseTask");
updateBounds(mStartActivity.getTask(), mLaunchBounds);
@@ -1967,7 +1954,7 @@
final ActivityRecord prev = mTargetStack.getTopActivity();
final TaskRecord task = (prev != null) ? prev.getTask() : mTargetStack.createTaskRecord(
mSupervisor.getNextTaskIdForUserLocked(mStartActivity.userId), mStartActivity.info,
- mIntent, null, null, true);
+ mIntent, null, null, true, mStartActivity, mSourceRecord, mOptions);
addOrReparentStartingActivity(task, "setTaskToCurrentTopOrCreateNewTask");
mTargetStack.positionChildWindowContainerAtTop(task);
if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + mStartActivity
@@ -2124,18 +2111,13 @@
return mReuseTask.getStack();
}
- final int vrDisplayId = mPreferredDisplayId == mService.mVr2dDisplayId
- ? mPreferredDisplayId : INVALID_DISPLAY;
- final ActivityStack launchStack = mSupervisor.getLaunchStack(r, aOptions, task, ON_TOP,
- vrDisplayId);
-
- if (launchStack != null) {
- return launchStack;
- }
-
if (((launchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) == 0)
|| mPreferredDisplayId != DEFAULT_DISPLAY) {
- return null;
+ // We don't pass in the default display id into the get launch stack call so it can do a
+ // full resolution.
+ final int candidateDisplay =
+ mPreferredDisplayId != DEFAULT_DISPLAY ? mPreferredDisplayId : INVALID_DISPLAY;
+ return mSupervisor.getLaunchStack(r, aOptions, task, ON_TOP, candidateDisplay);
}
// Otherwise handle adjacent launch.
@@ -2167,7 +2149,7 @@
mSupervisor.getDefaultDisplay().getSplitScreenPrimaryStack();
if (dockedStack != null && !dockedStack.shouldBeVisible(r)) {
// There is a docked stack, but it isn't visible, so we can't launch into that.
- return null;
+ return mSupervisor.getLaunchStack(r, aOptions, task, ON_TOP);
} else {
return dockedStack;
}
diff --git a/com/android/server/am/AppBindRecord.java b/com/android/server/am/AppBindRecord.java
index df833ad..7b38597 100644
--- a/com/android/server/am/AppBindRecord.java
+++ b/com/android/server/am/AppBindRecord.java
@@ -17,6 +17,9 @@
package com.android.server.am;
import android.util.ArraySet;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.server.am.proto.AppBindRecordProto;
import java.io.PrintWriter;
@@ -60,4 +63,18 @@
+ Integer.toHexString(System.identityHashCode(this))
+ " " + service.shortName + ":" + client.processName + "}";
}
+
+ void writeToProto(ProtoOutputStream proto, long fieldId) {
+ long token = proto.start(fieldId);
+ proto.write(AppBindRecordProto.HEX_HASH,
+ Integer.toHexString(System.identityHashCode(this)));
+ if (client != null) {
+ client.writeToProto(proto, AppBindRecordProto.CLIENT);
+ }
+ final int N = connections.size();
+ for (int i=0; i<N; i++) {
+ connections.valueAt(i).writeToProto(proto, AppBindRecordProto.CONNECTIONS);
+ }
+ proto.end(token);
+ }
}
diff --git a/com/android/server/am/AppTaskImpl.java b/com/android/server/am/AppTaskImpl.java
index 17626ea..ab86dbd 100644
--- a/com/android/server/am/AppTaskImpl.java
+++ b/com/android/server/am/AppTaskImpl.java
@@ -61,7 +61,7 @@
try {
// We remove the task from recents to preserve backwards
if (!mService.mStackSupervisor.removeTaskByIdLocked(mTaskId, false,
- REMOVE_FROM_RECENTS)) {
+ REMOVE_FROM_RECENTS, "finish-and-remove-task")) {
throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
}
} finally {
diff --git a/com/android/server/am/AppWarnings.java b/com/android/server/am/AppWarnings.java
new file mode 100644
index 0000000..a3c0345
--- /dev/null
+++ b/com/android/server/am/AppWarnings.java
@@ -0,0 +1,498 @@
+/*
+ * Copyright (C) 2018 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.server.am;
+
+import android.annotation.UiThread;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.AtomicFile;
+import android.util.DisplayMetrics;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Manages warning dialogs shown during application lifecycle.
+ */
+class AppWarnings {
+ private static final String TAG = "AppWarnings";
+ private static final String CONFIG_FILE_NAME = "packages-warnings.xml";
+
+ public static final int FLAG_HIDE_DISPLAY_SIZE = 0x01;
+ public static final int FLAG_HIDE_COMPILE_SDK = 0x02;
+
+ private final HashMap<String, Integer> mPackageFlags = new HashMap<>();
+
+ private final ActivityManagerService mAms;
+ private final Context mUiContext;
+ private final ConfigHandler mAmsHandler;
+ private final UiHandler mUiHandler;
+ private final AtomicFile mConfigFile;
+
+ private UnsupportedDisplaySizeDialog mUnsupportedDisplaySizeDialog;
+ private UnsupportedCompileSdkDialog mUnsupportedCompileSdkDialog;
+
+ /**
+ * Creates a new warning dialog manager.
+ * <p>
+ * <strong>Note:</strong> Must be called from the ActivityManagerService thread.
+ *
+ * @param ams
+ * @param uiContext
+ * @param amsHandler
+ * @param uiHandler
+ * @param systemDir
+ */
+ public AppWarnings(ActivityManagerService ams, Context uiContext, Handler amsHandler,
+ Handler uiHandler, File systemDir) {
+ mAms = ams;
+ mUiContext = uiContext;
+ mAmsHandler = new ConfigHandler(amsHandler.getLooper());
+ mUiHandler = new UiHandler(uiHandler.getLooper());
+ mConfigFile = new AtomicFile(new File(systemDir, CONFIG_FILE_NAME));
+
+ readConfigFromFileAmsThread();
+ }
+
+ /**
+ * Shows the "unsupported display size" warning, if necessary.
+ *
+ * @param r activity record for which the warning may be displayed
+ */
+ public void showUnsupportedDisplaySizeDialogIfNeeded(ActivityRecord r) {
+ final Configuration globalConfig = mAms.getGlobalConfiguration();
+ if (globalConfig.densityDpi != DisplayMetrics.DENSITY_DEVICE_STABLE
+ && r.appInfo.requiresSmallestWidthDp > globalConfig.smallestScreenWidthDp) {
+ mUiHandler.showUnsupportedDisplaySizeDialog(r);
+ }
+ }
+
+ /**
+ * Shows the "unsupported compile SDK" warning, if necessary.
+ *
+ * @param r activity record for which the warning may be displayed
+ */
+ public void showUnsupportedCompileSdkDialogIfNeeded(ActivityRecord r) {
+ if (r.appInfo.compileSdkVersion == 0 || r.appInfo.compileSdkVersionCodename == null) {
+ // We don't know enough about this package. Abort!
+ return;
+ }
+
+ // If the application was built against an pre-release SDK that's older than the current
+ // platform OR if the current platform is pre-release and older than the SDK against which
+ // the application was built OR both are pre-release with the same SDK_INT but different
+ // codenames (e.g. simultaneous pre-release development), then we're likely to run into
+ // compatibility issues. Warn the user and offer to check for an update.
+ final int compileSdk = r.appInfo.compileSdkVersion;
+ final int platformSdk = Build.VERSION.SDK_INT;
+ final boolean isCompileSdkPreview = !"REL".equals(r.appInfo.compileSdkVersionCodename);
+ final boolean isPlatformSdkPreview = !"REL".equals(Build.VERSION.CODENAME);
+ if ((isCompileSdkPreview && compileSdk < platformSdk)
+ || (isPlatformSdkPreview && platformSdk < compileSdk)
+ || (isCompileSdkPreview && isPlatformSdkPreview && platformSdk == compileSdk
+ && !Build.VERSION.CODENAME.equals(r.appInfo.compileSdkVersionCodename))) {
+ mUiHandler.showUnsupportedCompileSdkDialog(r);
+ }
+ }
+
+ /**
+ * Called when an activity is being started.
+ *
+ * @param r record for the activity being started
+ */
+ public void onStartActivity(ActivityRecord r) {
+ showUnsupportedCompileSdkDialogIfNeeded(r);
+ showUnsupportedDisplaySizeDialogIfNeeded(r);
+ }
+
+ /**
+ * Called when an activity was previously started and is being resumed.
+ *
+ * @param r record for the activity being resumed
+ */
+ public void onResumeActivity(ActivityRecord r) {
+ showUnsupportedDisplaySizeDialogIfNeeded(r);
+ }
+
+ /**
+ * Called by ActivityManagerService when package data has been cleared.
+ *
+ * @param name the package whose data has been cleared
+ */
+ public void onPackageDataCleared(String name) {
+ removePackageAndHideDialogs(name);
+ }
+
+ /**
+ * Called by ActivityManagerService when a package has been uninstalled.
+ *
+ * @param name the package that has been uninstalled
+ */
+ public void onPackageUninstalled(String name) {
+ removePackageAndHideDialogs(name);
+ }
+
+ /**
+ * Called by ActivityManagerService when the default display density has changed.
+ */
+ public void onDensityChanged() {
+ mUiHandler.hideUnsupportedDisplaySizeDialog();
+ }
+
+ /**
+ * Does what it says on the tin.
+ */
+ private void removePackageAndHideDialogs(String name) {
+ mUiHandler.hideDialogsForPackage(name);
+
+ synchronized (mPackageFlags) {
+ mPackageFlags.remove(name);
+ mAmsHandler.scheduleWrite();
+ }
+ }
+
+ /**
+ * Hides the "unsupported display size" warning.
+ * <p>
+ * <strong>Note:</strong> Must be called on the UI thread.
+ */
+ @UiThread
+ private void hideUnsupportedDisplaySizeDialogUiThread() {
+ if (mUnsupportedDisplaySizeDialog != null) {
+ mUnsupportedDisplaySizeDialog.dismiss();
+ mUnsupportedDisplaySizeDialog = null;
+ }
+ }
+
+ /**
+ * Shows the "unsupported display size" warning for the given application.
+ * <p>
+ * <strong>Note:</strong> Must be called on the UI thread.
+ *
+ * @param ar record for the activity that triggered the warning
+ */
+ @UiThread
+ private void showUnsupportedDisplaySizeDialogUiThread(ActivityRecord ar) {
+ if (mUnsupportedDisplaySizeDialog != null) {
+ mUnsupportedDisplaySizeDialog.dismiss();
+ mUnsupportedDisplaySizeDialog = null;
+ }
+ if (ar != null && !hasPackageFlag(
+ ar.packageName, FLAG_HIDE_DISPLAY_SIZE)) {
+ mUnsupportedDisplaySizeDialog = new UnsupportedDisplaySizeDialog(
+ AppWarnings.this, mUiContext, ar.info.applicationInfo);
+ mUnsupportedDisplaySizeDialog.show();
+ }
+ }
+
+ /**
+ * Shows the "unsupported compile SDK" warning for the given application.
+ * <p>
+ * <strong>Note:</strong> Must be called on the UI thread.
+ *
+ * @param ar record for the activity that triggered the warning
+ */
+ @UiThread
+ private void showUnsupportedCompileSdkDialogUiThread(ActivityRecord ar) {
+ if (mUnsupportedCompileSdkDialog != null) {
+ mUnsupportedCompileSdkDialog.dismiss();
+ mUnsupportedCompileSdkDialog = null;
+ }
+ if (ar != null && !hasPackageFlag(
+ ar.packageName, FLAG_HIDE_COMPILE_SDK)) {
+ mUnsupportedCompileSdkDialog = new UnsupportedCompileSdkDialog(
+ AppWarnings.this, mUiContext, ar.info.applicationInfo);
+ mUnsupportedCompileSdkDialog.show();
+ }
+ }
+
+ /**
+ * Dismisses all warnings for the given package.
+ * <p>
+ * <strong>Note:</strong> Must be called on the UI thread.
+ *
+ * @param name the package for which warnings should be dismissed, or {@code null} to dismiss
+ * all warnings
+ */
+ @UiThread
+ private void hideDialogsForPackageUiThread(String name) {
+ // Hides the "unsupported display" dialog if necessary.
+ if (mUnsupportedDisplaySizeDialog != null && (name == null || name.equals(
+ mUnsupportedDisplaySizeDialog.getPackageName()))) {
+ mUnsupportedDisplaySizeDialog.dismiss();
+ mUnsupportedDisplaySizeDialog = null;
+ }
+
+ // Hides the "unsupported compile SDK" dialog if necessary.
+ if (mUnsupportedCompileSdkDialog != null && (name == null || name.equals(
+ mUnsupportedCompileSdkDialog.getPackageName()))) {
+ mUnsupportedCompileSdkDialog.dismiss();
+ mUnsupportedCompileSdkDialog = null;
+ }
+ }
+
+ /**
+ * Returns the value of the flag for the given package.
+ *
+ * @param name the package from which to retrieve the flag
+ * @param flag the bitmask for the flag to retrieve
+ * @return {@code true} if the flag is enabled, {@code false} otherwise
+ */
+ boolean hasPackageFlag(String name, int flag) {
+ return (getPackageFlags(name) & flag) == flag;
+ }
+
+ /**
+ * Sets the flag for the given package to the specified value.
+ *
+ * @param name the package on which to set the flag
+ * @param flag the bitmask for flag to set
+ * @param enabled the value to set for the flag
+ */
+ void setPackageFlag(String name, int flag, boolean enabled) {
+ synchronized (mPackageFlags) {
+ final int curFlags = getPackageFlags(name);
+ final int newFlags = enabled ? (curFlags & ~flag) : (curFlags | flag);
+ if (curFlags != newFlags) {
+ if (newFlags != 0) {
+ mPackageFlags.put(name, newFlags);
+ } else {
+ mPackageFlags.remove(name);
+ }
+ mAmsHandler.scheduleWrite();
+ }
+ }
+ }
+
+ /**
+ * Returns the bitmask of flags set for the specified package.
+ */
+ private int getPackageFlags(String name) {
+ synchronized (mPackageFlags) {
+ return mPackageFlags.getOrDefault(name, 0);
+ }
+ }
+
+ /**
+ * Handles messages on the system process UI thread.
+ */
+ private final class UiHandler extends Handler {
+ private static final int MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG = 1;
+ private static final int MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG = 2;
+ private static final int MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG = 3;
+ private static final int MSG_HIDE_DIALOGS_FOR_PACKAGE = 4;
+
+ public UiHandler(Looper looper) {
+ super(looper, null, true);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG: {
+ final ActivityRecord ar = (ActivityRecord) msg.obj;
+ showUnsupportedDisplaySizeDialogUiThread(ar);
+ } break;
+ case MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG: {
+ hideUnsupportedDisplaySizeDialogUiThread();
+ } break;
+ case MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG: {
+ final ActivityRecord ar = (ActivityRecord) msg.obj;
+ showUnsupportedCompileSdkDialogUiThread(ar);
+ } break;
+ case MSG_HIDE_DIALOGS_FOR_PACKAGE: {
+ final String name = (String) msg.obj;
+ hideDialogsForPackageUiThread(name);
+ } break;
+ }
+ }
+
+ public void showUnsupportedDisplaySizeDialog(ActivityRecord r) {
+ removeMessages(MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG);
+ obtainMessage(MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG, r).sendToTarget();
+ }
+
+ public void hideUnsupportedDisplaySizeDialog() {
+ removeMessages(MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG);
+ sendEmptyMessage(MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG);
+ }
+
+ public void showUnsupportedCompileSdkDialog(ActivityRecord r) {
+ removeMessages(MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG);
+ obtainMessage(MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG, r).sendToTarget();
+ }
+
+ public void hideDialogsForPackage(String name) {
+ obtainMessage(MSG_HIDE_DIALOGS_FOR_PACKAGE, name).sendToTarget();
+ }
+ }
+
+ /**
+ * Handles messages on the ActivityManagerService thread.
+ */
+ private final class ConfigHandler extends Handler {
+ private static final int MSG_WRITE = ActivityManagerService.FIRST_COMPAT_MODE_MSG;
+
+ private static final int DELAY_MSG_WRITE = 10000;
+
+ public ConfigHandler(Looper looper) {
+ super(looper, null, true);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_WRITE:
+ writeConfigToFileAmsThread();
+ break;
+ }
+ }
+
+ public void scheduleWrite() {
+ removeMessages(MSG_WRITE);
+ sendEmptyMessageDelayed(MSG_WRITE, DELAY_MSG_WRITE);
+ }
+ }
+
+ /**
+ * Writes the configuration file.
+ * <p>
+ * <strong>Note:</strong> Should be called from the ActivityManagerService thread unless you
+ * don't care where you're doing I/O operations. But you <i>do</i> care, don't you?
+ */
+ private void writeConfigToFileAmsThread() {
+ // Create a shallow copy so that we don't have to synchronize on config.
+ final HashMap<String, Integer> packageFlags;
+ synchronized (mPackageFlags) {
+ packageFlags = new HashMap<>(mPackageFlags);
+ }
+
+ FileOutputStream fos = null;
+ try {
+ fos = mConfigFile.startWrite();
+
+ final XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(fos, StandardCharsets.UTF_8.name());
+ out.startDocument(null, true);
+ out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+ out.startTag(null, "packages");
+
+ for (Map.Entry<String, Integer> entry : packageFlags.entrySet()) {
+ String pkg = entry.getKey();
+ int mode = entry.getValue();
+ if (mode == 0) {
+ continue;
+ }
+ out.startTag(null, "package");
+ out.attribute(null, "name", pkg);
+ out.attribute(null, "flags", Integer.toString(mode));
+ out.endTag(null, "package");
+ }
+
+ out.endTag(null, "packages");
+ out.endDocument();
+
+ mConfigFile.finishWrite(fos);
+ } catch (java.io.IOException e1) {
+ Slog.w(TAG, "Error writing package metadata", e1);
+ if (fos != null) {
+ mConfigFile.failWrite(fos);
+ }
+ }
+ }
+
+ /**
+ * Reads the configuration file and populates the package flags.
+ * <p>
+ * <strong>Note:</strong> Must be called from the constructor (and thus on the
+ * ActivityManagerService thread) since we don't synchronize on config.
+ */
+ private void readConfigFromFileAmsThread() {
+ FileInputStream fis = null;
+
+ try {
+ fis = mConfigFile.openRead();
+
+ final XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(fis, StandardCharsets.UTF_8.name());
+
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.START_TAG &&
+ eventType != XmlPullParser.END_DOCUMENT) {
+ eventType = parser.next();
+ }
+ if (eventType == XmlPullParser.END_DOCUMENT) {
+ return;
+ }
+
+ String tagName = parser.getName();
+ if ("packages".equals(tagName)) {
+ eventType = parser.next();
+ do {
+ if (eventType == XmlPullParser.START_TAG) {
+ tagName = parser.getName();
+ if (parser.getDepth() == 2) {
+ if ("package".equals(tagName)) {
+ final String name = parser.getAttributeValue(null, "name");
+ if (name != null) {
+ final String flags = parser.getAttributeValue(
+ null, "flags");
+ int flagsInt = 0;
+ if (flags != null) {
+ try {
+ flagsInt = Integer.parseInt(flags);
+ } catch (NumberFormatException e) {
+ }
+ }
+ mPackageFlags.put(name, flagsInt);
+ }
+ }
+ }
+ }
+ eventType = parser.next();
+ } while (eventType != XmlPullParser.END_DOCUMENT);
+ }
+ } catch (XmlPullParserException e) {
+ Slog.w(TAG, "Error reading package metadata", e);
+ } catch (java.io.IOException e) {
+ if (fis != null) Slog.w(TAG, "Error reading package metadata", e);
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (java.io.IOException e1) {
+ }
+ }
+ }
+ }
+}
diff --git a/com/android/server/am/ClientLifecycleManager.java b/com/android/server/am/ClientLifecycleManager.java
new file mode 100644
index 0000000..c04d103
--- /dev/null
+++ b/com/android/server/am/ClientLifecycleManager.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2017 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.server.am;
+
+import android.annotation.NonNull;
+import android.app.IApplicationThread;
+import android.app.servertransaction.ClientTransaction;
+import android.app.servertransaction.ClientTransactionItem;
+import android.app.servertransaction.ActivityLifecycleItem;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+/**
+ * Class that is able to combine multiple client lifecycle transition requests and/or callbacks,
+ * and execute them as a single transaction.
+ *
+ * @see ClientTransaction
+ */
+class ClientLifecycleManager {
+ // TODO(lifecycler): Implement building transactions or global transaction.
+ // TODO(lifecycler): Use object pools for transactions and transaction items.
+
+ /**
+ * Schedule a transaction, which may consist of multiple callbacks and a lifecycle request.
+ * @param transaction A sequence of client transaction items.
+ * @throws RemoteException
+ *
+ * @see ClientTransaction
+ */
+ void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
+ transaction.schedule();
+ }
+
+ /**
+ * Schedule a single lifecycle request or callback to client activity.
+ * @param client Target client.
+ * @param activityToken Target activity token.
+ * @param stateRequest A request to move target activity to a desired lifecycle state.
+ * @throws RemoteException
+ *
+ * @see ClientTransactionItem
+ */
+ void scheduleTransaction(@NonNull IApplicationThread client, @NonNull IBinder activityToken,
+ @NonNull ActivityLifecycleItem stateRequest) throws RemoteException {
+ final ClientTransaction clientTransaction = transactionWithState(client, activityToken,
+ stateRequest);
+ scheduleTransaction(clientTransaction);
+ }
+
+ /**
+ * Schedule a single callback delivery to client activity.
+ * @param client Target client.
+ * @param activityToken Target activity token.
+ * @param callback A request to deliver a callback.
+ * @throws RemoteException
+ *
+ * @see ClientTransactionItem
+ */
+ void scheduleTransaction(@NonNull IApplicationThread client, @NonNull IBinder activityToken,
+ @NonNull ClientTransactionItem callback) throws RemoteException {
+ final ClientTransaction clientTransaction = transactionWithCallback(client, activityToken,
+ callback);
+ scheduleTransaction(clientTransaction);
+ }
+
+ /**
+ * Schedule a single callback delivery to client application.
+ * @param client Target client.
+ * @param callback A request to deliver a callback.
+ * @throws RemoteException
+ *
+ * @see ClientTransactionItem
+ */
+ void scheduleTransaction(@NonNull IApplicationThread client,
+ @NonNull ClientTransactionItem callback) throws RemoteException {
+ final ClientTransaction clientTransaction = transactionWithCallback(client,
+ null /* activityToken */, callback);
+ scheduleTransaction(clientTransaction);
+ }
+
+ /**
+ * @return A new instance of {@link ClientTransaction} with a single lifecycle state request.
+ *
+ * @see ClientTransaction
+ * @see ClientTransactionItem
+ */
+ private static ClientTransaction transactionWithState(@NonNull IApplicationThread client,
+ @NonNull IBinder activityToken, @NonNull ActivityLifecycleItem stateRequest) {
+ final ClientTransaction clientTransaction = new ClientTransaction(client, activityToken);
+ clientTransaction.setLifecycleStateRequest(stateRequest);
+ return clientTransaction;
+ }
+
+ /**
+ * @return A new instance of {@link ClientTransaction} with a single callback invocation.
+ *
+ * @see ClientTransaction
+ * @see ClientTransactionItem
+ */
+ private static ClientTransaction transactionWithCallback(@NonNull IApplicationThread client,
+ IBinder activityToken, @NonNull ClientTransactionItem callback) {
+ final ClientTransaction clientTransaction = new ClientTransaction(client, activityToken);
+ clientTransaction.addCallback(callback);
+ return clientTransaction;
+ }
+}
diff --git a/com/android/server/am/CompatModePackages.java b/com/android/server/am/CompatModePackages.java
index bfc0456..65c4a42 100644
--- a/com/android/server/am/CompatModePackages.java
+++ b/com/android/server/am/CompatModePackages.java
@@ -58,8 +58,6 @@
public static final int COMPAT_FLAG_DONT_ASK = 1<<0;
// Compatibility state: compatibility mode is enabled.
public static final int COMPAT_FLAG_ENABLED = 1<<1;
- // Unsupported zoom state: don't warn the user about unsupported zoom mode.
- public static final int UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY = 1<<2;
private final HashMap<String, Integer> mPackages = new HashMap<String, Integer>();
@@ -233,10 +231,6 @@
return (getPackageFlags(packageName)&COMPAT_FLAG_DONT_ASK) == 0;
}
- public boolean getPackageNotifyUnsupportedZoomLocked(String packageName) {
- return (getPackageFlags(packageName)&UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY) == 0;
- }
-
public void setFrontActivityAskCompatModeLocked(boolean ask) {
ActivityRecord r = mService.getFocusedStack().topRunningActivityLocked();
if (r != null) {
@@ -245,22 +239,12 @@
}
public void setPackageAskCompatModeLocked(String packageName, boolean ask) {
- int curFlags = getPackageFlags(packageName);
- int newFlags = ask ? (curFlags&~COMPAT_FLAG_DONT_ASK) : (curFlags|COMPAT_FLAG_DONT_ASK);
- if (curFlags != newFlags) {
- if (newFlags != 0) {
- mPackages.put(packageName, newFlags);
- } else {
- mPackages.remove(packageName);
- }
- scheduleWrite();
- }
+ setPackageFlagLocked(packageName, COMPAT_FLAG_DONT_ASK, ask);
}
- public void setPackageNotifyUnsupportedZoomLocked(String packageName, boolean notify) {
+ private void setPackageFlagLocked(String packageName, int flag, boolean set) {
final int curFlags = getPackageFlags(packageName);
- final int newFlags = notify ? (curFlags&~UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY) :
- (curFlags|UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY);
+ final int newFlags = set ? (curFlags & ~flag) : (curFlags | flag);
if (curFlags != newFlags) {
if (newFlags != 0) {
mPackages.put(packageName, newFlags);
diff --git a/com/android/server/am/ConnectionRecord.java b/com/android/server/am/ConnectionRecord.java
index 9b7a0c4..6df283c 100644
--- a/com/android/server/am/ConnectionRecord.java
+++ b/com/android/server/am/ConnectionRecord.java
@@ -19,6 +19,9 @@
import android.app.IServiceConnection;
import android.app.PendingIntent;
import android.content.Context;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.server.am.proto.ConnectionRecordProto;
import java.io.PrintWriter;
@@ -119,4 +122,70 @@
sb.append('}');
return stringName = sb.toString();
}
+
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ if (binding == null) return; // if binding is null, don't write data, something is wrong.
+ long token = proto.start(fieldId);
+ proto.write(ConnectionRecordProto.HEX_HASH,
+ Integer.toHexString(System.identityHashCode(this)));
+ if (binding.client != null) {
+ proto.write(ConnectionRecordProto.USER_ID, binding.client.userId);
+ }
+ if ((flags&Context.BIND_AUTO_CREATE) != 0) {
+ proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.AUTO_CREATE);
+ }
+ if ((flags&Context.BIND_DEBUG_UNBIND) != 0) {
+ proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.DEBUG_UNBIND);
+ }
+ if ((flags&Context.BIND_NOT_FOREGROUND) != 0) {
+ proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.NOT_FG);
+ }
+ if ((flags&Context.BIND_IMPORTANT_BACKGROUND) != 0) {
+ proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.IMPORTANT_BG);
+ }
+ if ((flags&Context.BIND_ABOVE_CLIENT) != 0) {
+ proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.ABOVE_CLIENT);
+ }
+ if ((flags&Context.BIND_ALLOW_OOM_MANAGEMENT) != 0) {
+ proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.ALLOW_OOM_MANAGEMENT);
+ }
+ if ((flags&Context.BIND_WAIVE_PRIORITY) != 0) {
+ proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.WAIVE_PRIORITY);
+ }
+ if ((flags&Context.BIND_IMPORTANT) != 0) {
+ proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.IMPORTANT);
+ }
+ if ((flags&Context.BIND_ADJUST_WITH_ACTIVITY) != 0) {
+ proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.ADJUST_WITH_ACTIVITY);
+ }
+ if ((flags&Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE) != 0) {
+ proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.FG_SERVICE_WHILE_WAKE);
+ }
+ if ((flags&Context.BIND_FOREGROUND_SERVICE) != 0) {
+ proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.FG_SERVICE);
+ }
+ if ((flags&Context.BIND_TREAT_LIKE_ACTIVITY) != 0) {
+ proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.TREAT_LIKE_ACTIVITY);
+ }
+ if ((flags&Context.BIND_VISIBLE) != 0) {
+ proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.VISIBLE);
+ }
+ if ((flags&Context.BIND_SHOWING_UI) != 0) {
+ proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.SHOWING_UI);
+ }
+ if ((flags&Context.BIND_NOT_VISIBLE) != 0) {
+ proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.NOT_VISIBLE);
+ }
+ if (serviceDead) {
+ proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.DEAD);
+ }
+ if (binding.service != null) {
+ proto.write(ConnectionRecordProto.SERVICE_NAME, binding.service.shortName);
+ }
+ if (conn != null) {
+ proto.write(ConnectionRecordProto.CONN_HEX_HASH,
+ Integer.toHexString(System.identityHashCode(conn.asBinder())));
+ }
+ proto.end(token);
+ }
}
diff --git a/com/android/server/am/IntentBindRecord.java b/com/android/server/am/IntentBindRecord.java
index be290e9..01ce64c 100644
--- a/com/android/server/am/IntentBindRecord.java
+++ b/com/android/server/am/IntentBindRecord.java
@@ -21,6 +21,10 @@
import android.os.IBinder;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.server.am.proto.AppBindRecordProto;
+import com.android.server.am.proto.IntentBindRecordProto;
import java.io.PrintWriter;
@@ -106,4 +110,32 @@
sb.append('}');
return stringName = sb.toString();
}
+
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ long token = proto.start(fieldId);
+ proto.write(IntentBindRecordProto.HEX_HASH,
+ Integer.toHexString(System.identityHashCode(this)));
+ proto.write(IntentBindRecordProto.IS_CREATE,
+ (collectFlags()&Context.BIND_AUTO_CREATE) != 0);
+ if (intent != null) {
+ intent.getIntent().writeToProto(proto,
+ IntentBindRecordProto.INTENT, false, true, false, false);
+ }
+ if (binder != null) {
+ proto.write(IntentBindRecordProto.BINDER, binder.toString());
+ }
+ proto.write(IntentBindRecordProto.REQUESTED, requested);
+ proto.write(IntentBindRecordProto.RECEIVED, received);
+ proto.write(IntentBindRecordProto.HAS_BOUND, hasBound);
+ proto.write(IntentBindRecordProto.DO_REBIND, doRebind);
+
+ final int N = apps.size();
+ for (int i=0; i<N; i++) {
+ AppBindRecord a = apps.valueAt(i);
+ if (a != null) {
+ a.writeToProto(proto, IntentBindRecordProto.APPS);
+ }
+ }
+ proto.end(token);
+ }
}
diff --git a/com/android/server/am/KeyguardController.java b/com/android/server/am/KeyguardController.java
index 76b4679..35f4f25 100644
--- a/com/android/server/am/KeyguardController.java
+++ b/com/android/server/am/KeyguardController.java
@@ -19,9 +19,9 @@
import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
-import static android.view.WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS;
-import static android.view.WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
-import static android.view.WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
+import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS;
+import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
+import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
@@ -41,8 +41,11 @@
import android.os.Trace;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
+
import com.android.internal.policy.IKeyguardDismissCallback;
+import com.android.server.policy.WindowManagerPolicy;
import com.android.server.wm.WindowManagerService;
+
import java.io.PrintWriter;
/**
@@ -118,7 +121,7 @@
/**
* Called when Keyguard is going away.
*
- * @param flags See {@link android.view.WindowManagerPolicy#KEYGUARD_GOING_AWAY_FLAG_TO_SHADE}
+ * @param flags See {@link WindowManagerPolicy#KEYGUARD_GOING_AWAY_FLAG_TO_SHADE}
* etc.
*/
void keyguardGoingAway(int flags) {
diff --git a/com/android/server/am/LaunchingActivityPositioner.java b/com/android/server/am/LaunchingActivityPositioner.java
index d5f9cf3..793884d 100644
--- a/com/android/server/am/LaunchingActivityPositioner.java
+++ b/com/android/server/am/LaunchingActivityPositioner.java
@@ -47,10 +47,10 @@
return RESULT_SKIP;
}
- final Rect bounds = TaskRecord.validateBounds(options.getLaunchBounds());
+ final Rect bounds = options.getLaunchBounds();
// Bounds weren't valid.
- if (bounds == null) {
+ if (bounds == null || bounds.isEmpty()) {
return RESULT_SKIP;
}
diff --git a/com/android/server/am/LaunchingBoundsController.java b/com/android/server/am/LaunchingBoundsController.java
index c762f7f..5aa7f58 100644
--- a/com/android/server/am/LaunchingBoundsController.java
+++ b/com/android/server/am/LaunchingBoundsController.java
@@ -101,8 +101,12 @@
* @return {@code true} if bounds were set on the task. {@code false} otherwise.
*/
boolean layoutTask(TaskRecord task, WindowLayout layout) {
- calculateBounds(task, layout, null /*activity*/, null /*source*/, null /*options*/,
- mTmpRect);
+ return layoutTask(task, layout, null /*activity*/, null /*source*/, null /*options*/);
+ }
+
+ boolean layoutTask(TaskRecord task, WindowLayout layout, ActivityRecord activity,
+ ActivityRecord source, ActivityOptions options) {
+ calculateBounds(task, layout, activity, source, options, mTmpRect);
if (mTmpRect.isEmpty()) {
return false;
diff --git a/com/android/server/am/LaunchingTaskPositioner.java b/com/android/server/am/LaunchingTaskPositioner.java
index c958fca..d89568e 100644
--- a/com/android/server/am/LaunchingTaskPositioner.java
+++ b/com/android/server/am/LaunchingTaskPositioner.java
@@ -88,7 +88,7 @@
final ArrayList<TaskRecord> tasks = task.getStack().getAllTasks();
- updateAvailableRect(task, mAvailableRect);
+ mAvailableRect.set(task.getParent().getBounds());
if (layout == null) {
positionCenter(tasks, mAvailableRect, getFreeformWidth(mAvailableRect),
@@ -123,17 +123,6 @@
return RESULT_CONTINUE;
}
- private void updateAvailableRect(TaskRecord task, Rect availableRect) {
- final Rect stackBounds = task.getStack().mBounds;
-
- if (stackBounds != null) {
- availableRect.set(stackBounds);
- } else {
- task.getStack().getDisplay().mDisplay.getSize(mDisplaySize);
- availableRect.set(0, 0, mDisplaySize.x, mDisplaySize.y);
- }
- }
-
@VisibleForTesting
static int getFreeformStartLeft(Rect bounds) {
return bounds.left + bounds.width() / MARGIN_SIZE_DENOMINATOR;
@@ -294,9 +283,9 @@
private static boolean boundsConflict(Rect proposal, ArrayList<TaskRecord> tasks) {
for (int i = tasks.size() - 1; i >= 0; i--) {
- TaskRecord task = tasks.get(i);
- if (!task.mActivities.isEmpty() && task.mBounds != null) {
- Rect bounds = task.mBounds;
+ final TaskRecord task = tasks.get(i);
+ if (!task.mActivities.isEmpty() && !task.matchParentBounds()) {
+ final Rect bounds = task.getOverrideBounds();
if (closeLeftTopCorner(proposal, bounds) || closeRightTopCorner(proposal, bounds)
|| closeLeftBottomCorner(proposal, bounds)
|| closeRightBottomCorner(proposal, bounds)) {
diff --git a/com/android/server/am/LockTaskController.java b/com/android/server/am/LockTaskController.java
index e87b4e6..d77e1a2 100644
--- a/com/android/server/am/LockTaskController.java
+++ b/com/android/server/am/LockTaskController.java
@@ -250,7 +250,24 @@
}
/**
- * @return whether the requested task is allowed to be launched.
+ * @return whether the requested task is allowed to be locked (either whitelisted, or declares
+ * lockTaskMode="always" in the manifest).
+ */
+ boolean isTaskWhitelisted(TaskRecord task) {
+ switch(task.mLockTaskAuth) {
+ case LOCK_TASK_AUTH_WHITELISTED:
+ case LOCK_TASK_AUTH_LAUNCHABLE:
+ case LOCK_TASK_AUTH_LAUNCHABLE_PRIV:
+ return true;
+ case LOCK_TASK_AUTH_PINNABLE:
+ case LOCK_TASK_AUTH_DONT_LOCK:
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * @return whether the requested task is disallowed to be launched.
*/
boolean isLockTaskModeViolation(TaskRecord task) {
return isLockTaskModeViolation(task, false);
@@ -258,7 +275,7 @@
/**
* @param isNewClearTask whether the task would be cleared as part of the operation.
- * @return whether the requested task is allowed to be launched.
+ * @return whether the requested task is disallowed to be launched.
*/
boolean isLockTaskModeViolation(TaskRecord task, boolean isNewClearTask) {
if (isLockTaskModeViolationInternal(task, isNewClearTask)) {
@@ -275,21 +292,18 @@
// If the task is already at the top and won't be cleared, then allow the operation
return false;
}
- final int lockTaskAuth = task.mLockTaskAuth;
- switch (lockTaskAuth) {
- case LOCK_TASK_AUTH_DONT_LOCK:
- return !mLockTaskModeTasks.isEmpty();
- case LOCK_TASK_AUTH_LAUNCHABLE_PRIV:
- case LOCK_TASK_AUTH_LAUNCHABLE:
- case LOCK_TASK_AUTH_WHITELISTED:
- return false;
- case LOCK_TASK_AUTH_PINNABLE:
- // Pinnable tasks can't be launched on top of locktask tasks.
- return !mLockTaskModeTasks.isEmpty();
- default:
- Slog.w(TAG, "isLockTaskModeViolation: invalid lockTaskAuth value=" + lockTaskAuth);
- return true;
+
+ // Allow recents activity if enabled by policy
+ if (task.isActivityTypeRecents() && isRecentsAllowed(task.userId)) {
+ return false;
}
+
+ return !(isTaskWhitelisted(task) || mLockTaskModeTasks.isEmpty());
+ }
+
+ private boolean isRecentsAllowed(int userId) {
+ return (getLockTaskFeaturesForUser(userId)
+ & DevicePolicyManager.LOCK_TASK_FEATURE_RECENTS) != 0;
}
/**
@@ -491,6 +505,7 @@
}
if (mLockTaskModeTasks.isEmpty()) {
+ mSupervisor.mRecentTasks.onLockTaskModeStateChanged(lockTaskModeState, task.userId);
// Start lock task on the handler thread
mHandler.post(() -> performStartLockTask(
task.intent.getComponent().getPackageName(),
diff --git a/com/android/server/am/ProcessList.java b/com/android/server/am/ProcessList.java
index 7810c5e..6fb3dbb 100644
--- a/com/android/server/am/ProcessList.java
+++ b/com/android/server/am/ProcessList.java
@@ -40,7 +40,7 @@
/**
* Activity manager code dealing with processes.
*/
-final class ProcessList {
+public final class ProcessList {
private static final String TAG = TAG_WITH_CLASS_NAME ? "ProcessList" : TAG_AM;
// The minimum time we allow between crashes, for us to consider this
@@ -400,6 +400,9 @@
case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
procState = "CACC";
break;
+ case ActivityManager.PROCESS_STATE_CACHED_RECENT:
+ procState = "CRE ";
+ break;
case ActivityManager.PROCESS_STATE_CACHED_EMPTY:
procState = "CEM ";
break;
@@ -494,6 +497,7 @@
PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+ PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_CACHED_RECENT
PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_CACHED_EMPTY
};
@@ -515,6 +519,7 @@
PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+ PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_RECENT
PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_EMPTY
};
@@ -536,6 +541,7 @@
PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+ PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_RECENT
PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_EMPTY
};
@@ -557,6 +563,7 @@
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+ PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_RECENT
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_EMPTY
};
@@ -578,6 +585,7 @@
PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+ PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_RECENT
PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_EMPTY
};
diff --git a/com/android/server/am/ProcessRecord.java b/com/android/server/am/ProcessRecord.java
index e847723..9d3c2ae 100644
--- a/com/android/server/am/ProcessRecord.java
+++ b/com/android/server/am/ProcessRecord.java
@@ -164,6 +164,8 @@
// all activities running in the process
final ArrayList<ActivityRecord> activities = new ArrayList<>();
+ // any tasks this process had run root activities in
+ final ArrayList<TaskRecord> recentTasks = new ArrayList<>();
// all ServiceRecord running in this process
final ArraySet<ServiceRecord> services = new ArraySet<>();
// services that are currently executing code (need to remain foreground).
@@ -396,6 +398,12 @@
pw.print(prefix); pw.print(" - "); pw.println(activities.get(i));
}
}
+ if (recentTasks.size() > 0) {
+ pw.print(prefix); pw.println("Recent Tasks:");
+ for (int i=0; i<recentTasks.size(); i++) {
+ pw.print(prefix); pw.print(" - "); pw.println(recentTasks.get(i));
+ }
+ }
if (services.size() > 0) {
pw.print(prefix); pw.println("Services:");
for (int i=0; i<services.size(); i++) {
@@ -512,6 +520,13 @@
}
}
+ public void clearRecentTasks() {
+ for (int i = recentTasks.size() - 1; i >= 0; i--) {
+ recentTasks.get(i).clearRootProcess();
+ }
+ recentTasks.clear();
+ }
+
/**
* This method returns true if any of the activities within the process record are interesting
* to the user. See HistoryRecord.isInterestingToUserLocked()
diff --git a/com/android/server/am/ProviderMap.java b/com/android/server/am/ProviderMap.java
index 32d03da..8a905f8 100644
--- a/com/android/server/am/ProviderMap.java
+++ b/com/android/server/am/ProviderMap.java
@@ -28,6 +28,7 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
@@ -322,8 +323,7 @@
return needSep;
}
- protected boolean dumpProvider(FileDescriptor fd, PrintWriter pw, String name, String[] args,
- int opti, boolean dumpAll) {
+ private ArrayList<ContentProviderRecord> getProvidersForName(String name) {
ArrayList<ContentProviderRecord> allProviders = new ArrayList<ContentProviderRecord>();
ArrayList<ContentProviderRecord> providers = new ArrayList<ContentProviderRecord>();
@@ -365,6 +365,12 @@
}
}
}
+ return providers;
+ }
+
+ protected boolean dumpProvider(FileDescriptor fd, PrintWriter pw, String name, String[] args,
+ int opti, boolean dumpAll) {
+ ArrayList<ContentProviderRecord> providers = getProvidersForName(name);
if (providers.size() <= 0) {
return false;
@@ -417,6 +423,33 @@
}
/**
+ * Similar to the dumpProvider, but only dumps the first matching provider.
+ * The provider is responsible for dumping as proto.
+ */
+ protected boolean dumpProviderProto(FileDescriptor fd, PrintWriter pw, String name,
+ String[] args) {
+ //add back the --proto arg, which was stripped out by PriorityDump
+ String[] newArgs = Arrays.copyOf(args, args.length + 1);
+ newArgs[args.length] = "--proto";
+
+ ArrayList<ContentProviderRecord> providers = getProvidersForName(name);
+
+ if (providers.size() <= 0) {
+ return false;
+ }
+
+ // Only dump the first provider, since we are dumping in proto format
+ for (int i = 0; i < providers.size(); i++) {
+ final ContentProviderRecord r = providers.get(i);
+ if (r.proc != null && r.proc.thread != null) {
+ dumpToTransferPipe(null, fd, pw, r, newArgs);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
* Invokes IApplicationThread.dumpProvider() on the thread of the specified provider without
* any meta string (e.g., provider info, indentation) written to the file descriptor.
*/
diff --git a/com/android/server/am/RecentTasks.java b/com/android/server/am/RecentTasks.java
index d35c37b..abb296e 100644
--- a/com/android/server/am/RecentTasks.java
+++ b/com/android/server/am/RecentTasks.java
@@ -80,6 +80,20 @@
/**
* Class for managing the recent tasks list. The list is ordered by most recent (index 0) to the
* least recent.
+ *
+ * The trimming logic can be boiled down to the following. For recent task list with a number of
+ * tasks, the visible tasks are an interleaving subset of tasks that would normally be presented to
+ * the user. Non-visible tasks are not considered for trimming. Of the visible tasks, only a
+ * sub-range are presented to the user, based on the device type, last task active time, or other
+ * task state. Tasks that are not in the visible range and are not returnable from the SystemUI
+ * (considering the back stack) are considered trimmable. If the device does not support recent
+ * tasks, then trimming is completely disabled.
+ *
+ * eg.
+ * L = [TTTTTTTTTTTTTTTTTTTTTTTTTT] // list of tasks
+ * [VVV VV VVVV V V V ] // Visible tasks
+ * [RRR RR XXXX X X X ] // Visible range tasks, eg. if the device only shows 5 tasks,
+ * // 'X' tasks are trimmed.
*/
class RecentTasks {
private static final String TAG = TAG_WITH_CLASS_NAME ? "RecentTasks" : TAG_AM;
@@ -488,6 +502,18 @@
}
}
+ void onLockTaskModeStateChanged(int lockTaskModeState, int userId) {
+ if (lockTaskModeState != ActivityManager.LOCK_TASK_MODE_LOCKED) {
+ return;
+ }
+ for (int i = mTasks.size() - 1; i >= 0; --i) {
+ final TaskRecord tr = mTasks.get(i);
+ if (tr.userId == userId && !mService.mLockTaskController.isTaskWhitelisted(tr)) {
+ remove(tr);
+ }
+ }
+ }
+
void removeTasksByPackageName(String packageName, int userId) {
for (int i = mTasks.size() - 1; i >= 0; --i) {
final TaskRecord tr = mTasks.get(i);
@@ -496,7 +522,8 @@
if (tr.userId != userId) return;
if (!taskPackageName.equals(packageName)) return;
- mService.mStackSupervisor.removeTaskByIdLocked(tr.taskId, true, REMOVE_FROM_RECENTS);
+ mService.mStackSupervisor.removeTaskByIdLocked(tr.taskId, true, REMOVE_FROM_RECENTS,
+ "remove-package-task");
}
}
@@ -513,7 +540,7 @@
&& (filterByClasses == null || filterByClasses.contains(cn.getClassName()));
if (sameComponent) {
mService.mStackSupervisor.removeTaskByIdLocked(tr.taskId, false,
- REMOVE_FROM_RECENTS);
+ REMOVE_FROM_RECENTS, "disabled-package");
}
}
}
@@ -1001,12 +1028,13 @@
continue;
} else {
numVisibleTasks++;
- if (isInVisibleRange(task, numVisibleTasks)) {
+ if (isInVisibleRange(task, numVisibleTasks) || !isTrimmable(task)) {
// Keep visible tasks in range
i++;
continue;
} else {
- // Fall through to trim visible tasks that are no longer in range
+ // Fall through to trim visible tasks that are no longer in range and
+ // trimmable
if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG,
"Trimming out-of-range visible task=" + task);
}
@@ -1122,6 +1150,28 @@
}
/**
+ * @return whether the given task can be trimmed even if it is outside the visible range.
+ */
+ protected boolean isTrimmable(TaskRecord task) {
+ final ActivityStack stack = task.getStack();
+ final ActivityStack homeStack = mService.mStackSupervisor.mHomeStack;
+
+ // No stack for task, just trim it
+ if (stack == null) {
+ return true;
+ }
+
+ // Ignore tasks from different displays
+ if (stack.getDisplay() != homeStack.getDisplay()) {
+ return false;
+ }
+
+ // Trim tasks that are in stacks that are behind the home stack
+ final ActivityDisplay display = stack.getDisplay();
+ return display.getIndexOf(stack) < display.getIndexOf(homeStack);
+ }
+
+ /**
* If needed, remove oldest existing entries in recents that are for the same kind
* of task as the given one.
*/
@@ -1426,9 +1476,6 @@
* Creates a new RecentTaskInfo from a TaskRecord.
*/
ActivityManager.RecentTaskInfo createRecentTaskInfo(TaskRecord tr) {
- // Update the task description to reflect any changes in the task stack
- tr.updateTaskDescription();
-
// Compose the recent task info
ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo();
rti.id = tr.getTopActivity() == null ? INVALID_TASK_ID : tr.taskId;
@@ -1444,8 +1491,8 @@
rti.affiliatedTaskId = tr.mAffiliatedTaskId;
rti.affiliatedTaskColor = tr.mAffiliatedTaskColor;
rti.numActivities = 0;
- if (tr.mBounds != null) {
- rti.bounds = new Rect(tr.mBounds);
+ if (!tr.matchParentBounds()) {
+ rti.bounds = new Rect(tr.getOverrideBounds());
}
rti.supportsSplitScreenMultiWindow = tr.supportsSplitScreenWindowingMode();
rti.resizeMode = tr.mResizeMode;
diff --git a/com/android/server/am/ServiceRecord.java b/com/android/server/am/ServiceRecord.java
index 16995e5..b6eff00 100644
--- a/com/android/server/am/ServiceRecord.java
+++ b/com/android/server/am/ServiceRecord.java
@@ -19,6 +19,7 @@
import com.android.internal.app.procstats.ServiceState;
import com.android.internal.os.BatteryStatsImpl;
import com.android.server.LocalServices;
+import com.android.server.am.proto.ServiceRecordProto;
import com.android.server.notification.NotificationManagerInternal;
import android.app.INotificationManager;
@@ -42,6 +43,8 @@
import android.util.ArrayMap;
import android.util.Slog;
import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
+import android.util.proto.ProtoUtils;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -79,7 +82,7 @@
final String permission;// permission needed to access service
final boolean exported; // from ServiceInfo.exported
final Runnable restarter; // used to schedule retries of starting the service
- final long createTime; // when this service was created
+ final long createRealTime; // when this service was created
final ArrayMap<Intent.FilterComparison, IntentBindRecord> bindings
= new ArrayMap<Intent.FilterComparison, IntentBindRecord>();
// All active bindings to the service.
@@ -103,7 +106,7 @@
boolean startRequested; // someone explicitly called start?
boolean delayedStop; // service has been stopped but is in a delayed start?
boolean stopIfKilled; // last onStart() said to stop if service killed?
- boolean callStart; // last onStart() has asked to alway be called on restart.
+ boolean callStart; // last onStart() has asked to always be called on restart.
int executeNesting; // number of outstanding operations keeping foreground.
boolean executeFg; // should we be executing in the foreground?
long executingStart; // start time of last execute request.
@@ -159,6 +162,27 @@
}
}
+ public void writeToProto(ProtoOutputStream proto, long fieldId, long now) {
+ long token = proto.start(fieldId);
+ proto.write(ServiceRecordProto.StartItemProto.ID, id);
+ ProtoUtils.toDuration(proto,
+ ServiceRecordProto.StartItemProto.DURATION, deliveredTime, now);
+ proto.write(ServiceRecordProto.StartItemProto.DELIVERY_COUNT, deliveryCount);
+ proto.write(ServiceRecordProto.StartItemProto.DONE_EXECUTING_COUNT, doneExecutingCount);
+ if (intent != null) {
+ intent.writeToProto(proto, ServiceRecordProto.StartItemProto.INTENT, true, true,
+ true, false);
+ }
+ if (neededGrants != null) {
+ neededGrants.writeToProto(proto, ServiceRecordProto.StartItemProto.NEEDED_GRANTS);
+ }
+ if (uriPermissions != null) {
+ uriPermissions.writeToProto(proto,
+ ServiceRecordProto.StartItemProto.URI_PERMISSIONS);
+ }
+ proto.end(token);
+ }
+
public String toString() {
if (stringName != null) {
return stringName;
@@ -209,6 +233,117 @@
}
}
+ void writeToProto(ProtoOutputStream proto, long fieldId) {
+ long token = proto.start(fieldId);
+ proto.write(ServiceRecordProto.SHORT_NAME, this.shortName);
+ proto.write(ServiceRecordProto.HEX_HASH,
+ Integer.toHexString(System.identityHashCode(this)));
+ proto.write(ServiceRecordProto.IS_RUNNING, app != null);
+ if (app != null) {
+ proto.write(ServiceRecordProto.PID, app.pid);
+ }
+ if (intent != null) {
+ intent.getIntent().writeToProto(proto, ServiceRecordProto.INTENT, false, true, false,
+ true);
+ }
+ proto.write(ServiceRecordProto.PACKAGE_NAME, packageName);
+ proto.write(ServiceRecordProto.PROCESS_NAME, processName);
+ proto.write(ServiceRecordProto.PERMISSION, permission);
+
+ long now = SystemClock.uptimeMillis();
+ long nowReal = SystemClock.elapsedRealtime();
+ if (appInfo != null) {
+ long appInfoToken = proto.start(ServiceRecordProto.APPINFO);
+ proto.write(ServiceRecordProto.AppInfo.BASE_DIR, appInfo.sourceDir);
+ if (!Objects.equals(appInfo.sourceDir, appInfo.publicSourceDir)) {
+ proto.write(ServiceRecordProto.AppInfo.RES_DIR, appInfo.publicSourceDir);
+ }
+ proto.write(ServiceRecordProto.AppInfo.DATA_DIR, appInfo.dataDir);
+ proto.end(appInfoToken);
+ }
+ if (app != null) {
+ app.writeToProto(proto, ServiceRecordProto.APP);
+ }
+ if (isolatedProc != null) {
+ isolatedProc.writeToProto(proto, ServiceRecordProto.ISOLATED_PROC);
+ }
+ proto.write(ServiceRecordProto.WHITELIST_MANAGER, whitelistManager);
+ proto.write(ServiceRecordProto.DELAYED, delayed);
+ if (isForeground || foregroundId != 0) {
+ long fgToken = proto.start(ServiceRecordProto.FOREGROUND);
+ proto.write(ServiceRecordProto.Foreground.ID, foregroundId);
+ foregroundNoti.writeToProto(proto, ServiceRecordProto.Foreground.NOTIFICATION);
+ proto.end(fgToken);
+ }
+ ProtoUtils.toDuration(proto, ServiceRecordProto.CREATE_REAL_TIME, createRealTime, nowReal);
+ ProtoUtils.toDuration(proto,
+ ServiceRecordProto.STARTING_BG_TIMEOUT, startingBgTimeout, now);
+ ProtoUtils.toDuration(proto, ServiceRecordProto.LAST_ACTIVITY_TIME, lastActivity, now);
+ ProtoUtils.toDuration(proto, ServiceRecordProto.RESTART_TIME, restartTime, now);
+ proto.write(ServiceRecordProto.CREATED_FROM_FG, createdFromFg);
+
+ if (startRequested || delayedStop || lastStartId != 0) {
+ long startToken = proto.start(ServiceRecordProto.START);
+ proto.write(ServiceRecordProto.Start.START_REQUESTED, startRequested);
+ proto.write(ServiceRecordProto.Start.DELAYED_STOP, delayedStop);
+ proto.write(ServiceRecordProto.Start.STOP_IF_KILLED, stopIfKilled);
+ proto.write(ServiceRecordProto.Start.LAST_START_ID, lastStartId);
+ proto.end(startToken);
+ }
+
+ if (executeNesting != 0) {
+ long executNestingToken = proto.start(ServiceRecordProto.EXECUTE);
+ proto.write(ServiceRecordProto.ExecuteNesting.EXECUTE_NESTING, executeNesting);
+ proto.write(ServiceRecordProto.ExecuteNesting.EXECUTE_FG, executeFg);
+ ProtoUtils.toDuration(proto,
+ ServiceRecordProto.ExecuteNesting.EXECUTING_START, executingStart, now);
+ proto.end(executNestingToken);
+ }
+ if (destroying || destroyTime != 0) {
+ ProtoUtils.toDuration(proto, ServiceRecordProto.DESTORY_TIME, destroyTime, now);
+ }
+ if (crashCount != 0 || restartCount != 0 || restartDelay != 0 || nextRestartTime != 0) {
+ long crashToken = proto.start(ServiceRecordProto.CRASH);
+ proto.write(ServiceRecordProto.Crash.RESTART_COUNT, restartCount);
+ ProtoUtils.toDuration(proto, ServiceRecordProto.Crash.RESTART_DELAY, restartDelay, now);
+ ProtoUtils.toDuration(proto,
+ ServiceRecordProto.Crash.NEXT_RESTART_TIME, nextRestartTime, now);
+ proto.write(ServiceRecordProto.Crash.CRASH_COUNT, crashCount);
+ proto.end(crashToken);
+ }
+
+ if (deliveredStarts.size() > 0) {
+ final int N = deliveredStarts.size();
+ for (int i = 0; i < N; i++) {
+ deliveredStarts.get(i).writeToProto(proto,
+ ServiceRecordProto.DELIVERED_STARTS, now);
+ }
+ }
+ if (pendingStarts.size() > 0) {
+ final int N = pendingStarts.size();
+ for (int i = 0; i < N; i++) {
+ pendingStarts.get(i).writeToProto(proto, ServiceRecordProto.PENDING_STARTS, now);
+ }
+ }
+ if (bindings.size() > 0) {
+ final int N = bindings.size();
+ for (int i=0; i<N; i++) {
+ IntentBindRecord b = bindings.valueAt(i);
+ b.writeToProto(proto, ServiceRecordProto.BINDINGS);
+ }
+ }
+ if (connections.size() > 0) {
+ final int N = connections.size();
+ for (int conni=0; conni<N; conni++) {
+ ArrayList<ConnectionRecord> c = connections.valueAt(conni);
+ for (int i=0; i<c.size(); i++) {
+ c.get(i).writeToProto(proto, ServiceRecordProto.CONNECTIONS);
+ }
+ }
+ }
+ proto.end(token);
+ }
+
void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("intent={");
pw.print(intent.getIntent().toShortString(false, true, false, true));
@@ -243,7 +378,7 @@
pw.print(" foregroundNoti="); pw.println(foregroundNoti);
}
pw.print(prefix); pw.print("createTime=");
- TimeUtils.formatDuration(createTime, nowReal, pw);
+ TimeUtils.formatDuration(createRealTime, nowReal, pw);
pw.print(" startingBgTimeout=");
TimeUtils.formatDuration(startingBgTimeout, now, pw);
pw.println();
@@ -329,7 +464,7 @@
permission = sInfo.permission;
exported = sInfo.exported;
this.restarter = restarter;
- createTime = SystemClock.elapsedRealtime();
+ createRealTime = SystemClock.elapsedRealtime();
lastActivity = SystemClock.uptimeMillis();
userId = UserHandle.getUserId(appInfo.uid);
createdFromFg = callerIsFg;
diff --git a/com/android/server/am/TaskRecord.java b/com/android/server/am/TaskRecord.java
index 949f51f..83965ee 100644
--- a/com/android/server/am/TaskRecord.java
+++ b/com/android/server/am/TaskRecord.java
@@ -19,9 +19,6 @@
import static android.app.ActivityManager.RESIZE_MODE_FORCED;
import static android.app.ActivityManager.RESIZE_MODE_SYSTEM;
import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
@@ -257,6 +254,11 @@
/** Current stack. Setter must always be used to update the value. */
private ActivityStack mStack;
+ /** The process that had previously hosted the root activity of this task.
+ * Used to know that we should try harder to keep this process around, in case the
+ * user wants to return to it. */
+ private ProcessRecord mRootProcess;
+
/** Takes on same value as first root activity */
boolean isPersistable = false;
int maxRecents;
@@ -288,11 +290,6 @@
final ActivityManagerService mService;
- // Whether or not this task covers the entire screen; by default tasks are fullscreen.
- boolean mFullscreen = true;
-
- // Bounds of the Task. null for fullscreen tasks.
- Rect mBounds = null;
private final Rect mTmpStableBounds = new Rect();
private final Rect mTmpNonDecorBounds = new Rect();
private final Rect mTmpRect = new Rect();
@@ -475,68 +472,76 @@
}
boolean resize(Rect bounds, int resizeMode, boolean preserveWindow, boolean deferResume) {
- if (!isResizeable()) {
- Slog.w(TAG, "resizeTask: task " + this + " not resizeable.");
- return true;
- }
+ mService.mWindowManager.deferSurfaceLayout();
- // If this is a forced resize, let it go through even if the bounds is not changing,
- // as we might need a relayout due to surface size change (to/from fullscreen).
- final boolean forced = (resizeMode & RESIZE_MODE_FORCED) != 0;
- if (Objects.equals(mBounds, bounds) && !forced) {
- // Nothing to do here...
- return true;
- }
- bounds = validateBounds(bounds);
-
- if (mWindowContainerController == null) {
- // Task doesn't exist in window manager yet (e.g. was restored from recents).
- // All we can do for now is update the bounds so it can be used when the task is
- // added to window manager.
- updateOverrideConfiguration(bounds);
- if (!inFreeformWindowingMode()) {
- // re-restore the task so it can have the proper stack association.
- mService.mStackSupervisor.restoreRecentTaskLocked(this, null, !ON_TOP);
+ try {
+ if (!isResizeable()) {
+ Slog.w(TAG, "resizeTask: task " + this + " not resizeable.");
+ return true;
}
- return true;
- }
- if (!canResizeToBounds(bounds)) {
- throw new IllegalArgumentException("resizeTask: Can not resize task=" + this
- + " to bounds=" + bounds + " resizeMode=" + mResizeMode);
- }
+ // If this is a forced resize, let it go through even if the bounds is not changing,
+ // as we might need a relayout due to surface size change (to/from fullscreen).
+ final boolean forced = (resizeMode & RESIZE_MODE_FORCED) != 0;
+ if (equivalentOverrideBounds(bounds) && !forced) {
+ // Nothing to do here...
+ return true;
+ }
- // Do not move the task to another stack here.
- // This method assumes that the task is already placed in the right stack.
- // we do not mess with that decision and we only do the resize!
+ if (mWindowContainerController == null) {
+ // Task doesn't exist in window manager yet (e.g. was restored from recents).
+ // All we can do for now is update the bounds so it can be used when the task is
+ // added to window manager.
+ updateOverrideConfiguration(bounds);
+ if (!inFreeformWindowingMode()) {
+ // re-restore the task so it can have the proper stack association.
+ mService.mStackSupervisor.restoreRecentTaskLocked(this, null, !ON_TOP);
+ }
+ return true;
+ }
- Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeTask_" + taskId);
+ if (!canResizeToBounds(bounds)) {
+ throw new IllegalArgumentException("resizeTask: Can not resize task=" + this
+ + " to bounds=" + bounds + " resizeMode=" + mResizeMode);
+ }
- final boolean updatedConfig = updateOverrideConfiguration(bounds);
- // This variable holds information whether the configuration didn't change in a significant
- // way and the activity was kept the way it was. If it's false, it means the activity had
- // to be relaunched due to configuration change.
- boolean kept = true;
- if (updatedConfig) {
- final ActivityRecord r = topRunningActivityLocked();
- if (r != null && !deferResume) {
- kept = r.ensureActivityConfigurationLocked(0 /* globalChanges */, preserveWindow);
- mService.mStackSupervisor.ensureActivitiesVisibleLocked(r, 0, !PRESERVE_WINDOWS);
- if (!kept) {
- mService.mStackSupervisor.resumeFocusedStackTopActivityLocked();
+ // Do not move the task to another stack here.
+ // This method assumes that the task is already placed in the right stack.
+ // we do not mess with that decision and we only do the resize!
+
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeTask_" + taskId);
+
+ final boolean updatedConfig = updateOverrideConfiguration(bounds);
+ // This variable holds information whether the configuration didn't change in a significant
+
+ // way and the activity was kept the way it was. If it's false, it means the activity
+ // had
+ // to be relaunched due to configuration change.
+ boolean kept = true;
+ if (updatedConfig) {
+ final ActivityRecord r = topRunningActivityLocked();
+ if (r != null && !deferResume) {
+ kept = r.ensureActivityConfigurationLocked(0 /* globalChanges */,
+ preserveWindow);
+ mService.mStackSupervisor.ensureActivitiesVisibleLocked(r, 0,
+ !PRESERVE_WINDOWS);
+ if (!kept) {
+ mService.mStackSupervisor.resumeFocusedStackTopActivityLocked();
+ }
}
}
- }
- mWindowContainerController.resize(mBounds, getOverrideConfiguration(), kept, forced);
+ mWindowContainerController.resize(kept, forced);
- Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
- return kept;
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ return kept;
+ } finally {
+ mService.mWindowManager.continueSurfaceLayout();
+ }
}
// TODO: Investigate combining with the resize() method above.
void resizeWindowContainer() {
- mWindowContainerController.resize(mBounds, getOverrideConfiguration(), false /* relayout */,
- false /* forced */);
+ mWindowContainerController.resize(false /* relayout */, false /* forced */);
}
void getWindowContainerBounds(Rect bounds) {
@@ -681,16 +686,17 @@
// Make sure the task has the appropriate bounds/size for the stack it is in.
final boolean toStackSplitScreenPrimary =
toStackWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+ final Rect configBounds = getOverrideBounds();
if ((toStackWindowingMode == WINDOWING_MODE_FULLSCREEN
|| toStackWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY)
- && !Objects.equals(mBounds, toStack.mBounds)) {
- kept = resize(toStack.mBounds, RESIZE_MODE_SYSTEM, !mightReplaceWindow,
+ && !Objects.equals(configBounds, toStack.getOverrideBounds())) {
+ kept = resize(toStack.getOverrideBounds(), RESIZE_MODE_SYSTEM, !mightReplaceWindow,
deferResume);
} else if (toStackWindowingMode == WINDOWING_MODE_FREEFORM) {
Rect bounds = getLaunchBounds();
if (bounds == null) {
mService.mStackSupervisor.getLaunchingBoundsController().layoutTask(this, null);
- bounds = mBounds;
+ bounds = configBounds;
}
kept = resize(bounds, RESIZE_MODE_FORCED, !mightReplaceWindow, deferResume);
} else if (toStackSplitScreenPrimary || toStackWindowingMode == WINDOWING_MODE_PINNED) {
@@ -699,7 +705,7 @@
// mode
mService.mStackSupervisor.moveRecentsStackToFront(reason);
}
- kept = resize(toStack.mBounds, RESIZE_MODE_SYSTEM, !mightReplaceWindow,
+ kept = resize(toStack.getOverrideBounds(), RESIZE_MODE_SYSTEM, !mightReplaceWindow,
deferResume);
}
} finally {
@@ -962,6 +968,8 @@
mService.notifyTaskPersisterLocked(this, false);
}
+ clearRootProcess();
+
// TODO: Use window container controller once tasks are better synced between AM and WM
mService.mWindowManager.notifyTaskRemovedFromRecents(taskId, userId);
}
@@ -1303,7 +1311,8 @@
* Completely remove all activities associated with an existing
* task starting at a specified index.
*/
- final void performClearTaskAtIndexLocked(int activityNdx, boolean pauseImmediately) {
+ final void performClearTaskAtIndexLocked(int activityNdx, boolean pauseImmediately,
+ String reason) {
int numActivities = mActivities.size();
for ( ; activityNdx < numActivities; ++activityNdx) {
final ActivityRecord r = mActivities.get(activityNdx);
@@ -1317,7 +1326,7 @@
--activityNdx;
--numActivities;
} else if (mStack.finishActivityLocked(r, Activity.RESULT_CANCELED, null,
- "clear-task-index", false, pauseImmediately)) {
+ reason, false, pauseImmediately)) {
--activityNdx;
--numActivities;
}
@@ -1329,7 +1338,7 @@
*/
void performClearTaskLocked() {
mReuseTask = true;
- performClearTaskAtIndexLocked(0, !PAUSE_IMMEDIATELY);
+ performClearTaskAtIndexLocked(0, !PAUSE_IMMEDIATELY, "clear-task-all");
mReuseTask = false;
}
@@ -1400,9 +1409,9 @@
return null;
}
- void removeTaskActivitiesLocked(boolean pauseImmediately) {
+ void removeTaskActivitiesLocked(boolean pauseImmediately, String reason) {
// Just remove the entire task.
- performClearTaskAtIndexLocked(0, pauseImmediately);
+ performClearTaskAtIndexLocked(0, pauseImmediately, reason);
}
String lockTaskAuthToString() {
@@ -1494,8 +1503,10 @@
return true;
}
final boolean landscape = bounds.width() > bounds.height();
+ final Rect configBounds = getOverrideBounds();
if (mResizeMode == RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION) {
- return mBounds == null || landscape == (mBounds.width() > mBounds.height());
+ return configBounds.isEmpty()
+ || landscape == (configBounds.width() > configBounds.height());
}
return (mResizeMode != RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY || !landscape)
&& (mResizeMode != RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY || landscape);
@@ -1616,6 +1627,9 @@
final int effectiveRootIndex = findEffectiveRootIndex();
final ActivityRecord r = mActivities.get(effectiveRootIndex);
setIntent(r);
+
+ // Update the task description when the activities change
+ updateTaskDescription();
}
void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException {
@@ -1913,8 +1927,9 @@
return;
}
+ final Rect configBounds = getOverrideBounds();
if (adjustWidth) {
- if (mBounds != null && bounds.right == mBounds.right) {
+ if (!configBounds.isEmpty() && bounds.right == configBounds.right) {
bounds.left = bounds.right - minWidth;
} else {
// Either left bounds match, or neither match, or the previous bounds were
@@ -1923,7 +1938,7 @@
}
}
if (adjustHeight) {
- if (mBounds != null && bounds.bottom == mBounds.bottom) {
+ if (!configBounds.isEmpty() && bounds.bottom == configBounds.bottom) {
bounds.top = bounds.bottom - minHeight;
} else {
// Either top bounds match, or neither match, or the previous bounds were
@@ -1970,42 +1985,45 @@
* @return True if the override configuration was updated.
*/
boolean updateOverrideConfiguration(Rect bounds, @Nullable Rect insetBounds) {
- if (Objects.equals(mBounds, bounds)) {
+ if (equivalentOverrideBounds(bounds)) {
return false;
}
+ final Rect currentBounds = getOverrideBounds();
+
mTmpConfig.setTo(getOverrideConfiguration());
- final boolean oldFullscreen = mFullscreen;
final Configuration newConfig = getOverrideConfiguration();
- mFullscreen = bounds == null;
+ final boolean matchParentBounds = bounds == null || bounds.isEmpty();
final boolean persistBounds = getWindowConfiguration().persistTaskBounds();
- if (mFullscreen) {
- if (mBounds != null && persistBounds) {
- mLastNonFullscreenBounds = mBounds;
+ if (matchParentBounds) {
+ if (!currentBounds.isEmpty() && persistBounds) {
+ mLastNonFullscreenBounds = currentBounds;
}
- mBounds = null;
+ setBounds(null);
newConfig.unset();
} else {
mTmpRect.set(bounds);
adjustForMinimalTaskDimensions(mTmpRect);
- if (mBounds == null) {
- mBounds = new Rect(mTmpRect);
- } else {
- mBounds.set(mTmpRect);
- }
+ setBounds(mTmpRect);
+
if (mStack == null || persistBounds) {
- mLastNonFullscreenBounds = mBounds;
+ mLastNonFullscreenBounds = getOverrideBounds();
}
computeOverrideConfiguration(newConfig, mTmpRect, insetBounds,
mTmpRect.right != bounds.right, mTmpRect.bottom != bounds.bottom);
}
onOverrideConfigurationChanged(newConfig);
+ return !mTmpConfig.equals(newConfig);
+ }
- if (mFullscreen != oldFullscreen) {
+ @Override
+ public void onConfigurationChanged(Configuration newParentConfig) {
+ final boolean wasInMultiWindowMode = inMultiWindowMode();
+ super.onConfigurationChanged(newParentConfig);
+ if (wasInMultiWindowMode != inMultiWindowMode()) {
mService.mStackSupervisor.scheduleUpdateMultiWindowMode(this);
}
-
- return !mTmpConfig.equals(newConfig);
+ // TODO: Should also take care of Pip mode changes here.
}
/** Clears passed config and fills it with new override values. */
@@ -2046,23 +2064,19 @@
final int longSize = Math.max(compatScreenHeightDp, compatScreenWidthDp);
final int shortSize = Math.min(compatScreenHeightDp, compatScreenWidthDp);
config.screenLayout = Configuration.reduceScreenLayout(sl, longSize, shortSize);
-
}
Rect updateOverrideConfigurationFromLaunchBounds() {
- final Rect bounds = validateBounds(getLaunchBounds());
+ final Rect bounds = getLaunchBounds();
updateOverrideConfiguration(bounds);
- if (bounds != null) {
- bounds.set(mBounds);
+ if (bounds != null && !bounds.isEmpty()) {
+ // TODO: Review if we actually want to do this - we are setting the launch bounds
+ // directly here.
+ bounds.set(getOverrideBounds());
}
return bounds;
}
- static Rect validateBounds(Rect bounds) {
- // TODO: Not needed once we have bounds in WindowConfiguration.
- return (bounds != null && bounds.isEmpty()) ? null : bounds;
- }
-
/** Updates the task's bounds and override configuration to match what is expected for the
* input stack. */
void updateOverrideConfigurationForStack(ActivityStack inStack) {
@@ -2075,7 +2089,7 @@
throw new IllegalArgumentException("Can not position non-resizeable task="
+ this + " in stack=" + inStack);
}
- if (mBounds != null) {
+ if (!matchParentBounds()) {
return;
}
if (mLastNonFullscreenBounds != null) {
@@ -2084,7 +2098,7 @@
mService.mStackSupervisor.getLaunchingBoundsController().layoutTask(this, null);
}
} else {
- updateOverrideConfiguration(inStack.mBounds);
+ updateOverrideConfiguration(inStack.getOverrideBounds());
}
}
@@ -2098,9 +2112,9 @@
if (!isActivityTypeStandardOrUndefined()
|| windowingMode == WINDOWING_MODE_FULLSCREEN
|| (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY && !isResizeable())) {
- return isResizeable() ? mStack.mBounds : null;
+ return isResizeable() ? mStack.getOverrideBounds() : null;
} else if (!getWindowConfiguration().persistTaskBounds()) {
- return mStack.mBounds;
+ return mStack.getOverrideBounds();
}
return mLastNonFullscreenBounds;
}
@@ -2114,6 +2128,22 @@
}
}
+ void setRootProcess(ProcessRecord proc) {
+ clearRootProcess();
+ if (intent != null &&
+ (intent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0) {
+ mRootProcess = proc;
+ proc.recentTasks.add(this);
+ }
+ }
+
+ void clearRootProcess() {
+ if (mRootProcess != null) {
+ mRootProcess.recentTasks.remove(this);
+ mRootProcess = null;
+ }
+ }
+
void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("userId="); pw.print(userId);
pw.print(" effectiveUid="); UserHandle.formatUid(pw, effectiveUid);
@@ -2198,6 +2228,9 @@
if (lastDescription != null) {
pw.print(prefix); pw.print("lastDescription="); pw.println(lastDescription);
}
+ if (mRootProcess != null) {
+ pw.print(prefix); pw.print("mRootProcess="); pw.println(mRootProcess);
+ }
pw.print(prefix); pw.print("stackId="); pw.println(getStackId());
pw.print(prefix + "hasBeenVisible=" + hasBeenVisible);
pw.print(" mResizeMode=" + ActivityInfo.resizeModeToString(mResizeMode));
@@ -2261,9 +2294,12 @@
}
proto.write(ACTIVITY_TYPE, getActivityType());
proto.write(RESIZE_MODE, mResizeMode);
- proto.write(FULLSCREEN, mFullscreen);
- if (mBounds != null) {
- mBounds.writeToProto(proto, BOUNDS);
+ // TODO: Remove, no longer needed with windowingMode.
+ proto.write(FULLSCREEN, matchParentBounds());
+
+ if (!matchParentBounds()) {
+ final Rect bounds = getOverrideBounds();
+ bounds.writeToProto(proto, BOUNDS);
}
proto.write(MIN_WIDTH, mMinWidth);
proto.write(MIN_HEIGHT, mMinHeight);
diff --git a/com/android/server/am/UnsupportedCompileSdkDialog.java b/com/android/server/am/UnsupportedCompileSdkDialog.java
new file mode 100644
index 0000000..b6f6ae6
--- /dev/null
+++ b/com/android/server/am/UnsupportedCompileSdkDialog.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2017 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.server.am;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.CheckBox;
+
+import com.android.internal.R;
+import com.android.server.utils.AppInstallerUtil;
+
+public class UnsupportedCompileSdkDialog {
+ private final AlertDialog mDialog;
+ private final String mPackageName;
+
+ public UnsupportedCompileSdkDialog(final AppWarnings manager, Context context,
+ ApplicationInfo appInfo) {
+ mPackageName = appInfo.packageName;
+
+ final PackageManager pm = context.getPackageManager();
+ final CharSequence label = appInfo.loadSafeLabel(pm);
+ final CharSequence message = context.getString(R.string.unsupported_compile_sdk_message,
+ label);
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context)
+ .setPositiveButton(R.string.ok, null)
+ .setMessage(message)
+ .setView(R.layout.unsupported_compile_sdk_dialog_content);
+
+ // If we might be able to update the app, show a button.
+ final Intent installerIntent = AppInstallerUtil.createIntent(context, appInfo.packageName);
+ if (installerIntent != null) {
+ builder.setNeutralButton(R.string.unsupported_compile_sdk_check_update,
+ (dialog, which) -> context.startActivity(installerIntent));
+ }
+
+ // Ensure the content view is prepared.
+ mDialog = builder.create();
+ mDialog.create();
+
+ final Window window = mDialog.getWindow();
+ window.setType(WindowManager.LayoutParams.TYPE_PHONE);
+
+ // DO NOT MODIFY. Used by CTS to verify the dialog is displayed.
+ window.getAttributes().setTitle("UnsupportedCompileSdkDialog");
+
+ final CheckBox alwaysShow = mDialog.findViewById(R.id.ask_checkbox);
+ alwaysShow.setChecked(true);
+ alwaysShow.setOnCheckedChangeListener((buttonView, isChecked) -> manager.setPackageFlag(
+ mPackageName, AppWarnings.FLAG_HIDE_COMPILE_SDK, !isChecked));
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ public void show() {
+ mDialog.show();
+ }
+
+ public void dismiss() {
+ mDialog.dismiss();
+ }
+}
diff --git a/com/android/server/am/UnsupportedDisplaySizeDialog.java b/com/android/server/am/UnsupportedDisplaySizeDialog.java
index 501cd6b..8850663 100644
--- a/com/android/server/am/UnsupportedDisplaySizeDialog.java
+++ b/com/android/server/am/UnsupportedDisplaySizeDialog.java
@@ -30,7 +30,7 @@
private final AlertDialog mDialog;
private final String mPackageName;
- public UnsupportedDisplaySizeDialog(final ActivityManagerService service, Context context,
+ public UnsupportedDisplaySizeDialog(final AppWarnings manager, Context context,
ApplicationInfo appInfo) {
mPackageName = appInfo.packageName;
@@ -54,14 +54,10 @@
// DO NOT MODIFY. Used by CTS to verify the dialog is displayed.
window.getAttributes().setTitle("UnsupportedDisplaySizeDialog");
- final CheckBox alwaysShow = (CheckBox) mDialog.findViewById(R.id.ask_checkbox);
+ final CheckBox alwaysShow = mDialog.findViewById(R.id.ask_checkbox);
alwaysShow.setChecked(true);
- alwaysShow.setOnCheckedChangeListener((buttonView, isChecked) -> {
- synchronized (service) {
- service.mCompatModePackages.setPackageNotifyUnsupportedZoomLocked(
- mPackageName, isChecked);
- }
- });
+ alwaysShow.setOnCheckedChangeListener((buttonView, isChecked) -> manager.setPackageFlag(
+ mPackageName, AppWarnings.FLAG_HIDE_DISPLAY_SIZE, !isChecked));
}
public String getPackageName() {
diff --git a/com/android/server/am/UriPermissionOwner.java b/com/android/server/am/UriPermissionOwner.java
index 28344df..fc07c1a 100644
--- a/com/android/server/am/UriPermissionOwner.java
+++ b/com/android/server/am/UriPermissionOwner.java
@@ -20,6 +20,9 @@
import android.os.Binder;
import android.os.IBinder;
import android.util.ArraySet;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.server.am.proto.UriPermissionOwnerProto;
import com.google.android.collect.Sets;
@@ -139,6 +142,26 @@
}
}
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ long token = proto.start(fieldId);
+ proto.write(UriPermissionOwnerProto.OWNER, owner.toString());
+ if (mReadPerms != null) {
+ synchronized (mReadPerms) {
+ for (UriPermission p : mReadPerms) {
+ p.uri.writeToProto(proto, UriPermissionOwnerProto.READ_PERMS);
+ }
+ }
+ }
+ if (mWritePerms != null) {
+ synchronized (mWritePerms) {
+ for (UriPermission p : mWritePerms) {
+ p.uri.writeToProto(proto, UriPermissionOwnerProto.WRITE_PERMS);
+ }
+ }
+ }
+ proto.end(token);
+ }
+
@Override
public String toString() {
return owner.toString();
diff --git a/com/android/server/am/UserController.java b/com/android/server/am/UserController.java
index 2df5dc9..4e3d8d2 100644
--- a/com/android/server/am/UserController.java
+++ b/com/android/server/am/UserController.java
@@ -89,6 +89,7 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
import com.android.internal.widget.LockPatternUtils;
+import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.SystemServiceManager;
import com.android.server.pm.UserManagerService;
@@ -355,27 +356,35 @@
// Only keep marching forward if user is actually unlocked
if (!StorageManager.isUserKeyUnlocked(userId)) return;
synchronized (mLock) {
- // Bail if we ended up with a stale user
- if (mStartedUsers.get(uss.mHandle.getIdentifier()) != uss) return;
-
- // Do not proceed if unexpected state
- if (!uss.setState(STATE_RUNNING_LOCKED, STATE_RUNNING_UNLOCKING)) {
+ // Do not proceed if unexpected state or a stale user
+ if (mStartedUsers.get(userId) != uss || uss.state != STATE_RUNNING_LOCKED) {
return;
}
}
- mInjector.getUserManagerInternal().setUserState(userId, uss.state);
uss.mUnlockProgress.start();
// Prepare app storage before we go any further
uss.mUnlockProgress.setProgress(5,
mInjector.getContext().getString(R.string.android_start_title));
- mInjector.getUserManager().onBeforeUnlockUser(userId);
- uss.mUnlockProgress.setProgress(20);
- // Dispatch unlocked to system services; when fully dispatched,
- // that calls through to the next "unlocked" phase
- mHandler.obtainMessage(SYSTEM_USER_UNLOCK_MSG, userId, 0, uss)
- .sendToTarget();
+ // Call onBeforeUnlockUser on a worker thread that allows disk I/O
+ FgThread.getHandler().post(() -> {
+ mInjector.getUserManager().onBeforeUnlockUser(userId);
+ synchronized (mLock) {
+ // Do not proceed if unexpected state
+ if (!uss.setState(STATE_RUNNING_LOCKED, STATE_RUNNING_UNLOCKING)) {
+ return;
+ }
+ }
+ mInjector.getUserManagerInternal().setUserState(userId, uss.state);
+
+ uss.mUnlockProgress.setProgress(20);
+
+ // Dispatch unlocked to system services; when fully dispatched,
+ // that calls through to the next "unlocked" phase
+ mHandler.obtainMessage(SYSTEM_USER_UNLOCK_MSG, userId, 0, uss)
+ .sendToTarget();
+ });
}
/**
@@ -1819,7 +1828,10 @@
case SYSTEM_USER_UNLOCK_MSG:
final int userId = msg.arg1;
mInjector.getSystemServiceManager().unlockUser(userId);
- mInjector.loadUserRecents(userId);
+ // Loads recents on a worker thread that allows disk I/O
+ FgThread.getHandler().post(() -> {
+ mInjector.loadUserRecents(userId);
+ });
if (userId == UserHandle.USER_SYSTEM) {
mInjector.startPersistentApps(PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
}
diff --git a/com/android/server/autofill/RemoteFillService.java b/com/android/server/autofill/RemoteFillService.java
index af55807..831c488 100644
--- a/com/android/server/autofill/RemoteFillService.java
+++ b/com/android/server/autofill/RemoteFillService.java
@@ -26,6 +26,7 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentSender;
import android.content.ServiceConnection;
import android.os.Handler;
import android.os.IBinder;
@@ -100,7 +101,8 @@
@NonNull String servicePackageName);
void onFillRequestFailure(@Nullable CharSequence message,
@NonNull String servicePackageName);
- void onSaveRequestSuccess(@NonNull String servicePackageName);
+ void onSaveRequestSuccess(@NonNull String servicePackageName,
+ @Nullable IntentSender intentSender);
void onSaveRequestFailure(@Nullable CharSequence message,
@NonNull String servicePackageName);
void onServiceDied(RemoteFillService service);
@@ -308,10 +310,11 @@
});
}
- private void dispatchOnSaveRequestSuccess(PendingRequest pendingRequest) {
+ private void dispatchOnSaveRequestSuccess(PendingRequest pendingRequest,
+ IntentSender intentSender) {
mHandler.getHandler().post(() -> {
if (handleResponseCallbackCommon(pendingRequest)) {
- mCallbacks.onSaveRequestSuccess(mComponentName.getPackageName());
+ mCallbacks.onSaveRequestSuccess(mComponentName.getPackageName(), intentSender);
}
});
}
@@ -624,12 +627,13 @@
mCallback = new ISaveCallback.Stub() {
@Override
- public void onSuccess() {
+ public void onSuccess(IntentSender intentSender) {
if (!finish()) return;
final RemoteFillService remoteService = getService();
if (remoteService != null) {
- remoteService.dispatchOnSaveRequestSuccess(PendingSaveRequest.this);
+ remoteService.dispatchOnSaveRequestSuccess(PendingSaveRequest.this,
+ intentSender);
}
}
diff --git a/com/android/server/autofill/Session.java b/com/android/server/autofill/Session.java
index af4668a..99b92b9 100644
--- a/com/android/server/autofill/Session.java
+++ b/com/android/server/autofill/Session.java
@@ -561,7 +561,8 @@
// FillServiceCallbacks
@Override
- public void onSaveRequestSuccess(@NonNull String servicePackageName) {
+ public void onSaveRequestSuccess(@NonNull String servicePackageName,
+ @Nullable IntentSender intentSender) {
synchronized (mLock) {
mIsSaving = false;
@@ -572,8 +573,12 @@
}
}
LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST, servicePackageName)
- .setType(MetricsEvent.TYPE_SUCCESS);
+ .setType(intentSender == null ? MetricsEvent.TYPE_SUCCESS : MetricsEvent.TYPE_OPEN);
mMetricsLogger.write(log);
+ if (intentSender != null) {
+ if (sDebug) Slog.d(TAG, "Starting intent sender on save()");
+ startIntentSender(intentSender);
+ }
// Nothing left to do...
removeSelf();
@@ -1167,7 +1172,16 @@
break;
}
}
+
value = getSanitizedValue(sanitizers, id, value);
+ if (value == null) {
+ if (sDebug) {
+ Slog.d(TAG, "value of required field " + id + " failed sanitization");
+ }
+ allRequiredAreNotEmpty = false;
+ break;
+ }
+ viewState.setSanitizedValue(value);
currentValues.put(id, value);
final AutofillValue filledValue = viewState.getAutofilledValue();
@@ -1337,7 +1351,7 @@
return sanitizers;
}
- @NonNull
+ @Nullable
private AutofillValue getSanitizedValue(
@Nullable ArrayMap<AutofillId, InternalSanitizer> sanitizers,
@NonNull AutofillId id,
@@ -1431,10 +1445,10 @@
if (sVerbose) Slog.v(TAG, "callSaveLocked(): updating " + context);
for (int viewStateNum = 0; viewStateNum < mViewStates.size(); viewStateNum++) {
- final ViewState state = mViewStates.valueAt(viewStateNum);
+ final ViewState viewState = mViewStates.valueAt(viewStateNum);
- final AutofillId id = state.id;
- final AutofillValue value = state.getCurrentValue();
+ final AutofillId id = viewState.id;
+ final AutofillValue value = viewState.getCurrentValue();
if (value == null) {
if (sVerbose) Slog.v(TAG, "callSaveLocked(): skipping " + id);
continue;
@@ -1446,9 +1460,17 @@
}
if (sVerbose) Slog.v(TAG, "callSaveLocked(): updating " + id + " to " + value);
- final AutofillValue sanitizedValue = getSanitizedValue(sanitizers, id, value);
+ AutofillValue sanitizedValue = viewState.getSanitizedValue();
- node.updateAutofillValue(sanitizedValue);
+ if (sanitizedValue == null) {
+ // Field is optional and haven't been sanitized yet.
+ sanitizedValue = getSanitizedValue(sanitizers, id, value);
+ }
+ if (sanitizedValue != null) {
+ node.updateAutofillValue(sanitizedValue);
+ } else if (sDebug) {
+ Slog.d(TAG, "Not updating field " + id + " because it failed sanitization");
+ }
}
// Sanitize structure before it's sent to service.
diff --git a/com/android/server/autofill/ViewState.java b/com/android/server/autofill/ViewState.java
index 832a66b..0dbdc13 100644
--- a/com/android/server/autofill/ViewState.java
+++ b/com/android/server/autofill/ViewState.java
@@ -76,6 +76,7 @@
private FillResponse mResponse;
private AutofillValue mCurrentValue;
private AutofillValue mAutofilledValue;
+ private AutofillValue mSanitizedValue;
private Rect mVirtualBounds;
private int mState;
private String mDatasetId;
@@ -117,6 +118,15 @@
}
@Nullable
+ AutofillValue getSanitizedValue() {
+ return mSanitizedValue;
+ }
+
+ void setSanitizedValue(@Nullable AutofillValue value) {
+ mSanitizedValue = value;
+ }
+
+ @Nullable
FillResponse getResponse() {
return mResponse;
}
@@ -218,6 +228,7 @@
}
pw.print(prefix); pw.print("currentValue:" ); pw.println(mCurrentValue);
pw.print(prefix); pw.print("autofilledValue:" ); pw.println(mAutofilledValue);
+ pw.print(prefix); pw.print("sanitizedValue:" ); pw.println(mSanitizedValue);
pw.print(prefix); pw.print("virtualBounds:" ); pw.println(mVirtualBounds);
}
}
\ No newline at end of file
diff --git a/com/android/server/backup/RefactoredBackupManagerService.java b/com/android/server/backup/RefactoredBackupManagerService.java
index a45a4f0..2788218 100644
--- a/com/android/server/backup/RefactoredBackupManagerService.java
+++ b/com/android/server/backup/RefactoredBackupManagerService.java
@@ -48,7 +48,6 @@
import android.app.backup.IFullBackupRestoreObserver;
import android.app.backup.IRestoreSession;
import android.app.backup.ISelectBackupTransportCallback;
-import android.app.backup.SelectBackupTransportCallback;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -103,6 +102,7 @@
import com.android.server.backup.internal.BackupHandler;
import com.android.server.backup.internal.BackupRequest;
import com.android.server.backup.internal.ClearDataObserver;
+import com.android.server.backup.internal.OnTaskFinishedListener;
import com.android.server.backup.internal.Operation;
import com.android.server.backup.internal.PerformInitializeTask;
import com.android.server.backup.internal.ProvisionedObserver;
@@ -117,6 +117,7 @@
import com.android.server.backup.params.RestoreParams;
import com.android.server.backup.restore.ActiveRestoreSession;
import com.android.server.backup.restore.PerformUnifiedRestoreTask;
+import com.android.server.backup.transport.TransportClient;
import com.android.server.backup.utils.AppBackupUtils;
import com.android.server.backup.utils.BackupManagerMonitorUtils;
import com.android.server.backup.utils.BackupObserverUtils;
@@ -1585,8 +1586,27 @@
return BackupManager.ERROR_BACKUP_NOT_ALLOWED;
}
+ // We're using pieces of the new binding on-demand infra-structure and the old always-bound
+ // infra-structure below this comment. The TransportManager.getCurrentTransportClient() line
+ // is using the new one and TransportManager.getCurrentTransportBinder() is using the old.
+ // This is weird but there is a reason.
+ // This is the natural place to put TransportManager.getCurrentTransportClient() because of
+ // the null handling below that should be the same for TransportClient.
+ // TransportClient.connect() would return a IBackupTransport for us (instead of using the
+ // old infra), but it may block and we don't want this in this thread.
+ // The only usage of transport in this method is for transport.transportDirName(). When the
+ // push-from-transport part of binding on-demand is in place we will replace the calls for
+ // IBackupTransport.transportDirName() with calls for
+ // TransportManager.transportDirName(transportName) or similar. So we'll leave the old piece
+ // here until we implement that.
+ // TODO(brufino): Remove always-bound code mTransportManager.getCurrentTransportBinder()
+ TransportClient transportClient =
+ mTransportManager.getCurrentTransportClient("BMS.requestBackup()");
IBackupTransport transport = mTransportManager.getCurrentTransportBinder();
- if (transport == null) {
+ if (transportClient == null || transport == null) {
+ if (transportClient != null) {
+ mTransportManager.disposeOfTransportClient(transportClient, "BMS.requestBackup()");
+ }
BackupObserverUtils.sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED);
monitor = BackupManagerMonitorUtils.monitorEvent(monitor,
BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_IS_NULL,
@@ -1594,6 +1614,9 @@
return BackupManager.ERROR_TRANSPORT_ABORTED;
}
+ OnTaskFinishedListener listener =
+ caller -> mTransportManager.disposeOfTransportClient(transportClient, caller);
+
ArrayList<String> fullBackupList = new ArrayList<>();
ArrayList<String> kvBackupList = new ArrayList<>();
for (String packageName : packages) {
@@ -1640,8 +1663,8 @@
boolean nonIncrementalBackup = (flags & BackupManager.FLAG_NON_INCREMENTAL_BACKUP) != 0;
Message msg = mBackupHandler.obtainMessage(MSG_REQUEST_BACKUP);
- msg.obj = new BackupParams(transport, dirName, kvBackupList, fullBackupList, observer,
- monitor, true, nonIncrementalBackup);
+ msg.obj = new BackupParams(transportClient, dirName, kvBackupList, fullBackupList, observer,
+ monitor, listener, true, nonIncrementalBackup);
mBackupHandler.sendMessage(msg);
return BackupManager.SUCCESS;
}
@@ -2135,8 +2158,17 @@
mFullBackupQueue.remove(0);
CountDownLatch latch = new CountDownLatch(1);
String[] pkg = new String[]{entry.packageName};
- mRunningFullBackupTask = new PerformFullTransportBackupTask(this, null, pkg, true,
- scheduledJob, latch, null, null, false /* userInitiated */);
+ mRunningFullBackupTask = PerformFullTransportBackupTask.newWithCurrentTransport(
+ this,
+ /* observer */ null,
+ pkg,
+ /* updateSchedule */ true,
+ scheduledJob,
+ latch,
+ /* backupObserver */ null,
+ /* monitor */ null,
+ /* userInitiated */ false,
+ "BMS.beginFullBackup()");
// Acquiring wakelock for PerformFullTransportBackupTask before its start.
mWakelock.acquire();
(new Thread(mRunningFullBackupTask)).start();
@@ -2490,8 +2522,17 @@
final long oldId = Binder.clearCallingIdentity();
try {
CountDownLatch latch = new CountDownLatch(1);
- PerformFullTransportBackupTask task = new PerformFullTransportBackupTask(this, null,
- pkgNames, false, null, latch, null, null, false /* userInitiated */);
+ Runnable task = PerformFullTransportBackupTask.newWithCurrentTransport(
+ this,
+ /* observer */ null,
+ pkgNames,
+ /* updateSchedule */ false,
+ /* runningJob */ null,
+ latch,
+ /* backupObserver */ null,
+ /* monitor */ null,
+ /* userInitiated */ false,
+ "BMS.fullTransportBackup()");
// Acquiring wakelock for PerformFullTransportBackupTask before its start.
mWakelock.acquire();
(new Thread(task, "full-transport-master")).start();
diff --git a/com/android/server/backup/TransportManager.java b/com/android/server/backup/TransportManager.java
index 7a0173f..a2b5cb8 100644
--- a/com/android/server/backup/TransportManager.java
+++ b/com/android/server/backup/TransportManager.java
@@ -16,8 +16,9 @@
package com.android.server.backup;
+import android.annotation.Nullable;
import android.app.backup.BackupManager;
-import android.app.backup.SelectBackupTransportCallback;
+import android.app.backup.BackupTransport;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -44,9 +45,11 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.backup.IBackupTransport;
import com.android.server.EventLogTags;
+import com.android.server.backup.transport.TransportClient;
+import com.android.server.backup.transport.TransportClientManager;
+import com.android.server.backup.transport.TransportConnectionListener;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -60,8 +63,7 @@
private static final String TAG = "BackupTransportManager";
@VisibleForTesting
- /* package */ static final String SERVICE_ACTION_TRANSPORT_HOST =
- "android.backup.TRANSPORT_HOST";
+ public static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST";
private static final long REBINDING_TIMEOUT_UNPROVISIONED_MS = 30 * 1000; // 30 sec
private static final long REBINDING_TIMEOUT_PROVISIONED_MS = 5 * 60 * 1000; // 5 mins
@@ -72,6 +74,7 @@
private final PackageManager mPackageManager;
private final Set<ComponentName> mTransportWhitelist;
private final Handler mHandler;
+ private final TransportClientManager mTransportClientManager;
/**
* This listener is called after we bind to any transport. If it returns true, this is a valid
@@ -95,6 +98,10 @@
@GuardedBy("mTransportLock")
private final Map<String, ComponentName> mBoundTransports = new ArrayMap<>();
+ /** Names of transports we've bound to at least once */
+ @GuardedBy("mTransportLock")
+ private final Map<String, ComponentName> mTransportsByName = new ArrayMap<>();
+
/**
* Callback interface for {@link #ensureTransportReady(ComponentName, TransportReadyCallback)}.
*/
@@ -123,6 +130,7 @@
mCurrentTransportName = defaultTransport;
mTransportBoundListener = listener;
mHandler = new RebindOnTimeoutHandler(looper);
+ mTransportClientManager = new TransportClientManager(context);
}
void onPackageAdded(String packageName) {
@@ -204,6 +212,67 @@
return null;
}
+ /**
+ * Returns the transport name associated with {@param transportClient} or {@code null} if not
+ * found.
+ */
+ @Nullable
+ public String getTransportName(TransportClient transportClient) {
+ ComponentName transportComponent = transportClient.getTransportComponent();
+ synchronized (mTransportLock) {
+ for (Map.Entry<String, ComponentName> transportEntry : mTransportsByName.entrySet()) {
+ if (transportEntry.getValue().equals(transportComponent)) {
+ return transportEntry.getKey();
+ }
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Returns a {@link TransportClient} for {@param transportName} or {@code null} if not found.
+ *
+ * @param transportName The name of the transport as returned by {@link BackupTransport#name()}.
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+ * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+ * details.
+ * @return A {@link TransportClient} or null if not found.
+ */
+ @Nullable
+ public TransportClient getTransportClient(String transportName, String caller) {
+ ComponentName transportComponent = mTransportsByName.get(transportName);
+ if (transportComponent == null) {
+ Slog.w(TAG, "Transport " + transportName + " not registered");
+ return null;
+ }
+ return mTransportClientManager.getTransportClient(transportComponent, caller);
+ }
+
+ /**
+ * Returns a {@link TransportClient} for the current transport or null if not found.
+ *
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+ * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+ * details.
+ * @return A {@link TransportClient} or null if not found.
+ */
+ @Nullable
+ public TransportClient getCurrentTransportClient(String caller) {
+ return getTransportClient(mCurrentTransportName, caller);
+ }
+
+ /**
+ * Disposes of the {@link TransportClient}.
+ *
+ * @param transportClient The {@link TransportClient} to be disposed of.
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+ * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+ * details.
+ */
+ public void disposeOfTransportClient(TransportClient transportClient, String caller) {
+ mTransportClientManager.disposeOfTransportClient(transportClient, caller);
+ }
+
String[] getBoundTransportNames() {
synchronized (mTransportLock) {
return mBoundTransports.keySet().toArray(new String[mBoundTransports.size()]);
@@ -374,6 +443,7 @@
String componentShortString = component.flattenToShortString().intern();
if (success) {
Slog.d(TAG, "Bound to transport: " + componentShortString);
+ mTransportsByName.put(mTransportName, component);
mBoundTransports.put(mTransportName, component);
for (TransportReadyCallback listener : mListeners) {
listener.onSuccess(mTransportName);
@@ -528,7 +598,7 @@
// These only exists to make it testable with Robolectric, which is not updated to API level 24
// yet.
// TODO: Get rid of this once Robolectric is updated.
- private static UserHandle createSystemUserHandle() {
+ public static UserHandle createSystemUserHandle() {
return new UserHandle(UserHandle.USER_SYSTEM);
}
}
diff --git a/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
index 90134e1..d5b3d98 100644
--- a/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -24,6 +24,7 @@
import static com.android.server.backup.RefactoredBackupManagerService.OP_TYPE_BACKUP_WAIT;
import static com.android.server.backup.RefactoredBackupManagerService.TIMEOUT_FULL_BACKUP_INTERVAL;
+import android.annotation.Nullable;
import android.app.IBackupAgent;
import android.app.backup.BackupManager;
import android.app.backup.BackupManagerMonitor;
@@ -46,7 +47,11 @@
import com.android.server.backup.BackupRestoreTask;
import com.android.server.backup.FullBackupJob;
import com.android.server.backup.RefactoredBackupManagerService;
+import com.android.server.backup.TransportManager;
+import com.android.server.backup.internal.OnTaskFinishedListener;
import com.android.server.backup.internal.Operation;
+import com.android.server.backup.transport.TransportClient;
+import com.android.server.backup.transport.TransportNotAvailableException;
import com.android.server.backup.utils.AppBackupUtils;
import com.android.server.backup.utils.BackupManagerMonitorUtils;
import com.android.server.backup.utils.BackupObserverUtils;
@@ -89,6 +94,36 @@
* mBackupRunner.getBackupResultBlocking().
*/
public class PerformFullTransportBackupTask extends FullBackupTask implements BackupRestoreTask {
+ public static PerformFullTransportBackupTask newWithCurrentTransport(
+ RefactoredBackupManagerService backupManagerService,
+ IFullBackupRestoreObserver observer,
+ String[] whichPackages,
+ boolean updateSchedule,
+ FullBackupJob runningJob,
+ CountDownLatch latch,
+ IBackupObserver backupObserver,
+ IBackupManagerMonitor monitor,
+ boolean userInitiated,
+ String caller) {
+ TransportManager transportManager = backupManagerService.getTransportManager();
+ TransportClient transportClient = transportManager.getCurrentTransportClient(caller);
+ OnTaskFinishedListener listener =
+ listenerCaller ->
+ transportManager.disposeOfTransportClient(transportClient, listenerCaller);
+ return new PerformFullTransportBackupTask(
+ backupManagerService,
+ transportClient,
+ observer,
+ whichPackages,
+ updateSchedule,
+ runningJob,
+ latch,
+ backupObserver,
+ monitor,
+ listener,
+ userInitiated);
+ }
+
private static final String TAG = "PFTBT";
private RefactoredBackupManagerService backupManagerService;
@@ -102,9 +137,10 @@
IBackupObserver mBackupObserver;
IBackupManagerMonitor mMonitor;
boolean mUserInitiated;
- private volatile IBackupTransport mTransport;
SinglePackageBackupRunner mBackupRunner;
private final int mBackupRunnerOpToken;
+ private final OnTaskFinishedListener mListener;
+ private final TransportClient mTransportClient;
// This is true when a backup operation for some package is in progress.
private volatile boolean mIsDoingBackup;
@@ -112,18 +148,22 @@
private final int mCurrentOpToken;
public PerformFullTransportBackupTask(RefactoredBackupManagerService backupManagerService,
+ TransportClient transportClient,
IFullBackupRestoreObserver observer,
String[] whichPackages, boolean updateSchedule,
FullBackupJob runningJob, CountDownLatch latch, IBackupObserver backupObserver,
- IBackupManagerMonitor monitor, boolean userInitiated) {
+ IBackupManagerMonitor monitor, @Nullable OnTaskFinishedListener listener,
+ boolean userInitiated) {
super(observer);
this.backupManagerService = backupManagerService;
+ mTransportClient = transportClient;
mUpdateSchedule = updateSchedule;
mLatch = latch;
mJob = runningJob;
mPackages = new ArrayList<>(whichPackages.length);
mBackupObserver = backupObserver;
mMonitor = monitor;
+ mListener = (listener != null) ? listener : OnTaskFinishedListener.NOP;
mUserInitiated = userInitiated;
mCurrentOpToken = backupManagerService.generateRandomIntegerToken();
mBackupRunnerOpToken = backupManagerService.generateRandomIntegerToken();
@@ -241,8 +281,11 @@
if (mIsDoingBackup) {
backupManagerService.handleCancel(mBackupRunnerOpToken, cancelAll);
try {
- mTransport.cancelFullBackup();
- } catch (RemoteException e) {
+ // If we're running a backup we should be connected to a transport
+ IBackupTransport transport =
+ mTransportClient.getConnectedTransport("PFTBT.handleCancel()");
+ transport.cancelFullBackup();
+ } catch (RemoteException | TransportNotAvailableException e) {
Slog.w(TAG, "Error calling cancelFullBackup() on transport: " + e);
// Can't do much.
}
@@ -291,8 +334,8 @@
return;
}
- mTransport = backupManagerService.getTransportManager().getCurrentTransportBinder();
- if (mTransport == null) {
+ IBackupTransport transport = mTransportClient.connect("PFTBT.run()");
+ if (transport == null) {
Slog.w(TAG, "Transport not present; full data backup not performed");
backupRunStatus = BackupManager.ERROR_TRANSPORT_ABORTED;
mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
@@ -325,17 +368,17 @@
if (mCancelAll) {
break;
}
- backupPackageStatus = mTransport.performFullBackup(currentPackage,
+ backupPackageStatus = transport.performFullBackup(currentPackage,
transportPipes[0], flags);
if (backupPackageStatus == BackupTransport.TRANSPORT_OK) {
- quota = mTransport.getBackupQuota(currentPackage.packageName,
+ quota = transport.getBackupQuota(currentPackage.packageName,
true /* isFullBackup */);
// Now set up the backup engine / data source end of things
enginePipes = ParcelFileDescriptor.createPipe();
mBackupRunner =
new SinglePackageBackupRunner(enginePipes[1], currentPackage,
- mTransport, quota, mBackupRunnerOpToken);
+ mTransportClient, quota, mBackupRunnerOpToken);
// The runner dup'd the pipe half, so we close it here
enginePipes[1].close();
enginePipes[1] = null;
@@ -389,7 +432,7 @@
out.write(buffer, 0, nRead);
synchronized (mCancelLock) {
if (!mCancelAll) {
- backupPackageStatus = mTransport.sendBackupData(nRead);
+ backupPackageStatus = transport.sendBackupData(nRead);
}
}
totalRead += nRead;
@@ -425,12 +468,12 @@
// result based on what finishBackup() returns. If we're in a
// failure case already, preserve that result and ignore whatever
// finishBackup() reports.
- final int finishResult = mTransport.finishBackup();
+ final int finishResult = transport.finishBackup();
if (backupPackageStatus == BackupTransport.TRANSPORT_OK) {
backupPackageStatus = finishResult;
}
} else {
- mTransport.cancelFullBackup();
+ transport.cancelFullBackup();
}
}
}
@@ -469,7 +512,7 @@
// Also ask the transport how long it wants us to wait before
// moving on to the next package, if any.
- backoff = mTransport.requestFullBackupTime();
+ backoff = transport.requestFullBackupTime();
if (DEBUG_SCHEDULING) {
Slog.i(TAG, "Transport suggested backoff=" + backoff);
}
@@ -591,6 +634,8 @@
backupManagerService.setRunningFullBackupTask(null);
}
+ mListener.onFinished("PFTBT.run()");
+
mLatch.countDown();
// Now that we're actually done with schedule-driven work, reschedule
@@ -633,12 +678,13 @@
class SinglePackageBackupPreflight implements BackupRestoreTask, FullBackupPreflight {
final AtomicLong mResult = new AtomicLong(BackupTransport.AGENT_ERROR);
final CountDownLatch mLatch = new CountDownLatch(1);
- final IBackupTransport mTransport;
+ final TransportClient mTransportClient;
final long mQuota;
private final int mCurrentOpToken;
- SinglePackageBackupPreflight(IBackupTransport transport, long quota, int currentOpToken) {
- mTransport = transport;
+ SinglePackageBackupPreflight(
+ TransportClient transportClient, long quota, int currentOpToken) {
+ mTransportClient = transportClient;
mQuota = quota;
mCurrentOpToken = currentOpToken;
}
@@ -672,7 +718,9 @@
Slog.v(TAG, "Got preflight response; size=" + totalSize);
}
- result = mTransport.checkFullBackupSize(totalSize);
+ IBackupTransport transport =
+ mTransportClient.connectOrThrow("PFTBT$SPBP.preflightFullBackup()");
+ result = transport.checkFullBackupSize(totalSize);
if (result == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
if (MORE_DEBUG) {
Slog.d(TAG, "Package hit quota limit on preflight " +
@@ -739,12 +787,13 @@
private volatile boolean mIsCancelled;
SinglePackageBackupRunner(ParcelFileDescriptor output, PackageInfo target,
- IBackupTransport transport, long quota, int currentOpToken) throws IOException {
+ TransportClient transportClient, long quota, int currentOpToken)
+ throws IOException {
mOutput = ParcelFileDescriptor.dup(output.getFileDescriptor());
mTarget = target;
mCurrentOpToken = currentOpToken;
mEphemeralToken = backupManagerService.generateRandomIntegerToken();
- mPreflight = new SinglePackageBackupPreflight(transport, quota, mEphemeralToken);
+ mPreflight = new SinglePackageBackupPreflight(transportClient, quota, mEphemeralToken);
mPreflightLatch = new CountDownLatch(1);
mBackupLatch = new CountDownLatch(1);
mPreflightResult = BackupTransport.AGENT_ERROR;
diff --git a/com/android/server/backup/internal/BackupHandler.java b/com/android/server/backup/internal/BackupHandler.java
index 8f82300..9011b95 100644
--- a/com/android/server/backup/internal/BackupHandler.java
+++ b/com/android/server/backup/internal/BackupHandler.java
@@ -38,6 +38,8 @@
import com.android.server.backup.BackupRestoreTask;
import com.android.server.backup.DataChangedJournal;
import com.android.server.backup.RefactoredBackupManagerService;
+import com.android.server.backup.transport.TransportClient;
+import com.android.server.backup.TransportManager;
import com.android.server.backup.fullbackup.PerformAdbBackupTask;
import com.android.server.backup.fullbackup.PerformFullTransportBackupTask;
import com.android.server.backup.params.AdbBackupParams;
@@ -51,10 +53,8 @@
import com.android.server.backup.restore.PerformAdbRestoreTask;
import com.android.server.backup.restore.PerformUnifiedRestoreTask;
-import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashSet;
/**
* Asynchronous backup/restore handler thread.
@@ -81,7 +81,7 @@
public static final int MSG_BACKUP_RESTORE_STEP = 20;
public static final int MSG_OP_COMPLETE = 21;
- private RefactoredBackupManagerService backupManagerService;
+ private final RefactoredBackupManagerService backupManagerService;
public BackupHandler(
RefactoredBackupManagerService backupManagerService, Looper looper) {
@@ -91,13 +91,23 @@
public void handleMessage(Message msg) {
+ TransportManager transportManager = backupManagerService.getTransportManager();
switch (msg.what) {
case MSG_RUN_BACKUP: {
backupManagerService.setLastBackupPass(System.currentTimeMillis());
+ String callerLogString = "BH/MSG_RUN_BACKUP";
+ TransportClient transportClient =
+ transportManager.getCurrentTransportClient(callerLogString);
IBackupTransport transport =
- backupManagerService.getTransportManager().getCurrentTransportBinder();
+ transportClient != null
+ ? transportClient.connect(callerLogString)
+ : null;
if (transport == null) {
+ if (transportClient != null) {
+ transportManager
+ .disposeOfTransportClient(transportClient, callerLogString);
+ }
Slog.v(TAG, "Backup requested but no transport available");
synchronized (backupManagerService.getQueueLock()) {
backupManagerService.setBackupRunning(false);
@@ -138,9 +148,13 @@
// Spin up a backup state sequence and set it running
try {
String dirName = transport.transportDirName();
+ OnTaskFinishedListener listener =
+ caller ->
+ transportManager
+ .disposeOfTransportClient(transportClient, caller);
PerformBackupTask pbt = new PerformBackupTask(
- backupManagerService, transport, dirName, queue,
- oldJournal, null, null, Collections.<String>emptyList(), false,
+ backupManagerService, transportClient, dirName, queue,
+ oldJournal, null, null, listener, Collections.emptyList(), false,
false /* nonIncremental */);
Message pbtMessage = obtainMessage(MSG_BACKUP_RESTORE_STEP, pbt);
sendMessage(pbtMessage);
@@ -157,6 +171,7 @@
}
if (!staged) {
+ transportManager.disposeOfTransportClient(transportClient, callerLogString);
// if we didn't actually hand off the wakelock, rewind until next time
synchronized (backupManagerService.getQueueLock()) {
backupManagerService.setBackupRunning(false);
@@ -382,9 +397,9 @@
PerformBackupTask pbt = new PerformBackupTask(
backupManagerService,
- params.transport, params.dirName,
- kvQueue, null, params.observer, params.monitor, params.fullPackages, true,
- params.nonIncrementalBackup);
+ params.transportClient, params.dirName,
+ kvQueue, null, params.observer, params.monitor, params.listener,
+ params.fullPackages, true, params.nonIncrementalBackup);
Message pbtMessage = obtainMessage(MSG_BACKUP_RESTORE_STEP, pbt);
sendMessage(pbtMessage);
break;
diff --git a/com/android/server/backup/internal/OnTaskFinishedListener.java b/com/android/server/backup/internal/OnTaskFinishedListener.java
new file mode 100644
index 0000000..e417f06
--- /dev/null
+++ b/com/android/server/backup/internal/OnTaskFinishedListener.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 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.server.backup.internal;
+
+import com.android.server.backup.transport.TransportClient;
+import com.android.server.backup.transport.TransportConnectionListener;
+
+/** Listener to be called when a task finishes, successfully or not. */
+public interface OnTaskFinishedListener {
+ OnTaskFinishedListener NOP = caller -> {};
+
+ /**
+ * Called when a task finishes, successfully or not.
+ *
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+ * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+ * details.
+ */
+ void onFinished(String caller);
+}
diff --git a/com/android/server/backup/internal/PerformBackupTask.java b/com/android/server/backup/internal/PerformBackupTask.java
index c0caa55..5be1b39 100644
--- a/com/android/server/backup/internal/PerformBackupTask.java
+++ b/com/android/server/backup/internal/PerformBackupTask.java
@@ -63,6 +63,8 @@
import com.android.server.backup.PackageManagerBackupAgent;
import com.android.server.backup.RefactoredBackupManagerService;
import com.android.server.backup.fullbackup.PerformFullTransportBackupTask;
+import com.android.server.backup.transport.TransportClient;
+import com.android.server.backup.transport.TransportUtils;
import com.android.server.backup.utils.AppBackupUtils;
import com.android.server.backup.utils.BackupManagerMonitorUtils;
import com.android.server.backup.utils.BackupObserverUtils;
@@ -112,7 +114,6 @@
private RefactoredBackupManagerService backupManagerService;
private final Object mCancelLock = new Object();
- IBackupTransport mTransport;
ArrayList<BackupRequest> mQueue;
ArrayList<BackupRequest> mOriginalQueue;
File mStateDir;
@@ -122,6 +123,8 @@
IBackupObserver mObserver;
IBackupManagerMonitor mMonitor;
+ private final TransportClient mTransportClient;
+ private final OnTaskFinishedListener mListener;
private final PerformFullTransportBackupTask mFullBackupTask;
private final int mCurrentOpToken;
private volatile int mEphemeralOpToken;
@@ -143,17 +146,19 @@
private volatile boolean mCancelAll;
public PerformBackupTask(RefactoredBackupManagerService backupManagerService,
- IBackupTransport transport, String dirName,
+ TransportClient transportClient, String dirName,
ArrayList<BackupRequest> queue, @Nullable DataChangedJournal journal,
IBackupObserver observer, IBackupManagerMonitor monitor,
- List<String> pendingFullBackups, boolean userInitiated, boolean nonIncremental) {
+ @Nullable OnTaskFinishedListener listener, List<String> pendingFullBackups,
+ boolean userInitiated, boolean nonIncremental) {
this.backupManagerService = backupManagerService;
- mTransport = transport;
+ mTransportClient = transportClient;
mOriginalQueue = queue;
mQueue = new ArrayList<>();
mJournal = journal;
mObserver = observer;
mMonitor = monitor;
+ mListener = (listener != null) ? listener : OnTaskFinishedListener.NOP;
mPendingFullBackups = pendingFullBackups;
mUserInitiated = userInitiated;
mNonIncremental = nonIncremental;
@@ -179,11 +184,11 @@
mPendingFullBackups.toArray(new String[mPendingFullBackups.size()]);
mFullBackupTask =
new PerformFullTransportBackupTask(backupManagerService,
- /*fullBackupRestoreObserver*/
- null,
+ transportClient,
+ /*fullBackupRestoreObserver*/ null,
fullBackups, /*updateSchedule*/ false, /*runningJob*/ null,
latch,
- mObserver, mMonitor, mUserInitiated);
+ mObserver, mMonitor, mListener, mUserInitiated);
registerTask();
backupManagerService.addBackupTrace("STATE => INITIAL");
@@ -289,10 +294,10 @@
if (DEBUG) {
Slog.v(TAG, "Beginning backup of " + mQueue.size() + " targets");
}
-
File pmState = new File(mStateDir, PACKAGE_MANAGER_SENTINEL);
try {
- final String transportName = mTransport.transportDirName();
+ IBackupTransport transport = mTransportClient.connectOrThrow("PBT.beginBackup()");
+ final String transportName = transport.transportDirName();
EventLog.writeEvent(EventLogTags.BACKUP_START, transportName);
// If we haven't stored package manager metadata yet, we must init the transport.
@@ -300,7 +305,7 @@
Slog.i(TAG, "Initializing (wiping) backup state and transport storage");
backupManagerService.addBackupTrace("initializing transport " + transportName);
backupManagerService.resetBackupState(mStateDir); // Just to make sure.
- mStatus = mTransport.initializeDevice();
+ mStatus = transport.initializeDevice();
backupManagerService.addBackupTrace("transport.initializeDevice() == " + mStatus);
if (mStatus == BackupTransport.TRANSPORT_OK) {
@@ -324,7 +329,7 @@
PackageManagerBackupAgent pmAgent = backupManagerService.makeMetadataAgent();
mStatus = invokeAgentForBackup(
PACKAGE_MANAGER_SENTINEL,
- IBackupAgent.Stub.asInterface(pmAgent.onBind()), mTransport);
+ IBackupAgent.Stub.asInterface(pmAgent.onBind()));
backupManagerService.addBackupTrace("PMBA invoke: " + mStatus);
// Because the PMBA is a local instance, it has already executed its
@@ -445,7 +450,7 @@
backupManagerService.addBackupTrace("agent bound; a? = " + (agent != null));
if (agent != null) {
mAgentBinder = agent;
- mStatus = invokeAgentForBackup(request.packageName, agent, mTransport);
+ mStatus = invokeAgentForBackup(request.packageName, agent);
// at this point we'll either get a completion callback from the
// agent, or a timeout message on the main handler. either way, we're
// done here as long as we're successful so far.
@@ -526,11 +531,14 @@
// If everything actually went through and this is the first time we've
// done a backup, we can now record what the current backup dataset token
// is.
+ String callerLogString = "PBT.finalizeBackup()";
if ((backupManagerService.getCurrentToken() == 0) && (mStatus
== BackupTransport.TRANSPORT_OK)) {
backupManagerService.addBackupTrace("success; recording token");
try {
- backupManagerService.setCurrentToken(mTransport.getCurrentRestoreSet());
+ IBackupTransport transport =
+ mTransportClient.connectOrThrow(callerLogString);
+ backupManagerService.setCurrentToken(transport.getCurrentRestoreSet());
backupManagerService.writeRestoreTokens();
} catch (Exception e) {
// nothing for it at this point, unfortunately, but this will be
@@ -553,13 +561,13 @@
backupManagerService.addBackupTrace("init required; rerunning");
try {
final String name = backupManagerService.getTransportManager().getTransportName(
- mTransport);
+ mTransportClient);
if (name != null) {
backupManagerService.getPendingInits().add(name);
} else {
if (DEBUG) {
- Slog.w(TAG, "Couldn't find name of transport " + mTransport
- + " for init");
+ Slog.w(TAG, "Couldn't find name of transport "
+ + mTransportClient.getTransportComponent() + " for init");
}
}
} catch (Exception e) {
@@ -580,14 +588,18 @@
Slog.d(TAG, "Starting full backups for: " + mPendingFullBackups);
// Acquiring wakelock for PerformFullTransportBackupTask before its start.
backupManagerService.getWakelock().acquire();
+ // The full-backup task is now responsible for calling onFinish() on mListener, which
+ // was the listener we passed it.
(new Thread(mFullBackupTask, "full-transport-requested")).start();
} else if (mCancelAll) {
+ mListener.onFinished(callerLogString);
if (mFullBackupTask != null) {
mFullBackupTask.unregisterTask();
}
BackupObserverUtils.sendBackupFinished(mObserver,
BackupManager.ERROR_BACKUP_CANCELLED);
} else {
+ mListener.onFinished(callerLogString);
mFullBackupTask.unregisterTask();
switch (mStatus) {
case BackupTransport.TRANSPORT_OK:
@@ -619,8 +631,7 @@
// Invoke an agent's doBackup() and start a timeout message spinning on the main
// handler in case it doesn't get back to us.
- int invokeAgentForBackup(String packageName, IBackupAgent agent,
- IBackupTransport transport) {
+ int invokeAgentForBackup(String packageName, IBackupAgent agent) {
if (DEBUG) {
Slog.d(TAG, "invokeAgentForBackup on " + packageName);
}
@@ -671,7 +682,10 @@
ParcelFileDescriptor.MODE_CREATE |
ParcelFileDescriptor.MODE_TRUNCATE);
- final long quota = mTransport.getBackupQuota(packageName, false /* isFullBackup */);
+ IBackupTransport transport =
+ mTransportClient.connectOrThrow("PBT.invokeAgentForBackup()");
+
+ final long quota = transport.getBackupQuota(packageName, false /* isFullBackup */);
callingAgent = true;
// Initiate the target's backup pass
@@ -888,10 +902,12 @@
clearAgentState();
backupManagerService.addBackupTrace("operation complete");
+ IBackupTransport transport = mTransportClient.connect("PBT.operationComplete()");
ParcelFileDescriptor backupData = null;
mStatus = BackupTransport.TRANSPORT_OK;
long size = 0;
try {
+ TransportUtils.checkTransport(transport);
size = mBackupDataName.length();
if (size > 0) {
if (mStatus == BackupTransport.TRANSPORT_OK) {
@@ -899,7 +915,7 @@
ParcelFileDescriptor.MODE_READ_ONLY);
backupManagerService.addBackupTrace("sending data to transport");
int flags = mUserInitiated ? BackupTransport.FLAG_USER_INITIATED : 0;
- mStatus = mTransport.performBackup(mCurrentPackage, backupData, flags);
+ mStatus = transport.performBackup(mCurrentPackage, backupData, flags);
}
// TODO - We call finishBackup() for each application backed up, because
@@ -910,7 +926,7 @@
backupManagerService.addBackupTrace("data delivered: " + mStatus);
if (mStatus == BackupTransport.TRANSPORT_OK) {
backupManagerService.addBackupTrace("finishing op on transport");
- mStatus = mTransport.finishBackup();
+ mStatus = transport.finishBackup();
backupManagerService.addBackupTrace("finished: " + mStatus);
} else if (mStatus == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
backupManagerService.addBackupTrace("transport rejected package");
@@ -981,8 +997,8 @@
}
if (mAgentBinder != null) {
try {
- long quota = mTransport.getBackupQuota(mCurrentPackage.packageName,
- false);
+ TransportUtils.checkTransport(transport);
+ long quota = transport.getBackupQuota(mCurrentPackage.packageName, false);
mAgentBinder.doQuotaExceeded(size, quota);
} catch (Exception e) {
Slog.e(TAG, "Unable to notify about quota exceeded: " + e.getMessage());
@@ -1052,7 +1068,9 @@
// by way of retry/backoff time.
long delay;
try {
- delay = mTransport.requestBackupTime();
+ IBackupTransport transport =
+ mTransportClient.connectOrThrow("PBT.revertAndEndBackup()");
+ delay = transport.requestBackupTime();
} catch (Exception e) {
Slog.w(TAG, "Unable to contact transport for recommended backoff: " + e.getMessage());
delay = 0; // use the scheduler's default
diff --git a/com/android/server/backup/params/BackupParams.java b/com/android/server/backup/params/BackupParams.java
index 4fd7ddb..2ba8ec1 100644
--- a/com/android/server/backup/params/BackupParams.java
+++ b/com/android/server/backup/params/BackupParams.java
@@ -19,30 +19,34 @@
import android.app.backup.IBackupManagerMonitor;
import android.app.backup.IBackupObserver;
-import com.android.internal.backup.IBackupTransport;
+import com.android.server.backup.internal.OnTaskFinishedListener;
+import com.android.server.backup.transport.TransportClient;
import java.util.ArrayList;
public class BackupParams {
- public IBackupTransport transport;
+ public TransportClient transportClient;
public String dirName;
public ArrayList<String> kvPackages;
public ArrayList<String> fullPackages;
public IBackupObserver observer;
public IBackupManagerMonitor monitor;
+ public OnTaskFinishedListener listener;
public boolean userInitiated;
public boolean nonIncrementalBackup;
- public BackupParams(IBackupTransport transport, String dirName, ArrayList<String> kvPackages,
- ArrayList<String> fullPackages, IBackupObserver observer,
- IBackupManagerMonitor monitor, boolean userInitiated, boolean nonIncrementalBackup) {
- this.transport = transport;
+ public BackupParams(TransportClient transportClient, String dirName,
+ ArrayList<String> kvPackages, ArrayList<String> fullPackages, IBackupObserver observer,
+ IBackupManagerMonitor monitor, OnTaskFinishedListener listener, boolean userInitiated,
+ boolean nonIncrementalBackup) {
+ this.transportClient = transportClient;
this.dirName = dirName;
this.kvPackages = kvPackages;
this.fullPackages = fullPackages;
this.observer = observer;
this.monitor = monitor;
+ this.listener = listener;
this.userInitiated = userInitiated;
this.nonIncrementalBackup = nonIncrementalBackup;
}
diff --git a/com/android/server/backup/transport/TransportClient.java b/com/android/server/backup/transport/TransportClient.java
new file mode 100644
index 0000000..65f9502
--- /dev/null
+++ b/com/android/server/backup/transport/TransportClient.java
@@ -0,0 +1,487 @@
+/*
+ * Copyright (C) 2017 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.server.backup.transport;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.annotation.WorkerThread;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.DeadObjectException;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.backup.IBackupTransport;
+import com.android.internal.util.Preconditions;
+import com.android.server.backup.TransportManager;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * A {@link TransportClient} manages the connection to an {@link IBackupTransport} service, obtained
+ * via the {@param bindIntent} parameter provided in the constructor. A {@link TransportClient} is
+ * responsible for only one connection to the transport service, not more.
+ *
+ * <p>After retrieved using {@link TransportManager#getTransportClient(String, String)}, you can
+ * call either {@link #connect(String)}, if you can block your thread, or {@link
+ * #connectAsync(TransportConnectionListener, String)}, otherwise, to obtain a {@link
+ * IBackupTransport} instance. It's meant to be passed around as a token to a connected transport.
+ * When the connection is not needed anymore you should call {@link #unbind(String)} or indirectly
+ * via {@link TransportManager#disposeOfTransportClient(TransportClient, String)}.
+ *
+ * <p>DO NOT forget to unbind otherwise there will be dangling connections floating around.
+ *
+ * <p>This class is thread-safe.
+ *
+ * @see TransportManager
+ */
+public class TransportClient {
+ private static final String TAG = "TransportClient";
+
+ private final Context mContext;
+ private final Intent mBindIntent;
+ private final String mIdentifier;
+ private final ComponentName mTransportComponent;
+ private final Handler mListenerHandler;
+ private final String mPrefixForLog;
+ private final Object mStateLock = new Object();
+
+ @GuardedBy("mStateLock")
+ private final Map<TransportConnectionListener, String> mListeners = new ArrayMap<>();
+
+ @GuardedBy("mStateLock")
+ @State
+ private int mState = State.IDLE;
+
+ @GuardedBy("mStateLock")
+ private volatile IBackupTransport mTransport;
+
+ TransportClient(
+ Context context,
+ Intent bindIntent,
+ ComponentName transportComponent,
+ String identifier) {
+ this(context, bindIntent, transportComponent, identifier, Handler.getMain());
+ }
+
+ @VisibleForTesting
+ TransportClient(
+ Context context,
+ Intent bindIntent,
+ ComponentName transportComponent,
+ String identifier,
+ Handler listenerHandler) {
+ mContext = context;
+ mTransportComponent = transportComponent;
+ mBindIntent = bindIntent;
+ mIdentifier = identifier;
+ mListenerHandler = listenerHandler;
+
+ // For logging
+ String classNameForLog = mTransportComponent.getShortClassName().replaceFirst(".*\\.", "");
+ mPrefixForLog = classNameForLog + "#" + mIdentifier + ": ";
+ }
+
+ public ComponentName getTransportComponent() {
+ return mTransportComponent;
+ }
+
+ // Calls to onServiceDisconnected() or onBindingDied() turn TransportClient UNUSABLE. After one
+ // of these calls, if a binding happen again the new service can be a different instance. Since
+ // transports are stateful, we don't want a new instance responding for an old instance's state.
+ private ServiceConnection mConnection =
+ new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName componentName, IBinder binder) {
+ IBackupTransport transport = IBackupTransport.Stub.asInterface(binder);
+ synchronized (mStateLock) {
+ checkStateIntegrityLocked();
+
+ if (mState != State.UNUSABLE) {
+ log(Log.DEBUG, "Transport connected");
+ setStateLocked(State.CONNECTED, transport);
+ notifyListenersAndClearLocked(transport);
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName componentName) {
+ synchronized (mStateLock) {
+ log(Log.ERROR, "Service disconnected: client UNUSABLE");
+ setStateLocked(State.UNUSABLE, null);
+ // After unbindService() no calls back to mConnection
+ mContext.unbindService(this);
+ }
+ }
+
+ @Override
+ public void onBindingDied(ComponentName name) {
+ synchronized (mStateLock) {
+ checkStateIntegrityLocked();
+
+ log(Log.ERROR, "Binding died: client UNUSABLE");
+ // After unbindService() no calls back to mConnection
+ switch (mState) {
+ case State.UNUSABLE:
+ break;
+ case State.IDLE:
+ log(Log.ERROR, "Unexpected state transition IDLE => UNUSABLE");
+ setStateLocked(State.UNUSABLE, null);
+ break;
+ case State.BOUND_AND_CONNECTING:
+ setStateLocked(State.UNUSABLE, null);
+ mContext.unbindService(this);
+ notifyListenersAndClearLocked(null);
+ break;
+ case State.CONNECTED:
+ setStateLocked(State.UNUSABLE, null);
+ mContext.unbindService(this);
+ break;
+ }
+ }
+ }
+ };
+
+ /**
+ * Attempts to connect to the transport (if needed).
+ *
+ * <p>Note that being bound is not the same as connected. To be connected you also need to be
+ * bound. You go from nothing to bound, then to bound and connected. To have a usable transport
+ * binder instance you need to be connected. This method will attempt to connect and return an
+ * usable transport binder regardless of the state of the object, it may already be connected,
+ * or bound but not connected, not bound at all or even unusable.
+ *
+ * <p>So, a {@link Context#bindServiceAsUser(Intent, ServiceConnection, int, UserHandle)} (or
+ * one of its variants) can be called or not depending on the inner state. However, it won't be
+ * called again if we're already bound. For example, if one was already requested but the
+ * framework has not yet returned (meaning we're bound but still trying to connect) it won't
+ * trigger another one, just piggyback on the original request.
+ *
+ * <p>It's guaranteed that you are going to get a call back to {@param listener} after this
+ * call. However, the {@param IBackupTransport} parameter, the transport binder, is not
+ * guaranteed to be non-null, or if it's non-null it's not guaranteed to be usable - i.e. it can
+ * throw {@link DeadObjectException}s on method calls. You should check for both in your code.
+ * The reasons for a null transport binder are:
+ *
+ * <ul>
+ * <li>Some code called {@link #unbind(String)} before you got a callback.
+ * <li>The framework had already called {@link
+ * ServiceConnection#onServiceDisconnected(ComponentName)} or {@link
+ * ServiceConnection#onBindingDied(ComponentName)} on this object's connection before.
+ * Check the documentation of those methods for when that happens.
+ * <li>The framework returns false for {@link Context#bindServiceAsUser(Intent,
+ * ServiceConnection, int, UserHandle)} (or one of its variants). Check documentation for
+ * when this happens.
+ * </ul>
+ *
+ * For unusable transport binders check {@link DeadObjectException}.
+ *
+ * @param listener The listener that will be called with the (possibly null or unusable) {@link
+ * IBackupTransport} instance and this {@link TransportClient} object.
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. This
+ * should be a human-readable short string that is easily identifiable in the logs. Ideally
+ * TAG.methodName(), where TAG is the one used in logcat. In cases where this is is not very
+ * descriptive like MyHandler.handleMessage() you should put something that someone reading
+ * the code would understand, like MyHandler/MSG_FOO.
+ * @see #connect(String)
+ * @see DeadObjectException
+ * @see ServiceConnection#onServiceConnected(ComponentName, IBinder)
+ * @see ServiceConnection#onServiceDisconnected(ComponentName)
+ * @see Context#bindServiceAsUser(Intent, ServiceConnection, int, UserHandle)
+ */
+ public void connectAsync(TransportConnectionListener listener, String caller) {
+ synchronized (mStateLock) {
+ checkStateIntegrityLocked();
+
+ switch (mState) {
+ case State.UNUSABLE:
+ log(Log.DEBUG, caller, "Async connect: UNUSABLE client");
+ notifyListener(listener, null, caller);
+ break;
+ case State.IDLE:
+ boolean hasBound =
+ mContext.bindServiceAsUser(
+ mBindIntent,
+ mConnection,
+ Context.BIND_AUTO_CREATE,
+ TransportManager.createSystemUserHandle());
+ if (hasBound) {
+ // We don't need to set a time-out because we are guaranteed to get a call
+ // back in ServiceConnection, either an onServiceConnected() or
+ // onBindingDied().
+ log(Log.DEBUG, caller, "Async connect: service bound, connecting");
+ setStateLocked(State.BOUND_AND_CONNECTING, null);
+ mListeners.put(listener, caller);
+ } else {
+ log(Log.ERROR, "Async connect: bindService returned false");
+ // mState remains State.IDLE
+ mContext.unbindService(mConnection);
+ notifyListener(listener, null, caller);
+ }
+ break;
+ case State.BOUND_AND_CONNECTING:
+ log(Log.DEBUG, caller, "Async connect: already connecting, adding listener");
+ mListeners.put(listener, caller);
+ break;
+ case State.CONNECTED:
+ log(Log.DEBUG, caller, "Async connect: reusing transport");
+ notifyListener(listener, mTransport, caller);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Removes the transport binding.
+ *
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+ * {@link #connectAsync(TransportConnectionListener, String)} for more details.
+ */
+ public void unbind(String caller) {
+ synchronized (mStateLock) {
+ checkStateIntegrityLocked();
+
+ log(Log.DEBUG, caller, "Unbind requested (was " + stateToString(mState) + ")");
+ switch (mState) {
+ case State.UNUSABLE:
+ case State.IDLE:
+ break;
+ case State.BOUND_AND_CONNECTING:
+ setStateLocked(State.IDLE, null);
+ // After unbindService() no calls back to mConnection
+ mContext.unbindService(mConnection);
+ notifyListenersAndClearLocked(null);
+ break;
+ case State.CONNECTED:
+ setStateLocked(State.IDLE, null);
+ mContext.unbindService(mConnection);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Attempts to connect to the transport (if needed) and returns it.
+ *
+ * <p>Synchronous version of {@link #connectAsync(TransportConnectionListener, String)}. The
+ * same observations about state are valid here. Also, what was said about the {@link
+ * IBackupTransport} parameter of {@link TransportConnectionListener} now apply to the return
+ * value of this method.
+ *
+ * <p>This is a potentially blocking operation, so be sure to call this carefully on the correct
+ * threads. You can't call this from the process main-thread (it throws an exception if you do
+ * so).
+ *
+ * <p>In most cases only the first call to this method will block, the following calls should
+ * return instantly. However, this is not guaranteed.
+ *
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+ * {@link #connectAsync(TransportConnectionListener, String)} for more details.
+ * @return A {@link IBackupTransport} transport binder instance or null. If it's non-null it can
+ * still be unusable - throws {@link DeadObjectException} on method calls
+ */
+ @WorkerThread
+ @Nullable
+ public IBackupTransport connect(String caller) {
+ // If called on the main-thread this could deadlock waiting because calls to
+ // ServiceConnection are on the main-thread as well
+ Preconditions.checkState(
+ !Looper.getMainLooper().isCurrentThread(), "Can't call connect() on main thread");
+
+ IBackupTransport transport = mTransport;
+ if (transport != null) {
+ log(Log.DEBUG, caller, "Sync connect: reusing transport");
+ return transport;
+ }
+
+ // If it's already UNUSABLE we return straight away, no need to go to main-thread
+ synchronized (mStateLock) {
+ if (mState == State.UNUSABLE) {
+ log(Log.DEBUG, caller, "Sync connect: UNUSABLE client");
+ return null;
+ }
+ }
+
+ CompletableFuture<IBackupTransport> transportFuture = new CompletableFuture<>();
+ TransportConnectionListener requestListener =
+ (requestedTransport, transportClient) ->
+ transportFuture.complete(requestedTransport);
+
+ log(Log.DEBUG, caller, "Sync connect: calling async");
+ connectAsync(requestListener, caller);
+
+ try {
+ return transportFuture.get();
+ } catch (InterruptedException | ExecutionException e) {
+ String error = e.getClass().getSimpleName();
+ log(Log.ERROR, caller, error + " while waiting for transport: " + e.getMessage());
+ return null;
+ }
+ }
+
+ /**
+ * Tries to connect to the transport, if it fails throws {@link TransportNotAvailableException}.
+ *
+ * <p>Same as {@link #connect(String)} except it throws instead of returning null.
+ *
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+ * {@link #connectAsync(TransportConnectionListener, String)} for more details.
+ * @return A {@link IBackupTransport} transport binder instance.
+ * @see #connect(String)
+ * @throws TransportNotAvailableException if connection attempt fails.
+ */
+ @WorkerThread
+ public IBackupTransport connectOrThrow(String caller) throws TransportNotAvailableException {
+ IBackupTransport transport = connect(caller);
+ if (transport == null) {
+ log(Log.ERROR, caller, "Transport connection failed");
+ throw new TransportNotAvailableException();
+ }
+ return transport;
+ }
+
+ /**
+ * If the {@link TransportClient} is already connected to the transport, returns the transport,
+ * otherwise throws {@link TransportNotAvailableException}.
+ *
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+ * {@link #connectAsync(TransportConnectionListener, String)} for more details.
+ * @return A {@link IBackupTransport} transport binder instance.
+ * @throws TransportNotAvailableException if not connected.
+ */
+ public IBackupTransport getConnectedTransport(String caller)
+ throws TransportNotAvailableException {
+ IBackupTransport transport = mTransport;
+ if (transport == null) {
+ log(Log.ERROR, caller, "Transport not connected");
+ throw new TransportNotAvailableException();
+ }
+ return transport;
+ }
+
+ @Override
+ public String toString() {
+ return "TransportClient{"
+ + mTransportComponent.flattenToShortString()
+ + "#"
+ + mIdentifier
+ + "}";
+ }
+
+ private void notifyListener(
+ TransportConnectionListener listener, IBackupTransport transport, String caller) {
+ log(Log.VERBOSE, caller, "Notifying listener of transport = " + transport);
+ mListenerHandler.post(() -> listener.onTransportConnectionResult(transport, this));
+ }
+
+ @GuardedBy("mStateLock")
+ private void notifyListenersAndClearLocked(IBackupTransport transport) {
+ for (Map.Entry<TransportConnectionListener, String> entry : mListeners.entrySet()) {
+ TransportConnectionListener listener = entry.getKey();
+ String caller = entry.getValue();
+ notifyListener(listener, transport, caller);
+ }
+ mListeners.clear();
+ }
+
+ @GuardedBy("mStateLock")
+ private void setStateLocked(@State int state, @Nullable IBackupTransport transport) {
+ log(Log.VERBOSE, "State: " + stateToString(mState) + " => " + stateToString(state));
+ mState = state;
+ mTransport = transport;
+ }
+
+ @GuardedBy("mStateLock")
+ private void checkStateIntegrityLocked() {
+ switch (mState) {
+ case State.UNUSABLE:
+ checkState(mListeners.isEmpty(), "Unexpected listeners when state = UNUSABLE");
+ checkState(
+ mTransport == null, "Transport expected to be null when state = UNUSABLE");
+ case State.IDLE:
+ checkState(mListeners.isEmpty(), "Unexpected listeners when state = IDLE");
+ checkState(mTransport == null, "Transport expected to be null when state = IDLE");
+ break;
+ case State.BOUND_AND_CONNECTING:
+ checkState(
+ mTransport == null,
+ "Transport expected to be null when state = BOUND_AND_CONNECTING");
+ break;
+ case State.CONNECTED:
+ checkState(mListeners.isEmpty(), "Unexpected listeners when state = CONNECTED");
+ checkState(
+ mTransport != null,
+ "Transport expected to be non-null when state = CONNECTED");
+ break;
+ default:
+ checkState(false, "Unexpected state = " + stateToString(mState));
+ }
+ }
+
+ private void checkState(boolean assertion, String message) {
+ if (!assertion) {
+ log(Log.ERROR, message);
+ }
+ }
+
+ private String stateToString(@State int state) {
+ switch (state) {
+ case State.UNUSABLE:
+ return "UNUSABLE";
+ case State.IDLE:
+ return "IDLE";
+ case State.BOUND_AND_CONNECTING:
+ return "BOUND_AND_CONNECTING";
+ case State.CONNECTED:
+ return "CONNECTED";
+ default:
+ return "<UNKNOWN = " + state + ">";
+ }
+ }
+
+ private void log(int priority, String message) {
+ TransportUtils.log(priority, TAG, message);
+ }
+
+ private void log(int priority, String caller, String msg) {
+ TransportUtils.log(priority, TAG, mPrefixForLog, caller, msg);
+ // TODO(brufino): Log in internal list for dump
+ // CharSequence time = DateFormat.format("yyyy-MM-dd HH:mm:ss", System.currentTimeMillis());
+ }
+
+ @IntDef({State.UNUSABLE, State.IDLE, State.BOUND_AND_CONNECTING, State.CONNECTED})
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface State {
+ int UNUSABLE = 0;
+ int IDLE = 1;
+ int BOUND_AND_CONNECTING = 2;
+ int CONNECTED = 3;
+ }
+}
diff --git a/com/android/server/backup/transport/TransportClientManager.java b/com/android/server/backup/transport/TransportClientManager.java
new file mode 100644
index 0000000..1cbe747
--- /dev/null
+++ b/com/android/server/backup/transport/TransportClientManager.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2017 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.server.backup.transport;
+
+import static com.android.server.backup.TransportManager.SERVICE_ACTION_TRANSPORT_HOST;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import com.android.server.backup.TransportManager;
+
+/**
+ * Manages the creation and disposal of {@link TransportClient}s. The only class that should use
+ * this is {@link TransportManager}, all the other usages should go to {@link TransportManager}.
+ *
+ * <p>TODO(brufino): Implement pool of TransportClients
+ */
+public class TransportClientManager {
+ private static final String TAG = "TransportClientManager";
+
+ private final Context mContext;
+ private final Object mTransportClientsLock = new Object();
+ private int mTransportClientsCreated = 0;
+
+ public TransportClientManager(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Retrieves a {@link TransportClient} for the transport identified by {@param
+ * transportComponent}.
+ *
+ * @param transportComponent The {@link ComponentName} of the transport.
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+ * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+ * details.
+ * @return A {@link TransportClient}.
+ */
+ public TransportClient getTransportClient(ComponentName transportComponent, String caller) {
+ Intent bindIntent =
+ new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(transportComponent);
+ synchronized (mTransportClientsLock) {
+ TransportClient transportClient =
+ new TransportClient(
+ mContext,
+ bindIntent,
+ transportComponent,
+ Integer.toString(mTransportClientsCreated));
+ mTransportClientsCreated++;
+ TransportUtils.log(Log.DEBUG, TAG, caller, "Retrieving " + transportClient);
+ return transportClient;
+ }
+ }
+
+ /**
+ * Disposes of the {@link TransportClient}.
+ *
+ * @param transportClient The {@link TransportClient} to be disposed of.
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+ * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+ * details.
+ */
+ public void disposeOfTransportClient(TransportClient transportClient, String caller) {
+ TransportUtils.log(Log.DEBUG, TAG, caller, "Disposing of " + transportClient);
+ transportClient.unbind(caller);
+ }
+}
diff --git a/com/android/server/backup/transport/TransportClientTest.java b/com/android/server/backup/transport/TransportClientTest.java
new file mode 100644
index 0000000..54d233a
--- /dev/null
+++ b/com/android/server/backup/transport/TransportClientTest.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2017 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.server.backup.transport;
+
+import static com.android.server.backup.TransportManager.SERVICE_ACTION_TRANSPORT_HOST;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+
+import com.android.internal.backup.IBackupTransport;
+
+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;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowLooper;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(manifest = Config.NONE, sdk = 23)
+@Presubmit
+public class TransportClientTest {
+ private static final String PACKAGE_NAME = "some.package.name";
+ private static final ComponentName TRANSPORT_COMPONENT =
+ new ComponentName(PACKAGE_NAME, PACKAGE_NAME + ".transport.Transport");
+
+ @Mock private Context mContext;
+ @Mock private TransportConnectionListener mTransportConnectionListener;
+ @Mock private TransportConnectionListener mTransportConnectionListener2;
+ @Mock private IBackupTransport.Stub mIBackupTransport;
+ private TransportClient mTransportClient;
+ private Intent mBindIntent;
+ private ShadowLooper mShadowLooper;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ Looper mainLooper = Looper.getMainLooper();
+ mShadowLooper = shadowOf(mainLooper);
+ mBindIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(TRANSPORT_COMPONENT);
+ mTransportClient =
+ new TransportClient(
+ mContext, mBindIntent, TRANSPORT_COMPONENT, "1", new Handler(mainLooper));
+
+ when(mContext.bindServiceAsUser(
+ eq(mBindIntent),
+ any(ServiceConnection.class),
+ anyInt(),
+ any(UserHandle.class)))
+ .thenReturn(true);
+ }
+
+ // TODO: Testing implementation? Remove?
+ @Test
+ public void testConnectAsync_callsBindService() throws Exception {
+ mTransportClient.connectAsync(mTransportConnectionListener, "caller");
+
+ verify(mContext)
+ .bindServiceAsUser(
+ eq(mBindIntent),
+ any(ServiceConnection.class),
+ anyInt(),
+ any(UserHandle.class));
+ }
+
+ @Test
+ public void testConnectAsync_callsListenerWhenConnected() throws Exception {
+ mTransportClient.connectAsync(mTransportConnectionListener, "caller");
+
+ // Simulate framework connecting
+ ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+ connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport);
+
+ mShadowLooper.runToEndOfTasks();
+ verify(mTransportConnectionListener)
+ .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient));
+ }
+
+ @Test
+ public void testConnectAsync_whenPendingConnection_callsAllListenersWhenConnected()
+ throws Exception {
+ mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
+ ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+
+ mTransportClient.connectAsync(mTransportConnectionListener2, "caller2");
+
+ connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport);
+
+ mShadowLooper.runToEndOfTasks();
+ verify(mTransportConnectionListener)
+ .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient));
+ verify(mTransportConnectionListener2)
+ .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient));
+ }
+
+ @Test
+ public void testConnectAsync_whenAlreadyConnected_callsListener() throws Exception {
+ mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
+ ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+ connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport);
+
+ mTransportClient.connectAsync(mTransportConnectionListener2, "caller2");
+
+ mShadowLooper.runToEndOfTasks();
+ verify(mTransportConnectionListener2)
+ .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient));
+ }
+
+ @Test
+ public void testConnectAsync_whenFrameworkDoesntBind_callsListener() throws Exception {
+ when(mContext.bindServiceAsUser(
+ eq(mBindIntent),
+ any(ServiceConnection.class),
+ anyInt(),
+ any(UserHandle.class)))
+ .thenReturn(false);
+
+ mTransportClient.connectAsync(mTransportConnectionListener, "caller");
+
+ mShadowLooper.runToEndOfTasks();
+ verify(mTransportConnectionListener)
+ .onTransportConnectionResult(isNull(), eq(mTransportClient));
+ }
+
+ @Test
+ public void testConnectAsync_whenFrameworkDoesntBind_releasesConnection() throws Exception {
+ when(mContext.bindServiceAsUser(
+ eq(mBindIntent),
+ any(ServiceConnection.class),
+ anyInt(),
+ any(UserHandle.class)))
+ .thenReturn(false);
+
+ mTransportClient.connectAsync(mTransportConnectionListener, "caller");
+
+ ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+ verify(mContext).unbindService(eq(connection));
+ }
+
+ @Test
+ public void testConnectAsync_afterServiceDisconnectedBeforeNewConnection_callsListener()
+ throws Exception {
+ mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
+ ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+ connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport);
+ connection.onServiceDisconnected(TRANSPORT_COMPONENT);
+
+ mTransportClient.connectAsync(mTransportConnectionListener2, "caller1");
+
+ verify(mTransportConnectionListener2)
+ .onTransportConnectionResult(isNull(), eq(mTransportClient));
+ }
+
+ @Test
+ public void testConnectAsync_afterServiceDisconnectedAfterNewConnection_callsListener()
+ throws Exception {
+ mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
+ ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+ connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport);
+ connection.onServiceDisconnected(TRANSPORT_COMPONENT);
+ connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport);
+
+ mTransportClient.connectAsync(mTransportConnectionListener2, "caller1");
+
+ // Yes, it should return null because the object became unusable, check design doc
+ verify(mTransportConnectionListener2)
+ .onTransportConnectionResult(isNull(), eq(mTransportClient));
+ }
+
+ // TODO(b/69153972): Support SDK 26 API (ServiceConnection.inBindingDied) for transport tests
+ /*@Test
+ public void testConnectAsync_callsListenerIfBindingDies() throws Exception {
+ mTransportClient.connectAsync(mTransportListener, "caller");
+
+ ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+ connection.onBindingDied(TRANSPORT_COMPONENT);
+
+ mShadowLooper.runToEndOfTasks();
+ verify(mTransportListener).onTransportBound(isNull(), eq(mTransportClient));
+ }
+
+ @Test
+ public void testConnectAsync_whenPendingConnection_callsListenersIfBindingDies()
+ throws Exception {
+ mTransportClient.connectAsync(mTransportListener, "caller1");
+ ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+
+ mTransportClient.connectAsync(mTransportListener2, "caller2");
+
+ connection.onBindingDied(TRANSPORT_COMPONENT);
+
+ mShadowLooper.runToEndOfTasks();
+ verify(mTransportListener).onTransportBound(isNull(), eq(mTransportClient));
+ verify(mTransportListener2).onTransportBound(isNull(), eq(mTransportClient));
+ }*/
+
+ private ServiceConnection verifyBindServiceAsUserAndCaptureServiceConnection(Context context) {
+ ArgumentCaptor<ServiceConnection> connectionCaptor =
+ ArgumentCaptor.forClass(ServiceConnection.class);
+ verify(context)
+ .bindServiceAsUser(
+ any(Intent.class),
+ connectionCaptor.capture(),
+ anyInt(),
+ any(UserHandle.class));
+ return connectionCaptor.getValue();
+ }
+}
diff --git a/com/android/server/backup/transport/TransportConnectionListener.java b/com/android/server/backup/transport/TransportConnectionListener.java
new file mode 100644
index 0000000..1ccffd0
--- /dev/null
+++ b/com/android/server/backup/transport/TransportConnectionListener.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2017 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.server.backup.transport;
+
+import android.annotation.Nullable;
+
+import com.android.internal.backup.IBackupTransport;
+
+/**
+ * Listener to be called by {@link TransportClient#connectAsync(TransportConnectionListener,
+ * String)}.
+ */
+public interface TransportConnectionListener {
+ /**
+ * Called when {@link TransportClient} has a transport binder available or that it decided it
+ * couldn't obtain one, in which case {@param transport} is null.
+ *
+ * @param transport A {@link IBackupTransport} transport binder or null.
+ * @param transportClient The {@link TransportClient} used to retrieve this transport binder.
+ */
+ void onTransportConnectionResult(
+ @Nullable IBackupTransport transport, TransportClient transportClient);
+}
diff --git a/com/android/server/backup/transport/TransportNotAvailableException.java b/com/android/server/backup/transport/TransportNotAvailableException.java
new file mode 100644
index 0000000..c4e5a1d
--- /dev/null
+++ b/com/android/server/backup/transport/TransportNotAvailableException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 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.server.backup.transport;
+
+import com.android.internal.backup.IBackupTransport;
+
+/**
+ * Exception thrown when the {@link IBackupTransport} is not available. This happen when a {@link
+ * TransportClient} connection attempt fails. Check {@link
+ * TransportClient#connectAsync(TransportConnectionListener, String)} for when that happens.
+ *
+ * @see TransportClient#connectAsync(TransportConnectionListener, String)
+ */
+public class TransportNotAvailableException extends Exception {
+ TransportNotAvailableException() {
+ super("Transport not available");
+ }
+}
diff --git a/com/android/server/backup/transport/TransportUtils.java b/com/android/server/backup/transport/TransportUtils.java
new file mode 100644
index 0000000..85599b7
--- /dev/null
+++ b/com/android/server/backup/transport/TransportUtils.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2017 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.server.backup.transport;
+
+import android.annotation.Nullable;
+import android.os.DeadObjectException;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.backup.IBackupTransport;
+
+/** Utility methods for transport-related operations. */
+public class TransportUtils {
+ private static final String TAG = "TransportUtils";
+
+ /**
+ * Throws {@link TransportNotAvailableException} if {@param transport} is null. The semantics is
+ * similar to a {@link DeadObjectException} coming from a dead transport binder.
+ */
+ public static IBackupTransport checkTransport(@Nullable IBackupTransport transport)
+ throws TransportNotAvailableException {
+ if (transport == null) {
+ log(Log.ERROR, TAG, "Transport not available");
+ throw new TransportNotAvailableException();
+ }
+ return transport;
+ }
+
+ static void log(int priority, String tag, String message) {
+ log(priority, tag, null, message);
+ }
+
+ static void log(int priority, String tag, @Nullable String caller, String message) {
+ log(priority, tag, "", caller, message);
+ }
+
+ static void log(
+ int priority, String tag, String prefix, @Nullable String caller, String message) {
+ if (Log.isLoggable(tag, priority)) {
+ if (caller != null) {
+ prefix += "[" + caller + "] ";
+ }
+ Slog.println(priority, tag, prefix + message);
+ }
+ }
+
+ private TransportUtils() {}
+}
diff --git a/com/android/server/broadcastradio/Tuner.java b/com/android/server/broadcastradio/Tuner.java
index e6ae320..2ea4271 100644
--- a/com/android/server/broadcastradio/Tuner.java
+++ b/com/android/server/broadcastradio/Tuner.java
@@ -27,8 +27,10 @@
import android.os.RemoteException;
import android.util.Slog;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
class Tuner extends ITuner.Stub {
private static final String TAG = "BroadcastRadioService.Tuner";
@@ -96,6 +98,10 @@
private native boolean nativeIsAnalogForced(long nativeContext);
private native void nativeSetAnalogForced(long nativeContext, boolean isForced);
+ private native Map<String, String> nativeSetParameters(long nativeContext,
+ Map<String, String> parameters);
+ private native Map<String, String> nativeGetParameters(long nativeContext, List<String> keys);
+
private native boolean nativeIsAntennaConnected(long nativeContext);
@Override
@@ -273,6 +279,31 @@
}
@Override
+ public Map setParameters(Map parameters) {
+ Map<String, String> results;
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ results = nativeSetParameters(mNativeContext, Objects.requireNonNull(parameters));
+ }
+ if (results == null) return Collections.emptyMap();
+ return results;
+ }
+
+ @Override
+ public Map getParameters(List<String> keys) {
+ if (keys == null) {
+ throw new IllegalArgumentException("The argument must not be a null pointer");
+ }
+ Map<String, String> results;
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ results = nativeGetParameters(mNativeContext, keys);
+ }
+ if (results == null) return Collections.emptyMap();
+ return results;
+ }
+
+ @Override
public boolean isAntennaConnected() {
synchronized (mLock) {
checkNotClosedLocked();
diff --git a/com/android/server/broadcastradio/TunerCallback.java b/com/android/server/broadcastradio/TunerCallback.java
index a87ae8d..2460c67 100644
--- a/com/android/server/broadcastradio/TunerCallback.java
+++ b/com/android/server/broadcastradio/TunerCallback.java
@@ -26,6 +26,9 @@
import android.os.RemoteException;
import android.util.Slog;
+import java.util.List;
+import java.util.Map;
+
class TunerCallback implements ITunerCallback {
private static final String TAG = "BroadcastRadioService.TunerCallback";
@@ -121,6 +124,11 @@
}
@Override
+ public void onParametersUpdated(Map parameters) {
+ dispatch(() -> mClientCallback.onParametersUpdated(parameters));
+ }
+
+ @Override
public IBinder asBinder() {
throw new RuntimeException("Not a binder");
}
diff --git a/com/android/server/connectivity/NetdEventListenerService.java b/com/android/server/connectivity/NetdEventListenerService.java
index 4bdbbe3..e243e56 100644
--- a/com/android/server/connectivity/NetdEventListenerService.java
+++ b/com/android/server/connectivity/NetdEventListenerService.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.INetdEventCallback;
+import android.net.MacAddress;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.metrics.ConnectStats;
@@ -35,6 +36,7 @@
import android.util.Log;
import android.util.ArrayMap;
import android.util.SparseArray;
+import android.util.StatsLog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -242,13 +244,17 @@
event.timestampMs = timestampMs;
event.uid = uid;
event.ethertype = ethertype;
- event.dstHwAddr = dstHw;
+ event.dstHwAddr = new MacAddress(dstHw);
event.srcIp = srcIp;
event.dstIp = dstIp;
event.ipNextHeader = ipNextHeader;
event.srcPort = srcPort;
event.dstPort = dstPort;
addWakeupEvent(event);
+
+ String dstMac = event.dstHwAddr.toString();
+ StatsLog.write(StatsLog.PACKET_WAKEUP_OCCURRED,
+ uid, iface, ethertype, dstMac, srcIp, dstIp, ipNextHeader, srcPort, dstPort);
}
private void addWakeupEvent(WakeupEvent event) {
diff --git a/com/android/server/devicepolicy/DevicePolicyManagerService.java b/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 2d8a0ee..60c36d1 100644
--- a/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -4917,7 +4917,8 @@
@Override
public boolean installKeyPair(ComponentName who, String callerPackage, byte[] privKey,
- byte[] cert, byte[] chain, String alias, boolean requestAccess) {
+ byte[] cert, byte[] chain, String alias, boolean requestAccess,
+ boolean isUserSelectable) {
enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
DELEGATION_CERT_INSTALL);
@@ -4935,6 +4936,7 @@
if (requestAccess) {
keyChain.setGrant(callingUid, alias, true);
}
+ keyChain.setUserSelectable(alias, isUserSelectable);
return true;
} catch (RemoteException e) {
Log.e(LOG_TAG, "Installing certificate", e);
diff --git a/com/android/server/devicepolicy/NetworkLoggingHandler.java b/com/android/server/devicepolicy/NetworkLoggingHandler.java
index 6086354..4a6bee5 100644
--- a/com/android/server/devicepolicy/NetworkLoggingHandler.java
+++ b/com/android/server/devicepolicy/NetworkLoggingHandler.java
@@ -64,6 +64,8 @@
private final DevicePolicyManagerService mDpm;
private final AlarmManager mAlarmManager;
+ private long mId;
+
private final OnAlarmListener mBatchTimeoutAlarmListener = new OnAlarmListener() {
@Override
public void onAlarm() {
@@ -185,6 +187,10 @@
private Bundle finalizeBatchAndBuildDeviceOwnerMessageLocked() {
Bundle notificationExtras = null;
if (mNetworkEvents.size() > 0) {
+ // Assign ids to the events.
+ for (NetworkEvent event : mNetworkEvents) {
+ event.setId(mId++);
+ }
// Finalize the batch and start a new one from scratch.
if (mBatches.size() >= MAX_BATCHES) {
// Remove the oldest batch if we hit the limit.
diff --git a/com/android/server/display/BrightnessIdleJob.java b/com/android/server/display/BrightnessIdleJob.java
new file mode 100644
index 0000000..876acf4
--- /dev/null
+++ b/com/android/server/display/BrightnessIdleJob.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2017 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.server.display;
+
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.hardware.display.DisplayManagerInternal;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * JobService used to persists brightness slider events when the device
+ * is idle and charging.
+ */
+public class BrightnessIdleJob extends JobService {
+
+ // Must be unique within the system server uid.
+ private static final int JOB_ID = 3923512;
+
+ public static void scheduleJob(Context context) {
+ JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+
+ JobInfo pending = jobScheduler.getPendingJob(JOB_ID);
+ JobInfo jobInfo =
+ new JobInfo.Builder(JOB_ID, new ComponentName(context, BrightnessIdleJob.class))
+ .setRequiresDeviceIdle(true)
+ .setRequiresCharging(true)
+ .setPeriodic(TimeUnit.HOURS.toMillis(24)).build();
+
+ if (pending != null && !pending.equals(jobInfo)) {
+ jobScheduler.cancel(JOB_ID);
+ pending = null;
+ }
+
+ if (pending == null) {
+ jobScheduler.schedule(jobInfo);
+ }
+ }
+
+ public static void cancelJob(Context context) {
+ JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+ jobScheduler.cancel(JOB_ID);
+ }
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ if (BrightnessTracker.DEBUG) {
+ Slog.d(BrightnessTracker.TAG, "Scheduled write of brightness events");
+ }
+ DisplayManagerInternal dmi = LocalServices.getService(DisplayManagerInternal.class);
+ dmi.persistBrightnessSliderEvents();
+ return false;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/com/android/server/display/BrightnessTracker.java b/com/android/server/display/BrightnessTracker.java
index 361d928..2c6fe94 100644
--- a/com/android/server/display/BrightnessTracker.java
+++ b/com/android/server/display/BrightnessTracker.java
@@ -34,6 +34,7 @@
import android.os.BatteryManager;
import android.os.Environment;
import android.os.Handler;
+import android.os.PowerManager;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -61,12 +62,12 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
-import java.util.List;
import java.util.concurrent.TimeUnit;
/**
@@ -75,8 +76,8 @@
*/
public class BrightnessTracker {
- private static final String TAG = "BrightnessTracker";
- private static final boolean DEBUG = false;
+ static final String TAG = "BrightnessTracker";
+ static final boolean DEBUG = false;
private static final String EVENTS_FILE = "brightness_events.xml";
private static final int MAX_EVENTS = 100;
@@ -103,6 +104,8 @@
@GuardedBy("mEventsLock")
private RingBuffer<BrightnessChangeEvent> mEvents
= new RingBuffer<>(BrightnessChangeEvent.class, MAX_EVENTS);
+ @GuardedBy("mEventsLock")
+ private boolean mEventsDirty;
private final Runnable mEventsWriter = () -> writeEvents();
private volatile boolean mWriteEventsScheduled;
@@ -160,7 +163,10 @@
UserHandle.USER_CURRENT);
mSensorListener = new SensorListener();
- mInjector.registerSensorListener(mContext, mSensorListener);
+
+ if (mInjector.isInteractive(mContext)) {
+ mInjector.registerSensorListener(mContext, mSensorListener, mBgHandler);
+ }
mSettingsObserver = new SettingsObserver(mBgHandler);
mInjector.registerBrightnessObserver(mContentResolver, mSettingsObserver);
@@ -168,8 +174,12 @@
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_SHUTDOWN);
intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
+ intentFilter.addAction(Intent.ACTION_SCREEN_ON);
+ intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
mBroadcastReceiver = new Receiver();
mInjector.registerReceiver(mContext, mBroadcastReceiver, intentFilter);
+
+ mInjector.scheduleIdleJob(mContext);
}
/** Stop listening for events */
@@ -181,6 +191,7 @@
mInjector.unregisterSensorListener(mContext, mSensorListener);
mInjector.unregisterReceiver(mContext, mBroadcastReceiver);
mInjector.unregisterBrightnessObserver(mContext, mSettingsObserver);
+ mInjector.cancelIdleJob(mContext);
}
/**
@@ -211,6 +222,10 @@
brightness, userId);
}
+ public void persistEvents() {
+ scheduleWriteEvents();
+ }
+
private void handleBrightnessChanged() {
if (DEBUG) {
Slog.d(TAG, "Brightness change");
@@ -278,6 +293,7 @@
Slog.d(TAG, "Event " + event.brightness + " " + event.packageName);
}
synchronized (mEventsLock) {
+ mEventsDirty = true;
mEvents.append(event);
}
}
@@ -291,8 +307,12 @@
private void writeEvents() {
mWriteEventsScheduled = false;
- // TODO kick off write on handler thread e.g. every 24 hours.
synchronized (mEventsLock) {
+ if (!mEventsDirty) {
+ // Nothing to write
+ return;
+ }
+
final AtomicFile writeTo = mInjector.getFile();
if (writeTo == null) {
return;
@@ -301,12 +321,14 @@
if (writeTo.exists()) {
writeTo.delete();
}
+ mEventsDirty = false;
} else {
FileOutputStream output = null;
try {
output = writeTo.startWrite();
writeEventsLocked(output);
writeTo.finishWrite(output);
+ mEventsDirty = false;
} catch (IOException e) {
writeTo.failWrite(output);
Slog.e(TAG, "Failed to write change mEvents.", e);
@@ -317,6 +339,8 @@
private void readEvents() {
synchronized (mEventsLock) {
+ // Read might prune events so mark as dirty.
+ mEventsDirty = true;
mEvents.clear();
final AtomicFile readFrom = mInjector.getFile();
if (readFrom != null && readFrom.exists()) {
@@ -344,13 +368,16 @@
out.startTag(null, TAG_EVENTS);
BrightnessChangeEvent[] toWrite = mEvents.toArray();
+ // Clear events, code below will add back the ones that are still within the time window.
+ mEvents.clear();
if (DEBUG) {
Slog.d(TAG, "Writing events " + toWrite.length);
}
- final long timeCutOff = System.currentTimeMillis() - MAX_EVENT_AGE;
+ final long timeCutOff = mInjector.currentTimeMillis() - MAX_EVENT_AGE;
for (int i = 0; i < toWrite.length; ++i) {
int userSerialNo = mInjector.getUserSerialNumber(mUserManager, toWrite[i].userId);
if (userSerialNo != -1 && toWrite[i].timeStamp > timeCutOff) {
+ mEvents.append(toWrite[i]);
out.startTag(null, TAG_EVENT);
out.attribute(null, ATTR_BRIGHTNESS, Integer.toString(toWrite[i].brightness));
out.attribute(null, ATTR_TIMESTAMP, Long.toString(toWrite[i].timeStamp));
@@ -465,6 +492,17 @@
}
}
+ public void dump(PrintWriter pw) {
+ synchronized (mEventsLock) {
+ pw.println("BrightnessTracker state:");
+ pw.println(" mEvents.size=" + mEvents.size());
+ pw.println(" mEventsDirty=" + mEventsDirty);
+ }
+ synchronized (mDataCollectionLock) {
+ pw.println(" mLastSensorReadings.size=" + mLastSensorReadings.size());
+ }
+ }
+
// Not allowed to keep the SensorEvent so used to copy the data we care about.
private static class LightData {
public float lux;
@@ -552,6 +590,11 @@
if (level != -1 && scale != 0) {
batteryLevelChanged(level, scale);
}
+ } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
+ mInjector.unregisterSensorListener(mContext, mSensorListener);
+ } else if (Intent.ACTION_SCREEN_ON.equals(action)) {
+ mInjector.registerSensorListener(mContext, mSensorListener,
+ mInjector.getBackgroundHandler());
}
}
}
@@ -559,11 +602,11 @@
@VisibleForTesting
static class Injector {
public void registerSensorListener(Context context,
- SensorEventListener sensorListener) {
+ SensorEventListener sensorListener, Handler handler) {
SensorManager sensorManager = context.getSystemService(SensorManager.class);
Sensor lightSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
sensorManager.registerListener(sensorListener,
- lightSensor, SensorManager.SENSOR_DELAY_NORMAL);
+ lightSensor, SensorManager.SENSOR_DELAY_NORMAL, handler);
}
public void unregisterSensorListener(Context context, SensorEventListener sensorListener) {
@@ -635,5 +678,17 @@
public ActivityManager.StackInfo getFocusedStack() throws RemoteException {
return ActivityManager.getService().getFocusedStackInfo();
}
+
+ public void scheduleIdleJob(Context context) {
+ BrightnessIdleJob.scheduleJob(context);
+ }
+
+ public void cancelIdleJob(Context context) {
+ BrightnessIdleJob.cancelJob(context);
+ }
+
+ public boolean isInteractive(Context context) {
+ return context.getSystemService(PowerManager.class).isInteractive();
+ }
}
}
diff --git a/com/android/server/display/DisplayManagerService.java b/com/android/server/display/DisplayManagerService.java
index f1e2011..379aaad 100644
--- a/com/android/server/display/DisplayManagerService.java
+++ b/com/android/server/display/DisplayManagerService.java
@@ -68,13 +68,13 @@
import android.view.Display;
import android.view.DisplayInfo;
import android.view.Surface;
-import android.view.WindowManagerInternal;
import com.android.server.AnimationThread;
import com.android.server.DisplayThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.UiThread;
+import com.android.server.wm.WindowManagerInternal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -1285,6 +1285,9 @@
pw.println();
mPersistentDataStore.dump(pw);
+
+ pw.println();
+ mBrightnessTracker.dump(pw);
}
}
@@ -1921,5 +1924,10 @@
public boolean isUidPresentOnDisplay(int uid, int displayId) {
return isUidPresentOnDisplayInternal(uid, displayId);
}
+
+ @Override
+ public void persistBrightnessSliderEvents() {
+ mBrightnessTracker.persistEvents();
+ }
}
}
diff --git a/com/android/server/display/DisplayPowerController.java b/com/android/server/display/DisplayPowerController.java
index 600bc42..29a007a 100644
--- a/com/android/server/display/DisplayPowerController.java
+++ b/com/android/server/display/DisplayPowerController.java
@@ -20,6 +20,7 @@
import com.android.internal.app.IBatteryStats;
import com.android.server.LocalServices;
import com.android.server.am.BatteryStatsService;
+import com.android.server.policy.WindowManagerPolicy;
import android.animation.Animator;
import android.animation.ObjectAnimator;
@@ -43,7 +44,6 @@
import android.util.Spline;
import android.util.TimeUtils;
import android.view.Display;
-import android.view.WindowManagerPolicy;
import java.io.PrintWriter;
diff --git a/com/android/server/input/InputManagerService.java b/com/android/server/input/InputManagerService.java
index fa9b107..4050790 100644
--- a/com/android/server/input/InputManagerService.java
+++ b/com/android/server/input/InputManagerService.java
@@ -33,6 +33,7 @@
import com.android.server.DisplayThread;
import com.android.server.LocalServices;
import com.android.server.Watchdog;
+import com.android.server.policy.WindowManagerPolicy;
import org.xmlpull.v1.XmlPullParser;
@@ -98,7 +99,6 @@
import android.view.PointerIcon;
import android.view.Surface;
import android.view.ViewConfiguration;
-import android.view.WindowManagerPolicy;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
diff --git a/com/android/server/job/JobSchedulerInternal.java b/com/android/server/job/JobSchedulerInternal.java
index 095526d..9bcf208 100644
--- a/com/android/server/job/JobSchedulerInternal.java
+++ b/com/android/server/job/JobSchedulerInternal.java
@@ -26,6 +26,18 @@
*/
public interface JobSchedulerInternal {
+ // Bookkeeping about app standby bucket scheduling
+
+ /**
+ * The current bucket heartbeat ordinal
+ */
+ long currentHeartbeat();
+
+ /**
+ * Heartbeat ordinal at which the given standby bucket's jobs next become runnable
+ */
+ long nextHeartbeatForBucket(int bucket);
+
/**
* Returns a list of pending jobs scheduled by the system service.
*/
diff --git a/com/android/server/job/JobSchedulerService.java b/com/android/server/job/JobSchedulerService.java
index b549768..4af86a0 100644
--- a/com/android/server/job/JobSchedulerService.java
+++ b/com/android/server/job/JobSchedulerService.java
@@ -29,6 +29,9 @@
import android.app.job.JobScheduler;
import android.app.job.JobService;
import android.app.job.JobWorkItem;
+import android.app.usage.AppStandby;
+import android.app.usage.UsageStatsManagerInternal;
+import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -37,6 +40,7 @@
import android.content.IntentFilter;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ServiceInfo;
import android.database.ContentObserver;
@@ -46,7 +50,6 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
-import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
@@ -65,6 +68,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
import com.android.internal.app.procstats.ProcessStats;
+import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.server.DeviceIdleController;
@@ -111,6 +115,7 @@
implements StateChangedListener, JobCompletedListener {
static final String TAG = "JobSchedulerService";
public static final boolean DEBUG = false;
+ public static final boolean DEBUG_STANDBY = DEBUG || false;
/** The maximum number of concurrent jobs we run at one time. */
private static final int MAX_JOB_CONTEXTS_COUNT = 16;
@@ -130,6 +135,8 @@
final Object mLock = new Object();
/** Master list of jobs. */
final JobStore mJobs;
+ /** Tracking the standby bucket state of each app */
+ final StandbyTracker mStandbyTracker;
/** Tracking amount of time each package runs for. */
final JobPackageTracker mJobPackageTracker = new JobPackageTracker();
@@ -151,6 +158,7 @@
StorageController mStorageController;
/** Need directly for sending uid state changes */
private BackgroundJobsController mBackgroundJobsController;
+ private DeviceIdleJobsController mDeviceIdleJobsController;
/**
* Queue of pending jobs. The JobServiceContext class will receive jobs from this list
* when ready to execute them.
@@ -162,8 +170,8 @@
final JobHandler mHandler;
final JobSchedulerStub mJobSchedulerStub;
+ PackageManagerInternal mLocalPM;
IBatteryStats mBatteryStats;
- PowerManager mPowerManager;
DeviceIdleController.LocalService mLocalDeviceIdleController;
/**
@@ -192,6 +200,17 @@
*/
final SparseIntArray mBackingUpUids = new SparseIntArray();
+ /**
+ * Count standby heartbeats, and keep track of which beat each bucket's jobs will
+ * next become runnable. Index into this array is by normalized bucket:
+ * { ACTIVE, WORKING, FREQUENT, RARE, NEVER }. The ACTIVE and NEVER bucket
+ * milestones are not updated: ACTIVE apps get jobs whenever they ask for them,
+ * and NEVER apps don't get them at all.
+ */
+ final long[] mNextBucketHeartbeat = { 0, 0, 0, 0, Long.MAX_VALUE };
+ long mHeartbeat = 0;
+ long mLastHeartbeatTime = 0;
+
// -- Pre-allocated temporaries only for use in assignJobsToContextsLocked --
/**
@@ -236,6 +255,10 @@
private static final String KEY_MAX_WORK_RESCHEDULE_COUNT = "max_work_reschedule_count";
private static final String KEY_MIN_LINEAR_BACKOFF_TIME = "min_linear_backoff_time";
private static final String KEY_MIN_EXP_BACKOFF_TIME = "min_exp_backoff_time";
+ private static final String KEY_STANDBY_HEARTBEAT_TIME = "standby_heartbeat_time";
+ private static final String KEY_STANDBY_WORKING_BEATS = "standby_working_beats";
+ private static final String KEY_STANDBY_FREQUENT_BEATS = "standby_frequent_beats";
+ private static final String KEY_STANDBY_RARE_BEATS = "standby_rare_beats";
private static final int DEFAULT_MIN_IDLE_COUNT = 1;
private static final int DEFAULT_MIN_CHARGING_COUNT = 1;
@@ -255,6 +278,10 @@
private static final int DEFAULT_MAX_WORK_RESCHEDULE_COUNT = Integer.MAX_VALUE;
private static final long DEFAULT_MIN_LINEAR_BACKOFF_TIME = JobInfo.MIN_BACKOFF_MILLIS;
private static final long DEFAULT_MIN_EXP_BACKOFF_TIME = JobInfo.MIN_BACKOFF_MILLIS;
+ private static final long DEFAULT_STANDBY_HEARTBEAT_TIME = 11 * 60 * 1000L;
+ private static final int DEFAULT_STANDBY_WORKING_BEATS = 5; // ~ 1 hour, with 11-min beats
+ private static final int DEFAULT_STANDBY_FREQUENT_BEATS = 31; // ~ 6 hours
+ private static final int DEFAULT_STANDBY_RARE_BEATS = 130; // ~ 24 hours
/**
* Minimum # of idle jobs that must be ready in order to force the JMS to schedule things
@@ -343,6 +370,27 @@
* The minimum backoff time to allow for exponential backoff.
*/
long MIN_EXP_BACKOFF_TIME = DEFAULT_MIN_EXP_BACKOFF_TIME;
+ /**
+ * How often we recalculate runnability based on apps' standby bucket assignment.
+ * This should be prime relative to common time interval lengths such as a quarter-
+ * hour or day, so that the heartbeat drifts relative to wall-clock milestones.
+ */
+ long STANDBY_HEARTBEAT_TIME = DEFAULT_STANDBY_HEARTBEAT_TIME;
+
+ /**
+ * Mapping: standby bucket -> number of heartbeats between each sweep of that
+ * bucket's jobs.
+ *
+ * Bucket assignments as recorded in the JobStatus objects are normalized to be
+ * indices into this array, rather than the raw constants used
+ * by AppIdleHistory.
+ */
+ final int[] STANDBY_BEATS = {
+ 0,
+ DEFAULT_STANDBY_WORKING_BEATS,
+ DEFAULT_STANDBY_FREQUENT_BEATS,
+ DEFAULT_STANDBY_RARE_BEATS
+ };
private ContentResolver mResolver;
private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -422,6 +470,14 @@
DEFAULT_MIN_LINEAR_BACKOFF_TIME);
MIN_EXP_BACKOFF_TIME = mParser.getLong(KEY_MIN_EXP_BACKOFF_TIME,
DEFAULT_MIN_EXP_BACKOFF_TIME);
+ STANDBY_HEARTBEAT_TIME = mParser.getLong(KEY_STANDBY_HEARTBEAT_TIME,
+ DEFAULT_STANDBY_HEARTBEAT_TIME);
+ STANDBY_BEATS[1] = mParser.getInt(KEY_STANDBY_WORKING_BEATS,
+ DEFAULT_STANDBY_WORKING_BEATS);
+ STANDBY_BEATS[2] = mParser.getInt(KEY_STANDBY_FREQUENT_BEATS,
+ DEFAULT_STANDBY_FREQUENT_BEATS);
+ STANDBY_BEATS[3] = mParser.getInt(KEY_STANDBY_RARE_BEATS,
+ DEFAULT_STANDBY_RARE_BEATS);
}
}
@@ -481,6 +537,17 @@
pw.print(" "); pw.print(KEY_MIN_EXP_BACKOFF_TIME); pw.print("=");
pw.print(MIN_EXP_BACKOFF_TIME); pw.println();
+
+ pw.print(" "); pw.print(KEY_STANDBY_HEARTBEAT_TIME); pw.print("=");
+ pw.print(STANDBY_HEARTBEAT_TIME); pw.println();
+
+ pw.print(" standby_beats={");
+ pw.print(STANDBY_BEATS[0]);
+ for (int i = 1; i < STANDBY_BEATS.length; i++) {
+ pw.print(", ");
+ pw.print(STANDBY_BEATS[i]);
+ }
+ pw.println('}');
}
}
@@ -623,13 +690,13 @@
cancelJobsForUid(uid, "uid gone");
}
synchronized (mLock) {
- mBackgroundJobsController.setUidActiveLocked(uid, false);
+ mDeviceIdleJobsController.setUidActiveLocked(uid, false);
}
}
@Override public void onUidActive(int uid) throws RemoteException {
synchronized (mLock) {
- mBackgroundJobsController.setUidActiveLocked(uid, true);
+ mDeviceIdleJobsController.setUidActiveLocked(uid, true);
}
}
@@ -638,7 +705,7 @@
cancelJobsForUid(uid, "app uid idle");
}
synchronized (mLock) {
- mBackgroundJobsController.setUidActiveLocked(uid, false);
+ mDeviceIdleJobsController.setUidActiveLocked(uid, false);
}
}
@@ -683,6 +750,7 @@
}
} catch (RemoteException e) {
}
+
synchronized (mLock) {
final JobStatus toCancel = mJobs.getJobByUidAndJobId(uId, job.getId());
@@ -934,9 +1002,22 @@
*/
public JobSchedulerService(Context context) {
super(context);
+
+ mLocalPM = LocalServices.getService(PackageManagerInternal.class);
+
mHandler = new JobHandler(context.getMainLooper());
mConstants = new Constants(mHandler);
mJobSchedulerStub = new JobSchedulerStub();
+
+ // Set up the app standby bucketing tracker
+ UsageStatsManagerInternal usageStats = LocalServices.getService(UsageStatsManagerInternal.class);
+ mStandbyTracker = new StandbyTracker(usageStats);
+ usageStats.addAppIdleStateChangeListener(mStandbyTracker);
+
+ // The job store needs to call back
+ publishLocalService(JobSchedulerInternal.class, new LocalService());
+
+ // Initialize the job store and set up any persisted jobs
mJobs = JobStore.initAndGet(this);
// Create the controllers.
@@ -948,11 +1029,11 @@
mControllers.add(mBatteryController);
mStorageController = StorageController.get(this);
mControllers.add(mStorageController);
- mBackgroundJobsController = BackgroundJobsController.get(this);
- mControllers.add(mBackgroundJobsController);
+ mControllers.add(BackgroundJobsController.get(this));
mControllers.add(AppIdleController.get(this));
mControllers.add(ContentObserverController.get(this));
- mControllers.add(DeviceIdleJobsController.get(this));
+ mDeviceIdleJobsController = DeviceIdleJobsController.get(this);
+ mControllers.add(mDeviceIdleJobsController);
// If the job store determined that it can't yet reschedule persisted jobs,
// we need to start watching the clock.
@@ -1006,7 +1087,6 @@
@Override
public void onStart() {
- publishLocalService(JobSchedulerInternal.class, new LocalService());
publishBinderService(Context.JOB_SCHEDULER_SERVICE, mJobSchedulerStub);
}
@@ -1026,7 +1106,6 @@
final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
getContext().registerReceiverAsUser(
mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
- mPowerManager = (PowerManager)getContext().getSystemService(Context.POWER_SERVICE);
try {
ActivityManager.getService().registerUidObserver(mUidObserver,
ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE
@@ -1206,7 +1285,8 @@
}
delayMillis =
Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS);
- JobStatus newJob = new JobStatus(failureToReschedule, elapsedNowMillis + delayMillis,
+ JobStatus newJob = new JobStatus(failureToReschedule, getCurrentHeartbeat(),
+ elapsedNowMillis + delayMillis,
JobStatus.NO_LATEST_RUNTIME, backoffAttempts,
failureToReschedule.getLastSuccessfulRunTime(), sSystemClock.millis());
for (int ic=0; ic<mControllers.size(); ic++) {
@@ -1220,10 +1300,12 @@
* Called after a periodic has executed so we can reschedule it. We take the last execution
* time of the job to be the time of completion (i.e. the time at which this function is
* called).
- * This could be inaccurate b/c the job can run for as long as
+ * <p>This could be inaccurate b/c the job can run for as long as
* {@link com.android.server.job.JobServiceContext#EXECUTING_TIMESLICE_MILLIS}, but will lead
* to underscheduling at least, rather than if we had taken the last execution time to be the
* start of the execution.
+ * <p>Unlike a reschedule prior to execution, in this case we advance the next-heartbeat
+ * tracking as though the job were newly-scheduled.
* @return A new job representing the execution criteria for this instantiation of the
* recurring job.
*/
@@ -1245,8 +1327,9 @@
Slog.v(TAG, "Rescheduling executed periodic. New execution window [" +
newEarliestRunTimeElapsed/1000 + ", " + newLatestRuntimeElapsed/1000 + "]s");
}
- return new JobStatus(periodicToReschedule, newEarliestRunTimeElapsed,
- newLatestRuntimeElapsed, 0 /* backoffAttempt */,
+ return new JobStatus(periodicToReschedule, getCurrentHeartbeat(),
+ newEarliestRunTimeElapsed, newLatestRuntimeElapsed,
+ 0 /* backoffAttempt */,
sSystemClock.millis() /* lastSuccessfulRunTime */,
periodicToReschedule.getLastFailedRunTime());
}
@@ -1393,7 +1476,9 @@
noteJobsNonpending(mPendingJobs);
mPendingJobs.clear();
stopNonReadyActiveJobsLocked();
+ boolean updated = updateStandbyHeartbeatLocked();
mJobs.forEachJob(mReadyQueueFunctor);
+ if (updated) updateNextStandbyHeartbeatsLocked();
mReadyQueueFunctor.postProcess();
if (DEBUG) {
@@ -1547,16 +1632,45 @@
noteJobsNonpending(mPendingJobs);
mPendingJobs.clear();
stopNonReadyActiveJobsLocked();
+ boolean updated = updateStandbyHeartbeatLocked();
mJobs.forEachJob(mMaybeQueueFunctor);
+ if (updated) updateNextStandbyHeartbeatsLocked();
mMaybeQueueFunctor.postProcess();
}
+ private boolean updateStandbyHeartbeatLocked() {
+ final long sinceLast = sElapsedRealtimeClock.millis() - mLastHeartbeatTime;
+ final long beatsElapsed = sinceLast / mConstants.STANDBY_HEARTBEAT_TIME;
+ if (beatsElapsed > 0) {
+ mHeartbeat += beatsElapsed;
+ mLastHeartbeatTime += beatsElapsed * mConstants.STANDBY_HEARTBEAT_TIME;
+ if (DEBUG_STANDBY) {
+ Slog.v(TAG, "Advancing standby heartbeat by " + beatsElapsed + " to " + mHeartbeat);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private void updateNextStandbyHeartbeatsLocked() {
+ // don't update ACTIVE or NEVER bucket milestones
+ for (int i = 1; i < mNextBucketHeartbeat.length - 1; i++) {
+ while (mHeartbeat >= mNextBucketHeartbeat[i]) {
+ mNextBucketHeartbeat[i] += mConstants.STANDBY_BEATS[i];
+ }
+ if (DEBUG_STANDBY) {
+ Slog.v(TAG, " Bucket " + i + " next heartbeat " + mNextBucketHeartbeat[i]);
+ }
+ }
+ }
+
/**
* Criteria for moving a job into the pending queue:
* - It's ready.
* - It's not pending.
* - It's not already running on a JSC.
* - The user that requested the job is running.
+ * - The job's standby bucket has come due to be runnable.
* - The component is enabled and runnable.
*/
private boolean isReadyToBeExecutedLocked(JobStatus job) {
@@ -1605,6 +1719,22 @@
return false;
}
+ // If the app is in a non-active standby bucket, make sure we've waited
+ // an appropriate amount of time since the last invocation
+ if (mHeartbeat < mNextBucketHeartbeat[job.getStandbyBucket()]) {
+ // TODO: log/trace that we're deferring the job due to bucketing if we hit this
+ if (job.getWhenStandbyDeferred() == 0) {
+ if (DEBUG_STANDBY) {
+ Slog.v(TAG, "Bucket deferral: " + mHeartbeat + " < "
+ + mNextBucketHeartbeat[job.getStandbyBucket()] + " for " + job);
+ }
+ job.setWhenStandbyDeferred(sElapsedRealtimeClock.millis());
+ }
+ return false;
+ }
+
+ // The expensive check last: validate that the defined package+service is
+ // still present & viable.
final boolean componentPresent;
try {
componentPresent = (AppGlobals.getPackageManager().getServiceInfo(
@@ -1818,6 +1948,22 @@
final class LocalService implements JobSchedulerInternal {
/**
+ * The current bucket heartbeat ordinal
+ */
+ public long currentHeartbeat() {
+ return getCurrentHeartbeat();
+ }
+
+ /**
+ * Heartbeat ordinal at which the given standby bucket's jobs next become runnable
+ */
+ public long nextHeartbeatForBucket(int bucket) {
+ synchronized (mLock) {
+ return mNextBucketHeartbeat[bucket];
+ }
+ }
+
+ /**
* Returns a list of all pending jobs. A running job is not considered pending. Periodic
* jobs are always considered pending.
*/
@@ -1878,6 +2024,79 @@
}
/**
+ * Tracking of app assignments to standby buckets
+ */
+ final class StandbyTracker extends AppIdleStateChangeListener {
+ final UsageStatsManagerInternal mUsageStats;
+
+ StandbyTracker(UsageStatsManagerInternal usageStats) {
+ mUsageStats = usageStats;
+ }
+
+ // AppIdleStateChangeListener interface for live updates
+
+ @Override
+ public void onAppIdleStateChanged(final String packageName, final int userId,
+ boolean idle, int bucket) {
+ final int uid = mLocalPM.getPackageUid(packageName,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
+ if (uid < 0) {
+ if (DEBUG_STANDBY) {
+ Slog.i(TAG, "App idle state change for unknown app "
+ + packageName + "/" + userId);
+ }
+ return;
+ }
+
+ final int bucketIndex = standbyBucketToBucketIndex(bucket);
+ // update job bookkeeping out of band
+ BackgroundThread.getHandler().post(() -> {
+ if (DEBUG_STANDBY) {
+ Slog.i(TAG, "Moving uid " + uid + " to bucketIndex " + bucketIndex);
+ }
+ synchronized (mLock) {
+ // TODO: update to be more efficient once we can slice by source UID
+ mJobs.forEachJob((JobStatus job) -> {
+ if (job.getSourceUid() == uid) {
+ job.setStandbyBucket(bucketIndex);
+ }
+ });
+ onControllerStateChanged();
+ }
+ });
+ }
+
+ @Override
+ public void onParoleStateChanged(boolean isParoleOn) {
+ // Unused
+ }
+ }
+
+ public static int standbyBucketToBucketIndex(int bucket) {
+ // Normalize AppStandby constants to indices into our bookkeeping
+ if (bucket == AppStandby.STANDBY_BUCKET_NEVER) return 4;
+ else if (bucket >= AppStandby.STANDBY_BUCKET_RARE) return 3;
+ else if (bucket >= AppStandby.STANDBY_BUCKET_FREQUENT) return 2;
+ else if (bucket >= AppStandby.STANDBY_BUCKET_WORKING_SET) return 1;
+ else return 0;
+ }
+
+ public static int standbyBucketForPackage(String packageName, int userId, long elapsedNow) {
+ UsageStatsManagerInternal usageStats = LocalServices.getService(
+ UsageStatsManagerInternal.class);
+ int bucket = usageStats != null
+ ? usageStats.getAppStandbyBucket(packageName, userId, elapsedNow)
+ : 0;
+
+ bucket = standbyBucketToBucketIndex(bucket);
+
+ if (DEBUG_STANDBY) {
+ Slog.v(TAG, packageName + "/" + userId + " standby bucket index: " + bucket);
+ }
+ return bucket;
+ }
+
+ /**
* Binder stub trampoline implementation
*/
final class JobSchedulerStub extends IJobScheduler.Stub {
@@ -1942,6 +2161,7 @@
}
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
+ final int userId = UserHandle.getUserId(uid);
enforceValidJobRequest(uid, job);
if (job.isPersisted()) {
@@ -1958,7 +2178,8 @@
long ident = Binder.clearCallingIdentity();
try {
- return JobSchedulerService.this.scheduleAsPackage(job, null, uid, null, -1, null);
+ return JobSchedulerService.this.scheduleAsPackage(job, null, uid, null, userId,
+ null);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -1970,8 +2191,8 @@
if (DEBUG) {
Slog.d(TAG, "Enqueueing job: " + job.toString() + " work: " + work);
}
- final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
+ final int userId = UserHandle.getUserId(uid);
enforceValidJobRequest(uid, job);
if (job.isPersisted()) {
@@ -1988,7 +2209,8 @@
long ident = Binder.clearCallingIdentity();
try {
- return JobSchedulerService.this.scheduleAsPackage(job, work, uid, null, -1, null);
+ return JobSchedulerService.this.scheduleAsPackage(job, work, uid, null, userId,
+ null);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -2000,7 +2222,7 @@
final int callerUid = Binder.getCallingUid();
if (DEBUG) {
Slog.d(TAG, "Caller uid " + callerUid + " scheduling job: " + job.toString()
- + " on behalf of " + packageName);
+ + " on behalf of " + packageName + "/");
}
if (packageName == null) {
@@ -2234,6 +2456,12 @@
}
}
+ long getCurrentHeartbeat() {
+ synchronized (mLock) {
+ return mHeartbeat;
+ }
+ }
+
int getJobState(PrintWriter pw, String pkgName, int userId, int jobId) {
try {
final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0,
diff --git a/com/android/server/job/JobServiceContext.java b/com/android/server/job/JobServiceContext.java
index ac7297e..6a3fd04 100644
--- a/com/android/server/job/JobServiceContext.java
+++ b/com/android/server/job/JobServiceContext.java
@@ -64,6 +64,8 @@
*/
public final class JobServiceContext implements ServiceConnection {
private static final boolean DEBUG = JobSchedulerService.DEBUG;
+ private static final boolean DEBUG_STANDBY = JobSchedulerService.DEBUG_STANDBY;
+
private static final String TAG = "JobServiceContext";
/** Amount of time a job is allowed to execute for before being considered timed-out. */
public static final long EXECUTING_TIMESLICE_MILLIS = 10 * 60 * 1000; // 10mins.
@@ -220,6 +222,17 @@
isDeadlineExpired, triggeredUris, triggeredAuthorities, job.network);
mExecutionStartTimeElapsed = sElapsedRealtimeClock.millis();
+ if (DEBUG_STANDBY) {
+ final long whenDeferred = job.getWhenStandbyDeferred();
+ if (whenDeferred > 0) {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("Starting job deferred for standby by ");
+ TimeUtils.formatDuration(mExecutionStartTimeElapsed - whenDeferred, sb);
+ sb.append(" : ");
+ sb.append(job.toShortString());
+ Slog.v(TAG, sb.toString());
+ }
+ }
// Once we'e begun executing a job, we by definition no longer care whether
// it was inflated from disk with not-yet-coherent delay/deadline bounds.
job.clearPersistedUtcTimes();
diff --git a/com/android/server/job/JobStore.java b/com/android/server/job/JobStore.java
index 1af3b39..219bc61 100644
--- a/com/android/server/job/JobStore.java
+++ b/com/android/server/job/JobStore.java
@@ -43,6 +43,7 @@
import com.android.internal.util.BitUtils;
import com.android.internal.util.FastXmlSerializer;
import com.android.server.IoThread;
+import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerInternal.JobStorePersistStats;
import com.android.server.job.controllers.JobStatus;
@@ -174,7 +175,8 @@
if (utcTimes != null) {
Pair<Long, Long> elapsedRuntimes =
convertRtcBoundsToElapsed(utcTimes, elapsedNow);
- toAdd.add(new JobStatus(job, elapsedRuntimes.first, elapsedRuntimes.second,
+ toAdd.add(new JobStatus(job, job.getBaseHeartbeat(),
+ elapsedRuntimes.first, elapsedRuntimes.second,
0, job.getLastSuccessfulRunTime(), job.getLastFailedRunTime()));
toRemove.add(job);
}
@@ -250,7 +252,7 @@
/**
* @param userHandle User for whom we are querying the list of jobs.
- * @return A list of all the jobs scheduled by the provided user. Never null.
+ * @return A list of all the jobs scheduled for the provided user. Never null.
*/
public List<JobStatus> getJobsByUser(int userHandle) {
return mJobSet.getJobsByUser(userHandle);
@@ -287,6 +289,10 @@
mJobSet.forEachJob(uid, functor);
}
+ public void forEachJobForSourceUid(int sourceUid, JobStatusFunctor functor) {
+ mJobSet.forEachJobForSourceUid(sourceUid, functor);
+ }
+
public interface JobStatusFunctor {
public void process(JobStatus jobStatus);
}
@@ -842,8 +848,13 @@
}
// And now we're done
+ JobSchedulerInternal service = LocalServices.getService(JobSchedulerInternal.class);
+ final int appBucket = JobSchedulerService.standbyBucketForPackage(sourcePackageName,
+ sourceUserId, elapsedNow);
+ long currentHeartbeat = service != null ? service.currentHeartbeat() : 0;
JobStatus js = new JobStatus(
- jobBuilder.build(), uid, sourcePackageName, sourceUserId, sourceTag,
+ jobBuilder.build(), uid, sourcePackageName, sourceUserId,
+ appBucket, currentHeartbeat, sourceTag,
elapsedRuntimes.first, elapsedRuntimes.second,
lastSuccessfulRunTime, lastFailedRunTime,
(rtcIsGood) ? null : rtcRuntimes);
@@ -979,9 +990,12 @@
static final class JobSet {
// Key is the getUid() originator of the jobs in each sheaf
private SparseArray<ArraySet<JobStatus>> mJobs;
+ // Same data but with the key as getSourceUid() of the jobs in each sheaf
+ private SparseArray<ArraySet<JobStatus>> mJobsPerSourceUid;
public JobSet() {
mJobs = new SparseArray<ArraySet<JobStatus>>();
+ mJobsPerSourceUid = new SparseArray<>();
}
public List<JobStatus> getJobsByUid(int uid) {
@@ -995,10 +1009,10 @@
// By user, not by uid, so we need to traverse by key and check
public List<JobStatus> getJobsByUser(int userId) {
- ArrayList<JobStatus> result = new ArrayList<JobStatus>();
- for (int i = mJobs.size() - 1; i >= 0; i--) {
- if (UserHandle.getUserId(mJobs.keyAt(i)) == userId) {
- ArraySet<JobStatus> jobs = mJobs.valueAt(i);
+ final ArrayList<JobStatus> result = new ArrayList<JobStatus>();
+ for (int i = mJobsPerSourceUid.size() - 1; i >= 0; i--) {
+ if (UserHandle.getUserId(mJobsPerSourceUid.keyAt(i)) == userId) {
+ final ArraySet<JobStatus> jobs = mJobsPerSourceUid.valueAt(i);
if (jobs != null) {
result.addAll(jobs);
}
@@ -1009,32 +1023,60 @@
public boolean add(JobStatus job) {
final int uid = job.getUid();
+ final int sourceUid = job.getSourceUid();
ArraySet<JobStatus> jobs = mJobs.get(uid);
if (jobs == null) {
jobs = new ArraySet<JobStatus>();
mJobs.put(uid, jobs);
}
- return jobs.add(job);
+ ArraySet<JobStatus> jobsForSourceUid = mJobsPerSourceUid.get(sourceUid);
+ if (jobsForSourceUid == null) {
+ jobsForSourceUid = new ArraySet<>();
+ mJobsPerSourceUid.put(sourceUid, jobsForSourceUid);
+ }
+ return jobs.add(job) && jobsForSourceUid.add(job);
}
public boolean remove(JobStatus job) {
final int uid = job.getUid();
- ArraySet<JobStatus> jobs = mJobs.get(uid);
- boolean didRemove = (jobs != null) ? jobs.remove(job) : false;
- if (didRemove && jobs.size() == 0) {
- // no more jobs for this uid; let the now-empty set object be GC'd.
- mJobs.remove(uid);
+ final ArraySet<JobStatus> jobs = mJobs.get(uid);
+ final int sourceUid = job.getSourceUid();
+ final ArraySet<JobStatus> jobsForSourceUid = mJobsPerSourceUid.get(sourceUid);
+ boolean didRemove = jobs != null && jobs.remove(job) && jobsForSourceUid.remove(job);
+ if (didRemove) {
+ if (jobs.size() == 0) {
+ // no more jobs for this uid; let the now-empty set object be GC'd.
+ mJobs.remove(uid);
+ }
+ if (jobsForSourceUid.size() == 0) {
+ mJobsPerSourceUid.remove(sourceUid);
+ }
+ return true;
}
- return didRemove;
+ return false;
}
- // Remove the jobs all users not specified by the whitelist of user ids
+ /**
+ * Removes the jobs of all users not specified by the whitelist of user ids.
+ * The jobs scheduled by non existent users will not be removed if they were
+ */
public void removeJobsOfNonUsers(int[] whitelist) {
- for (int jobIndex = mJobs.size() - 1; jobIndex >= 0; jobIndex--) {
- int jobUserId = UserHandle.getUserId(mJobs.keyAt(jobIndex));
- // check if job's user id is not in the whitelist
+ for (int jobSetIndex = mJobsPerSourceUid.size() - 1; jobSetIndex >= 0; jobSetIndex--) {
+ final int jobUserId = UserHandle.getUserId(mJobsPerSourceUid.keyAt(jobSetIndex));
if (!ArrayUtils.contains(whitelist, jobUserId)) {
- mJobs.removeAt(jobIndex);
+ mJobsPerSourceUid.removeAt(jobSetIndex);
+ }
+ }
+ for (int jobSetIndex = mJobs.size() - 1; jobSetIndex >= 0; jobSetIndex--) {
+ final ArraySet<JobStatus> jobsForUid = mJobs.valueAt(jobSetIndex);
+ for (int jobIndex = jobsForUid.size() - 1; jobIndex >= 0; jobIndex--) {
+ final int jobUserId = jobsForUid.valueAt(jobIndex).getUserId();
+ if (!ArrayUtils.contains(whitelist, jobUserId)) {
+ jobsForUid.removeAt(jobIndex);
+ }
+ }
+ if (jobsForUid.size() == 0) {
+ mJobs.removeAt(jobSetIndex);
}
}
}
@@ -1077,6 +1119,7 @@
public void clear() {
mJobs.clear();
+ mJobsPerSourceUid.clear();
}
public int size() {
@@ -1112,8 +1155,17 @@
}
}
- public void forEachJob(int uid, JobStatusFunctor functor) {
- ArraySet<JobStatus> jobs = mJobs.get(uid);
+ public void forEachJob(int callingUid, JobStatusFunctor functor) {
+ ArraySet<JobStatus> jobs = mJobs.get(callingUid);
+ if (jobs != null) {
+ for (int i = jobs.size() - 1; i >= 0; i--) {
+ functor.process(jobs.valueAt(i));
+ }
+ }
+ }
+
+ public void forEachJobForSourceUid(int sourceUid, JobStatusFunctor functor) {
+ final ArraySet<JobStatus> jobs = mJobsPerSourceUid.get(sourceUid);
if (jobs != null) {
for (int i = jobs.size() - 1; i >= 0; i--) {
functor.process(jobs.valueAt(i));
diff --git a/com/android/server/job/controllers/AppIdleController.java b/com/android/server/job/controllers/AppIdleController.java
index caa8522..a7ed2f5 100644
--- a/com/android/server/job/controllers/AppIdleController.java
+++ b/com/android/server/job/controllers/AppIdleController.java
@@ -180,6 +180,7 @@
if (mAppIdleParoleOn) {
return;
}
+
PackageUpdateFunc update = new PackageUpdateFunc(userId, packageName, idle);
mJobSchedulerService.getJobStore().forEachJob(update);
if (update.mChanged) {
diff --git a/com/android/server/job/controllers/BackgroundJobsController.java b/com/android/server/job/controllers/BackgroundJobsController.java
index 78b4160..fc4015d 100644
--- a/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/com/android/server/job/controllers/BackgroundJobsController.java
@@ -16,24 +16,16 @@
package com.android.server.job.controllers;
-import android.app.AppOpsManager;
-import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
import android.os.IDeviceIdleController;
-import android.os.PowerManager;
-import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemClock;
import android.os.UserHandle;
-import android.util.ArraySet;
import android.util.Slog;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-import com.android.internal.app.IAppOpsCallback;
-import com.android.internal.app.IAppOpsService;
import com.android.internal.util.ArrayUtils;
+import com.android.server.ForceAppStandbyTracker;
+import com.android.server.ForceAppStandbyTracker.Listener;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.JobStore;
@@ -49,18 +41,10 @@
private static volatile BackgroundJobsController sController;
private final JobSchedulerService mJobSchedulerService;
- private final IAppOpsService mAppOpsService;
private final IDeviceIdleController mDeviceIdleController;
- private final SparseBooleanArray mForegroundUids;
- private int[] mPowerWhitelistedUserAppIds;
- private int[] mTempWhitelistedAppIds;
- /**
- * Only tracks jobs for which source package app op RUN_ANY_IN_BACKGROUND is not ALLOWED.
- * Maps jobs to the sourceUid unlike the global {@link JobSchedulerService#mJobs JobStore}
- * which uses callingUid.
- */
- private SparseArray<ArraySet<JobStatus>> mTrackedJobs;
+ private final ForceAppStandbyTracker mForceAppStandbyTracker;
+
public static BackgroundJobsController get(JobSchedulerService service) {
synchronized (sCreationLock) {
@@ -72,232 +56,148 @@
}
}
- private BroadcastReceiver mDozeWhitelistReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- synchronized (mLock) {
- try {
- switch (intent.getAction()) {
- case PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED:
- mPowerWhitelistedUserAppIds =
- mDeviceIdleController.getAppIdUserWhitelist();
- break;
- case PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED:
- mTempWhitelistedAppIds = mDeviceIdleController.getAppIdTempWhitelist();
- break;
- }
- } catch (RemoteException rexc) {
- Slog.e(LOG_TAG, "Device idle controller not reachable");
- }
- if (checkAllTrackedJobsLocked()) {
- mStateChangedListener.onControllerStateChanged();
- }
- }
- }
- };
-
private BackgroundJobsController(JobSchedulerService service, Context context, Object lock) {
super(service, context, lock);
mJobSchedulerService = service;
- mAppOpsService = IAppOpsService.Stub.asInterface(
- ServiceManager.getService(Context.APP_OPS_SERVICE));
mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
- mForegroundUids = new SparseBooleanArray();
- mTrackedJobs = new SparseArray<>();
- try {
- mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, null,
- new AppOpsWatcher());
- mPowerWhitelistedUserAppIds = mDeviceIdleController.getAppIdUserWhitelist();
- mTempWhitelistedAppIds = mDeviceIdleController.getAppIdTempWhitelist();
- } catch (RemoteException rexc) {
- // Shouldn't happen as they are in the same process.
- Slog.e(LOG_TAG, "AppOps or DeviceIdle service not reachable", rexc);
- }
- IntentFilter powerWhitelistFilter = new IntentFilter();
- powerWhitelistFilter.addAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED);
- powerWhitelistFilter.addAction(PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED);
- context.registerReceiverAsUser(mDozeWhitelistReceiver, UserHandle.ALL, powerWhitelistFilter,
- null, null);
+ mForceAppStandbyTracker = ForceAppStandbyTracker.getInstance(context);
+
+ mForceAppStandbyTracker.addListener(mForceAppStandbyListener);
+ mForceAppStandbyTracker.start();
}
@Override
public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
- final int uid = jobStatus.getSourceUid();
- final String packageName = jobStatus.getSourcePackageName();
- try {
- final int mode = mAppOpsService.checkOperation(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND,
- uid, packageName);
- if (mode == AppOpsManager.MODE_ALLOWED) {
- jobStatus.setBackgroundNotRestrictedConstraintSatisfied(true);
- return;
- }
- } catch (RemoteException rexc) {
- Slog.e(LOG_TAG, "Cannot reach app ops service", rexc);
- }
- jobStatus.setBackgroundNotRestrictedConstraintSatisfied(canRunJobLocked(uid));
- startTrackingJobLocked(jobStatus);
+ updateSingleJobRestrictionLocked(jobStatus);
}
@Override
public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
boolean forUpdate) {
- stopTrackingJobLocked(jobStatus);
- }
-
- /* Called by JobSchedulerService to report uid state changes between active and idle */
- public void setUidActiveLocked(int uid, boolean active) {
- final boolean changed = (active != mForegroundUids.get(uid));
- if (!changed) {
- return;
- }
- if (DEBUG) {
- Slog.d(LOG_TAG, "uid " + uid + " going to " + (active ? "fg" : "bg"));
- }
- if (active) {
- mForegroundUids.put(uid, true);
- } else {
- mForegroundUids.delete(uid);
- }
- if (checkTrackedJobsForUidLocked(uid)) {
- mStateChangedListener.onControllerStateChanged();
- }
}
@Override
public void dumpControllerStateLocked(final PrintWriter pw, final int filterUid) {
pw.println("BackgroundJobsController");
- pw.print("Foreground uids: [");
- for (int i = 0; i < mForegroundUids.size(); i++) {
- if (mForegroundUids.valueAt(i)) pw.print(mForegroundUids.keyAt(i) + " ");
- }
- pw.println("]");
- mJobSchedulerService.getJobStore().forEachJob(new JobStore.JobStatusFunctor() {
- @Override
- public void process(JobStatus jobStatus) {
- if (!jobStatus.shouldDump(filterUid)) {
- return;
- }
- final int uid = jobStatus.getSourceUid();
- pw.print(" #");
- jobStatus.printUniqueId(pw);
- pw.print(" from ");
- UserHandle.formatUid(pw, uid);
- pw.print(mForegroundUids.get(uid) ? " foreground" : " background");
- if (isWhitelistedLocked(uid)) {
- pw.print(", whitelisted");
- }
- pw.print(": ");
- pw.print(jobStatus.getSourcePackageName());
- pw.print(" [background restrictions");
- final ArraySet<JobStatus> jobsForUid = mTrackedJobs.get(uid);
- pw.print(jobsForUid != null && jobsForUid.contains(jobStatus) ? " on]" : " off]");
- if ((jobStatus.satisfiedConstraints
- & JobStatus.CONSTRAINT_BACKGROUND_NOT_RESTRICTED) != 0) {
- pw.println(" RUNNABLE");
- } else {
- pw.println(" WAITING");
- }
+
+ mForceAppStandbyTracker.dump(pw, "");
+
+ pw.println("Job state:");
+ mJobSchedulerService.getJobStore().forEachJob((jobStatus) -> {
+ if (!jobStatus.shouldDump(filterUid)) {
+ return;
+ }
+ final int uid = jobStatus.getSourceUid();
+ pw.print(" #");
+ jobStatus.printUniqueId(pw);
+ pw.print(" from ");
+ UserHandle.formatUid(pw, uid);
+ pw.print(mForceAppStandbyTracker.isInForeground(uid) ? " foreground" : " background");
+ if (mForceAppStandbyTracker.isUidPowerSaveWhitelisted(uid) ||
+ mForceAppStandbyTracker.isUidTempPowerSaveWhitelisted(uid)) {
+ pw.print(", whitelisted");
+ }
+ pw.print(": ");
+ pw.print(jobStatus.getSourcePackageName());
+
+ pw.print(" [RUN_ANY_IN_BACKGROUND ");
+ pw.print(mForceAppStandbyTracker.isRunAnyInBackgroundAppOpsAllowed(
+ jobStatus.getSourceUid(), jobStatus.getSourcePackageName())
+ ? "allowed]" : "disallowed]");
+
+ if ((jobStatus.satisfiedConstraints
+ & JobStatus.CONSTRAINT_BACKGROUND_NOT_RESTRICTED) != 0) {
+ pw.println(" RUNNABLE");
+ } else {
+ pw.println(" WAITING");
}
});
}
- void startTrackingJobLocked(JobStatus jobStatus) {
+ private void updateAllJobRestrictionsLocked() {
+ updateJobRestrictionsLocked(/*filterUid=*/ -1);
+ }
+
+ private void updateJobRestrictionsForUidLocked(int uid) {
+
+ // TODO Use forEachJobForSourceUid() once we have it.
+
+ updateJobRestrictionsLocked(/*filterUid=*/ uid);
+ }
+
+ private void updateJobRestrictionsLocked(int filterUid) {
+ final UpdateJobFunctor updateTrackedJobs =
+ new UpdateJobFunctor(filterUid);
+
+ final long start = DEBUG ? SystemClock.elapsedRealtimeNanos() : 0;
+
+ mJobSchedulerService.getJobStore().forEachJob(updateTrackedJobs);
+
+ final long time = DEBUG ? (SystemClock.elapsedRealtimeNanos() - start) : 0;
+ if (DEBUG) {
+ Slog.d(LOG_TAG, String.format(
+ "Job status updated: %d/%d checked/total jobs, %d us",
+ updateTrackedJobs.mCheckedCount,
+ updateTrackedJobs.mTotalCount,
+ (time / 1000)
+ ));
+ }
+
+ if (updateTrackedJobs.mChanged) {
+ mStateChangedListener.onControllerStateChanged();
+ }
+ }
+
+ boolean updateSingleJobRestrictionLocked(JobStatus jobStatus) {
+
final int uid = jobStatus.getSourceUid();
- ArraySet<JobStatus> jobsForUid = mTrackedJobs.get(uid);
- if (jobsForUid == null) {
- jobsForUid = new ArraySet<>();
- mTrackedJobs.put(uid, jobsForUid);
- }
- jobsForUid.add(jobStatus);
+ final String packageName = jobStatus.getSourcePackageName();
+
+ final boolean canRun = !mForceAppStandbyTracker.areJobsRestricted(uid, packageName);
+
+ return jobStatus.setBackgroundNotRestrictedConstraintSatisfied(canRun);
}
- void stopTrackingJobLocked(JobStatus jobStatus) {
- final int uid = jobStatus.getSourceUid();
- ArraySet<JobStatus> jobsForUid = mTrackedJobs.get(uid);
- if (jobsForUid != null) {
- jobsForUid.remove(jobStatus);
- }
- }
+ private final class UpdateJobFunctor implements JobStore.JobStatusFunctor {
+ private final int mFilterUid;
- boolean checkAllTrackedJobsLocked() {
- boolean changed = false;
- for (int i = 0; i < mTrackedJobs.size(); i++) {
- changed |= checkTrackedJobsForUidLocked(mTrackedJobs.keyAt(i));
- }
- return changed;
- }
+ boolean mChanged = false;
+ int mTotalCount = 0;
+ int mCheckedCount = 0;
- private boolean checkTrackedJobsForUidLocked(int uid) {
- final ArraySet<JobStatus> jobsForUid = mTrackedJobs.get(uid);
- boolean changed = false;
- if (jobsForUid != null) {
- for (int i = 0; i < jobsForUid.size(); i++) {
- JobStatus jobStatus = jobsForUid.valueAt(i);
- changed |= jobStatus.setBackgroundNotRestrictedConstraintSatisfied(
- canRunJobLocked(uid));
- }
- }
- return changed;
- }
-
- boolean isWhitelistedLocked(int uid) {
- return ArrayUtils.contains(mTempWhitelistedAppIds, UserHandle.getAppId(uid))
- || ArrayUtils.contains(mPowerWhitelistedUserAppIds, UserHandle.getAppId(uid));
- }
-
- boolean canRunJobLocked(int uid) {
- return mForegroundUids.get(uid) || isWhitelistedLocked(uid);
- }
-
- private final class AppOpsWatcher extends IAppOpsCallback.Stub {
- @Override
- public void opChanged(int op, int uid, String packageName) throws RemoteException {
- synchronized (mLock) {
- final int mode = mAppOpsService.checkOperation(op, uid, packageName);
- if (DEBUG) {
- Slog.d(LOG_TAG,
- "Appop changed for " + uid + ", " + packageName + " to " + mode);
- }
- final boolean shouldTrack = (mode != AppOpsManager.MODE_ALLOWED);
- UpdateTrackedJobsFunc updateTrackedJobs = new UpdateTrackedJobsFunc(uid,
- packageName, shouldTrack);
- mJobSchedulerService.getJobStore().forEachJob(updateTrackedJobs);
- if (updateTrackedJobs.mChanged) {
- mStateChangedListener.onControllerStateChanged();
- }
- }
- }
- }
-
- private final class UpdateTrackedJobsFunc implements JobStore.JobStatusFunctor {
- private final String mPackageName;
- private final int mUid;
- private final boolean mShouldTrack;
- private boolean mChanged = false;
-
- UpdateTrackedJobsFunc(int uid, String packageName, boolean shouldTrack) {
- mUid = uid;
- mPackageName = packageName;
- mShouldTrack = shouldTrack;
+ UpdateJobFunctor(int filterUid) {
+ mFilterUid = filterUid;
}
@Override
public void process(JobStatus jobStatus) {
- final String packageName = jobStatus.getSourcePackageName();
- final int uid = jobStatus.getSourceUid();
- if (mUid != uid || !mPackageName.equals(packageName)) {
+ mTotalCount++;
+ if ((mFilterUid > 0) && (mFilterUid != jobStatus.getSourceUid())) {
return;
}
- if (mShouldTrack) {
- mChanged |= jobStatus.setBackgroundNotRestrictedConstraintSatisfied(
- canRunJobLocked(uid));
- startTrackingJobLocked(jobStatus);
- } else {
- mChanged |= jobStatus.setBackgroundNotRestrictedConstraintSatisfied(true);
- stopTrackingJobLocked(jobStatus);
+ mCheckedCount++;
+ if (updateSingleJobRestrictionLocked(jobStatus)) {
+ mChanged = true;
}
}
}
+
+ private final Listener mForceAppStandbyListener = new Listener() {
+ @Override
+ public void updateAllJobs() {
+ updateAllJobRestrictionsLocked();
+ }
+
+ @Override
+ public void updateJobsForUid(int uid) {
+ updateJobRestrictionsForUidLocked(uid);
+ }
+
+ @Override
+ public void updateJobsForUidPackage(int uid, String packageName) {
+ updateJobRestrictionsForUidLocked(uid);
+ }
+ };
}
diff --git a/com/android/server/job/controllers/DeviceIdleJobsController.java b/com/android/server/job/controllers/DeviceIdleJobsController.java
index 374ab43..b7eb9e0 100644
--- a/com/android/server/job/controllers/DeviceIdleJobsController.java
+++ b/com/android/server/job/controllers/DeviceIdleJobsController.java
@@ -16,14 +16,19 @@
package com.android.server.job.controllers;
+import android.app.job.JobInfo;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
import android.os.PowerManager;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.Slog;
+import android.util.SparseBooleanArray;
import com.android.internal.util.ArrayUtils;
import com.android.server.DeviceIdleController;
@@ -42,11 +47,22 @@
private static final String LOG_TAG = "DeviceIdleJobsController";
private static final boolean LOG_DEBUG = false;
+ private static final long BACKGROUND_JOBS_DELAY = 3000;
+
+ static final int PROCESS_BACKGROUND_JOBS = 1;
// Singleton factory
private static Object sCreationLock = new Object();
private static DeviceIdleJobsController sController;
+ /**
+ * These are jobs added with a special flag to indicate that they should be exempted from doze
+ * when the app is temp whitelisted or in the foreground.
+ */
+ private final ArraySet<JobStatus> mAllowInIdleJobs;
+ private final SparseBooleanArray mForegroundUids;
+ private final DeviceIdleUpdateFunctor mDeviceIdleUpdateFunctor;
+ private final DeviceIdleJobsDelayHandler mHandler;
private final JobSchedulerService mJobSchedulerService;
private final PowerManager mPowerManager;
private final DeviceIdleController.LocalService mLocalDeviceIdleController;
@@ -57,14 +73,6 @@
private boolean mDeviceIdleMode;
private int[] mDeviceIdleWhitelistAppIds;
private int[] mPowerSaveTempWhitelistAppIds;
- // These jobs were added when the app was in temp whitelist, these should be exempted from doze
- private final ArraySet<JobStatus> mTempWhitelistedJobs;
-
- final JobStore.JobStatusFunctor mUpdateFunctor = new JobStore.JobStatusFunctor() {
- @Override public void process(JobStatus jobStatus) {
- updateTaskStateLocked(jobStatus);
- }
- };
/**
* Returns a singleton for the DeviceIdleJobsController
@@ -108,8 +116,8 @@
+ Arrays.toString(mPowerSaveTempWhitelistAppIds));
}
boolean changed = false;
- for (int i = 0; i < mTempWhitelistedJobs.size(); i ++) {
- changed |= updateTaskStateLocked(mTempWhitelistedJobs.valueAt(i));
+ for (int i = 0; i < mAllowInIdleJobs.size(); i++) {
+ changed |= updateTaskStateLocked(mAllowInIdleJobs.valueAt(i));
}
if (changed) {
mStateChangedListener.onControllerStateChanged();
@@ -125,6 +133,7 @@
super(jobSchedulerService, context, lock);
mJobSchedulerService = jobSchedulerService;
+ mHandler = new DeviceIdleJobsDelayHandler(context.getMainLooper());
// Register for device idle mode changes
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mLocalDeviceIdleController =
@@ -132,7 +141,9 @@
mDeviceIdleWhitelistAppIds = mLocalDeviceIdleController.getPowerSaveWhitelistUserAppIds();
mPowerSaveTempWhitelistAppIds =
mLocalDeviceIdleController.getPowerSaveTempWhitelistAppIds();
- mTempWhitelistedJobs = new ArraySet<>();
+ mDeviceIdleUpdateFunctor = new DeviceIdleUpdateFunctor();
+ mAllowInIdleJobs = new ArraySet<>();
+ mForegroundUids = new SparseBooleanArray();
final IntentFilter filter = new IntentFilter();
filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
filter.addAction(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
@@ -150,7 +161,20 @@
}
mDeviceIdleMode = enabled;
if (LOG_DEBUG) Slog.d(LOG_TAG, "mDeviceIdleMode=" + mDeviceIdleMode);
- mJobSchedulerService.getJobStore().forEachJob(mUpdateFunctor);
+ if (enabled) {
+ mHandler.removeMessages(PROCESS_BACKGROUND_JOBS);
+ mJobSchedulerService.getJobStore().forEachJob(mDeviceIdleUpdateFunctor);
+ } else {
+ // When coming out of doze, process all foreground uids immediately, while others
+ // will be processed after a delay of 3 seconds.
+ for (int i = 0; i < mForegroundUids.size(); i++) {
+ if (mForegroundUids.valueAt(i)) {
+ mJobSchedulerService.getJobStore().forEachJobForSourceUid(
+ mForegroundUids.keyAt(i), mDeviceIdleUpdateFunctor);
+ }
+ }
+ mHandler.sendEmptyMessageDelayed(PROCESS_BACKGROUND_JOBS, BACKGROUND_JOBS_DELAY);
+ }
}
// Inform the job scheduler service about idle mode changes
if (changed) {
@@ -159,11 +183,30 @@
}
/**
+ * Called by jobscheduler service to report uid state changes between active and idle
+ */
+ public void setUidActiveLocked(int uid, boolean active) {
+ final boolean changed = (active != mForegroundUids.get(uid));
+ if (!changed) {
+ return;
+ }
+ if (LOG_DEBUG) {
+ Slog.d(LOG_TAG, "uid " + uid + " going " + (active ? "active" : "inactive"));
+ }
+ mForegroundUids.put(uid, active);
+ mDeviceIdleUpdateFunctor.mChanged = false;
+ mJobSchedulerService.getJobStore().forEachJobForSourceUid(uid, mDeviceIdleUpdateFunctor);
+ if (mDeviceIdleUpdateFunctor.mChanged) {
+ mStateChangedListener.onControllerStateChanged();
+ }
+ }
+
+ /**
* Checks if the given job's scheduling app id exists in the device idle user whitelist.
*/
boolean isWhitelistedLocked(JobStatus job) {
- return ArrayUtils.contains(mDeviceIdleWhitelistAppIds,
- UserHandle.getAppId(job.getSourceUid()));
+ return Arrays.binarySearch(mDeviceIdleWhitelistAppIds,
+ UserHandle.getAppId(job.getSourceUid())) >= 0;
}
/**
@@ -175,31 +218,33 @@
}
private boolean updateTaskStateLocked(JobStatus task) {
- final boolean whitelisted = isWhitelistedLocked(task)
- || (mTempWhitelistedJobs.contains(task) && isTempWhitelistedLocked(task));
- final boolean enableTask = !mDeviceIdleMode || whitelisted;
+ final boolean allowInIdle = ((task.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0)
+ && (mForegroundUids.get(task.getSourceUid()) || isTempWhitelistedLocked(task));
+ final boolean whitelisted = isWhitelistedLocked(task);
+ final boolean enableTask = !mDeviceIdleMode || whitelisted || allowInIdle;
return task.setDeviceNotDozingConstraintSatisfied(enableTask, whitelisted);
}
@Override
public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
- if (isTempWhitelistedLocked(jobStatus)) {
- mTempWhitelistedJobs.add(jobStatus);
- jobStatus.setDeviceNotDozingConstraintSatisfied(true, true);
- } else {
- updateTaskStateLocked(jobStatus);
+ if ((jobStatus.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) {
+ mAllowInIdleJobs.add(jobStatus);
}
+ updateTaskStateLocked(jobStatus);
}
@Override
public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
boolean forUpdate) {
- mTempWhitelistedJobs.remove(jobStatus);
+ if ((jobStatus.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) {
+ mAllowInIdleJobs.remove(jobStatus);
+ }
}
@Override
public void dumpControllerStateLocked(final PrintWriter pw, final int filterUid) {
pw.println("DeviceIdleJobsController");
+ pw.println("mDeviceIdleMode=" + mDeviceIdleMode);
mJobSchedulerService.getJobStore().forEachJob(new JobStore.JobStatusFunctor() {
@Override public void process(JobStatus jobStatus) {
if (!jobStatus.shouldDump(filterUid)) {
@@ -217,8 +262,42 @@
if (jobStatus.dozeWhitelisted) {
pw.print(" WHITELISTED");
}
+ if (mAllowInIdleJobs.contains(jobStatus)) {
+ pw.print(" ALLOWED_IN_DOZE");
+ }
pw.println();
}
});
}
+
+ final class DeviceIdleUpdateFunctor implements JobStore.JobStatusFunctor {
+ boolean mChanged;
+
+ @Override
+ public void process(JobStatus jobStatus) {
+ mChanged |= updateTaskStateLocked(jobStatus);
+ }
+ }
+
+ final class DeviceIdleJobsDelayHandler extends Handler {
+ public DeviceIdleJobsDelayHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case PROCESS_BACKGROUND_JOBS:
+ // Just process all the jobs, the ones in foreground should already be running.
+ synchronized (mLock) {
+ mDeviceIdleUpdateFunctor.mChanged = false;
+ mJobSchedulerService.getJobStore().forEachJob(mDeviceIdleUpdateFunctor);
+ if (mDeviceIdleUpdateFunctor.mChanged) {
+ mStateChangedListener.onControllerStateChanged();
+ }
+ }
+ break;
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/com/android/server/job/controllers/JobStatus.java b/com/android/server/job/controllers/JobStatus.java
index a5906cb..e71b8ec 100644
--- a/com/android/server/job/controllers/JobStatus.java
+++ b/com/android/server/job/controllers/JobStatus.java
@@ -34,7 +34,9 @@
import android.util.Slog;
import android.util.TimeUtils;
+import com.android.server.LocalServices;
import com.android.server.job.GrantedUriPermissions;
+import com.android.server.job.JobSchedulerInternal;
import com.android.server.job.JobSchedulerService;
import java.io.PrintWriter;
@@ -120,6 +122,24 @@
/** How many times this job has failed, used to compute back-off. */
private final int numFailures;
+ /**
+ * Current standby heartbeat when this job was scheduled or last ran. Used to
+ * pin the runnability check regardless of the job's app moving between buckets.
+ */
+ private final long baseHeartbeat;
+
+ /**
+ * Which app standby bucket this job's app is in. Updated when the app is moved to a
+ * different bucket.
+ */
+ private int standbyBucket;
+
+ /**
+ * Debugging: timestamp if we ever defer this job based on standby bucketing, this
+ * is when we did so.
+ */
+ private long whenStandbyDeferred;
+
// Constraints.
final int requiredConstraints;
int satisfiedConstraints = 0;
@@ -221,10 +241,13 @@
}
private JobStatus(JobInfo job, int callingUid, String sourcePackageName,
- int sourceUserId, String tag, int numFailures, long earliestRunTimeElapsedMillis,
- long latestRunTimeElapsedMillis, long lastSuccessfulRunTime, long lastFailedRunTime) {
+ int sourceUserId, int standbyBucket, long heartbeat, String tag, int numFailures,
+ long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
+ long lastSuccessfulRunTime, long lastFailedRunTime) {
this.job = job;
this.callingUid = callingUid;
+ this.standbyBucket = standbyBucket;
+ this.baseHeartbeat = heartbeat;
int tempSourceUid = -1;
if (sourceUserId != -1 && sourcePackageName != null) {
@@ -283,6 +306,7 @@
public JobStatus(JobStatus jobStatus) {
this(jobStatus.getJob(), jobStatus.getUid(),
jobStatus.getSourcePackageName(), jobStatus.getSourceUserId(),
+ jobStatus.getStandbyBucket(), jobStatus.getBaseHeartbeat(),
jobStatus.getSourceTag(), jobStatus.getNumFailures(),
jobStatus.getEarliestRunTime(), jobStatus.getLatestRunTimeElapsed(),
jobStatus.getLastSuccessfulRunTime(), jobStatus.getLastFailedRunTime());
@@ -299,13 +323,17 @@
* {@link android.app.job.JobInfo} time criteria because we can load a persisted periodic job
* from the {@link com.android.server.job.JobStore} and still want to respect its
* wallclock runtime rather than resetting it on every boot.
- * We consider a freshly loaded job to no longer be in back-off.
+ * We consider a freshly loaded job to no longer be in back-off, and the associated
+ * standby bucket is whatever the OS thinks it should be at this moment.
*/
- public JobStatus(JobInfo job, int callingUid, String sourcePackageName, int sourceUserId,
- String sourceTag, long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
+ public JobStatus(JobInfo job, int callingUid, String sourcePkgName, int sourceUserId,
+ int standbyBucket, long baseHeartbeat, String sourceTag,
+ long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
long lastSuccessfulRunTime, long lastFailedRunTime,
Pair<Long, Long> persistedExecutionTimesUTC) {
- this(job, callingUid, sourcePackageName, sourceUserId, sourceTag, 0,
+ this(job, callingUid, sourcePkgName, sourceUserId,
+ standbyBucket, baseHeartbeat,
+ sourceTag, 0,
earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
lastSuccessfulRunTime, lastFailedRunTime);
@@ -322,11 +350,13 @@
}
/** Create a new job to be rescheduled with the provided parameters. */
- public JobStatus(JobStatus rescheduling, long newEarliestRuntimeElapsedMillis,
+ public JobStatus(JobStatus rescheduling, long newBaseHeartbeat,
+ long newEarliestRuntimeElapsedMillis,
long newLatestRuntimeElapsedMillis, int backoffAttempt,
long lastSuccessfulRunTime, long lastFailedRunTime) {
this(rescheduling.job, rescheduling.getUid(),
rescheduling.getSourcePackageName(), rescheduling.getSourceUserId(),
+ rescheduling.getStandbyBucket(), newBaseHeartbeat,
rescheduling.getSourceTag(), backoffAttempt, newEarliestRuntimeElapsedMillis,
newLatestRuntimeElapsedMillis,
lastSuccessfulRunTime, lastFailedRunTime);
@@ -335,11 +365,12 @@
/**
* Create a newly scheduled job.
* @param callingUid Uid of the package that scheduled this job.
- * @param sourcePackageName Package name on whose behalf this job is scheduled. Null indicates
+ * @param sourcePkg Package name on whose behalf this job is scheduled. Null indicates
* the calling package is the source.
* @param sourceUserId User id for whom this job is scheduled. -1 indicates this is same as the
+ * caller.
*/
- public static JobStatus createFromJobInfo(JobInfo job, int callingUid, String sourcePackageName,
+ public static JobStatus createFromJobInfo(JobInfo job, int callingUid, String sourcePkg,
int sourceUserId, String tag) {
final long elapsedNow = sElapsedRealtimeClock.millis();
final long earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis;
@@ -352,7 +383,14 @@
latestRunTimeElapsedMillis = job.hasLateConstraint() ?
elapsedNow + job.getMaxExecutionDelayMillis() : NO_LATEST_RUNTIME;
}
- return new JobStatus(job, callingUid, sourcePackageName, sourceUserId, tag, 0,
+ String jobPackage = (sourcePkg != null) ? sourcePkg : job.getService().getPackageName();
+
+ int standbyBucket = JobSchedulerService.standbyBucketForPackage(jobPackage,
+ sourceUserId, elapsedNow);
+ JobSchedulerInternal js = LocalServices.getService(JobSchedulerInternal.class);
+ long currentHeartbeat = js != null ? js.currentHeartbeat() : 0;
+ return new JobStatus(job, callingUid, sourcePkg, sourceUserId,
+ standbyBucket, currentHeartbeat, tag, 0,
earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */);
}
@@ -528,6 +566,29 @@
return UserHandle.getUserId(callingUid);
}
+ public int getStandbyBucket() {
+ return standbyBucket;
+ }
+
+ public long getBaseHeartbeat() {
+ return baseHeartbeat;
+ }
+
+ // Called only by the standby monitoring code
+ public void setStandbyBucket(int newBucket) {
+ standbyBucket = newBucket;
+ }
+
+ // Called only by the standby monitoring code
+ public long getWhenStandbyDeferred() {
+ return whenStandbyDeferred;
+ }
+
+ // Called only by the standby monitoring code
+ public void setWhenStandbyDeferred(long now) {
+ whenStandbyDeferred = now;
+ }
+
public String getSourceTag() {
return sourceTag;
}
@@ -950,6 +1011,19 @@
}
}
+ // normalized bucket indices, not the AppStandby constants
+ private String bucketName(int bucket) {
+ switch (bucket) {
+ case 0: return "ACTIVE";
+ case 1: return "WORKING_SET";
+ case 2: return "FREQUENT";
+ case 3: return "RARE";
+ case 4: return "NEVER";
+ default:
+ return "Unknown: " + bucket;
+ }
+ }
+
// Dumpsys infrastructure
public void dump(PrintWriter pw, String prefix, boolean full, long elapsedRealtimeMillis) {
pw.print(prefix); UserHandle.formatUid(pw, callingUid);
@@ -1098,6 +1172,8 @@
dumpJobWorkItem(pw, prefix, executingWork.get(i), i);
}
}
+ pw.print(prefix); pw.print("Standby bucket: ");
+ pw.println(bucketName(standbyBucket));
pw.print(prefix); pw.print("Enqueue time: ");
TimeUtils.formatDuration(enqueueTime, elapsedRealtimeMillis, pw);
pw.println();
diff --git a/com/android/server/location/ContextHubClientBroker.java b/com/android/server/location/ContextHubClientBroker.java
new file mode 100644
index 0000000..41d9feb
--- /dev/null
+++ b/com/android/server/location/ContextHubClientBroker.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2017 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.server.location;
+
+import android.content.Context;
+import android.hardware.contexthub.V1_0.ContextHubMsg;
+import android.hardware.contexthub.V1_0.IContexthub;
+import android.hardware.contexthub.V1_0.Result;
+import android.hardware.location.ContextHubTransaction;
+import android.hardware.location.IContextHubClient;
+import android.hardware.location.IContextHubClientCallback;
+import android.hardware.location.NanoAppMessage;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A class that acts as a broker for the ContextHubClient, which handles messaging and life-cycle
+ * notification callbacks. This class implements the IContextHubClient object, and the implemented
+ * APIs must be thread-safe.
+ *
+ * @hide
+ */
+public class ContextHubClientBroker extends IContextHubClient.Stub
+ implements IBinder.DeathRecipient {
+ private static final String TAG = "ContextHubClientBroker";
+
+ /*
+ * The context of the service.
+ */
+ private final Context mContext;
+
+ /*
+ * The proxy to talk to the Context Hub HAL.
+ */
+ private final IContexthub mContextHubProxy;
+
+ /*
+ * The manager that registered this client.
+ */
+ private final ContextHubClientManager mClientManager;
+
+ /*
+ * The ID of the hub that this client is attached to.
+ */
+ private final int mAttachedContextHubId;
+
+ /*
+ * The host end point ID of this client.
+ */
+ private final short mHostEndPointId;
+
+ /*
+ * The remote callback interface for this client.
+ */
+ private final IContextHubClientCallback mCallbackInterface;
+
+ /*
+ * false if the connection has been closed by the client, true otherwise.
+ */
+ private final AtomicBoolean mConnectionOpen = new AtomicBoolean(true);
+
+ /* package */ ContextHubClientBroker(
+ Context context, IContexthub contextHubProxy, ContextHubClientManager clientManager,
+ int contextHubId, short hostEndPointId, IContextHubClientCallback callback) {
+ mContext = context;
+ mContextHubProxy = contextHubProxy;
+ mClientManager = clientManager;
+ mAttachedContextHubId = contextHubId;
+ mHostEndPointId = hostEndPointId;
+ mCallbackInterface = callback;
+ }
+
+ /**
+ * Attaches a death recipient for this client
+ *
+ * @throws RemoteException if the client has already died
+ */
+ /* package */ void attachDeathRecipient() throws RemoteException {
+ mCallbackInterface.asBinder().linkToDeath(this, 0 /* flags */);
+ }
+
+ /**
+ * Sends from this client to a nanoapp.
+ *
+ * @param message the message to send
+ * @return the error code of sending the message
+ */
+ @ContextHubTransaction.Result
+ @Override
+ public int sendMessageToNanoApp(NanoAppMessage message) {
+ ContextHubServiceUtil.checkPermissions(mContext);
+
+ int result;
+ if (mConnectionOpen.get()) {
+ ContextHubMsg messageToNanoApp = ContextHubServiceUtil.createHidlContextHubMessage(
+ mHostEndPointId, message);
+
+ try {
+ result = mContextHubProxy.sendMessageToHub(mAttachedContextHubId, messageToNanoApp);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException in sendMessageToNanoApp (target hub ID = "
+ + mAttachedContextHubId + ")", e);
+ result = Result.UNKNOWN_FAILURE;
+ }
+ } else {
+ Log.e(TAG, "Failed to send message to nanoapp: client connection is closed");
+ result = Result.UNKNOWN_FAILURE;
+ }
+
+ return ContextHubServiceUtil.toTransactionResult(result);
+ }
+
+ /**
+ * Closes the connection for this client with the service.
+ */
+ @Override
+ public void close() {
+ if (mConnectionOpen.getAndSet(false)) {
+ mClientManager.unregisterClient(mHostEndPointId);
+ }
+ }
+
+ /**
+ * Invoked when the underlying binder of this broker has died at the client process.
+ */
+ public void binderDied() {
+ try {
+ IContextHubClient.Stub.asInterface(this).close();
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while closing client on death", e);
+ }
+ }
+
+ /**
+ * @return the ID of the context hub this client is attached to
+ */
+ /* package */ int getAttachedContextHubId() {
+ return mAttachedContextHubId;
+ }
+
+ /**
+ * @return the host endpoint ID of this client
+ */
+ /* package */ short getHostEndPointId() {
+ return mHostEndPointId;
+ }
+
+ /**
+ * Sends a message to the client associated with this object.
+ *
+ * @param message the message that came from a nanoapp
+ */
+ /* package */ void sendMessageToClient(NanoAppMessage message) {
+ if (mConnectionOpen.get()) {
+ try {
+ mCallbackInterface.onMessageFromNanoApp(message);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while sending message to client (host endpoint ID = "
+ + mHostEndPointId + ")", e);
+ }
+ }
+ }
+
+ /**
+ * Handles a nanoapp load event.
+ *
+ * @param nanoAppId the ID of the nanoapp that was loaded.
+ */
+ /* package */ void onNanoAppLoaded(long nanoAppId) {
+ if (mConnectionOpen.get()) {
+ try {
+ mCallbackInterface.onNanoAppLoaded(nanoAppId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while calling onNanoAppLoaded on client"
+ + " (host endpoint ID = " + mHostEndPointId + ")", e);
+ }
+ }
+ }
+
+ /**
+ * Handles a nanoapp unload event.
+ *
+ * @param nanoAppId the ID of the nanoapp that was unloaded.
+ */
+ /* package */ void onNanoAppUnloaded(long nanoAppId) {
+ if (mConnectionOpen.get()) {
+ try {
+ mCallbackInterface.onNanoAppUnloaded(nanoAppId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while calling onNanoAppUnloaded on client"
+ + " (host endpoint ID = " + mHostEndPointId + ")", e);
+ }
+ }
+ }
+
+ /**
+ * Handles a hub reset for this client.
+ */
+ /* package */ void onHubReset() {
+ if (mConnectionOpen.get()) {
+ try {
+ mCallbackInterface.onHubReset();
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while calling onHubReset on client" +
+ " (host endpoint ID = " + mHostEndPointId + ")", e);
+ }
+ }
+ }
+}
diff --git a/com/android/server/location/ContextHubClientManager.java b/com/android/server/location/ContextHubClientManager.java
new file mode 100644
index 0000000..d58a746
--- /dev/null
+++ b/com/android/server/location/ContextHubClientManager.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright 2017 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.server.location;
+
+import android.content.Context;
+import android.hardware.contexthub.V1_0.ContextHubMsg;
+import android.hardware.contexthub.V1_0.IContexthub;
+import android.hardware.location.IContextHubClient;
+import android.hardware.location.IContextHubClientCallback;
+import android.hardware.location.NanoAppMessage;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.NoSuchElementException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+
+/**
+ * A class that manages registration/unregistration of clients and manages messages to/from clients.
+ *
+ * @hide
+ */
+/* package */ class ContextHubClientManager {
+ private static final String TAG = "ContextHubClientManager";
+
+ /*
+ * The maximum host endpoint ID value that a client can be assigned.
+ */
+ private static final int MAX_CLIENT_ID = 0x7fff;
+
+ /*
+ * Local flag to enable debug logging.
+ */
+ private static final boolean DEBUG_LOG_ENABLED = true;
+
+ /*
+ * The context of the service.
+ */
+ private final Context mContext;
+
+ /*
+ * The proxy to talk to the Context Hub.
+ */
+ private final IContexthub mContextHubProxy;
+
+ /*
+ * A mapping of host endpoint IDs to the ContextHubClientBroker object of registered clients.
+ * A concurrent data structure is used since the registration/unregistration can occur in
+ * multiple threads.
+ */
+ private final ConcurrentHashMap<Short, ContextHubClientBroker> mHostEndPointIdToClientMap =
+ new ConcurrentHashMap<>();
+
+ /*
+ * The next host endpoint ID to start iterating for the next available host endpoint ID.
+ */
+ private int mNextHostEndpointId = 0;
+
+ /* package */ ContextHubClientManager(
+ Context context, IContexthub contextHubProxy) {
+ mContext = context;
+ mContextHubProxy = contextHubProxy;
+ }
+
+ /**
+ * Registers a new client with the service.
+ *
+ * @param clientCallback the callback interface of the client to register
+ * @param contextHubId the ID of the hub this client is attached to
+ *
+ * @return the client interface
+ *
+ * @throws IllegalStateException if max number of clients have already registered
+ */
+ /* package */ IContextHubClient registerClient(
+ IContextHubClientCallback clientCallback, int contextHubId) {
+ ContextHubClientBroker broker = createNewClientBroker(clientCallback, contextHubId);
+
+ try {
+ broker.attachDeathRecipient();
+ } catch (RemoteException e) {
+ // The client process has died, so we close the connection and return null.
+ Log.e(TAG, "Failed to attach death recipient to client");
+ broker.close();
+ return null;
+ }
+
+ Log.d(TAG, "Registered client with host endpoint ID " + broker.getHostEndPointId());
+ return IContextHubClient.Stub.asInterface(broker);
+ }
+
+ /**
+ * Handles a message sent from a nanoapp.
+ *
+ * @param contextHubId the ID of the hub where the nanoapp sent the message from
+ * @param message the message send by a nanoapp
+ */
+ /* package */ void onMessageFromNanoApp(int contextHubId, ContextHubMsg message) {
+ NanoAppMessage clientMessage = ContextHubServiceUtil.createNanoAppMessage(message);
+
+ if (DEBUG_LOG_ENABLED) {
+ String targetAudience = clientMessage.isBroadcastMessage() ? "broadcast" : "unicast";
+ Log.v(TAG, "Received a " + targetAudience + " message from nanoapp 0x"
+ + Long.toHexString(clientMessage.getNanoAppId()));
+ }
+
+ if (clientMessage.isBroadcastMessage()) {
+ broadcastMessage(contextHubId, clientMessage);
+ } else {
+ ContextHubClientBroker proxy = mHostEndPointIdToClientMap.get(message.hostEndPoint);
+ if (proxy != null) {
+ proxy.sendMessageToClient(clientMessage);
+ } else {
+ Log.e(TAG, "Cannot send message to unregistered client (host endpoint ID = "
+ + message.hostEndPoint + ")");
+ }
+ }
+ }
+
+ /**
+ * Unregisters a client from the service.
+ *
+ * This method should be invoked as a result of a client calling the ContextHubClient.close(),
+ * or if the client process has died.
+ *
+ * @param hostEndPointId the host endpoint ID of the client that has died
+ */
+ /* package */ void unregisterClient(short hostEndPointId) {
+ if (mHostEndPointIdToClientMap.remove(hostEndPointId) != null) {
+ Log.d(TAG, "Unregistered client with host endpoint ID " + hostEndPointId);
+ } else {
+ Log.e(TAG, "Cannot unregister non-existing client with host endpoint ID "
+ + hostEndPointId);
+ }
+ }
+
+ /**
+ * Handles a nanoapp load event.
+ *
+ * @param contextHubId the ID of the hub where the nanoapp was loaded.
+ * @param nanoAppId the ID of the nanoapp that was loaded.
+ */
+ /* package */ void onNanoAppLoaded(int contextHubId, long nanoAppId) {
+ forEachClientOfHub(contextHubId, client -> client.onNanoAppLoaded(nanoAppId));
+ }
+
+ /**
+ * Handles a nanoapp unload event.
+ *
+ * @param contextHubId the ID of the hub where the nanoapp was unloaded.
+ * @param nanoAppId the ID of the nanoapp that was unloaded.
+ */
+ /* package */ void onNanoAppUnloaded(int contextHubId, long nanoAppId) {
+ forEachClientOfHub(contextHubId, client -> client.onNanoAppUnloaded(nanoAppId));
+ }
+
+ /**
+ * Handles a hub reset.
+ *
+ * @param contextHubId the ID of the hub that has reset.
+ */
+ /* package */ void onHubReset(int contextHubId) {
+ forEachClientOfHub(contextHubId, client -> client.onHubReset());
+ }
+
+ /**
+ * Creates a new ContextHubClientBroker object for a client and registers it with the client
+ * manager.
+ *
+ * @param clientCallback the callback interface of the client to register
+ * @param contextHubId the ID of the hub this client is attached to
+ *
+ * @return the ContextHubClientBroker object
+ *
+ * @throws IllegalStateException if max number of clients have already registered
+ */
+ private synchronized ContextHubClientBroker createNewClientBroker(
+ IContextHubClientCallback clientCallback, int contextHubId) {
+ if (mHostEndPointIdToClientMap.size() == MAX_CLIENT_ID + 1) {
+ throw new IllegalStateException("Could not register client - max limit exceeded");
+ }
+
+ ContextHubClientBroker broker = null;
+ int id = mNextHostEndpointId;
+ for (int i = 0; i <= MAX_CLIENT_ID; i++) {
+ if (!mHostEndPointIdToClientMap.containsKey(id)) {
+ broker = new ContextHubClientBroker(
+ mContext, mContextHubProxy, this, contextHubId, (short)id, clientCallback);
+ mHostEndPointIdToClientMap.put((short)id, broker);
+ mNextHostEndpointId = (id == MAX_CLIENT_ID) ? 0 : id + 1;
+ break;
+ }
+
+ id = (id == MAX_CLIENT_ID) ? 0 : id + 1;
+ }
+
+ return broker;
+ }
+
+ /**
+ * Broadcasts a message from a nanoapp to all clients attached to the associated hub.
+ *
+ * @param contextHubId the ID of the hub where the nanoapp sent the message from
+ * @param message the message send by a nanoapp
+ */
+ private void broadcastMessage(int contextHubId, NanoAppMessage message) {
+ forEachClientOfHub(contextHubId, client -> client.sendMessageToClient(message));
+ }
+
+ /**
+ * Runs a command for each client that is attached to a hub with the given ID.
+ *
+ * @param contextHubId the ID of the hub
+ * @param callback the command to invoke for the client
+ */
+ private void forEachClientOfHub(int contextHubId, Consumer<ContextHubClientBroker> callback) {
+ for (ContextHubClientBroker broker : mHostEndPointIdToClientMap.values()) {
+ if (broker.getAttachedContextHubId() == contextHubId) {
+ callback.accept(broker);
+ }
+ }
+ }
+}
diff --git a/com/android/server/location/ContextHubService.java b/com/android/server/location/ContextHubService.java
index 5e9f355..e08c659 100644
--- a/com/android/server/location/ContextHubService.java
+++ b/com/android/server/location/ContextHubService.java
@@ -16,17 +16,29 @@
package com.android.server.location;
-import android.Manifest;
import android.content.Context;
-import android.content.pm.PackageManager;
+import android.hardware.contexthub.V1_0.AsyncEventType;
+import android.hardware.contexthub.V1_0.ContextHub;
+import android.hardware.contexthub.V1_0.ContextHubMsg;
+import android.hardware.contexthub.V1_0.HubAppInfo;
+import android.hardware.contexthub.V1_0.IContexthub;
+import android.hardware.contexthub.V1_0.IContexthubCallback;
+import android.hardware.contexthub.V1_0.Result;
+import android.hardware.contexthub.V1_0.TransactionResult;
import android.hardware.location.ContextHubInfo;
-import android.hardware.location.ContextHubManager;
import android.hardware.location.ContextHubMessage;
-import android.hardware.location.IContextHubService;
+import android.hardware.location.ContextHubTransaction;
import android.hardware.location.IContextHubCallback;
-import android.hardware.location.NanoAppFilter;
+import android.hardware.location.IContextHubClient;
+import android.hardware.location.IContextHubClientCallback;
+import android.hardware.location.IContextHubService;
+import android.hardware.location.IContextHubTransactionCallback;
import android.hardware.location.NanoApp;
+import android.hardware.location.NanoAppBinary;
+import android.hardware.location.NanoAppFilter;
import android.hardware.location.NanoAppInstanceInfo;
+import android.hardware.location.NanoAppMessage;
+import android.hardware.location.NanoAppState;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;
@@ -38,65 +50,230 @@
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
-import java.util.concurrent.ConcurrentHashMap;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.concurrent.ConcurrentHashMap;
/**
* @hide
*/
public class ContextHubService extends IContextHubService.Stub {
private static final String TAG = "ContextHubService";
- private static final String HARDWARE_PERMISSION = Manifest.permission.LOCATION_HARDWARE;
- private static final String ENFORCE_HW_PERMISSION_MESSAGE = "Permission '"
- + HARDWARE_PERMISSION + "' not granted to access ContextHub Hardware";
- public static final int ANY_HUB = -1;
- public static final int MSG_LOAD_NANO_APP = 3;
+ /*
+ * Constants for the type of transaction that is defined by ContextHubService.
+ * This is used to report the transaction callback to clients, and is different from
+ * ContextHubTransaction.Type.
+ */
+ public static final int MSG_ENABLE_NANO_APP = 1;
+ public static final int MSG_DISABLE_NANO_APP = 2;
+ public static final int MSG_LOAD_NANO_APP = 3;
public static final int MSG_UNLOAD_NANO_APP = 4;
+ public static final int MSG_QUERY_NANO_APPS = 5;
+ public static final int MSG_QUERY_MEMORY = 6;
+ public static final int MSG_HUB_RESET = 7;
private static final String PRE_LOADED_GENERIC_UNKNOWN = "Preloaded app, unknown";
private static final String PRE_LOADED_APP_NAME = PRE_LOADED_GENERIC_UNKNOWN;
private static final String PRE_LOADED_APP_PUBLISHER = PRE_LOADED_GENERIC_UNKNOWN;
private static final int PRE_LOADED_APP_MEM_REQ = 0;
- private static final int MSG_HEADER_SIZE = 4;
- private static final int HEADER_FIELD_MSG_TYPE = 0;
- private static final int HEADER_FIELD_MSG_VERSION = 1;
- private static final int HEADER_FIELD_HUB_HANDLE = 2;
- private static final int HEADER_FIELD_APP_INSTANCE = 3;
-
- private static final int HEADER_FIELD_LOAD_APP_ID_LO = MSG_HEADER_SIZE;
- private static final int HEADER_FIELD_LOAD_APP_ID_HI = MSG_HEADER_SIZE + 1;
- private static final int MSG_LOAD_APP_HEADER_SIZE = MSG_HEADER_SIZE + 2;
-
private static final int OS_APP_INSTANCE = -1;
private final Context mContext;
+
+ // TODO(b/69270990): Remove once old ContextHubManager API is deprecated
+ // Service cache maintaining of instance ID to nanoapp infos
private final ConcurrentHashMap<Integer, NanoAppInstanceInfo> mNanoAppHash =
new ConcurrentHashMap<>();
+ // The next available instance ID (managed by the service) to assign to a nanoapp
+ private int mNextAvailableInstanceId = 0;
+ // A map of the long nanoapp ID to instance ID managed by the service
+ private final ConcurrentHashMap<Long, Integer> mNanoAppIdToInstanceMap =
+ new ConcurrentHashMap<>();
+
private final ContextHubInfo[] mContextHubInfo;
private final RemoteCallbackList<IContextHubCallback> mCallbacksList =
new RemoteCallbackList<>();
- private native int nativeSendMessage(int[] header, byte[] data);
- private native ContextHubInfo[] nativeInitialize();
+ // Proxy object to communicate with the Context Hub HAL
+ private final IContexthub mContextHubProxy;
+
+ // The manager for transaction queue
+ private final ContextHubTransactionManager mTransactionManager;
+
+ // The manager for sending messages to/from clients
+ private final ContextHubClientManager mClientManager;
+
+ // The default client for old API clients
+ private final Map<Integer, IContextHubClient> mDefaultClientMap;
+
+ /**
+ * Class extending the callback to register with a Context Hub.
+ */
+ private class ContextHubServiceCallback extends IContexthubCallback.Stub {
+ private final int mContextHubId;
+
+ ContextHubServiceCallback(int contextHubId) {
+ mContextHubId = contextHubId;
+ }
+
+ @Override
+ public void handleClientMsg(ContextHubMsg message) {
+ handleClientMessageCallback(mContextHubId, message);
+ }
+
+ @Override
+ public void handleTxnResult(int transactionId, int result) {
+ handleTransactionResultCallback(mContextHubId, transactionId, result);
+ }
+
+ @Override
+ public void handleHubEvent(int eventType) {
+ handleHubEventCallback(mContextHubId, eventType);
+ }
+
+ @Override
+ public void handleAppAbort(long nanoAppId, int abortCode) {
+ handleAppAbortCallback(mContextHubId, nanoAppId, abortCode);
+ }
+
+ @Override
+ public void handleAppsInfo(ArrayList<HubAppInfo> nanoAppInfoList) {
+ handleQueryAppsCallback(mContextHubId, nanoAppInfoList);
+ }
+ }
public ContextHubService(Context context) {
mContext = context;
- mContextHubInfo = nativeInitialize();
+
+ mContextHubProxy = getContextHubProxy();
+ if (mContextHubProxy == null) {
+ mTransactionManager = null;
+ mClientManager = null;
+ mDefaultClientMap = Collections.EMPTY_MAP;
+ mContextHubInfo = new ContextHubInfo[0];
+ return;
+ }
+
+ mClientManager = new ContextHubClientManager(mContext, mContextHubProxy);
+ mTransactionManager = new ContextHubTransactionManager(mContextHubProxy, mClientManager);
+
+ List<ContextHub> hubList;
+ try {
+ hubList = mContextHubProxy.getHubs();
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while getting Context Hub info", e);
+ hubList = Collections.emptyList();
+ }
+ mContextHubInfo = ContextHubServiceUtil.createContextHubInfoArray(hubList);
+
+ HashMap<Integer, IContextHubClient> defaultClientMap = new HashMap<>();
+ for (ContextHubInfo contextHubInfo : mContextHubInfo) {
+ int contextHubId = contextHubInfo.getId();
+
+ IContextHubClient client = mClientManager.registerClient(
+ createDefaultClientCallback(contextHubId), contextHubId);
+ defaultClientMap.put(contextHubId, client);
+ }
+ mDefaultClientMap = Collections.unmodifiableMap(defaultClientMap);
+
+ for (ContextHubInfo contextHubInfo : mContextHubInfo) {
+ int contextHubId = contextHubInfo.getId();
+ try {
+ mContextHubProxy.registerCallback(
+ contextHubId, new ContextHubServiceCallback(contextHubId));
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while registering service callback for hub (ID = "
+ + contextHubId + ")", e);
+ }
+ }
+
+ // Do a query to initialize the service cache list of nanoapps
+ // TODO(b/69270990): Remove this when old API is deprecated
+ for (ContextHubInfo contextHubInfo : mContextHubInfo) {
+ queryNanoAppsInternal(contextHubInfo.getId());
+ }
for (int i = 0; i < mContextHubInfo.length; i++) {
Log.d(TAG, "ContextHub[" + i + "] id: " + mContextHubInfo[i].getId()
- + ", name: " + mContextHubInfo[i].getName());
+ + ", name: " + mContextHubInfo[i].getName());
}
}
+ /**
+ * Creates a default client callback for old API clients.
+ *
+ * @param contextHubId the ID of the hub to attach this client to
+ * @return the internal callback interface
+ */
+ private IContextHubClientCallback createDefaultClientCallback(int contextHubId) {
+ return new IContextHubClientCallback.Stub() {
+ @Override
+ public void onMessageFromNanoApp(NanoAppMessage message) {
+ int nanoAppInstanceId =
+ mNanoAppIdToInstanceMap.containsKey(message.getNanoAppId()) ?
+ mNanoAppIdToInstanceMap.get(message.getNanoAppId()) : -1;
+
+ onMessageReceiptOldApi(
+ message.getMessageType(), contextHubId, nanoAppInstanceId,
+ message.getMessageBody());
+ }
+
+ @Override
+ public void onHubReset() {
+ byte[] data = {TransactionResult.SUCCESS};
+ onMessageReceiptOldApi(MSG_HUB_RESET, contextHubId, OS_APP_INSTANCE, data);
+ }
+
+ @Override
+ public void onNanoAppAborted(long nanoAppId, int abortCode) {
+ }
+
+ @Override
+ public void onNanoAppLoaded(long nanoAppId) {
+ }
+
+ @Override
+ public void onNanoAppUnloaded(long nanoAppId) {
+ }
+
+ @Override
+ public void onNanoAppEnabled(long nanoAppId) {
+ }
+
+ @Override
+ public void onNanoAppDisabled(long nanoAppId) {
+ }
+ };
+ }
+
+ /**
+ * @return the IContexthub proxy interface
+ */
+ private IContexthub getContextHubProxy() {
+ IContexthub proxy = null;
+ try {
+ proxy = IContexthub.getService(true /* retry */);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while attaching to Context Hub HAL proxy", e);
+ } catch (NoSuchElementException e) {
+ Log.i(TAG, "Context Hub HAL service not found");
+ }
+
+ return proxy;
+ }
+
@Override
public int registerCallback(IContextHubCallback callback) throws RemoteException {
checkPermissions();
mCallbacksList.register(callback);
+
Log.d(TAG, "Added callback, total callbacks " +
- mCallbacksList.getRegisteredCallbackCount());
+ mCallbacksList.getRegisteredCallbackCount());
return 0;
}
@@ -109,29 +286,112 @@
for (int i = 0; i < returnArray.length; ++i) {
returnArray[i] = i;
Log.d(TAG, String.format("Hub %s is mapped to %d",
- mContextHubInfo[i].getName(), returnArray[i]));
+ mContextHubInfo[i].getName(), returnArray[i]));
}
return returnArray;
}
@Override
- public ContextHubInfo getContextHubInfo(int contextHubHandle) throws RemoteException {
+ public ContextHubInfo getContextHubInfo(int contextHubId) throws RemoteException {
checkPermissions();
- if (!(contextHubHandle >= 0 && contextHubHandle < mContextHubInfo.length)) {
- Log.e(TAG, "Invalid context hub handle " + contextHubHandle);
+ if (!(contextHubId >= 0 && contextHubId < mContextHubInfo.length)) {
+ Log.e(TAG, "Invalid context hub handle " + contextHubId);
return null; // null means fail
}
- return mContextHubInfo[contextHubHandle];
+ return mContextHubInfo[contextHubId];
+ }
+
+ /**
+ * Creates an internal load transaction callback to be used for old API clients
+ *
+ * @param contextHubId the ID of the hub to load the binary
+ * @param nanoAppBinary the binary to load
+ * @return the callback interface
+ */
+ private IContextHubTransactionCallback createLoadTransactionCallback(
+ int contextHubId, NanoAppBinary nanoAppBinary) {
+ return new IContextHubTransactionCallback.Stub() {
+ @Override
+ public void onTransactionComplete(int result) {
+ handleLoadResponseOldApi(contextHubId, result, nanoAppBinary);
+ }
+
+ @Override
+ public void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) {
+ }
+ };
+ }
+
+ /**
+ * Creates an internal unload transaction callback to be used for old API clients
+ *
+ * @param contextHubId the ID of the hub to unload the nanoapp
+ * @param nanoAppId the ID of the nanoapp to unload
+ * @return the callback interface
+ */
+ private IContextHubTransactionCallback createUnloadTransactionCallback(
+ int contextHubId, long nanoAppId) {
+ return new IContextHubTransactionCallback.Stub() {
+ @Override
+ public void onTransactionComplete(int result) {
+ handleUnloadResponseOldApi(contextHubId, result, nanoAppId);
+ }
+
+ @Override
+ public void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) {
+ }
+ };
+ }
+
+ /**
+ * Creates an internal query transaction callback to be used for old API clients
+ *
+ * @param contextHubId the ID of the hub to query
+ * @return the callback interface
+ */
+ private IContextHubTransactionCallback createQueryTransactionCallback(int contextHubId) {
+ return new IContextHubTransactionCallback.Stub() {
+ @Override
+ public void onTransactionComplete(int result) {
+ }
+
+ @Override
+ public void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) {
+ byte[] data = {(byte) result};
+ onMessageReceiptOldApi(MSG_QUERY_NANO_APPS, contextHubId, OS_APP_INSTANCE, data);
+ }
+ };
+ }
+
+ /**
+ * Adds a new transaction to the transaction manager queue
+ *
+ * @param transaction the transaction to add
+ * @return the result of adding the transaction
+ */
+ private int addTransaction(ContextHubServiceTransaction transaction) {
+ int result = Result.OK;
+ try {
+ mTransactionManager.addTransaction(transaction);
+ } catch (IllegalStateException e) {
+ Log.e(TAG, e.getMessage());
+ result = Result.TRANSACTION_PENDING; /* failed */
+ }
+
+ return result;
}
@Override
- public int loadNanoApp(int contextHubHandle, NanoApp app) throws RemoteException {
+ public int loadNanoApp(int contextHubId, NanoApp app) throws RemoteException {
checkPermissions();
+ if (mContextHubProxy == null) {
+ return -1;
+ }
- if (!(contextHubHandle >= 0 && contextHubHandle < mContextHubInfo.length)) {
- Log.e(TAG, "Invalid contextHubhandle " + contextHubHandle);
+ if (!(contextHubId >= 0 && contextHubId < mContextHubInfo.length)) {
+ Log.e(TAG, "Invalid contextHubhandle " + contextHubId);
return -1;
}
if (app == null) {
@@ -139,20 +399,17 @@
return -1;
}
- int[] msgHeader = new int[MSG_LOAD_APP_HEADER_SIZE];
- msgHeader[HEADER_FIELD_HUB_HANDLE] = contextHubHandle;
- msgHeader[HEADER_FIELD_APP_INSTANCE] = OS_APP_INSTANCE;
- msgHeader[HEADER_FIELD_MSG_VERSION] = 0;
- msgHeader[HEADER_FIELD_MSG_TYPE] = MSG_LOAD_NANO_APP;
+ // Create an internal IContextHubTransactionCallback for the old API clients
+ NanoAppBinary nanoAppBinary = new NanoAppBinary(app.getAppBinary());
+ IContextHubTransactionCallback onCompleteCallback =
+ createLoadTransactionCallback(contextHubId, nanoAppBinary);
- long appId = app.getAppId();
+ ContextHubServiceTransaction transaction = mTransactionManager.createLoadTransaction(
+ contextHubId, nanoAppBinary, onCompleteCallback);
- msgHeader[HEADER_FIELD_LOAD_APP_ID_LO] = (int)(appId & 0xFFFFFFFF);
- msgHeader[HEADER_FIELD_LOAD_APP_ID_HI] = (int)((appId >> 32) & 0xFFFFFFFF);
-
- int errVal = nativeSendMessage(msgHeader, app.getAppBinary());
- if (errVal != 0) {
- Log.e(TAG, "Send Message returns error" + contextHubHandle);
+ int result = addTransaction(transaction);
+ if (result != Result.OK) {
+ Log.e(TAG, "Failed to load nanoapp with error code " + result);
return -1;
}
@@ -163,23 +420,26 @@
@Override
public int unloadNanoApp(int nanoAppInstanceHandle) throws RemoteException {
checkPermissions();
+ if (mContextHubProxy == null) {
+ return -1;
+ }
+
NanoAppInstanceInfo info = mNanoAppHash.get(nanoAppInstanceHandle);
if (info == null) {
Log.e(TAG, "Cannot find app with handle " + nanoAppInstanceHandle);
return -1; //means failed
}
- // Call Native interface here
- int[] msgHeader = new int[MSG_HEADER_SIZE];
- msgHeader[HEADER_FIELD_HUB_HANDLE] = ANY_HUB;
- msgHeader[HEADER_FIELD_APP_INSTANCE] = nanoAppInstanceHandle;
- msgHeader[HEADER_FIELD_MSG_VERSION] = 0;
- msgHeader[HEADER_FIELD_MSG_TYPE] = MSG_UNLOAD_NANO_APP;
+ int contextHubId = info.getContexthubId();
+ long nanoAppId = info.getAppId();
+ IContextHubTransactionCallback onCompleteCallback =
+ createUnloadTransactionCallback(contextHubId, nanoAppId);
+ ContextHubServiceTransaction transaction = mTransactionManager.createUnloadTransaction(
+ contextHubId, nanoAppId, onCompleteCallback);
- byte msg[] = new byte[0];
-
- if (nativeSendMessage(msgHeader, msg) != 0) {
- Log.e(TAG, "native send message fails");
+ int result = addTransaction(transaction);
+ if (result != Result.OK) {
+ Log.e(TAG, "Failed to unload nanoapp with error code " + result);
return -1;
}
@@ -189,7 +449,7 @@
@Override
public NanoAppInstanceInfo getNanoAppInstanceInfo(int nanoAppInstanceHandle)
- throws RemoteException {
+ throws RemoteException {
checkPermissions();
// This assumes that all the nanoAppInfo is current. This is reasonable
// for the use cases for tightly controlled nanoApps.
@@ -206,7 +466,7 @@
checkPermissions();
ArrayList<Integer> foundInstances = new ArrayList<Integer>();
- for (Integer nanoAppInstance: mNanoAppHash.keySet()) {
+ for (Integer nanoAppInstance : mNanoAppHash.keySet()) {
NanoAppInstanceInfo info = mNanoAppHash.get(nanoAppInstance);
if (filter.testMatch(info)) {
@@ -223,23 +483,259 @@
return retArray;
}
- @Override
- public int sendMessage(int hubHandle, int nanoAppHandle, ContextHubMessage msg)
- throws RemoteException {
- checkPermissions();
+ /**
+ * Performs a query at the specified hub.
+ *
+ * This method should only be invoked internally by the service, either to update the service
+ * cache or as a result of an explicit query requested by a client through the sendMessage API.
+ *
+ * @param contextHubId the ID of the hub to do the query
+ * @return the result of the query
+ */
+ private int queryNanoAppsInternal(int contextHubId) {
+ if (mContextHubProxy == null) {
+ return Result.UNKNOWN_FAILURE;
+ }
- if (msg == null || msg.getData() == null) {
- Log.w(TAG, "null ptr");
+ IContextHubTransactionCallback onCompleteCallback =
+ createQueryTransactionCallback(contextHubId);
+ ContextHubServiceTransaction transaction = mTransactionManager.createQueryTransaction(
+ contextHubId, onCompleteCallback);
+
+ return addTransaction(transaction);
+ }
+
+ @Override
+ public int sendMessage(
+ int hubHandle, int nanoAppHandle, ContextHubMessage msg) throws RemoteException {
+ checkPermissions();
+ if (mContextHubProxy == null) {
+ return -1;
+ }
+ if (msg == null) {
+ Log.e(TAG, "ContextHubMessage cannot be null");
+ return -1;
+ }
+ if (msg.getData() == null) {
+ Log.e(TAG, "ContextHubMessage message body cannot be null");
+ return -1;
+ }
+ if (!mDefaultClientMap.containsKey(hubHandle)) {
+ Log.e(TAG, "Hub with ID " + hubHandle + " does not exist");
return -1;
}
- int[] msgHeader = new int[MSG_HEADER_SIZE];
- msgHeader[HEADER_FIELD_HUB_HANDLE] = hubHandle;
- msgHeader[HEADER_FIELD_APP_INSTANCE] = nanoAppHandle;
- msgHeader[HEADER_FIELD_MSG_VERSION] = msg.getVersion();
- msgHeader[HEADER_FIELD_MSG_TYPE] = msg.getMsgType();
+ boolean success = false;
+ if (nanoAppHandle == OS_APP_INSTANCE) {
+ if (msg.getMsgType() == MSG_QUERY_NANO_APPS) {
+ success = (queryNanoAppsInternal(hubHandle) == Result.OK);
+ } else {
+ Log.e(TAG, "Invalid OS message params of type " + msg.getMsgType());
+ }
+ } else {
+ NanoAppInstanceInfo info = getNanoAppInstanceInfo(nanoAppHandle);
+ if (info != null) {
+ NanoAppMessage message = NanoAppMessage.createMessageToNanoApp(
+ info.getAppId(), msg.getMsgType(), msg.getData());
- return nativeSendMessage(msgHeader, msg.getData());
+ IContextHubClient client = mDefaultClientMap.get(hubHandle);
+ success = (client.sendMessageToNanoApp(message) ==
+ ContextHubTransaction.TRANSACTION_SUCCESS);
+ } else {
+ Log.e(TAG, "Failed to send nanoapp message - nanoapp with instance ID "
+ + nanoAppHandle + " does not exist.");
+ }
+ }
+
+ return success ? 0 : -1;
+ }
+
+ /**
+ * Handles a unicast or broadcast message from a nanoapp.
+ *
+ * @param contextHubId the ID of the hub the message came from
+ * @param message the message contents
+ */
+ private void handleClientMessageCallback(int contextHubId, ContextHubMsg message) {
+ mClientManager.onMessageFromNanoApp(contextHubId, message);
+ }
+
+ /**
+ * A helper function to handle a load response from the Context Hub for the old API.
+ *
+ * TODO(b/69270990): Remove this once the old APIs are obsolete.
+ */
+ private void handleLoadResponseOldApi(
+ int contextHubId, int result, NanoAppBinary nanoAppBinary) {
+ if (nanoAppBinary == null) {
+ Log.e(TAG, "Nanoapp binary field was null for a load transaction");
+ return;
+ }
+
+ // NOTE: The legacy JNI code used to do a query right after a load success
+ // to synchronize the service cache. Instead store the binary that was requested to
+ // load to update the cache later without doing a query.
+ int instanceId = 0;
+ long nanoAppId = nanoAppBinary.getNanoAppId();
+ int nanoAppVersion = nanoAppBinary.getNanoAppVersion();
+ if (result == TransactionResult.SUCCESS) {
+ if (mNanoAppIdToInstanceMap.containsKey(nanoAppId)) {
+ instanceId = mNanoAppIdToInstanceMap.get(nanoAppId);
+ } else {
+ instanceId = mNextAvailableInstanceId++;
+ mNanoAppIdToInstanceMap.put(nanoAppId, instanceId);
+ }
+
+ addAppInstance(contextHubId, instanceId, nanoAppId, nanoAppVersion);
+ }
+
+ byte[] data = new byte[5];
+ data[0] = (byte) result;
+ ByteBuffer.wrap(data, 1, 4).order(ByteOrder.nativeOrder()).putInt(instanceId);
+
+ onMessageReceiptOldApi(MSG_LOAD_NANO_APP, contextHubId, OS_APP_INSTANCE, data);
+ }
+
+ /**
+ * A helper function to handle an unload response from the Context Hub for the old API.
+ *
+ * TODO(b/69270990): Remove this once the old APIs are obsolete.
+ */
+ private void handleUnloadResponseOldApi(
+ int contextHubId, int result, long nanoAppId) {
+ if (result == TransactionResult.SUCCESS) {
+ int instanceId = mNanoAppIdToInstanceMap.get(nanoAppId);
+ deleteAppInstance(instanceId);
+ mNanoAppIdToInstanceMap.remove(nanoAppId);
+ }
+
+ byte[] data = new byte[1];
+ data[0] = (byte) result;
+ onMessageReceiptOldApi(MSG_UNLOAD_NANO_APP, contextHubId, OS_APP_INSTANCE, data);
+ }
+
+ /**
+ * Handles a transaction response from a Context Hub.
+ *
+ * @param contextHubId the ID of the hub the response came from
+ * @param transactionId the ID of the transaction
+ * @param result the result of the transaction reported by the hub
+ */
+ private void handleTransactionResultCallback(int contextHubId, int transactionId, int result) {
+ mTransactionManager.onTransactionResponse(transactionId, result);
+ }
+
+ /**
+ * Handles an asynchronous event from a Context Hub.
+ *
+ * @param contextHubId the ID of the hub the response came from
+ * @param eventType the type of the event as defined in Context Hub HAL AsyncEventType
+ */
+ private void handleHubEventCallback(int contextHubId, int eventType) {
+ if (eventType == AsyncEventType.RESTARTED) {
+ mTransactionManager.onHubReset();
+ queryNanoAppsInternal(contextHubId);
+
+ mClientManager.onHubReset(contextHubId);
+ } else {
+ Log.i(TAG, "Received unknown hub event (hub ID = " + contextHubId + ", type = "
+ + eventType + ")");
+ }
+ }
+
+ /**
+ * Handles an asynchronous abort event of a nanoapp.
+ *
+ * @param contextHubId the ID of the hub that the nanoapp aborted in
+ * @param nanoAppId the ID of the aborted nanoapp
+ * @param abortCode the nanoapp-specific abort code
+ */
+ private void handleAppAbortCallback(int contextHubId, long nanoAppId, int abortCode) {
+ // TODO(b/31049861): Implement this
+ }
+
+ /**
+ * Handles a query response from a Context Hub.
+ *
+ * @param contextHubId the ID of the hub of the response
+ * @param nanoAppInfoList the list of loaded nanoapps
+ */
+ private void handleQueryAppsCallback(int contextHubId, List<HubAppInfo> nanoAppInfoList) {
+ List<NanoAppState> nanoAppStateList =
+ ContextHubServiceUtil.createNanoAppStateList(nanoAppInfoList);
+
+ updateServiceCache(contextHubId, nanoAppInfoList);
+ mTransactionManager.onQueryResponse(nanoAppStateList);
+ }
+
+ /**
+ * Updates the service's cache of the list of loaded nanoapps using a nanoapp list response.
+ *
+ * TODO(b/69270990): Remove this when the old API functionality is removed.
+ *
+ * @param contextHubId the ID of the hub the response came from
+ * @param nanoAppInfoList the list of loaded nanoapps
+ */
+ private void updateServiceCache(int contextHubId, List<HubAppInfo> nanoAppInfoList) {
+ synchronized (mNanoAppHash) {
+ for (int instanceId : mNanoAppHash.keySet()) {
+ if (mNanoAppHash.get(instanceId).getContexthubId() == contextHubId) {
+ deleteAppInstance(instanceId);
+ }
+ }
+
+ for (HubAppInfo appInfo : nanoAppInfoList) {
+ int instanceId;
+ long nanoAppId = appInfo.appId;
+ if (mNanoAppIdToInstanceMap.containsKey(nanoAppId)) {
+ instanceId = mNanoAppIdToInstanceMap.get(nanoAppId);
+ } else {
+ instanceId = mNextAvailableInstanceId++;
+ mNanoAppIdToInstanceMap.put(nanoAppId, instanceId);
+ }
+
+ addAppInstance(contextHubId, instanceId, nanoAppId, appInfo.version);
+ }
+ }
+ }
+
+ /**
+ * @param contextHubId the hub ID to validate
+ * @return {@code true} if the ID represents that of an available hub, {@code false} otherwise
+ */
+ private boolean isValidContextHubId(int contextHubId) {
+ for (ContextHubInfo hubInfo : mContextHubInfo) {
+ if (hubInfo.getId() == contextHubId) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Creates and registers a client at the service for the specified Context Hub.
+ *
+ * @param clientCallback the client interface to register with the service
+ * @param contextHubId the ID of the hub this client is attached to
+ * @return the generated client interface, null if registration was unsuccessful
+ *
+ * @throws IllegalArgumentException if contextHubId is not a valid ID
+ * @throws IllegalStateException if max number of clients have already registered
+ * @throws NullPointerException if clientCallback is null
+ */
+ @Override
+ public IContextHubClient createClient(
+ IContextHubClientCallback clientCallback, int contextHubId) throws RemoteException {
+ checkPermissions();
+ if (!isValidContextHubId(contextHubId)) {
+ throw new IllegalArgumentException("Invalid context hub ID " + contextHubId);
+ }
+ if (clientCallback == null) {
+ throw new NullPointerException("Cannot register client with null callback");
+ }
+
+ return mClientManager.registerClient(clientCallback, contextHubId);
}
@Override
@@ -257,7 +753,7 @@
pw.println("");
pw.println("=================== NANOAPPS ====================");
// Dump nanoAppHash
- for (Integer nanoAppInstance: mNanoAppHash.keySet()) {
+ for (Integer nanoAppInstance : mNanoAppHash.keySet()) {
pw.println(nanoAppInstance + " : " + mNanoAppHash.get(nanoAppInstance).toString());
}
@@ -265,22 +761,18 @@
}
private void checkPermissions() {
- mContext.enforceCallingPermission(HARDWARE_PERMISSION, ENFORCE_HW_PERMISSION_MESSAGE);
+ ContextHubServiceUtil.checkPermissions(mContext);
}
- private int onMessageReceipt(int[] header, byte[] data) {
- if (header == null || data == null || header.length < MSG_HEADER_SIZE) {
- return -1;
+ private int onMessageReceiptOldApi(int msgType, int hubHandle, int appInstance, byte[] data) {
+ if (data == null) {
+ return -1;
}
+ int msgVersion = 0;
int callbacksCount = mCallbacksList.beginBroadcast();
- int msgType = header[HEADER_FIELD_MSG_TYPE];
- int msgVersion = header[HEADER_FIELD_MSG_VERSION];
- int hubHandle = header[HEADER_FIELD_HUB_HANDLE];
- int appInstance = header[HEADER_FIELD_APP_INSTANCE];
-
Log.d(TAG, "Sending message " + msgType + " version " + msgVersion + " from hubHandle " +
- hubHandle + ", appInstance " + appInstance + ", callBackCount " + callbacksCount);
+ hubHandle + ", appInstance " + appInstance + ", callBackCount " + callbacksCount);
if (callbacksCount < 1) {
Log.v(TAG, "No message callbacks registered.");
@@ -323,8 +815,8 @@
}
mNanoAppHash.put(appInstanceHandle, appInfo);
- Log.d(TAG, action + " app instance " + appInstanceHandle + " with id "
- + appId + " version " + appVersion);
+ Log.d(TAG, action + " app instance " + appInstanceHandle + " with id 0x"
+ + Long.toHexString(appId) + " version 0x" + Integer.toHexString(appVersion));
return 0;
}
diff --git a/com/android/server/location/ContextHubServiceTransaction.java b/com/android/server/location/ContextHubServiceTransaction.java
new file mode 100644
index 0000000..66145bb
--- /dev/null
+++ b/com/android/server/location/ContextHubServiceTransaction.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2017 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.server.location;
+
+import android.hardware.location.ContextHubTransaction;
+import android.hardware.location.NanoAppState;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * An abstract class representing transactions requested to the Context Hub Service.
+ *
+ * @hide
+ */
+/* package */ abstract class ContextHubServiceTransaction {
+ private final int mTransactionId;
+ @ContextHubTransaction.Type
+ private final int mTransactionType;
+
+ /*
+ * true if the transaction has already completed, false otherwise
+ */
+ private boolean mIsComplete = false;
+
+ /* package */ ContextHubServiceTransaction(int id, int type) {
+ mTransactionId = id;
+ mTransactionType = type;
+ }
+
+ /**
+ * Starts this transaction with a Context Hub.
+ *
+ * All instances of this class must implement this method by making an asynchronous request to
+ * a hub.
+ *
+ * @return the synchronous error code of the transaction start
+ */
+ /* package */
+ abstract int onTransact();
+
+ /**
+ * A function to invoke when a transaction times out.
+ *
+ * All instances of this class must implement this method by reporting the timeout to the
+ * client.
+ */
+ /* package */
+ abstract void onTimeout();
+
+ /**
+ * A function to invoke when the transaction completes.
+ *
+ * Only relevant for load, unload, enable, or disable transactions.
+ *
+ * @param result the result of the transaction
+ */
+ /* package */ void onTransactionComplete(int result) {
+ }
+
+ /**
+ * A function to invoke when a query transaction completes.
+ *
+ * Only relevant for query transactions.
+ *
+ * @param result the result of the query
+ * @param nanoAppStateList the list of nanoapps given by the query response
+ */
+ /* package */ void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) {
+ }
+
+ /**
+ * @return the ID of this transaction
+ */
+ /* package */ int getTransactionId() {
+ return mTransactionId;
+ }
+
+ /**
+ * @return the type of this transaction
+ * @see ContextHubTransaction.Type
+ */
+ @ContextHubTransaction.Type
+ /* package */ int getTransactionType() {
+ return mTransactionType;
+ }
+
+ /**
+ * Gets the timeout period as defined in IContexthub.hal
+ *
+ * @return the timeout of this transaction in the specified time unit
+ */
+ /* package */ long getTimeout(TimeUnit unit) {
+ switch (mTransactionType) {
+ case ContextHubTransaction.TYPE_LOAD_NANOAPP:
+ return unit.convert(30L, TimeUnit.SECONDS);
+ case ContextHubTransaction.TYPE_UNLOAD_NANOAPP:
+ case ContextHubTransaction.TYPE_ENABLE_NANOAPP:
+ case ContextHubTransaction.TYPE_DISABLE_NANOAPP:
+ case ContextHubTransaction.TYPE_QUERY_NANOAPPS:
+ // Note: query timeout is not specified at the HAL
+ default: /* fall through */
+ return unit.convert(5L, TimeUnit.SECONDS);
+ }
+ }
+
+ /**
+ * Marks the transaction as complete.
+ *
+ * Should only be called as a result of a response from a Context Hub callback
+ */
+ /* package */ void setComplete() {
+ mIsComplete = true;
+ }
+
+ /**
+ * @return true if the transaction has already completed, false otherwise
+ */
+ /* package */ boolean isComplete() {
+ return mIsComplete;
+ }
+
+ /**
+ * @return the human-readable string of this transaction's type
+ */
+ private String getTransactionTypeString() {
+ switch (mTransactionType) {
+ case ContextHubTransaction.TYPE_LOAD_NANOAPP:
+ return "Load";
+ case ContextHubTransaction.TYPE_UNLOAD_NANOAPP:
+ return "Unload";
+ case ContextHubTransaction.TYPE_ENABLE_NANOAPP:
+ return "Enable";
+ case ContextHubTransaction.TYPE_DISABLE_NANOAPP:
+ return "Disable";
+ case ContextHubTransaction.TYPE_QUERY_NANOAPPS:
+ return "Query";
+ default:
+ return "Unknown";
+ }
+ }
+
+ @Override
+ public String toString() {
+ return getTransactionTypeString() + " transaction (ID = " + mTransactionId + ")";
+ }
+}
diff --git a/com/android/server/location/ContextHubServiceUtil.java b/com/android/server/location/ContextHubServiceUtil.java
new file mode 100644
index 0000000..6faeb72
--- /dev/null
+++ b/com/android/server/location/ContextHubServiceUtil.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright 2017 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.server.location;
+
+import android.Manifest;
+import android.content.Context;
+import android.hardware.contexthub.V1_0.ContextHub;
+import android.hardware.contexthub.V1_0.ContextHubMsg;
+import android.hardware.contexthub.V1_0.HostEndPoint;
+import android.hardware.contexthub.V1_0.HubAppInfo;
+import android.hardware.contexthub.V1_0.Result;
+import android.hardware.location.ContextHubInfo;
+import android.hardware.location.ContextHubTransaction;
+import android.hardware.location.NanoAppBinary;
+import android.hardware.location.NanoAppMessage;
+import android.hardware.location.NanoAppState;
+import android.util.Log;
+
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * A class encapsulating helper functions used by the ContextHubService class
+ */
+/* package */ class ContextHubServiceUtil {
+ private static final String TAG = "ContextHubServiceUtil";
+ private static final String HARDWARE_PERMISSION = Manifest.permission.LOCATION_HARDWARE;
+ private static final String ENFORCE_HW_PERMISSION_MESSAGE = "Permission '"
+ + HARDWARE_PERMISSION + "' not granted to access ContextHub Hardware";
+
+ /**
+ * Creates a ContextHubInfo array from an ArrayList of HIDL ContextHub objects.
+ *
+ * @param hubList the ContextHub ArrayList
+ * @return the ContextHubInfo array
+ */
+ /* package */
+ static ContextHubInfo[] createContextHubInfoArray(List<ContextHub> hubList) {
+ ContextHubInfo[] contextHubInfoList = new ContextHubInfo[hubList.size()];
+ for (int i = 0; i < hubList.size(); i++) {
+ contextHubInfoList[i] = new ContextHubInfo(hubList.get(i));
+ }
+
+ return contextHubInfoList;
+ }
+
+ /**
+ * Copies a primitive byte array to a ArrayList<Byte>.
+ *
+ * @param inputArray the primitive byte array
+ * @param outputArray the ArrayList<Byte> array to append
+ */
+ /* package */
+ static void copyToByteArrayList(byte[] inputArray, ArrayList<Byte> outputArray) {
+ outputArray.clear();
+ outputArray.ensureCapacity(inputArray.length);
+ for (byte element : inputArray) {
+ outputArray.add(element);
+ }
+ }
+
+ /**
+ * Creates a byte array given a ArrayList<Byte> and copies its contents.
+ *
+ * @param array the ArrayList<Byte> object
+ * @return the byte array
+ */
+ /* package */
+ static byte[] createPrimitiveByteArray(ArrayList<Byte> array) {
+ byte[] primitiveArray = new byte[array.size()];
+ for (int i = 0; i < array.size(); i++) {
+ primitiveArray[i] = array.get(i);
+ }
+
+ return primitiveArray;
+ }
+
+ /**
+ * Generates the Context Hub HAL's NanoAppBinary object from the client-facing
+ * android.hardware.location.NanoAppBinary object.
+ *
+ * @param nanoAppBinary the client-facing NanoAppBinary object
+ * @return the Context Hub HAL's NanoAppBinary object
+ */
+ /* package */
+ static android.hardware.contexthub.V1_0.NanoAppBinary createHidlNanoAppBinary(
+ NanoAppBinary nanoAppBinary) {
+ android.hardware.contexthub.V1_0.NanoAppBinary hidlNanoAppBinary =
+ new android.hardware.contexthub.V1_0.NanoAppBinary();
+
+ hidlNanoAppBinary.appId = nanoAppBinary.getNanoAppId();
+ hidlNanoAppBinary.appVersion = nanoAppBinary.getNanoAppVersion();
+ hidlNanoAppBinary.flags = nanoAppBinary.getFlags();
+ hidlNanoAppBinary.targetChreApiMajorVersion = nanoAppBinary.getTargetChreApiMajorVersion();
+ hidlNanoAppBinary.targetChreApiMinorVersion = nanoAppBinary.getTargetChreApiMinorVersion();
+
+ // Log exceptions while processing the binary, but continue to pass down the binary
+ // since the error checking is deferred to the Context Hub.
+ try {
+ copyToByteArrayList(nanoAppBinary.getBinaryNoHeader(), hidlNanoAppBinary.customBinary);
+ } catch (IndexOutOfBoundsException e) {
+ Log.w(TAG, e.getMessage());
+ } catch (NullPointerException e) {
+ Log.w(TAG, "NanoApp binary was null");
+ }
+
+ return hidlNanoAppBinary;
+ }
+
+ /**
+ * Generates a client-facing NanoAppState array from a HAL HubAppInfo array.
+ *
+ * @param nanoAppInfoList the array of HubAppInfo objects
+ * @return the corresponding array of NanoAppState objects
+ */
+ /* package */
+ static List<NanoAppState> createNanoAppStateList(
+ List<HubAppInfo> nanoAppInfoList) {
+ ArrayList<NanoAppState> nanoAppStateList = new ArrayList<>();
+ for (HubAppInfo appInfo : nanoAppInfoList) {
+ nanoAppStateList.add(
+ new NanoAppState(appInfo.appId, appInfo.version, appInfo.enabled));
+ }
+
+ return nanoAppStateList;
+ }
+
+ /**
+ * Creates a HIDL ContextHubMsg object to send to a nanoapp.
+ *
+ * @param hostEndPoint the ID of the client sending the message
+ * @param message the client-facing NanoAppMessage object describing the message
+ * @return the HIDL ContextHubMsg object
+ */
+ /* package */
+ static ContextHubMsg createHidlContextHubMessage(short hostEndPoint, NanoAppMessage message) {
+ ContextHubMsg hidlMessage = new ContextHubMsg();
+
+ hidlMessage.appName = message.getNanoAppId();
+ hidlMessage.hostEndPoint = hostEndPoint;
+ hidlMessage.msgType = message.getMessageType();
+ copyToByteArrayList(message.getMessageBody(), hidlMessage.msg);
+
+ return hidlMessage;
+ }
+
+ /**
+ * Creates a client-facing NanoAppMessage object to send to a client.
+ *
+ * @param message the HIDL ContextHubMsg object from a nanoapp
+ * @return the NanoAppMessage object
+ */
+ /* package */
+ static NanoAppMessage createNanoAppMessage(ContextHubMsg message) {
+ byte[] messageArray = createPrimitiveByteArray(message.msg);
+
+ return NanoAppMessage.createMessageFromNanoApp(
+ message.appName, message.msgType, messageArray,
+ message.hostEndPoint == HostEndPoint.BROADCAST);
+ }
+
+ /**
+ * Checks for location hardware permissions.
+ *
+ * @param context the context of the service
+ */
+ /* package */
+ static void checkPermissions(Context context) {
+ context.enforceCallingPermission(HARDWARE_PERMISSION, ENFORCE_HW_PERMISSION_MESSAGE);
+ }
+
+ /**
+ * Helper function to convert from the HAL Result enum error code to the
+ * ContextHubTransaction.Result type.
+ *
+ * @param halResult the Result enum error code
+ * @return the ContextHubTransaction.Result equivalent
+ */
+ @ContextHubTransaction.Result
+ /* package */
+ static int toTransactionResult(int halResult) {
+ switch (halResult) {
+ case Result.OK:
+ return ContextHubTransaction.TRANSACTION_SUCCESS;
+ case Result.BAD_PARAMS:
+ return ContextHubTransaction.TRANSACTION_FAILED_BAD_PARAMS;
+ case Result.NOT_INIT:
+ return ContextHubTransaction.TRANSACTION_FAILED_UNINITIALIZED;
+ case Result.TRANSACTION_PENDING:
+ return ContextHubTransaction.TRANSACTION_FAILED_PENDING;
+ case Result.TRANSACTION_FAILED:
+ case Result.UNKNOWN_FAILURE:
+ default: /* fall through */
+ return ContextHubTransaction.TRANSACTION_FAILED_UNKNOWN;
+ }
+ }
+}
diff --git a/com/android/server/location/ContextHubTransactionManager.java b/com/android/server/location/ContextHubTransactionManager.java
new file mode 100644
index 0000000..47d9d56
--- /dev/null
+++ b/com/android/server/location/ContextHubTransactionManager.java
@@ -0,0 +1,351 @@
+/*
+ * Copyright 2017 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.server.location;
+
+import android.hardware.contexthub.V1_0.IContexthub;
+import android.hardware.contexthub.V1_0.Result;
+import android.hardware.contexthub.V1_0.TransactionResult;
+import android.hardware.location.ContextHubTransaction;
+import android.hardware.location.IContextHubTransactionCallback;
+import android.hardware.location.NanoAppBinary;
+import android.hardware.location.NanoAppState;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayDeque;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Manages transactions at the Context Hub Service.
+ *
+ * This class maintains a queue of transaction requests made to the ContextHubService by clients,
+ * and executes them through the Context Hub. At any point in time, either the transaction queue is
+ * empty, or there is a pending transaction that is waiting for an asynchronous response from the
+ * hub. This class also handles synchronous errors and timeouts of each transaction.
+ *
+ * @hide
+ */
+/* package */ class ContextHubTransactionManager {
+ private static final String TAG = "ContextHubTransactionManager";
+
+ /*
+ * Maximum number of transaction requests that can be pending at a time
+ */
+ private static final int MAX_PENDING_REQUESTS = 10;
+
+ /*
+ * The proxy to talk to the Context Hub
+ */
+ private final IContexthub mContextHubProxy;
+
+ /*
+ * The manager for all clients for the service.
+ */
+ private final ContextHubClientManager mClientManager;
+
+ /*
+ * A queue containing the current transactions
+ */
+ private final ArrayDeque<ContextHubServiceTransaction> mTransactionQueue = new ArrayDeque<>();
+
+ /*
+ * The next available transaction ID
+ */
+ private final AtomicInteger mNextAvailableId = new AtomicInteger();
+
+ /*
+ * An executor and the future object for scheduling timeout timers
+ */
+ private final ScheduledThreadPoolExecutor mTimeoutExecutor = new ScheduledThreadPoolExecutor(1);
+ private ScheduledFuture<?> mTimeoutFuture = null;
+
+ /* package */ ContextHubTransactionManager(
+ IContexthub contextHubProxy, ContextHubClientManager clientManager) {
+ mContextHubProxy = contextHubProxy;
+ mClientManager = clientManager;
+ }
+
+ /**
+ * Creates a transaction for loading a nanoapp.
+ *
+ * @param contextHubId the ID of the hub to load the nanoapp to
+ * @param nanoAppBinary the binary of the nanoapp to load
+ * @param onCompleteCallback the client on complete callback
+ * @return the generated transaction
+ */
+ /* package */ ContextHubServiceTransaction createLoadTransaction(
+ int contextHubId, NanoAppBinary nanoAppBinary,
+ IContextHubTransactionCallback onCompleteCallback) {
+ return new ContextHubServiceTransaction(
+ mNextAvailableId.getAndIncrement(), ContextHubTransaction.TYPE_LOAD_NANOAPP) {
+ @Override
+ /* package */ int onTransact() {
+ android.hardware.contexthub.V1_0.NanoAppBinary hidlNanoAppBinary =
+ ContextHubServiceUtil.createHidlNanoAppBinary(nanoAppBinary);
+ try {
+ return mContextHubProxy.loadNanoApp(
+ contextHubId, hidlNanoAppBinary, this.getTransactionId());
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while trying to load nanoapp with ID 0x" +
+ Long.toHexString(nanoAppBinary.getNanoAppId()));
+ return Result.UNKNOWN_FAILURE;
+ }
+ }
+
+ @Override
+ /* package */ void onTimeout() {
+ onTransactionComplete(ContextHubTransaction.TRANSACTION_FAILED_TIMEOUT);
+ }
+
+ @Override
+ /* package */ void onTransactionComplete(int result) {
+ try {
+ onCompleteCallback.onTransactionComplete(result);
+ if (result == Result.OK) {
+ mClientManager.onNanoAppLoaded(contextHubId, nanoAppBinary.getNanoAppId());
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while calling client onTransactionComplete");
+ }
+ }
+ };
+ }
+
+ /**
+ * Creates a transaction for unloading a nanoapp.
+ *
+ * @param contextHubId the ID of the hub to load the nanoapp to
+ * @param nanoAppId the ID of the nanoapp to unload
+ * @param onCompleteCallback the client on complete callback
+ * @return the generated transaction
+ */
+ /* package */ ContextHubServiceTransaction createUnloadTransaction(
+ int contextHubId, long nanoAppId, IContextHubTransactionCallback onCompleteCallback) {
+ return new ContextHubServiceTransaction(
+ mNextAvailableId.getAndIncrement(), ContextHubTransaction.TYPE_UNLOAD_NANOAPP) {
+ @Override
+ /* package */ int onTransact() {
+ try {
+ return mContextHubProxy.unloadNanoApp(
+ contextHubId, nanoAppId, this.getTransactionId());
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while trying to unload nanoapp with ID 0x" +
+ Long.toHexString(nanoAppId));
+ return Result.UNKNOWN_FAILURE;
+ }
+ }
+
+ @Override
+ /* package */ void onTimeout() {
+ onTransactionComplete(ContextHubTransaction.TRANSACTION_FAILED_TIMEOUT);
+ }
+
+ @Override
+ /* package */ void onTransactionComplete(int result) {
+ try {
+ onCompleteCallback.onTransactionComplete(result);
+ if (result == Result.OK) {
+ mClientManager.onNanoAppUnloaded(contextHubId, nanoAppId);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while calling client onTransactionComplete");
+ }
+ }
+ };
+ }
+
+ /**
+ * Creates a transaction for querying for a list of nanoapps.
+ *
+ * @param contextHubId the ID of the hub to query
+ * @param onCompleteCallback the client on complete callback
+ * @return the generated transaction
+ */
+ /* package */ ContextHubServiceTransaction createQueryTransaction(
+ int contextHubId, IContextHubTransactionCallback onCompleteCallback) {
+ return new ContextHubServiceTransaction(
+ mNextAvailableId.getAndIncrement(), ContextHubTransaction.TYPE_QUERY_NANOAPPS) {
+ @Override
+ /* package */ int onTransact() {
+ try {
+ return mContextHubProxy.queryApps(contextHubId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while trying to query for nanoapps");
+ return Result.UNKNOWN_FAILURE;
+ }
+ }
+
+ @Override
+ /* package */ void onTimeout() {
+ onQueryResponse(ContextHubTransaction.TRANSACTION_FAILED_TIMEOUT,
+ Collections.emptyList());
+ }
+
+ @Override
+ /* package */ void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) {
+ try {
+ onCompleteCallback.onQueryResponse(result, nanoAppStateList);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while calling client onQueryComplete");
+ }
+ }
+ };
+ }
+
+ /**
+ * Adds a new transaction to the queue.
+ *
+ * If there was no pending transaction at the time, the transaction that was added will be
+ * started in this method.
+ *
+ * @param transaction the transaction to add
+ * @throws IllegalStateException if the queue is full
+ */
+ /* package */
+ synchronized void addTransaction(
+ ContextHubServiceTransaction transaction) throws IllegalStateException {
+ if (mTransactionQueue.size() == MAX_PENDING_REQUESTS) {
+ throw new IllegalStateException("Transaction transaction queue is full (capacity = "
+ + MAX_PENDING_REQUESTS + ")");
+ }
+ mTransactionQueue.add(transaction);
+
+ if (mTransactionQueue.size() == 1) {
+ startNextTransaction();
+ }
+ }
+
+ /**
+ * Handles a transaction response from a Context Hub.
+ *
+ * @param transactionId the transaction ID of the response
+ * @param result the result of the transaction
+ */
+ /* package */
+ synchronized void onTransactionResponse(int transactionId, int result) {
+ ContextHubServiceTransaction transaction = mTransactionQueue.peek();
+ if (transaction == null) {
+ Log.w(TAG, "Received unexpected transaction response (no transaction pending)");
+ return;
+ }
+ if (transaction.getTransactionId() != transactionId) {
+ Log.w(TAG, "Received unexpected transaction response (expected ID = "
+ + transaction.getTransactionId() + ", received ID = " + transactionId + ")");
+ return;
+ }
+
+ transaction.onTransactionComplete(result);
+ removeTransactionAndStartNext();
+ }
+
+ /**
+ * Handles a query response from a Context Hub.
+ *
+ * @param nanoAppStateList the list of nanoapps included in the response
+ */
+ /* package */
+ synchronized void onQueryResponse(List<NanoAppState> nanoAppStateList) {
+ ContextHubServiceTransaction transaction = mTransactionQueue.peek();
+ if (transaction == null) {
+ Log.w(TAG, "Received unexpected query response (no transaction pending)");
+ return;
+ }
+ if (transaction.getTransactionType() != ContextHubTransaction.TYPE_QUERY_NANOAPPS) {
+ Log.w(TAG, "Received unexpected query response (expected " + transaction + ")");
+ return;
+ }
+
+ transaction.onQueryResponse(TransactionResult.SUCCESS, nanoAppStateList);
+ removeTransactionAndStartNext();
+ }
+
+ /**
+ * Handles a hub reset event by stopping a pending transaction and starting the next.
+ */
+ /* package */
+ synchronized void onHubReset() {
+ ContextHubServiceTransaction transaction = mTransactionQueue.peek();
+ if (transaction == null) {
+ return;
+ }
+
+ removeTransactionAndStartNext();
+ }
+
+ /**
+ * Pops the front transaction from the queue and starts the next pending transaction request.
+ *
+ * Removing elements from the transaction queue must only be done through this method. When a
+ * pending transaction is removed, the timeout timer is cancelled and the transaction is marked
+ * complete.
+ *
+ * It is assumed that the transaction queue is non-empty when this method is invoked, and that
+ * the caller has obtained a lock on this ContextHubTransactionManager object.
+ */
+ private void removeTransactionAndStartNext() {
+ mTimeoutFuture.cancel(false /* mayInterruptIfRunning */);
+
+ ContextHubServiceTransaction transaction = mTransactionQueue.remove();
+ transaction.setComplete();
+
+ if (!mTransactionQueue.isEmpty()) {
+ startNextTransaction();
+ }
+ }
+
+ /**
+ * Starts the next pending transaction request.
+ *
+ * Starting new transactions must only be done through this method. This method continues to
+ * process the transaction queue as long as there are pending requests, and no transaction is
+ * pending.
+ *
+ * It is assumed that the caller has obtained a lock on this ContextHubTransactionManager
+ * object.
+ */
+ private void startNextTransaction() {
+ int result = Result.UNKNOWN_FAILURE;
+ while (result != Result.OK && !mTransactionQueue.isEmpty()) {
+ ContextHubServiceTransaction transaction = mTransactionQueue.peek();
+ result = transaction.onTransact();
+
+ if (result == Result.OK) {
+ Runnable onTimeoutFunc = () -> {
+ synchronized (this) {
+ if (!transaction.isComplete()) {
+ Log.d(TAG, transaction + " timed out");
+ transaction.onTimeout();
+
+ removeTransactionAndStartNext();
+ }
+ }
+ };
+
+ long timeoutSeconds = transaction.getTimeout(TimeUnit.SECONDS);
+ mTimeoutFuture = mTimeoutExecutor.schedule(onTimeoutFunc, timeoutSeconds,
+ TimeUnit.SECONDS);
+ } else {
+ mTransactionQueue.remove();
+ }
+ }
+ }
+}
diff --git a/com/android/server/locksettings/LockSettingsService.java b/com/android/server/locksettings/LockSettingsService.java
index a1a0106..60f451a 100644
--- a/com/android/server/locksettings/LockSettingsService.java
+++ b/com/android/server/locksettings/LockSettingsService.java
@@ -603,160 +603,156 @@
}
private void migrateOldData() {
- try {
- // These Settings moved before multi-user was enabled, so we only have to do it for the
- // root user.
- if (getString("migrated", null, 0) == null) {
- final ContentResolver cr = mContext.getContentResolver();
- for (String validSetting : VALID_SETTINGS) {
- String value = Settings.Secure.getString(cr, validSetting);
- if (value != null) {
- setString(validSetting, value, 0);
- }
+ // These Settings moved before multi-user was enabled, so we only have to do it for the
+ // root user.
+ if (getString("migrated", null, 0) == null) {
+ final ContentResolver cr = mContext.getContentResolver();
+ for (String validSetting : VALID_SETTINGS) {
+ String value = Settings.Secure.getString(cr, validSetting);
+ if (value != null) {
+ setString(validSetting, value, 0);
}
- // No need to move the password / pattern files. They're already in the right place.
- setString("migrated", "true", 0);
- Slog.i(TAG, "Migrated lock settings to new location");
}
+ // No need to move the password / pattern files. They're already in the right place.
+ setString("migrated", "true", 0);
+ Slog.i(TAG, "Migrated lock settings to new location");
+ }
- // These Settings changed after multi-user was enabled, hence need to be moved per user.
- if (getString("migrated_user_specific", null, 0) == null) {
- final ContentResolver cr = mContext.getContentResolver();
- List<UserInfo> users = mUserManager.getUsers();
- for (int user = 0; user < users.size(); user++) {
- // Migrate owner info
- final int userId = users.get(user).id;
- final String OWNER_INFO = Secure.LOCK_SCREEN_OWNER_INFO;
- String ownerInfo = Settings.Secure.getStringForUser(cr, OWNER_INFO, userId);
- if (!TextUtils.isEmpty(ownerInfo)) {
- setString(OWNER_INFO, ownerInfo, userId);
- Settings.Secure.putStringForUser(cr, OWNER_INFO, "", userId);
- }
-
- // Migrate owner info enabled. Note there was a bug where older platforms only
- // stored this value if the checkbox was toggled at least once. The code detects
- // this case by handling the exception.
- final String OWNER_INFO_ENABLED = Secure.LOCK_SCREEN_OWNER_INFO_ENABLED;
- boolean enabled;
- try {
- int ivalue = Settings.Secure.getIntForUser(cr, OWNER_INFO_ENABLED, userId);
- enabled = ivalue != 0;
- setLong(OWNER_INFO_ENABLED, enabled ? 1 : 0, userId);
- } catch (SettingNotFoundException e) {
- // Setting was never stored. Store it if the string is not empty.
- if (!TextUtils.isEmpty(ownerInfo)) {
- setLong(OWNER_INFO_ENABLED, 1, userId);
- }
- }
- Settings.Secure.putIntForUser(cr, OWNER_INFO_ENABLED, 0, userId);
- }
- // No need to move the password / pattern files. They're already in the right place.
- setString("migrated_user_specific", "true", 0);
- Slog.i(TAG, "Migrated per-user lock settings to new location");
- }
-
- // Migrates biometric weak such that the fallback mechanism becomes the primary.
- if (getString("migrated_biometric_weak", null, 0) == null) {
- List<UserInfo> users = mUserManager.getUsers();
- for (int i = 0; i < users.size(); i++) {
- int userId = users.get(i).id;
- long type = getLong(LockPatternUtils.PASSWORD_TYPE_KEY,
- DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
- userId);
- long alternateType = getLong(LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY,
- DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
- userId);
- if (type == DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK) {
- setLong(LockPatternUtils.PASSWORD_TYPE_KEY,
- alternateType,
- userId);
- }
- setLong(LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY,
- DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
- userId);
- }
- setString("migrated_biometric_weak", "true", 0);
- Slog.i(TAG, "Migrated biometric weak to use the fallback instead");
- }
-
- // Migrates lockscreen.disabled. Prior to M, the flag was ignored when more than one
- // user was present on the system, so if we're upgrading to M and there is more than one
- // user we disable the flag to remain consistent.
- if (getString("migrated_lockscreen_disabled", null, 0) == null) {
- final List<UserInfo> users = mUserManager.getUsers();
- final int userCount = users.size();
- int switchableUsers = 0;
- for (int i = 0; i < userCount; i++) {
- if (users.get(i).supportsSwitchTo()) {
- switchableUsers++;
- }
+ // These Settings changed after multi-user was enabled, hence need to be moved per user.
+ if (getString("migrated_user_specific", null, 0) == null) {
+ final ContentResolver cr = mContext.getContentResolver();
+ List<UserInfo> users = mUserManager.getUsers();
+ for (int user = 0; user < users.size(); user++) {
+ // Migrate owner info
+ final int userId = users.get(user).id;
+ final String OWNER_INFO = Secure.LOCK_SCREEN_OWNER_INFO;
+ String ownerInfo = Settings.Secure.getStringForUser(cr, OWNER_INFO, userId);
+ if (!TextUtils.isEmpty(ownerInfo)) {
+ setString(OWNER_INFO, ownerInfo, userId);
+ Settings.Secure.putStringForUser(cr, OWNER_INFO, "", userId);
}
- if (switchableUsers > 1) {
- for (int i = 0; i < userCount; i++) {
- int id = users.get(i).id;
-
- if (getBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id)) {
- setBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id);
- }
- }
- }
-
- setString("migrated_lockscreen_disabled", "true", 0);
- Slog.i(TAG, "Migrated lockscreen disabled flag");
- }
-
- final List<UserInfo> users = mUserManager.getUsers();
- for (int i = 0; i < users.size(); i++) {
- final UserInfo userInfo = users.get(i);
- if (userInfo.isManagedProfile() && mStorage.hasChildProfileLock(userInfo.id)) {
- // When managed profile has a unified lock, the password quality stored has 2
- // possibilities only.
- // 1). PASSWORD_QUALITY_UNSPECIFIED, which is upgraded from dp2, and we are
- // going to set it back to PASSWORD_QUALITY_ALPHANUMERIC.
- // 2). PASSWORD_QUALITY_ALPHANUMERIC, which is the actual password quality for
- // unified lock.
- final long quality = getLong(LockPatternUtils.PASSWORD_TYPE_KEY,
- DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userInfo.id);
- if (quality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
- // Only possible when it's upgraded from nyc dp3
- Slog.i(TAG, "Migrated tied profile lock type");
- setLong(LockPatternUtils.PASSWORD_TYPE_KEY,
- DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, userInfo.id);
- } else if (quality != DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC) {
- // It should not happen
- Slog.e(TAG, "Invalid tied profile lock type: " + quality);
- }
- }
+ // Migrate owner info enabled. Note there was a bug where older platforms only
+ // stored this value if the checkbox was toggled at least once. The code detects
+ // this case by handling the exception.
+ final String OWNER_INFO_ENABLED = Secure.LOCK_SCREEN_OWNER_INFO_ENABLED;
+ boolean enabled;
try {
- final String alias = LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT + userInfo.id;
- java.security.KeyStore keyStore =
- java.security.KeyStore.getInstance("AndroidKeyStore");
- keyStore.load(null);
- if (keyStore.containsAlias(alias)) {
- keyStore.deleteEntry(alias);
+ int ivalue = Settings.Secure.getIntForUser(cr, OWNER_INFO_ENABLED, userId);
+ enabled = ivalue != 0;
+ setLong(OWNER_INFO_ENABLED, enabled ? 1 : 0, userId);
+ } catch (SettingNotFoundException e) {
+ // Setting was never stored. Store it if the string is not empty.
+ if (!TextUtils.isEmpty(ownerInfo)) {
+ setLong(OWNER_INFO_ENABLED, 1, userId);
}
- } catch (KeyStoreException | NoSuchAlgorithmException |
- CertificateException | IOException e) {
- Slog.e(TAG, "Unable to remove tied profile key", e);
+ }
+ Settings.Secure.putIntForUser(cr, OWNER_INFO_ENABLED, 0, userId);
+ }
+ // No need to move the password / pattern files. They're already in the right place.
+ setString("migrated_user_specific", "true", 0);
+ Slog.i(TAG, "Migrated per-user lock settings to new location");
+ }
+
+ // Migrates biometric weak such that the fallback mechanism becomes the primary.
+ if (getString("migrated_biometric_weak", null, 0) == null) {
+ List<UserInfo> users = mUserManager.getUsers();
+ for (int i = 0; i < users.size(); i++) {
+ int userId = users.get(i).id;
+ long type = getLong(LockPatternUtils.PASSWORD_TYPE_KEY,
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
+ userId);
+ long alternateType = getLong(LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY,
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
+ userId);
+ if (type == DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK) {
+ setLong(LockPatternUtils.PASSWORD_TYPE_KEY,
+ alternateType,
+ userId);
+ }
+ setLong(LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY,
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
+ userId);
+ }
+ setString("migrated_biometric_weak", "true", 0);
+ Slog.i(TAG, "Migrated biometric weak to use the fallback instead");
+ }
+
+ // Migrates lockscreen.disabled. Prior to M, the flag was ignored when more than one
+ // user was present on the system, so if we're upgrading to M and there is more than one
+ // user we disable the flag to remain consistent.
+ if (getString("migrated_lockscreen_disabled", null, 0) == null) {
+ final List<UserInfo> users = mUserManager.getUsers();
+ final int userCount = users.size();
+ int switchableUsers = 0;
+ for (int i = 0; i < userCount; i++) {
+ if (users.get(i).supportsSwitchTo()) {
+ switchableUsers++;
}
}
- boolean isWatch = mContext.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_WATCH);
- // Wear used to set DISABLE_LOCKSCREEN to 'true', but because Wear now allows accounts
- // and device management the lockscreen must be re-enabled now for users that upgrade.
- if (isWatch && getString("migrated_wear_lockscreen_disabled", null, 0) == null) {
- final int userCount = users.size();
+ if (switchableUsers > 1) {
for (int i = 0; i < userCount; i++) {
int id = users.get(i).id;
- setBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id);
+
+ if (getBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id)) {
+ setBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id);
+ }
}
- setString("migrated_wear_lockscreen_disabled", "true", 0);
- Slog.i(TAG, "Migrated lockscreen_disabled for Wear devices");
}
- } catch (RemoteException re) {
- Slog.e(TAG, "Unable to migrate old data", re);
+
+ setString("migrated_lockscreen_disabled", "true", 0);
+ Slog.i(TAG, "Migrated lockscreen disabled flag");
+ }
+
+ final List<UserInfo> users = mUserManager.getUsers();
+ for (int i = 0; i < users.size(); i++) {
+ final UserInfo userInfo = users.get(i);
+ if (userInfo.isManagedProfile() && mStorage.hasChildProfileLock(userInfo.id)) {
+ // When managed profile has a unified lock, the password quality stored has 2
+ // possibilities only.
+ // 1). PASSWORD_QUALITY_UNSPECIFIED, which is upgraded from dp2, and we are
+ // going to set it back to PASSWORD_QUALITY_ALPHANUMERIC.
+ // 2). PASSWORD_QUALITY_ALPHANUMERIC, which is the actual password quality for
+ // unified lock.
+ final long quality = getLong(LockPatternUtils.PASSWORD_TYPE_KEY,
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userInfo.id);
+ if (quality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
+ // Only possible when it's upgraded from nyc dp3
+ Slog.i(TAG, "Migrated tied profile lock type");
+ setLong(LockPatternUtils.PASSWORD_TYPE_KEY,
+ DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, userInfo.id);
+ } else if (quality != DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC) {
+ // It should not happen
+ Slog.e(TAG, "Invalid tied profile lock type: " + quality);
+ }
+ }
+ try {
+ final String alias = LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT + userInfo.id;
+ java.security.KeyStore keyStore =
+ java.security.KeyStore.getInstance("AndroidKeyStore");
+ keyStore.load(null);
+ if (keyStore.containsAlias(alias)) {
+ keyStore.deleteEntry(alias);
+ }
+ } catch (KeyStoreException | NoSuchAlgorithmException |
+ CertificateException | IOException e) {
+ Slog.e(TAG, "Unable to remove tied profile key", e);
+ }
+ }
+
+ boolean isWatch = mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_WATCH);
+ // Wear used to set DISABLE_LOCKSCREEN to 'true', but because Wear now allows accounts
+ // and device management the lockscreen must be re-enabled now for users that upgrade.
+ if (isWatch && getString("migrated_wear_lockscreen_disabled", null, 0) == null) {
+ final int userCount = users.size();
+ for (int i = 0; i < userCount; i++) {
+ int id = users.get(i).id;
+ setBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id);
+ }
+ setString("migrated_wear_lockscreen_disabled", "true", 0);
+ Slog.i(TAG, "Migrated lockscreen_disabled for Wear devices");
}
}
@@ -868,7 +864,7 @@
}
@Override
- public boolean getSeparateProfileChallengeEnabled(int userId) throws RemoteException {
+ public boolean getSeparateProfileChallengeEnabled(int userId) {
checkReadPermission(SEPARATE_PROFILE_CHALLENGE_KEY, userId);
synchronized (mSeparateChallengeLock) {
return getBoolean(SEPARATE_PROFILE_CHALLENGE_KEY, false, userId);
@@ -877,7 +873,7 @@
@Override
public void setSeparateProfileChallengeEnabled(int userId, boolean enabled,
- String managedUserPassword) throws RemoteException {
+ String managedUserPassword) {
checkWritePermission(userId);
synchronized (mSeparateChallengeLock) {
setBoolean(SEPARATE_PROFILE_CHALLENGE_KEY, enabled, userId);
@@ -891,19 +887,19 @@
}
@Override
- public void setBoolean(String key, boolean value, int userId) throws RemoteException {
+ public void setBoolean(String key, boolean value, int userId) {
checkWritePermission(userId);
setStringUnchecked(key, userId, value ? "1" : "0");
}
@Override
- public void setLong(String key, long value, int userId) throws RemoteException {
+ public void setLong(String key, long value, int userId) {
checkWritePermission(userId);
setStringUnchecked(key, userId, Long.toString(value));
}
@Override
- public void setString(String key, String value, int userId) throws RemoteException {
+ public void setString(String key, String value, int userId) {
checkWritePermission(userId);
setStringUnchecked(key, userId, value);
}
@@ -2103,7 +2099,7 @@
long handle = getSyntheticPasswordHandleLocked(userId);
authResult = mSpManager.unwrapPasswordBasedSyntheticPassword(
- getGateKeeperService(), handle, userCredential, userId);
+ getGateKeeperService(), handle, userCredential, userId, progressCallback);
if (authResult.credentialType != credentialType) {
Slog.e(TAG, "Credential type mismatch.");
@@ -2126,9 +2122,6 @@
}
if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
- if (progressCallback != null) {
- progressCallback.onCredentialVerified();
- }
notifyActivePasswordMetricsAvailable(userCredential, userId);
unlockKeystore(authResult.authToken.deriveKeyStorePassword(), userId);
@@ -2227,7 +2220,7 @@
}
long handle = getSyntheticPasswordHandleLocked(userId);
AuthenticationResult authResult = mSpManager.unwrapPasswordBasedSyntheticPassword(
- getGateKeeperService(), handle, savedCredential, userId);
+ getGateKeeperService(), handle, savedCredential, userId, null);
VerifyCredentialResponse response = authResult.gkResponse;
AuthenticationToken auth = authResult.authToken;
@@ -2281,7 +2274,7 @@
} else /* isSyntheticPasswordBasedCredentialLocked(userId) */ {
long pwdHandle = getSyntheticPasswordHandleLocked(userId);
auth = mSpManager.unwrapPasswordBasedSyntheticPassword(getGateKeeperService(),
- pwdHandle, null, userId).authToken;
+ pwdHandle, null, userId, null).authToken;
}
}
if (isSyntheticPasswordBasedCredentialLocked(userId)) {
diff --git a/com/android/server/locksettings/SyntheticPasswordManager.java b/com/android/server/locksettings/SyntheticPasswordManager.java
index 1a1aa56..7a3a746 100644
--- a/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -781,7 +781,8 @@
* unknown. Caller might choose to validate it by examining AuthenticationResult.credentialType
*/
public AuthenticationResult unwrapPasswordBasedSyntheticPassword(IGateKeeperService gatekeeper,
- long handle, String credential, int userId) throws RemoteException {
+ long handle, String credential, int userId,
+ ICheckCredentialProgressCallback progressCallback) throws RemoteException {
if (credential == null) {
credential = DEFAULT_PASSWORD;
}
@@ -841,7 +842,11 @@
applicationId = transformUnderSecdiscardable(pwdToken,
loadSecdiscardable(handle, userId));
}
-
+ // Supplied credential passes first stage weaver/gatekeeper check so it should be correct.
+ // Notify the callback so the keyguard UI can proceed immediately.
+ if (progressCallback != null) {
+ progressCallback.onCredentialVerified();
+ }
result.authToken = unwrapSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_PASSWORD_BASED,
applicationId, sid, userId);
diff --git a/com/android/server/media/AudioPlayerStateMonitor.java b/com/android/server/media/AudioPlayerStateMonitor.java
index be223f1..7881a95 100644
--- a/com/android/server/media/AudioPlayerStateMonitor.java
+++ b/com/android/server/media/AudioPlayerStateMonitor.java
@@ -16,7 +16,7 @@
package com.android.server.media;
-import android.annotation.Nullable;
+import android.annotation.NonNull;
import android.content.Context;
import android.media.AudioPlaybackConfiguration;
import android.media.IAudioService;
@@ -27,14 +27,14 @@
import android.os.Message;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.IntArray;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import java.io.PrintWriter;
-import java.util.HashSet;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -49,47 +49,57 @@
private static AudioPlayerStateMonitor sInstance = new AudioPlayerStateMonitor();
/**
- * Called when the state of audio player is changed.
+ * Listener for handling the active state changes of audio players.
*/
- interface OnAudioPlayerStateChangedListener {
- void onAudioPlayerStateChanged(
- int uid, int prevState, @Nullable AudioPlaybackConfiguration config);
+ interface OnAudioPlayerActiveStateChangedListener {
+ /**
+ * Called when the active state of audio player is changed.
+ *
+ * @param config The audio playback configuration for the audio player for which active
+ * state was changed. If {@param isRemoved} is {@code true}, this holds
+ * outdated information.
+ * @param isRemoved {@code true} if the audio player is removed.
+ */
+ void onAudioPlayerActiveStateChanged(
+ @NonNull AudioPlaybackConfiguration config, boolean isRemoved);
}
private final static class MessageHandler extends Handler {
- private static final int MSG_AUDIO_PLAYER_STATE_CHANGED = 1;
+ private static final int MSG_AUDIO_PLAYER_ACTIVE_STATE_CHANGED = 1;
- private final OnAudioPlayerStateChangedListener mListsner;
+ private final OnAudioPlayerActiveStateChangedListener mListener;
- public MessageHandler(Looper looper, OnAudioPlayerStateChangedListener listener) {
+ MessageHandler(Looper looper, OnAudioPlayerActiveStateChangedListener listener) {
super(looper);
- mListsner = listener;
+ mListener = listener;
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case MSG_AUDIO_PLAYER_STATE_CHANGED:
- mListsner.onAudioPlayerStateChanged(
- msg.arg1, msg.arg2, (AudioPlaybackConfiguration) msg.obj);
+ case MSG_AUDIO_PLAYER_ACTIVE_STATE_CHANGED:
+ mListener.onAudioPlayerActiveStateChanged((AudioPlaybackConfiguration) msg.obj,
+ msg.arg1 != 0);
break;
}
}
- public void sendAudioPlayerStateChangedMessage(int uid, int prevState,
- AudioPlaybackConfiguration config) {
- obtainMessage(MSG_AUDIO_PLAYER_STATE_CHANGED, uid, prevState, config).sendToTarget();
+ void sendAudioPlayerActiveStateChangedMessage(
+ final AudioPlaybackConfiguration config, final boolean isRemoved) {
+ obtainMessage(MSG_AUDIO_PLAYER_ACTIVE_STATE_CHANGED,
+ isRemoved ? 1 : 0, 0 /* unused */, config).sendToTarget();
}
}
private final Object mLock = new Object();
@GuardedBy("mLock")
- private final Map<OnAudioPlayerStateChangedListener, MessageHandler> mListenerMap =
- new HashMap<>();
+ private final Map<OnAudioPlayerActiveStateChangedListener, MessageHandler> mListenerMap =
+ new ArrayMap<>();
@GuardedBy("mLock")
- private final Map<Integer, Integer> mAudioPlayerStates = new HashMap<>();
+ private final Set<Integer> mActiveAudioUids = new ArraySet<>();
@GuardedBy("mLock")
- private final Map<Integer, HashSet<Integer>> mAudioPlayersForUid = new HashMap<>();
+ private ArrayMap<Integer, AudioPlaybackConfiguration> mPrevActiveAudioPlaybackConfigs =
+ new ArrayMap<>();
// Sorted array of UIDs that had active audio playback. (i.e. playing an audio/video)
// The UID whose audio playback becomes active at the last comes first.
// TODO(b/35278867): Find and use unique identifier for apps because apps may share the UID.
@@ -122,32 +132,24 @@
}
final long token = Binder.clearCallingIdentity();
try {
- final Map<Integer, Integer> prevAudioPlayerStates = new HashMap<>(mAudioPlayerStates);
- final Map<Integer, HashSet<Integer>> prevAudioPlayersForUid =
- new HashMap<>(mAudioPlayersForUid);
synchronized (mLock) {
- mAudioPlayerStates.clear();
- mAudioPlayersForUid.clear();
+ // Update mActiveAudioUids
+ mActiveAudioUids.clear();
+ ArrayMap<Integer, AudioPlaybackConfiguration> activeAudioPlaybackConfigs =
+ new ArrayMap<>();
for (AudioPlaybackConfiguration config : configs) {
- int pii = config.getPlayerInterfaceId();
- int uid = config.getClientUid();
- mAudioPlayerStates.put(pii, config.getPlayerState());
- HashSet<Integer> players = mAudioPlayersForUid.get(uid);
- if (players == null) {
- players = new HashSet<Integer>();
- players.add(pii);
- mAudioPlayersForUid.put(uid, players);
- } else {
- players.add(pii);
+ if (config.isActive()) {
+ mActiveAudioUids.add(config.getClientUid());
+ activeAudioPlaybackConfigs.put(config.getPlayerInterfaceId(), config);
}
}
- for (AudioPlaybackConfiguration config : configs) {
- if (!config.isActive()) {
- continue;
- }
- int uid = config.getClientUid();
- if (!isActiveState(prevAudioPlayerStates.get(config.getPlayerInterfaceId()))) {
+ // Update mSortedAuioPlaybackClientUids.
+ for (int i = 0; i < activeAudioPlaybackConfigs.size(); ++i) {
+ AudioPlaybackConfiguration config = activeAudioPlaybackConfigs.valueAt(i);
+ final int uid = config.getClientUid();
+ if (!mPrevActiveAudioPlaybackConfigs.containsKey(
+ config.getPlayerInterfaceId())) {
if (DEBUG) {
Log.d(TAG, "Found a new active media playback. " +
AudioPlaybackConfiguration.toLogFriendlyString(config));
@@ -163,40 +165,21 @@
mSortedAudioPlaybackClientUids.add(0, uid);
}
}
- // Notify the change of audio player states.
+ // Notify the active state change of audio players.
for (AudioPlaybackConfiguration config : configs) {
- final Integer prevState = prevAudioPlayerStates.get(config.getPlayerInterfaceId());
- final int prevStateInt =
- (prevState == null) ? AudioPlaybackConfiguration.PLAYER_STATE_UNKNOWN :
- prevState.intValue();
- if (prevStateInt != config.getPlayerState()) {
- sendAudioPlayerStateChangedMessageLocked(
- config.getClientUid(), prevStateInt, config);
+ final int pii = config.getPlayerInterfaceId();
+ boolean wasActive = mPrevActiveAudioPlaybackConfigs.remove(pii) != null;
+ if (wasActive != config.isActive()) {
+ sendAudioPlayerActiveStateChangedMessageLocked(
+ config, /* isRemoved */ false);
}
}
- for (Integer prevUid : prevAudioPlayersForUid.keySet()) {
- // If all players for prevUid is removed, notify the prev state was
- // PLAYER_STATE_STARTED only when there were a player whose state was
- // PLAYER_STATE_STARTED, otherwise any inactive state is okay to notify.
- if (!mAudioPlayersForUid.containsKey(prevUid)) {
- Set<Integer> prevPlayers = prevAudioPlayersForUid.get(prevUid);
- int prevState = AudioPlaybackConfiguration.PLAYER_STATE_UNKNOWN;
- for (int pii : prevPlayers) {
- Integer state = prevAudioPlayerStates.get(pii);
- if (state == null) {
- continue;
- }
- if (state == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
- prevState = state;
- break;
- } else if (prevState
- == AudioPlaybackConfiguration.PLAYER_STATE_UNKNOWN) {
- prevState = state;
- }
- }
- sendAudioPlayerStateChangedMessageLocked(prevUid, prevState, null);
- }
+ for (AudioPlaybackConfiguration config : mPrevActiveAudioPlaybackConfigs.values()) {
+ sendAudioPlayerActiveStateChangedMessageLocked(config, /* isRemoved */ true);
}
+
+ // Update mPrevActiveAudioPlaybackConfigs
+ mPrevActiveAudioPlaybackConfigs = activeAudioPlaybackConfigs;
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -204,9 +187,10 @@
}
/**
- * Registers OnAudioPlayerStateChangedListener.
+ * Registers OnAudioPlayerActiveStateChangedListener.
*/
- public void registerListener(OnAudioPlayerStateChangedListener listener, Handler handler) {
+ public void registerListener(
+ OnAudioPlayerActiveStateChangedListener listener, Handler handler) {
synchronized (mLock) {
mListenerMap.put(listener, new MessageHandler((handler == null) ?
Looper.myLooper() : handler.getLooper(), listener));
@@ -214,9 +198,9 @@
}
/**
- * Unregisters OnAudioPlayerStateChangedListener.
+ * Unregisters OnAudioPlayerActiveStateChangedListener.
*/
- public void unregisterListener(OnAudioPlayerStateChangedListener listener) {
+ public void unregisterListener(OnAudioPlayerActiveStateChangedListener listener) {
synchronized (mLock) {
mListenerMap.remove(listener);
}
@@ -239,16 +223,7 @@
*/
public boolean isPlaybackActive(int uid) {
synchronized (mLock) {
- Set<Integer> players = mAudioPlayersForUid.get(uid);
- if (players == null) {
- return false;
- }
- for (Integer pii : players) {
- if (isActiveState(mAudioPlayerStates.get(pii))) {
- return true;
- }
- }
- return false;
+ return mActiveAudioUids.contains(uid);
}
}
@@ -314,14 +289,10 @@
}
}
- private void sendAudioPlayerStateChangedMessageLocked(
- final int uid, final int prevState, final AudioPlaybackConfiguration config) {
+ private void sendAudioPlayerActiveStateChangedMessageLocked(
+ final AudioPlaybackConfiguration config, final boolean isRemoved) {
for (MessageHandler messageHandler : mListenerMap.values()) {
- messageHandler.sendAudioPlayerStateChangedMessage(uid, prevState, config);
+ messageHandler.sendAudioPlayerActiveStateChangedMessage(config, isRemoved);
}
}
-
- private static boolean isActiveState(Integer state) {
- return state != null && state.equals(AudioPlaybackConfiguration.PLAYER_STATE_STARTED);
- }
}
diff --git a/com/android/server/media/MediaRouterService.java b/com/android/server/media/MediaRouterService.java
index 3c9e1d4..0b089fb 100644
--- a/com/android/server/media/MediaRouterService.java
+++ b/com/android/server/media/MediaRouterService.java
@@ -19,7 +19,7 @@
import com.android.internal.util.DumpUtils;
import com.android.server.Watchdog;
-import android.annotation.Nullable;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -100,7 +100,9 @@
private final IAudioService mAudioService;
private final AudioPlayerStateMonitor mAudioPlayerStateMonitor;
private final Handler mHandler = new Handler();
- private final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo();
+ private final AudioRoutesInfo mAudioRoutesInfo = new AudioRoutesInfo();
+ private final IntArray mActivePlayerMinPriorityQueue = new IntArray();
+ private final IntArray mActivePlayerUidMinPriorityQueue = new IntArray();
public MediaRouterService(Context context) {
mContext = context;
@@ -111,7 +113,7 @@
mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance();
mAudioPlayerStateMonitor.registerListener(
- new AudioPlayerStateMonitor.OnAudioPlayerStateChangedListener() {
+ new AudioPlayerStateMonitor.OnAudioPlayerActiveStateChangedListener() {
static final long WAIT_MS = 500;
final Runnable mRestoreBluetoothA2dpRunnable = new Runnable() {
@Override
@@ -121,39 +123,41 @@
};
@Override
- public void onAudioPlayerStateChanged(
- int uid, int prevState, @Nullable AudioPlaybackConfiguration config) {
+ public void onAudioPlayerActiveStateChanged(
+ @NonNull AudioPlaybackConfiguration config, boolean isRemoved) {
+ final boolean active = !isRemoved && config.isActive();
+ final int pii = config.getPlayerInterfaceId();
+ final int uid = config.getClientUid();
+
+ final int idx = mActivePlayerMinPriorityQueue.indexOf(pii);
+ // Keep the latest active player and its uid at the end of the queue.
+ if (idx >= 0) {
+ mActivePlayerMinPriorityQueue.remove(idx);
+ mActivePlayerUidMinPriorityQueue.remove(idx);
+ }
+
int restoreUid = -1;
- boolean active = config == null ? false : config.isActive();
if (active) {
+ mActivePlayerMinPriorityQueue.add(config.getPlayerInterfaceId());
+ mActivePlayerUidMinPriorityQueue.add(uid);
restoreUid = uid;
- } else if (prevState != AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
- // Noting to do if the prev state is not an active state.
- return;
- } else {
- IntArray sortedAudioPlaybackClientUids =
- mAudioPlayerStateMonitor.getSortedAudioPlaybackClientUids();
- for (int i = 0; i < sortedAudioPlaybackClientUids.size(); ++i) {
- if (mAudioPlayerStateMonitor.isPlaybackActive(
- sortedAudioPlaybackClientUids.get(i))) {
- restoreUid = sortedAudioPlaybackClientUids.get(i);
- break;
- }
- }
+ } else if (mActivePlayerUidMinPriorityQueue.size() > 0) {
+ restoreUid = mActivePlayerUidMinPriorityQueue.get(
+ mActivePlayerUidMinPriorityQueue.size() - 1);
}
mHandler.removeCallbacks(mRestoreBluetoothA2dpRunnable);
if (restoreUid >= 0) {
restoreRoute(restoreUid);
if (DEBUG) {
- Slog.d(TAG, "onAudioPlayerStateChanged: " + "uid " + uid
- + " active " + active + " restoring " + restoreUid);
+ Slog.d(TAG, "onAudioPlayerActiveStateChanged: " + "uid=" + uid
+ + ", active=" + active + ", restoreUid=" + restoreUid);
}
} else {
mHandler.postDelayed(mRestoreBluetoothA2dpRunnable, WAIT_MS);
if (DEBUG) {
- Slog.d(TAG, "onAudioPlayerStateChanged: " + "uid " + uid
- + " active " + active + " delaying");
+ Slog.d(TAG, "onAudioPlayerActiveStateChanged: " + "uid=" + uid
+ + ", active=" + active + ", delaying");
}
}
}
@@ -166,7 +170,7 @@
@Override
public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) {
synchronized (mLock) {
- if (newRoutes.mainType != mCurAudioRoutesInfo.mainType) {
+ if (newRoutes.mainType != mAudioRoutesInfo.mainType) {
if ((newRoutes.mainType & (AudioRoutesInfo.MAIN_HEADSET
| AudioRoutesInfo.MAIN_HEADPHONES
| AudioRoutesInfo.MAIN_USB)) == 0) {
@@ -176,10 +180,10 @@
// headset was plugged in.
mGlobalBluetoothA2dpOn = false;
}
- mCurAudioRoutesInfo.mainType = newRoutes.mainType;
+ mAudioRoutesInfo.mainType = newRoutes.mainType;
}
if (!TextUtils.equals(
- newRoutes.bluetoothName, mCurAudioRoutesInfo.bluetoothName)) {
+ newRoutes.bluetoothName, mAudioRoutesInfo.bluetoothName)) {
if (newRoutes.bluetoothName == null) {
// BT was disconnected.
mGlobalBluetoothA2dpOn = false;
@@ -187,8 +191,14 @@
// BT was connected or changed.
mGlobalBluetoothA2dpOn = true;
}
- mCurAudioRoutesInfo.bluetoothName = newRoutes.bluetoothName;
+ mAudioRoutesInfo.bluetoothName = newRoutes.bluetoothName;
}
+ // Although a Bluetooth device is connected before a new audio playback is
+ // started, dispatchAudioRoutChanged() can be called after
+ // onAudioPlayerActiveStateChanged(). That causes restoreBluetoothA2dp()
+ // is called before mGlobalBluetoothA2dpOn is updated.
+ // Calling restoreBluetoothA2dp() here could prevent that.
+ restoreBluetoothA2dp();
}
}
});
@@ -405,12 +415,17 @@
void restoreBluetoothA2dp() {
try {
+ boolean btConnected = false;
boolean a2dpOn = false;
synchronized (mLock) {
+ btConnected = mAudioRoutesInfo.bluetoothName != null;
a2dpOn = mGlobalBluetoothA2dpOn;
}
- Slog.v(TAG, "restoreBluetoothA2dp(" + a2dpOn + ")");
- mAudioService.setBluetoothA2dpOn(a2dpOn);
+ // We don't need to change a2dp status when bluetooth is not connected.
+ if (btConnected) {
+ Slog.v(TAG, "restoreBluetoothA2dp(" + a2dpOn + ")");
+ mAudioService.setBluetoothA2dpOn(a2dpOn);
+ }
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException while calling setBluetoothA2dpOn.");
}
diff --git a/com/android/server/media/MediaSessionService.java b/com/android/server/media/MediaSessionService.java
index f6a81d0..06f4f5e 100644
--- a/com/android/server/media/MediaSessionService.java
+++ b/com/android/server/media/MediaSessionService.java
@@ -16,7 +16,6 @@
package com.android.server.media;
-import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.INotificationManager;
import android.app.KeyguardManager;
@@ -138,23 +137,19 @@
mAudioService = getAudioService();
mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance();
mAudioPlayerStateMonitor.registerListener(
- new AudioPlayerStateMonitor.OnAudioPlayerStateChangedListener() {
- @Override
- public void onAudioPlayerStateChanged(
- int uid, int prevState, @Nullable AudioPlaybackConfiguration config) {
- if (config == null || !config.isActive() || config.getPlayerType()
- == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
- return;
- }
- synchronized (mLock) {
- FullUserRecord user =
- getFullUserRecordLocked(UserHandle.getUserId(uid));
- if (user != null) {
- user.mPriorityStack.updateMediaButtonSessionIfNeeded();
+ (config, isRemoved) -> {
+ if (isRemoved || !config.isActive() || config.getPlayerType()
+ == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
+ return;
}
- }
- }
- }, null /* handler */);
+ synchronized (mLock) {
+ FullUserRecord user = getFullUserRecordLocked(
+ UserHandle.getUserId(config.getClientUid()));
+ if (user != null) {
+ user.mPriorityStack.updateMediaButtonSessionIfNeeded();
+ }
+ }
+ }, null /* handler */);
mAudioPlayerStateMonitor.registerSelfIntoAudioServiceIfNeeded(mAudioService);
mContentResolver = getContext().getContentResolver();
mSettingsObserver = new SettingsObserver();
diff --git a/com/android/server/net/NetworkPolicyLogger.java b/com/android/server/net/NetworkPolicyLogger.java
new file mode 100644
index 0000000..2bd9cab
--- /dev/null
+++ b/com/android/server/net/NetworkPolicyLogger.java
@@ -0,0 +1,514 @@
+/*
+ * Copyright (C) 2017 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.server.net;
+
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_DOZABLE;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_POWERSAVE;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_STANDBY;
+import static android.net.NetworkPolicyManager.FIREWALL_RULE_ALLOW;
+import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
+import static android.net.NetworkPolicyManager.FIREWALL_RULE_DENY;
+
+import android.app.ActivityManager;
+import android.net.NetworkPolicyManager;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.RingBuffer;
+import com.android.server.am.ProcessList;
+
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+
+public class NetworkPolicyLogger {
+ static final String TAG = "NetworkPolicy";
+
+ static final boolean LOGD = Log.isLoggable(TAG, Log.DEBUG);
+ static final boolean LOGV = Log.isLoggable(TAG, Log.VERBOSE);
+
+ private static final int MAX_LOG_SIZE =
+ ActivityManager.isLowRamDeviceStatic() ? 20 : 50;
+ private static final int MAX_NETWORK_BLOCKED_LOG_SIZE =
+ ActivityManager.isLowRamDeviceStatic() ? 50 : 100;
+
+ private static final int EVENT_TYPE_GENERIC = 0;
+ private static final int EVENT_NETWORK_BLOCKED = 1;
+ private static final int EVENT_UID_STATE_CHANGED = 2;
+ private static final int EVENT_POLICIES_CHANGED = 3;
+ private static final int EVENT_METEREDNESS_CHANGED = 4;
+ private static final int EVENT_USER_STATE_REMOVED = 5;
+ private static final int EVENT_RESTRICT_BG_CHANGED = 6;
+ private static final int EVENT_DEVICE_IDLE_MODE_ENABLED = 7;
+ private static final int EVENT_APP_IDLE_STATE_CHANGED = 8;
+ private static final int EVENT_PAROLE_STATE_CHANGED = 9;
+ private static final int EVENT_TEMP_POWER_SAVE_WL_CHANGED = 10;
+ private static final int EVENT_UID_FIREWALL_RULE_CHANGED = 11;
+ private static final int EVENT_FIREWALL_CHAIN_ENABLED = 12;
+
+ static final int NTWK_BLOCKED_POWER = 0;
+ static final int NTWK_ALLOWED_NON_METERED = 1;
+ static final int NTWK_BLOCKED_BLACKLIST = 2;
+ static final int NTWK_ALLOWED_WHITELIST = 3;
+ static final int NTWK_ALLOWED_TMP_WHITELIST = 4;
+ static final int NTWK_BLOCKED_BG_RESTRICT = 5;
+ static final int NTWK_ALLOWED_DEFAULT = 6;
+
+ private final LogBuffer mNetworkBlockedBuffer = new LogBuffer(MAX_NETWORK_BLOCKED_LOG_SIZE);
+ private final LogBuffer mUidStateChangeBuffer = new LogBuffer(MAX_LOG_SIZE);
+ private final LogBuffer mEventsBuffer = new LogBuffer(MAX_LOG_SIZE);
+
+ private final Object mLock = new Object();
+
+ void networkBlocked(int uid, int reason) {
+ synchronized (mLock) {
+ if (LOGD) Slog.d(TAG, uid + " is " + getBlockedReason(reason));
+ mNetworkBlockedBuffer.networkBlocked(uid, reason);
+ }
+ }
+
+ void uidStateChanged(int uid, int procState, long procStateSeq) {
+ synchronized (mLock) {
+ if (LOGV) Slog.v(TAG,
+ uid + " state changed to " + procState + " with seq=" + procStateSeq);
+ mUidStateChangeBuffer.uidStateChanged(uid, procState, procStateSeq);
+ }
+ }
+
+ void event(String msg) {
+ synchronized (mLock) {
+ if (LOGV) Slog.v(TAG, msg);
+ mEventsBuffer.event(msg);
+ }
+ }
+
+ void uidPolicyChanged(int uid, int oldPolicy, int newPolicy) {
+ synchronized (mLock) {
+ if (LOGV) Slog.v(TAG, getPolicyChangedLog(uid, oldPolicy, newPolicy));
+ mEventsBuffer.uidPolicyChanged(uid, oldPolicy, newPolicy);
+ }
+ }
+
+ void meterednessChanged(int netId, boolean newMetered) {
+ synchronized (mLock) {
+ if (LOGD) Slog.d(TAG, getMeterednessChangedLog(netId, newMetered));
+ mEventsBuffer.meterednessChanged(netId, newMetered);
+ }
+ }
+
+ void removingUserState(int userId) {
+ synchronized (mLock) {
+ if (LOGD) Slog.d(TAG, getUserRemovedLog(userId));
+ mEventsBuffer.userRemoved(userId);
+ }
+ }
+
+ void restrictBackgroundChanged(boolean oldValue, boolean newValue) {
+ synchronized (mLock) {
+ if (LOGD) Slog.d(TAG,
+ getRestrictBackgroundChangedLog(oldValue, newValue));
+ mEventsBuffer.restrictBackgroundChanged(oldValue, newValue);
+ }
+ }
+
+ void deviceIdleModeEnabled(boolean enabled) {
+ synchronized (mLock) {
+ if (LOGD) Slog.d(TAG, getDeviceIdleModeEnabled(enabled));
+ mEventsBuffer.deviceIdleModeEnabled(enabled);
+ }
+ }
+
+ void appIdleStateChanged(int uid, boolean idle) {
+ synchronized (mLock) {
+ if (LOGD) Slog.d(TAG, getAppIdleChangedLog(uid, idle));
+ mEventsBuffer.appIdleStateChanged(uid, idle);
+ }
+ }
+
+ void paroleStateChanged(boolean paroleOn) {
+ synchronized (mLock) {
+ if (LOGD) Slog.d(TAG, getParoleStateChanged(paroleOn));
+ mEventsBuffer.paroleStateChanged(paroleOn);
+ }
+ }
+
+ void tempPowerSaveWlChanged(int appId, boolean added) {
+ synchronized (mLock) {
+ if (LOGV) Slog.v(TAG, getTempPowerSaveWlChangedLog(appId, added));
+ mEventsBuffer.tempPowerSaveWlChanged(appId, added);
+ }
+ }
+
+ void uidFirewallRuleChanged(int chain, int uid, int rule) {
+ synchronized (mLock) {
+ if (LOGV) Slog.v(TAG, getUidFirewallRuleChangedLog(chain, uid, rule));
+ mEventsBuffer.uidFirewallRuleChanged(chain, uid, rule);
+ }
+ }
+
+ void firewallChainEnabled(int chain, boolean enabled) {
+ synchronized (mLock) {
+ if (LOGD) Slog.d(TAG, getFirewallChainEnabledLog(chain, enabled));
+ mEventsBuffer.firewallChainEnabled(chain, enabled);
+ }
+ }
+
+ void firewallRulesChanged(int chain, int[] uids, int[] rules) {
+ synchronized (mLock) {
+ final String log = "Firewall rules changed for " + getFirewallChainName(chain)
+ + "; uids=" + Arrays.toString(uids) + "; rules=" + Arrays.toString(rules);
+ if (LOGD) Slog.d(TAG, log);
+ mEventsBuffer.event(log);
+ }
+ }
+
+ void dumpLogs(IndentingPrintWriter pw) {
+ synchronized (mLock) {
+ pw.println();
+ pw.println("mEventLogs (most recent first):");
+ pw.increaseIndent();
+ mEventsBuffer.reverseDump(pw);
+ pw.decreaseIndent();
+
+ pw.println();
+ pw.println("mNetworkBlockedLogs (most recent first):");
+ pw.increaseIndent();
+ mNetworkBlockedBuffer.reverseDump(pw);
+ pw.decreaseIndent();
+
+ pw.println();
+ pw.println("mUidStateChangeLogs (most recent first):");
+ pw.increaseIndent();
+ mUidStateChangeBuffer.reverseDump(pw);
+ pw.decreaseIndent();
+ }
+ }
+
+ private static String getBlockedReason(int reason) {
+ switch (reason) {
+ case NTWK_BLOCKED_POWER:
+ return "blocked by power restrictions";
+ case NTWK_ALLOWED_NON_METERED:
+ return "allowed on unmetered network";
+ case NTWK_BLOCKED_BLACKLIST:
+ return "blacklisted on metered network";
+ case NTWK_ALLOWED_WHITELIST:
+ return "whitelisted on metered network";
+ case NTWK_ALLOWED_TMP_WHITELIST:
+ return "temporary whitelisted on metered network";
+ case NTWK_BLOCKED_BG_RESTRICT:
+ return "blocked when background is restricted";
+ case NTWK_ALLOWED_DEFAULT:
+ return "allowed by default";
+ default:
+ return String.valueOf(reason);
+ }
+ }
+
+ private static String getPolicyChangedLog(int uid, int oldPolicy, int newPolicy) {
+ return "Policy for " + uid + " changed from "
+ + NetworkPolicyManager.uidPoliciesToString(oldPolicy) + " to "
+ + NetworkPolicyManager.uidPoliciesToString(newPolicy);
+ }
+
+ private static String getMeterednessChangedLog(int netId, boolean newMetered) {
+ return "Meteredness of netId=" + netId + " changed to " + newMetered;
+ }
+
+ private static String getUserRemovedLog(int userId) {
+ return "Remove state for u" + userId;
+ }
+
+ private static String getRestrictBackgroundChangedLog(boolean oldValue, boolean newValue) {
+ return "Changed restrictBackground: " + oldValue + "->" + newValue;
+ }
+
+ private static String getDeviceIdleModeEnabled(boolean enabled) {
+ return "DeviceIdleMode enabled: " + enabled;
+ }
+
+ private static String getAppIdleChangedLog(int uid, boolean idle) {
+ return "App idle state of uid " + uid + ": " + idle;
+ }
+
+ private static String getParoleStateChanged(boolean paroleOn) {
+ return "Parole state: " + paroleOn;
+ }
+
+ private static String getTempPowerSaveWlChangedLog(int appId, boolean added) {
+ return "temp-power-save whitelist for " + appId + " changed to: " + added;
+ }
+
+ private static String getUidFirewallRuleChangedLog(int chain, int uid, int rule) {
+ return String.format("Firewall rule changed: %d-%s-%s",
+ uid, getFirewallChainName(chain), getFirewallRuleName(rule));
+ }
+
+ private static String getFirewallChainEnabledLog(int chain, boolean enabled) {
+ return "Firewall chain " + getFirewallChainName(chain) + " state: " + enabled;
+ }
+
+ private static String getFirewallChainName(int chain) {
+ switch (chain) {
+ case FIREWALL_CHAIN_DOZABLE:
+ return FIREWALL_CHAIN_NAME_DOZABLE;
+ case FIREWALL_CHAIN_STANDBY:
+ return FIREWALL_CHAIN_NAME_STANDBY;
+ case FIREWALL_CHAIN_POWERSAVE:
+ return FIREWALL_CHAIN_NAME_POWERSAVE;
+ default:
+ return String.valueOf(chain);
+ }
+ }
+
+ private static String getFirewallRuleName(int rule) {
+ switch (rule) {
+ case FIREWALL_RULE_DEFAULT:
+ return "default";
+ case FIREWALL_RULE_ALLOW:
+ return "allow";
+ case FIREWALL_RULE_DENY:
+ return "deny";
+ default:
+ return String.valueOf(rule);
+ }
+ }
+
+ private final static class LogBuffer extends RingBuffer<Data> {
+ private static final SimpleDateFormat sFormatter
+ = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss:SSS");
+ private static final Date sDate = new Date();
+
+ public LogBuffer(int capacity) {
+ super(Data.class, capacity);
+ }
+
+ public void uidStateChanged(int uid, int procState, long procStateSeq) {
+ final Data data = getNextSlot();
+ if (data == null) return;
+
+ data.reset();
+ data.type = EVENT_UID_STATE_CHANGED;
+ data.ifield1 = uid;
+ data.ifield2 = procState;
+ data.lfield1 = procStateSeq;
+ data.timeStamp = System.currentTimeMillis();
+ }
+
+ public void event(String msg) {
+ final Data data = getNextSlot();
+ if (data == null) return;
+
+ data.reset();
+ data.type = EVENT_TYPE_GENERIC;
+ data.sfield1 = msg;
+ data.timeStamp = System.currentTimeMillis();
+ }
+
+ public void networkBlocked(int uid, int reason) {
+ final Data data = getNextSlot();
+ if (data == null) return;
+
+ data.reset();
+ data.type = EVENT_NETWORK_BLOCKED;
+ data.ifield1 = uid;
+ data.ifield2 = reason;
+ data.timeStamp = System.currentTimeMillis();
+ }
+
+ public void uidPolicyChanged(int uid, int oldPolicy, int newPolicy) {
+ final Data data = getNextSlot();
+ if (data == null) return;
+
+ data.reset();
+ data.type = EVENT_POLICIES_CHANGED;
+ data.ifield1 = uid;
+ data.ifield2 = oldPolicy;
+ data.ifield3 = newPolicy;
+ data.timeStamp = System.currentTimeMillis();
+ }
+
+ public void meterednessChanged(int netId, boolean newMetered) {
+ final Data data = getNextSlot();
+ if (data == null) return;
+
+ data.reset();
+ data.type = EVENT_METEREDNESS_CHANGED;
+ data.ifield1 = netId;
+ data.bfield1 = newMetered;
+ data.timeStamp = System.currentTimeMillis();
+ }
+
+ public void userRemoved(int userId) {
+ final Data data = getNextSlot();
+ if (data == null) return;
+
+ data.reset();
+ data.type = EVENT_USER_STATE_REMOVED;
+ data.ifield1 = userId;
+ data.timeStamp = System.currentTimeMillis();
+ }
+
+ public void restrictBackgroundChanged(boolean oldValue, boolean newValue) {
+ final Data data = getNextSlot();
+ if (data == null) return;
+
+ data.reset();
+ data.type = EVENT_RESTRICT_BG_CHANGED;
+ data.bfield1 = oldValue;
+ data.bfield2 = newValue;
+ data.timeStamp = System.currentTimeMillis();
+ }
+
+ public void deviceIdleModeEnabled(boolean enabled) {
+ final Data data = getNextSlot();
+ if (data == null) return;
+
+ data.reset();
+ data.type = EVENT_DEVICE_IDLE_MODE_ENABLED;
+ data.bfield1 = enabled;
+ data.timeStamp = System.currentTimeMillis();
+ }
+
+ public void appIdleStateChanged(int uid, boolean idle) {
+ final Data data = getNextSlot();
+ if (data == null) return;
+
+ data.reset();
+ data.type = EVENT_APP_IDLE_STATE_CHANGED;
+ data.ifield1 = uid;
+ data.bfield1 = idle;
+ data.timeStamp = System.currentTimeMillis();
+ }
+
+ public void paroleStateChanged(boolean paroleOn) {
+ final Data data = getNextSlot();
+ if (data == null) return;
+
+ data.reset();
+ data.type = EVENT_PAROLE_STATE_CHANGED;
+ data.bfield1 = paroleOn;
+ data.timeStamp = System.currentTimeMillis();
+ }
+
+ public void tempPowerSaveWlChanged(int appId, boolean added) {
+ final Data data = getNextSlot();
+ if (data == null) return;
+
+ data.reset();
+ data.type = EVENT_TEMP_POWER_SAVE_WL_CHANGED;
+ data.ifield1 = appId;
+ data.bfield1 = added;
+ data.timeStamp = System.currentTimeMillis();
+ }
+
+ public void uidFirewallRuleChanged(int chain, int uid, int rule) {
+ final Data data = getNextSlot();
+ if (data == null) return;
+
+ data.reset();
+ data.type = EVENT_UID_FIREWALL_RULE_CHANGED;
+ data.ifield1 = chain;
+ data.ifield2 = uid;
+ data.ifield3 = rule;
+ data.timeStamp = System.currentTimeMillis();
+ }
+
+ public void firewallChainEnabled(int chain, boolean enabled) {
+ final Data data = getNextSlot();
+ if (data == null) return;
+
+ data.reset();
+ data.type = EVENT_FIREWALL_CHAIN_ENABLED;
+ data.ifield1 = chain;
+ data.bfield1 = enabled;
+ data.timeStamp = System.currentTimeMillis();
+ }
+
+ public void reverseDump(IndentingPrintWriter pw) {
+ final Data[] allData = toArray();
+ for (int i = allData.length - 1; i >= 0; --i) {
+ if (allData[i] == null) {
+ pw.println("NULL");
+ continue;
+ }
+ pw.print(formatDate(allData[i].timeStamp));
+ pw.print(" - ");
+ pw.println(getContent(allData[i]));
+ }
+ }
+
+ public String getContent(Data data) {
+ switch (data.type) {
+ case EVENT_TYPE_GENERIC:
+ return data.sfield1;
+ case EVENT_NETWORK_BLOCKED:
+ return data.ifield1 + "-" + getBlockedReason(data.ifield2);
+ case EVENT_UID_STATE_CHANGED:
+ return data.ifield1 + "-" + ProcessList.makeProcStateString(data.ifield2)
+ + "-" + data.lfield1;
+ case EVENT_POLICIES_CHANGED:
+ return getPolicyChangedLog(data.ifield1, data.ifield2, data.ifield3);
+ case EVENT_METEREDNESS_CHANGED:
+ return getMeterednessChangedLog(data.ifield1, data.bfield1);
+ case EVENT_USER_STATE_REMOVED:
+ return getUserRemovedLog(data.ifield1);
+ case EVENT_RESTRICT_BG_CHANGED:
+ return getRestrictBackgroundChangedLog(data.bfield1, data.bfield2);
+ case EVENT_DEVICE_IDLE_MODE_ENABLED:
+ return getDeviceIdleModeEnabled(data.bfield1);
+ case EVENT_APP_IDLE_STATE_CHANGED:
+ return getAppIdleChangedLog(data.ifield1, data.bfield1);
+ case EVENT_PAROLE_STATE_CHANGED:
+ return getParoleStateChanged(data.bfield1);
+ case EVENT_TEMP_POWER_SAVE_WL_CHANGED:
+ return getTempPowerSaveWlChangedLog(data.ifield1, data.bfield1);
+ case EVENT_UID_FIREWALL_RULE_CHANGED:
+ return getUidFirewallRuleChangedLog(data.ifield1, data.ifield2, data.ifield3);
+ case EVENT_FIREWALL_CHAIN_ENABLED:
+ return getFirewallChainEnabledLog(data.ifield1, data.bfield1);
+ default:
+ return String.valueOf(data.type);
+ }
+ }
+
+ private String formatDate(long millis) {
+ sDate.setTime(millis);
+ return sFormatter.format(sDate);
+ }
+ }
+
+ public final static class Data {
+ int type;
+ long timeStamp;
+
+ int ifield1;
+ int ifield2;
+ int ifield3;
+ long lfield1;
+ boolean bfield1;
+ boolean bfield2;
+ String sfield1;
+
+ public void reset(){
+ sfield1 = null;
+ }
+ }
+}
diff --git a/com/android/server/net/NetworkPolicyManagerService.java b/com/android/server/net/NetworkPolicyManagerService.java
index 3fa3cd4..fdfe241 100644
--- a/com/android/server/net/NetworkPolicyManagerService.java
+++ b/com/android/server/net/NetworkPolicyManagerService.java
@@ -82,6 +82,13 @@
import static com.android.internal.util.XmlUtils.writeLongAttribute;
import static com.android.internal.util.XmlUtils.writeStringAttribute;
import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_DEFAULT;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_NON_METERED;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_TMP_WHITELIST;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_WHITELIST;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_BG_RESTRICT;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_BLACKLIST;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_POWER;
import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_UPDATED;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
@@ -248,9 +255,9 @@
* </ul>
*/
public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
- static final String TAG = "NetworkPolicy";
- private static final boolean LOGD = false;
- private static final boolean LOGV = false;
+ static final String TAG = NetworkPolicyLogger.TAG;
+ private static final boolean LOGD = NetworkPolicyLogger.LOGD;
+ private static final boolean LOGV = NetworkPolicyLogger.LOGV;
private static final int VERSION_INIT = 1;
private static final int VERSION_ADDED_SNOOZE = 2;
@@ -265,13 +272,6 @@
private static final int VERSION_ADDED_CYCLE = 11;
private static final int VERSION_LATEST = VERSION_ADDED_CYCLE;
- /**
- * Max items written to {@link #ProcStateSeqHistory}.
- */
- @VisibleForTesting
- public static final int MAX_PROC_STATE_SEQ_HISTORY =
- ActivityManager.isLowRamDeviceStatic() ? 50 : 200;
-
@VisibleForTesting
public static final int TYPE_WARNING = SystemMessage.NOTE_NET_WARNING;
@VisibleForTesting
@@ -471,13 +471,7 @@
private ActivityManagerInternal mActivityManagerInternal;
- /**
- * This is used for debugging purposes. Whenever the IUidObserver.onUidStateChanged is called,
- * the uid and procStateSeq will be written to this and will be printed as part of dump.
- */
- @VisibleForTesting
- public ProcStateSeqHistory mObservedHistory
- = new ProcStateSeqHistory(MAX_PROC_STATE_SEQ_HISTORY);
+ private final NetworkPolicyLogger mLogger = new NetworkPolicyLogger();
// TODO: keep whitelist of system-critical services that should never have
// rules enforced, such as system, phone, and radio UIDs.
@@ -966,6 +960,7 @@
.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
if ((oldMetered != newMetered) || mNetworkMetered.indexOfKey(network.netId) < 0) {
+ mLogger.meterednessChanged(network.netId, newMetered);
mNetworkMetered.put(network.netId, newMetered);
updateNetworkRulesNL();
}
@@ -2148,6 +2143,7 @@
final int oldPolicy = mUidPolicy.get(uid, POLICY_NONE);
if (oldPolicy != policy) {
setUidPolicyUncheckedUL(uid, oldPolicy, policy, true);
+ mLogger.uidPolicyChanged(uid, oldPolicy, policy);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -2168,6 +2164,7 @@
policy |= oldPolicy;
if (oldPolicy != policy) {
setUidPolicyUncheckedUL(uid, oldPolicy, policy, true);
+ mLogger.uidPolicyChanged(uid, oldPolicy, policy);
}
}
}
@@ -2185,6 +2182,7 @@
policy = oldPolicy & ~policy;
if (oldPolicy != policy) {
setUidPolicyUncheckedUL(uid, oldPolicy, policy, true);
+ mLogger.uidPolicyChanged(uid, oldPolicy, policy);
}
}
}
@@ -2264,7 +2262,7 @@
*/
boolean removeUserStateUL(int userId, boolean writePolicy) {
- if (LOGV) Slog.v(TAG, "removeUserStateUL()");
+ mLogger.removingUserState(userId);
boolean changed = false;
// Remove entries from revoked default restricted background UID whitelist
@@ -2429,7 +2427,6 @@
@Override
public void onTetheringChanged(String iface, boolean tethering) {
// No need to enforce permission because setRestrictBackground() will do it.
- if (LOGD) Log.d(TAG, "onTetherStateChanged(" + iface + ", " + tethering + ")");
synchronized (mUidRulesFirstLock) {
if (mRestrictBackground && tethering) {
Log.d(TAG, "Tethering on (" + iface +"); disable Data Saver");
@@ -2486,6 +2483,7 @@
}
sendRestrictBackgroundChangedMsg();
+ mLogger.restrictBackgroundChanged(oldRestrictBackground, mRestrictBackground);
if (mRestrictBackgroundPowerState.globalBatterySaverEnabled) {
mRestrictBackgroundChangedInBsm = true;
@@ -2551,6 +2549,7 @@
return;
}
mDeviceIdleMode = enabled;
+ mLogger.deviceIdleModeEnabled(enabled);
if (mSystemReady) {
// Device idle change means we need to rebuild rules for all
// known apps, so do a global refresh.
@@ -2964,10 +2963,7 @@
}
fout.decreaseIndent();
- fout.println("Observed uid state changes:");
- fout.increaseIndent();
- mObservedHistory.dumpUL(fout);
- fout.decreaseIndent();
+ mLogger.dumpLogs(fout);
}
}
}
@@ -3750,8 +3746,8 @@
try {
final int uid = mContext.getPackageManager().getPackageUidAsUser(packageName,
PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
- if (LOGV) Log.v(TAG, "onAppIdleStateChanged(): uid=" + uid + ", idle=" + idle);
synchronized (mUidRulesFirstLock) {
+ mLogger.appIdleStateChanged(uid, idle);
updateRuleForAppIdleUL(uid);
updateRulesForPowerRestrictionsUL(uid);
}
@@ -3762,6 +3758,7 @@
@Override
public void onParoleStateChanged(boolean isParoleOn) {
synchronized (mUidRulesFirstLock) {
+ mLogger.paroleStateChanged(isParoleOn);
updateRulesForAppIdleParoleUL();
}
}
@@ -3947,7 +3944,7 @@
synchronized (mUidRulesFirstLock) {
// We received a uid state change callback, add it to the history so that it
// will be useful for debugging.
- mObservedHistory.addProcStateSeqUL(uid, procStateSeq);
+ mLogger.uidStateChanged(uid, procState, procStateSeq);
// Now update the network policy rules as per the updated uid state.
updateUidStateUL(uid, procState);
// Updating the network rules is done, so notify AMS about this.
@@ -4081,6 +4078,7 @@
rules[index] = uidRules.valueAt(index);
}
mNetworkManager.setFirewallUidRules(chain, uids, rules);
+ mLogger.firewallRulesChanged(chain, uids, rules);
} catch (IllegalStateException e) {
Log.wtf(TAG, "problem setting firewall uid rules", e);
} catch (RemoteException e) {
@@ -4107,6 +4105,7 @@
try {
mNetworkManager.setFirewallUidRule(chain, uid, rule);
+ mLogger.uidFirewallRuleChanged(chain, uid, rule);
} catch (IllegalStateException e) {
Log.wtf(TAG, "problem setting firewall uid rules", e);
} catch (RemoteException e) {
@@ -4129,6 +4128,7 @@
mFirewallChainStates.put(chain, enable);
try {
mNetworkManager.setFirewallChainEnabled(chain, enable);
+ mLogger.firewallChainEnabled(chain, enable);
} catch (IllegalStateException e) {
Log.wtf(TAG, "problem enable firewall chain", e);
} catch (RemoteException e) {
@@ -4305,30 +4305,30 @@
isBackgroundRestricted = mRestrictBackground;
}
if (hasRule(uidRules, RULE_REJECT_ALL)) {
- if (LOGV) logUidStatus(uid, "blocked by power restrictions");
+ mLogger.networkBlocked(uid, NTWK_BLOCKED_POWER);
return true;
}
if (!isNetworkMetered) {
- if (LOGV) logUidStatus(uid, "allowed on unmetered network");
+ mLogger.networkBlocked(uid, NTWK_ALLOWED_NON_METERED);
return false;
}
if (hasRule(uidRules, RULE_REJECT_METERED)) {
- if (LOGV) logUidStatus(uid, "blacklisted on metered network");
+ mLogger.networkBlocked(uid, NTWK_BLOCKED_BLACKLIST);
return true;
}
if (hasRule(uidRules, RULE_ALLOW_METERED)) {
- if (LOGV) logUidStatus(uid, "whitelisted on metered network");
+ mLogger.networkBlocked(uid, NTWK_ALLOWED_WHITELIST);
return false;
}
if (hasRule(uidRules, RULE_TEMPORARY_ALLOW_METERED)) {
- if (LOGV) logUidStatus(uid, "temporary whitelisted on metered network");
+ mLogger.networkBlocked(uid, NTWK_ALLOWED_TMP_WHITELIST);
return false;
}
if (isBackgroundRestricted) {
- if (LOGV) logUidStatus(uid, "blocked when background is restricted");
+ mLogger.networkBlocked(uid, NTWK_BLOCKED_BG_RESTRICT);
return true;
}
- if (LOGV) logUidStatus(uid, "allowed by default");
+ mLogger.networkBlocked(uid, NTWK_ALLOWED_DEFAULT);
return false;
}
@@ -4379,6 +4379,7 @@
@Override
public void onTempPowerSaveWhitelistChange(int appId, boolean added) {
synchronized (mUidRulesFirstLock) {
+ mLogger.tempPowerSaveWlChanged(appId, added);
if (added) {
mPowerSaveTempWhitelistAppIds.put(appId, true);
} else {
@@ -4393,80 +4394,6 @@
return (uidRules & rule) != 0;
}
- private static void logUidStatus(int uid, String descr) {
- Slog.d(TAG, String.format("uid %d is %s", uid, descr));
- }
-
- /**
- * This class is used for storing and dumping the last {@link #MAX_PROC_STATE_SEQ_HISTORY}
- * (uid, procStateSeq) pairs.
- */
- @VisibleForTesting
- public static final class ProcStateSeqHistory {
- private static final int INVALID_UID = -1;
-
- /**
- * Denotes maximum number of items this history can hold.
- */
- private final int mMaxCapacity;
- /**
- * Used for storing the uid information.
- */
- private final int[] mUids;
- /**
- * Used for storing the sequence numbers associated with {@link #mUids}.
- */
- private final long[] mProcStateSeqs;
- /**
- * Points to the next available slot for writing (uid, procStateSeq) pair.
- */
- private int mHistoryNext;
-
- public ProcStateSeqHistory(int maxCapacity) {
- mMaxCapacity = maxCapacity;
- mUids = new int[mMaxCapacity];
- Arrays.fill(mUids, INVALID_UID);
- mProcStateSeqs = new long[mMaxCapacity];
- }
-
- @GuardedBy("mUidRulesFirstLock")
- public void addProcStateSeqUL(int uid, long procStateSeq) {
- mUids[mHistoryNext] = uid;
- mProcStateSeqs[mHistoryNext] = procStateSeq;
- mHistoryNext = increaseNext(mHistoryNext, 1);
- }
-
- @GuardedBy("mUidRulesFirstLock")
- public void dumpUL(IndentingPrintWriter fout) {
- if (mUids[0] == INVALID_UID) {
- fout.println("NONE");
- return;
- }
- int index = mHistoryNext;
- do {
- index = increaseNext(index, -1);
- if (mUids[index] == INVALID_UID) {
- break;
- }
- fout.println(getString(mUids[index], mProcStateSeqs[index]));
- } while (index != mHistoryNext);
- }
-
- public static String getString(int uid, long procStateSeq) {
- return "UID=" + uid + " Seq=" + procStateSeq;
- }
-
- private int increaseNext(int next, int increment) {
- next += increment;
- if (next >= mMaxCapacity) {
- next = 0;
- } else if (next < 0) {
- next = mMaxCapacity - 1;
- }
- return next;
- }
- }
-
private class NotificationId {
private final String mTag;
private final int mId;
diff --git a/com/android/server/notification/NotificationManagerService.java b/com/android/server/notification/NotificationManagerService.java
index 557ba42..bec6fc2 100644
--- a/com/android/server/notification/NotificationManagerService.java
+++ b/com/android/server/notification/NotificationManagerService.java
@@ -16,15 +16,21 @@
package com.android.server.notification;
+import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED;
+import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_MIN;
import static android.app.NotificationManager.IMPORTANCE_NONE;
-import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
import static android.content.pm.PackageManager.FEATURE_LEANBACK;
import static android.content.pm.PackageManager.FEATURE_TELEVISION;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.UserHandle.USER_NULL;
import static android.service.notification.NotificationListenerService
+ .HINT_HOST_DISABLE_CALL_EFFECTS;
+import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS;
+import static android.service.notification.NotificationListenerService
+ .HINT_HOST_DISABLE_NOTIFICATION_EFFECTS;
+import static android.service.notification.NotificationListenerService
.NOTIFICATION_CHANNEL_OR_GROUP_ADDED;
import static android.service.notification.NotificationListenerService
.NOTIFICATION_CHANNEL_OR_GROUP_DELETED;
@@ -32,12 +38,13 @@
.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED;
import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL;
-import static android.service.notification.NotificationListenerService.REASON_CHANNEL_BANNED;
import static android.service.notification.NotificationListenerService.REASON_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL;
+import static android.service.notification.NotificationListenerService.REASON_CHANNEL_BANNED;
import static android.service.notification.NotificationListenerService.REASON_CLICK;
import static android.service.notification.NotificationListenerService.REASON_ERROR;
-import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
+import static android.service.notification.NotificationListenerService
+ .REASON_GROUP_SUMMARY_CANCELED;
import static android.service.notification.NotificationListenerService.REASON_LISTENER_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_LISTENER_CANCEL_ALL;
import static android.service.notification.NotificationListenerService.REASON_PACKAGE_BANNED;
@@ -48,14 +55,10 @@
import static android.service.notification.NotificationListenerService.REASON_TIMEOUT;
import static android.service.notification.NotificationListenerService.REASON_UNAUTOBUNDLED;
import static android.service.notification.NotificationListenerService.REASON_USER_STOPPED;
-import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS;
-import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS;
-import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS;
import static android.service.notification.NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_OFF;
import static android.service.notification.NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_ON;
import static android.service.notification.NotificationListenerService.TRIM_FULL;
import static android.service.notification.NotificationListenerService.TRIM_LIGHT;
-
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
@@ -68,17 +71,17 @@
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.AutomaticZenRule;
-import android.app.NotificationChannelGroup;
-import android.app.backup.BackupManager;
import android.app.IActivityManager;
import android.app.INotificationManager;
import android.app.ITransientNotification;
import android.app.Notification;
import android.app.NotificationChannel;
-import android.app.NotificationManager.Policy;
+import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
+import android.app.NotificationManager.Policy;
import android.app.PendingIntent;
import android.app.StatusBarManager;
+import android.app.backup.BackupManager;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManagerInternal;
import android.companion.ICompanionDeviceManager;
@@ -119,8 +122,8 @@
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
-import android.os.Vibrator;
import android.os.VibrationEffect;
+import android.os.Vibrator;
import android.provider.Settings;
import android.service.notification.Adjustment;
import android.service.notification.Condition;
@@ -149,7 +152,6 @@
import android.util.SparseArray;
import android.util.Xml;
import android.util.proto.ProtoOutputStream;
-import android.view.WindowManagerInternal;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.widget.Toast;
@@ -174,9 +176,10 @@
import com.android.server.lights.Light;
import com.android.server.lights.LightsManager;
import com.android.server.notification.ManagedServices.ManagedServiceInfo;
+import com.android.server.notification.ManagedServices.UserProfiles;
import com.android.server.policy.PhoneWindowManager;
import com.android.server.statusbar.StatusBarManagerInternal;
-import com.android.server.notification.ManagedServices.UserProfiles;
+import com.android.server.wm.WindowManagerInternal;
import libcore.io.IoUtils;
@@ -196,7 +199,6 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
-import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -736,6 +738,11 @@
for (NotificationVisibility nv : newlyVisibleKeys) {
NotificationRecord r = mNotificationsByKey.get(nv.key);
if (r == null) continue;
+ if (!r.isSeen()) {
+ // Report to usage stats that notification was made visible
+ if (DBG) Slog.d(TAG, "Marking notification as visible " + nv.key);
+ reportSeen(r);
+ }
r.setVisibility(true, nv.rank);
nv.recycle();
}
@@ -766,7 +773,7 @@
.setType(expanded ? MetricsEvent.TYPE_DETAIL
: MetricsEvent.TYPE_COLLAPSE));
}
- if (expanded) {
+ if (expanded && userAction) {
r.recordExpanded();
}
EventLogTags.writeNotificationExpansion(key,
@@ -1515,7 +1522,11 @@
}
}
}
+ final NotificationChannel preUpdate =
+ mRankingHelper.getNotificationChannel(pkg, uid, channel.getId(), true);
+
mRankingHelper.updateNotificationChannel(pkg, uid, channel, true);
+ maybeNotifyChannelOwner(pkg, uid, preUpdate, channel);
if (!fromListener) {
final NotificationChannel modifiedChannel =
@@ -1528,12 +1539,40 @@
savePolicyFile();
}
+ private void maybeNotifyChannelOwner(String pkg, int uid, NotificationChannel preUpdate,
+ NotificationChannel update) {
+ try {
+ if ((preUpdate.getImportance() == IMPORTANCE_NONE
+ && update.getImportance() != IMPORTANCE_NONE)
+ || (preUpdate.getImportance() != IMPORTANCE_NONE
+ && update.getImportance() == IMPORTANCE_NONE)) {
+ getContext().sendBroadcastAsUser(
+ new Intent(ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED)
+ .putExtra(NotificationManager.EXTRA_BLOCK_STATE_CHANGED_ID,
+ update.getId())
+ .putExtra(NotificationManager.EXTRA_BLOCKED_STATE,
+ update.getImportance() == IMPORTANCE_NONE)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ .setPackage(pkg),
+ UserHandle.of(UserHandle.getUserId(uid)), null);
+ }
+ } catch (SecurityException e) {
+ Slog.w(TAG, "Can't notify app about channel change", e);
+ }
+ }
+
private void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
boolean fromApp, boolean fromListener) {
Preconditions.checkNotNull(group);
Preconditions.checkNotNull(pkg);
+
+ final NotificationChannelGroup preUpdate =
+ mRankingHelper.getNotificationChannelGroup(group.getId(), pkg, uid);
mRankingHelper.createNotificationChannelGroup(pkg, uid, group,
fromApp);
+ if (!fromApp) {
+ maybeNotifyChannelGroupOwner(pkg, uid, preUpdate, group);
+ }
if (!fromListener) {
mListeners.notifyNotificationChannelGroupChanged(pkg,
UserHandle.of(UserHandle.getCallingUserId()), group,
@@ -1541,6 +1580,25 @@
}
}
+ private void maybeNotifyChannelGroupOwner(String pkg, int uid,
+ NotificationChannelGroup preUpdate, NotificationChannelGroup update) {
+ try {
+ if (preUpdate.isBlocked() != update.isBlocked()) {
+ getContext().sendBroadcastAsUser(
+ new Intent(ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED)
+ .putExtra(NotificationManager.EXTRA_BLOCK_STATE_CHANGED_ID,
+ update.getId())
+ .putExtra(NotificationManager.EXTRA_BLOCKED_STATE,
+ update.isBlocked())
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ .setPackage(pkg),
+ UserHandle.of(UserHandle.getUserId(uid)), null);
+ }
+ } catch (SecurityException e) {
+ Slog.w(TAG, "Can't notify app about group change", e);
+ }
+ }
+
private ArrayList<ComponentName> getSuppressors() {
ArrayList<ComponentName> names = new ArrayList<ComponentName>();
for (int i = mListenersDisablingEffects.size() - 1; i >= 0; --i) {
@@ -1643,6 +1701,14 @@
return INotificationManager.Stub.asInterface(mService);
}
+ protected void reportSeen(NotificationRecord r) {
+ final int userId = r.sbn.getUserId();
+ mAppUsageStats.reportEvent(r.sbn.getPackageName(),
+ userId == UserHandle.USER_ALL ? UserHandle.USER_SYSTEM
+ : userId,
+ UsageEvents.Event.NOTIFICATION_SEEN);
+ }
+
@VisibleForTesting
NotificationManagerInternal getInternalService() {
return mInternalService;
@@ -1911,11 +1977,18 @@
}
@Override
+ public NotificationChannelGroup getNotificationChannelGroup(String pkg, String groupId) {
+ checkCallerIsSystemOrSameApp(pkg);
+ return mRankingHelper.getNotificationChannelGroupWithChannels(
+ pkg, Binder.getCallingUid(), groupId, false);
+ }
+
+ @Override
public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(
String pkg) {
checkCallerIsSystemOrSameApp(pkg);
- return new ParceledListSlice<>(new ArrayList(
- mRankingHelper.getNotificationChannelGroups(pkg, Binder.getCallingUid())));
+ return mRankingHelper.getNotificationChannelGroups(
+ pkg, Binder.getCallingUid(), false, false);
}
@Override
@@ -1985,7 +2058,7 @@
public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroupsForPackage(
String pkg, int uid, boolean includeDeleted) {
checkCallerIsSystem();
- return mRankingHelper.getNotificationChannelGroups(pkg, uid, includeDeleted);
+ return mRankingHelper.getNotificationChannelGroups(pkg, uid, includeDeleted, true);
}
@Override
@@ -2269,10 +2342,7 @@
}
if (!r.isSeen()) {
if (DBG) Slog.d(TAG, "Marking notification as seen " + keys[i]);
- mAppUsageStats.reportEvent(r.sbn.getPackageName(),
- userId == UserHandle.USER_ALL ? UserHandle.USER_SYSTEM
- : userId,
- UsageEvents.Event.USER_INTERACTION);
+ reportSeen(r);
r.setSeen();
}
}
@@ -4673,7 +4743,8 @@
}
void sendAccessibilityEvent(Notification notification, CharSequence packageName) {
- if (!mAccessibilityManager.isEnabled()) {
+ if (!mAccessibilityManager.isObservedEventType(
+ AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED)) {
return;
}
diff --git a/com/android/server/notification/RankingConfig.java b/com/android/server/notification/RankingConfig.java
index b9c0d90..b1b0bf2 100644
--- a/com/android/server/notification/RankingConfig.java
+++ b/com/android/server/notification/RankingConfig.java
@@ -36,7 +36,7 @@
void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
boolean fromTargetApp);
ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
- int uid, boolean includeDeleted);
+ int uid, boolean includeDeleted, boolean includeNonGrouped);
void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
boolean fromTargetApp);
void updateNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromUser);
diff --git a/com/android/server/notification/RankingHelper.java b/com/android/server/notification/RankingHelper.java
index d566a45..c0dccb5 100644
--- a/com/android/server/notification/RankingHelper.java
+++ b/com/android/server/notification/RankingHelper.java
@@ -750,12 +750,15 @@
int uid) {
Preconditions.checkNotNull(pkg);
Record r = getRecord(pkg, uid);
+ if (r == null) {
+ return null;
+ }
return r.groups.get(groupId);
}
@Override
public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
- int uid, boolean includeDeleted) {
+ int uid, boolean includeDeleted, boolean includeNonGrouped) {
Preconditions.checkNotNull(pkg);
Map<String, NotificationChannelGroup> groups = new ArrayMap<>();
Record r = getRecord(pkg, uid);
@@ -783,7 +786,7 @@
}
}
}
- if (nonGrouped.getChannels().size() > 0) {
+ if (includeNonGrouped && nonGrouped.getChannels().size() > 0) {
groups.put(null, nonGrouped);
}
return new ParceledListSlice<>(new ArrayList<>(groups.values()));
diff --git a/com/android/server/pm/BackgroundDexOptService.java b/com/android/server/pm/BackgroundDexOptService.java
index 679250c..8591304 100644
--- a/com/android/server/pm/BackgroundDexOptService.java
+++ b/com/android/server/pm/BackgroundDexOptService.java
@@ -18,6 +18,7 @@
import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
+import android.annotation.Nullable;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
@@ -40,6 +41,7 @@
import com.android.server.pm.dex.DexoptOptions;
import java.io.File;
+import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.TimeUnit;
@@ -402,14 +404,22 @@
}
/**
- * Execute the idle optimizations immediately.
+ * Execute idle optimizations immediately on packages in packageNames. If packageNames is null,
+ * then execute on all packages.
*/
- public static boolean runIdleOptimizationsNow(PackageManagerService pm, Context context) {
+ public static boolean runIdleOptimizationsNow(PackageManagerService pm, Context context,
+ @Nullable List<String> packageNames) {
// Create a new object to make sure we don't interfere with the scheduled jobs.
// Note that this may still run at the same time with the job scheduled by the
// JobScheduler but the scheduler will not be able to cancel it.
BackgroundDexOptService bdos = new BackgroundDexOptService();
- int result = bdos.idleOptimization(pm, pm.getOptimizablePackages(), context);
+ ArraySet<String> packagesToOptimize;
+ if (packageNames == null) {
+ packagesToOptimize = pm.getOptimizablePackages();
+ } else {
+ packagesToOptimize = new ArraySet<>(packageNames);
+ }
+ int result = bdos.idleOptimization(pm, packagesToOptimize, context);
return result == OPTIMIZE_PROCESSED;
}
diff --git a/com/android/server/pm/Installer.java b/com/android/server/pm/Installer.java
index 210eb13..6a06be2 100644
--- a/com/android/server/pm/Installer.java
+++ b/com/android/server/pm/Installer.java
@@ -486,6 +486,16 @@
}
}
+ public byte[] hashSecondaryDexFile(String dexPath, String packageName, int uid,
+ @Nullable String volumeUuid, int flags) throws InstallerException {
+ if (!checkBeforeRemote()) return new byte[0];
+ try {
+ return mInstalld.hashSecondaryDexFile(dexPath, packageName, uid, volumeUuid, flags);
+ } catch (Exception e) {
+ throw InstallerException.from(e);
+ }
+ }
+
public void invalidateMounts() throws InstallerException {
if (!checkBeforeRemote()) return;
try {
diff --git a/com/android/server/pm/PackageDexOptimizer.java b/com/android/server/pm/PackageDexOptimizer.java
index 29f48ee..00cfa31 100644
--- a/com/android/server/pm/PackageDexOptimizer.java
+++ b/com/android/server/pm/PackageDexOptimizer.java
@@ -154,7 +154,13 @@
targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo);
final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
final List<String> paths = pkg.getAllCodePaths();
- final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
+
+ int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
+ if (sharedGid == -1) {
+ Slog.wtf(TAG, "Well this is awkward; package " + pkg.applicationInfo.name + " had UID "
+ + pkg.applicationInfo.uid, new Throwable());
+ sharedGid = android.os.Process.NOBODY_UID;
+ }
// Get the class loader context dependencies.
// For each code path in the package, this array contains the class loader context that
diff --git a/com/android/server/pm/PackageManagerService.java b/com/android/server/pm/PackageManagerService.java
index 83cffe5..2d5f7c7 100644
--- a/com/android/server/pm/PackageManagerService.java
+++ b/com/android/server/pm/PackageManagerService.java
@@ -81,15 +81,12 @@
import static android.content.pm.PackageManager.MOVE_FAILED_SYSTEM_PACKAGE;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.content.pm.PackageParser.PARSE_IS_OEM;
-import static android.content.pm.PackageParser.PARSE_IS_PRIVILEGED;
import static android.content.pm.PackageParser.isApkFile;
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
import static android.os.storage.StorageManager.FLAG_STORAGE_CE;
import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
import static android.system.OsConstants.O_CREAT;
import static android.system.OsConstants.O_RDWR;
-
import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE;
import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_PARENT;
import static com.android.internal.content.NativeLibraryHelper.LIB64_DIR_NAME;
@@ -169,11 +166,13 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.PackageManager.LegacyPackageDeleteObserver;
+import android.content.pm.PackageManager.PackageInfoFlags;
import android.content.pm.PackageParser;
import android.content.pm.PackageParser.ActivityIntentInfo;
import android.content.pm.PackageParser.Package;
import android.content.pm.PackageParser.PackageLite;
import android.content.pm.PackageParser.PackageParserException;
+import android.content.pm.PackageParser.ParseFlags;
import android.content.pm.PackageStats;
import android.content.pm.PackageUserState;
import android.content.pm.ParceledListSlice;
@@ -293,6 +292,7 @@
import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.Settings.DatabaseVersion;
import com.android.server.pm.Settings.VersionInfo;
+import com.android.server.pm.dex.DexLogger;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.DexoptOptions;
import com.android.server.pm.dex.PackageDexUsage;
@@ -447,27 +447,48 @@
// package apks to install directory.
private static final String INSTALL_PACKAGE_SUFFIX = "-";
- static final int SCAN_NO_DEX = 1<<1;
- static final int SCAN_FORCE_DEX = 1<<2;
- static final int SCAN_UPDATE_SIGNATURE = 1<<3;
- static final int SCAN_NEW_INSTALL = 1<<4;
- static final int SCAN_UPDATE_TIME = 1<<5;
- static final int SCAN_BOOTING = 1<<6;
- static final int SCAN_TRUSTED_OVERLAY = 1<<7;
- static final int SCAN_DELETE_DATA_ON_FAILURES = 1<<8;
- static final int SCAN_REPLACING = 1<<9;
- static final int SCAN_REQUIRE_KNOWN = 1<<10;
- static final int SCAN_MOVE = 1<<11;
- static final int SCAN_INITIAL = 1<<12;
- static final int SCAN_CHECK_ONLY = 1<<13;
- static final int SCAN_DONT_KILL_APP = 1<<14;
- static final int SCAN_IGNORE_FROZEN = 1<<15;
- static final int SCAN_FIRST_BOOT_OR_UPGRADE = 1<<16;
- static final int SCAN_AS_INSTANT_APP = 1<<17;
- static final int SCAN_AS_FULL_APP = 1<<18;
- static final int SCAN_AS_VIRTUAL_PRELOAD = 1<<19;
- /** Should not be with the scan flags */
- static final int FLAGS_REMOVE_CHATTY = 1<<31;
+ static final int SCAN_NO_DEX = 1<<0;
+ static final int SCAN_UPDATE_SIGNATURE = 1<<1;
+ static final int SCAN_NEW_INSTALL = 1<<2;
+ static final int SCAN_UPDATE_TIME = 1<<3;
+ static final int SCAN_BOOTING = 1<<4;
+ static final int SCAN_TRUSTED_OVERLAY = 1<<5;
+ static final int SCAN_DELETE_DATA_ON_FAILURES = 1<<6;
+ static final int SCAN_REQUIRE_KNOWN = 1<<7;
+ static final int SCAN_MOVE = 1<<8;
+ static final int SCAN_INITIAL = 1<<9;
+ static final int SCAN_CHECK_ONLY = 1<<10;
+ static final int SCAN_DONT_KILL_APP = 1<<11;
+ static final int SCAN_IGNORE_FROZEN = 1<<12;
+ static final int SCAN_FIRST_BOOT_OR_UPGRADE = 1<<13;
+ static final int SCAN_AS_INSTANT_APP = 1<<14;
+ static final int SCAN_AS_FULL_APP = 1<<15;
+ static final int SCAN_AS_VIRTUAL_PRELOAD = 1<<16;
+ static final int SCAN_AS_SYSTEM = 1<<17;
+ static final int SCAN_AS_PRIVILEGED = 1<<18;
+ static final int SCAN_AS_OEM = 1<<19;
+
+ @IntDef(flag = true, prefix = { "SCAN_" }, value = {
+ SCAN_NO_DEX,
+ SCAN_UPDATE_SIGNATURE,
+ SCAN_NEW_INSTALL,
+ SCAN_UPDATE_TIME,
+ SCAN_BOOTING,
+ SCAN_TRUSTED_OVERLAY,
+ SCAN_DELETE_DATA_ON_FAILURES,
+ SCAN_REQUIRE_KNOWN,
+ SCAN_MOVE,
+ SCAN_INITIAL,
+ SCAN_CHECK_ONLY,
+ SCAN_DONT_KILL_APP,
+ SCAN_IGNORE_FROZEN,
+ SCAN_FIRST_BOOT_OR_UPGRADE,
+ SCAN_AS_INSTANT_APP,
+ SCAN_AS_FULL_APP,
+ SCAN_AS_VIRTUAL_PRELOAD,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ScanFlags {}
private static final String STATIC_SHARED_LIB_DELIMITER = "_";
/** Extension of the compressed packages */
@@ -2380,7 +2401,10 @@
mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context,
"*dexopt*");
- mDexManager = new DexManager(this, mPackageDexOptimizer, installer, mInstallLock);
+ DexManager.Listener dexManagerListener = DexLogger.getListener(this,
+ installer, mInstallLock);
+ mDexManager = new DexManager(this, mPackageDexOptimizer, installer, mInstallLock,
+ dexManagerListener);
mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper());
mOnPermissionChangeListeners = new OnPermissionChangeListeners(
@@ -2511,32 +2535,44 @@
// Collect vendor overlay packages. (Do this before scanning any apps.)
// For security and version matching reason, only consider
// overlay packages if they reside in the right directory.
- scanDirTracedLI(new File(VENDOR_OVERLAY_DIR), mDefParseFlags
- | PackageParser.PARSE_IS_SYSTEM
- | PackageParser.PARSE_IS_SYSTEM_DIR
- | PackageParser.PARSE_TRUSTED_OVERLAY, scanFlags | SCAN_TRUSTED_OVERLAY, 0);
+ scanDirTracedLI(new File(VENDOR_OVERLAY_DIR),
+ mDefParseFlags
+ | PackageParser.PARSE_IS_SYSTEM_DIR,
+ scanFlags
+ | SCAN_AS_SYSTEM
+ | SCAN_TRUSTED_OVERLAY,
+ 0);
mParallelPackageParserCallback.findStaticOverlayPackages();
// Find base frameworks (resource packages without code).
- scanDirTracedLI(frameworkDir, mDefParseFlags
- | PackageParser.PARSE_IS_SYSTEM
- | PackageParser.PARSE_IS_SYSTEM_DIR
- | PackageParser.PARSE_IS_PRIVILEGED,
- scanFlags | SCAN_NO_DEX, 0);
+ scanDirTracedLI(frameworkDir,
+ mDefParseFlags
+ | PackageParser.PARSE_IS_SYSTEM_DIR,
+ scanFlags
+ | SCAN_NO_DEX
+ | SCAN_AS_SYSTEM
+ | SCAN_AS_PRIVILEGED,
+ 0);
// Collected privileged system packages.
final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app");
- scanDirTracedLI(privilegedAppDir, mDefParseFlags
- | PackageParser.PARSE_IS_SYSTEM
- | PackageParser.PARSE_IS_SYSTEM_DIR
- | PackageParser.PARSE_IS_PRIVILEGED, scanFlags, 0);
+ scanDirTracedLI(privilegedAppDir,
+ mDefParseFlags
+ | PackageParser.PARSE_IS_SYSTEM_DIR,
+ scanFlags
+ | SCAN_AS_SYSTEM
+ | SCAN_AS_PRIVILEGED,
+ 0);
// Collect ordinary system packages.
final File systemAppDir = new File(Environment.getRootDirectory(), "app");
- scanDirTracedLI(systemAppDir, mDefParseFlags
- | PackageParser.PARSE_IS_SYSTEM
- | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);
+ scanDirTracedLI(systemAppDir,
+ mDefParseFlags
+ | PackageParser.PARSE_IS_SYSTEM_DIR,
+ scanFlags
+ | SCAN_AS_SYSTEM,
+ 0);
// Collect all vendor packages.
File vendorAppDir = new File("/vendor/app");
@@ -2545,16 +2581,22 @@
} catch (IOException e) {
// failed to look up canonical path, continue with original one
}
- scanDirTracedLI(vendorAppDir, mDefParseFlags
- | PackageParser.PARSE_IS_SYSTEM
- | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);
+ scanDirTracedLI(vendorAppDir,
+ mDefParseFlags
+ | PackageParser.PARSE_IS_SYSTEM_DIR,
+ scanFlags
+ | SCAN_AS_SYSTEM,
+ 0);
// Collect all OEM packages.
final File oemAppDir = new File(Environment.getOemDirectory(), "app");
- scanDirTracedLI(oemAppDir, mDefParseFlags
- | PackageParser.PARSE_IS_SYSTEM
- | PackageParser.PARSE_IS_SYSTEM_DIR
- | PackageParser.PARSE_IS_OEM, scanFlags, 0);
+ scanDirTracedLI(oemAppDir,
+ mDefParseFlags
+ | PackageParser.PARSE_IS_SYSTEM_DIR,
+ scanFlags
+ | SCAN_AS_SYSTEM
+ | SCAN_AS_OEM,
+ 0);
// Prune any system packages that no longer exist.
final List<String> possiblyDeletedUpdatedSystemApps = new ArrayList<>();
@@ -2711,21 +2753,38 @@
logCriticalInfo(Log.WARN, "Expected better " + packageName
+ " but never showed up; reverting to system");
- int reparseFlags = mDefParseFlags;
+ final @ParseFlags int reparseFlags;
+ final @ScanFlags int rescanFlags;
if (FileUtils.contains(privilegedAppDir, scanFile)) {
- reparseFlags = PackageParser.PARSE_IS_SYSTEM
- | PackageParser.PARSE_IS_SYSTEM_DIR
- | PackageParser.PARSE_IS_PRIVILEGED;
+ reparseFlags =
+ mDefParseFlags |
+ PackageParser.PARSE_IS_SYSTEM_DIR;
+ rescanFlags =
+ scanFlags
+ | SCAN_AS_SYSTEM
+ | SCAN_AS_PRIVILEGED;
} else if (FileUtils.contains(systemAppDir, scanFile)) {
- reparseFlags = PackageParser.PARSE_IS_SYSTEM
- | PackageParser.PARSE_IS_SYSTEM_DIR;
+ reparseFlags =
+ mDefParseFlags |
+ PackageParser.PARSE_IS_SYSTEM_DIR;
+ rescanFlags =
+ scanFlags
+ | SCAN_AS_SYSTEM;
} else if (FileUtils.contains(vendorAppDir, scanFile)) {
- reparseFlags = PackageParser.PARSE_IS_SYSTEM
- | PackageParser.PARSE_IS_SYSTEM_DIR;
+ reparseFlags =
+ mDefParseFlags |
+ PackageParser.PARSE_IS_SYSTEM_DIR;
+ rescanFlags =
+ scanFlags
+ | SCAN_AS_SYSTEM;
} else if (FileUtils.contains(oemAppDir, scanFile)) {
- reparseFlags = PackageParser.PARSE_IS_SYSTEM
- | PackageParser.PARSE_IS_SYSTEM_DIR
- | PackageParser.PARSE_IS_OEM;
+ reparseFlags =
+ mDefParseFlags |
+ PackageParser.PARSE_IS_SYSTEM_DIR;
+ rescanFlags =
+ scanFlags
+ | SCAN_AS_SYSTEM
+ | SCAN_AS_OEM;
} else {
Slog.e(TAG, "Ignoring unexpected fallback path " + scanFile);
continue;
@@ -2734,7 +2793,7 @@
mSettings.enableSystemPackageLPw(packageName);
try {
- scanPackageTracedLI(scanFile, reparseFlags, scanFlags, 0, null);
+ scanPackageTracedLI(scanFile, reparseFlags, rescanFlags, 0, null);
} catch (PackageManagerException e) {
Slog.e(TAG, "Failed to parse original system package: "
+ e.getMessage());
@@ -3581,7 +3640,7 @@
final int N = list.size();
for (int i = 0; i < N; i++) {
ResolveInfo info = list.get(i);
- if (packageName.equals(info.activityInfo.packageName)) {
+ if (info.priority >= 0 && packageName.equals(info.activityInfo.packageName)) {
return true;
}
}
@@ -7986,96 +8045,95 @@
return finalList;
}
- private void scanDirTracedLI(File dir, final int parseFlags, int scanFlags, long currentTime) {
- Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + dir.getAbsolutePath() + "]");
+ private void scanDirTracedLI(File scanDir, final int parseFlags, int scanFlags, long currentTime) {
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]");
try {
- scanDirLI(dir, parseFlags, scanFlags, currentTime);
+ scanDirLI(scanDir, parseFlags, scanFlags, currentTime);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
- private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) {
- final File[] files = dir.listFiles();
+ private void scanDirLI(File scanDir, int parseFlags, int scanFlags, long currentTime) {
+ final File[] files = scanDir.listFiles();
if (ArrayUtils.isEmpty(files)) {
- Log.d(TAG, "No files in app dir " + dir);
+ Log.d(TAG, "No files in app dir " + scanDir);
return;
}
if (DEBUG_PACKAGE_SCANNING) {
- Log.d(TAG, "Scanning app dir " + dir + " scanFlags=" + scanFlags
+ Log.d(TAG, "Scanning app dir " + scanDir + " scanFlags=" + scanFlags
+ " flags=0x" + Integer.toHexString(parseFlags));
}
- ParallelPackageParser parallelPackageParser = new ParallelPackageParser(
+ try (ParallelPackageParser parallelPackageParser = new ParallelPackageParser(
mSeparateProcesses, mOnlyCore, mMetrics, mCacheDir,
- mParallelPackageParserCallback);
-
- // Submit files for parsing in parallel
- int fileCount = 0;
- for (File file : files) {
- final boolean isPackage = (isApkFile(file) || file.isDirectory())
- && !PackageInstallerService.isStageName(file.getName());
- if (!isPackage) {
- // Ignore entries which are not packages
- continue;
- }
- parallelPackageParser.submit(file, parseFlags);
- fileCount++;
- }
-
- // Process results one by one
- for (; fileCount > 0; fileCount--) {
- ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take();
- Throwable throwable = parseResult.throwable;
- int errorCode = PackageManager.INSTALL_SUCCEEDED;
-
- if (throwable == null) {
- // Static shared libraries have synthetic package names
- if (parseResult.pkg.applicationInfo.isStaticSharedLibrary()) {
- renameStaticSharedLibraryPackage(parseResult.pkg);
+ mParallelPackageParserCallback)) {
+ // Submit files for parsing in parallel
+ int fileCount = 0;
+ for (File file : files) {
+ final boolean isPackage = (isApkFile(file) || file.isDirectory())
+ && !PackageInstallerService.isStageName(file.getName());
+ if (!isPackage) {
+ // Ignore entries which are not packages
+ continue;
}
- try {
- if (errorCode == PackageManager.INSTALL_SUCCEEDED) {
- scanPackageLI(parseResult.pkg, parseResult.scanFile, parseFlags, scanFlags,
- currentTime, null);
+ parallelPackageParser.submit(file, parseFlags);
+ fileCount++;
+ }
+
+ // Process results one by one
+ for (; fileCount > 0; fileCount--) {
+ ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take();
+ Throwable throwable = parseResult.throwable;
+ int errorCode = PackageManager.INSTALL_SUCCEEDED;
+
+ if (throwable == null) {
+ // Static shared libraries have synthetic package names
+ if (parseResult.pkg.applicationInfo.isStaticSharedLibrary()) {
+ renameStaticSharedLibraryPackage(parseResult.pkg);
}
- } catch (PackageManagerException e) {
+ try {
+ if (errorCode == PackageManager.INSTALL_SUCCEEDED) {
+ scanPackageChildLI(parseResult.pkg, parseFlags, scanFlags,
+ currentTime, null);
+ }
+ } catch (PackageManagerException e) {
+ errorCode = e.error;
+ Slog.w(TAG, "Failed to scan " + parseResult.scanFile + ": " + e.getMessage());
+ }
+ } else if (throwable instanceof PackageParser.PackageParserException) {
+ PackageParser.PackageParserException e = (PackageParser.PackageParserException)
+ throwable;
errorCode = e.error;
- Slog.w(TAG, "Failed to scan " + parseResult.scanFile + ": " + e.getMessage());
+ Slog.w(TAG, "Failed to parse " + parseResult.scanFile + ": " + e.getMessage());
+ } else {
+ throw new IllegalStateException("Unexpected exception occurred while parsing "
+ + parseResult.scanFile, throwable);
}
- } else if (throwable instanceof PackageParser.PackageParserException) {
- PackageParser.PackageParserException e = (PackageParser.PackageParserException)
- throwable;
- errorCode = e.error;
- Slog.w(TAG, "Failed to parse " + parseResult.scanFile + ": " + e.getMessage());
- } else {
- throw new IllegalStateException("Unexpected exception occurred while parsing "
- + parseResult.scanFile, throwable);
- }
- // Delete invalid userdata apps
- if ((parseFlags & PackageParser.PARSE_IS_SYSTEM) == 0 &&
- errorCode == PackageManager.INSTALL_FAILED_INVALID_APK) {
- logCriticalInfo(Log.WARN,
- "Deleting invalid package at " + parseResult.scanFile);
- removeCodePathLI(parseResult.scanFile);
+ // Delete invalid userdata apps
+ if ((scanFlags & SCAN_AS_SYSTEM) == 0 &&
+ errorCode == PackageManager.INSTALL_FAILED_INVALID_APK) {
+ logCriticalInfo(Log.WARN,
+ "Deleting invalid package at " + parseResult.scanFile);
+ removeCodePathLI(parseResult.scanFile);
+ }
}
}
- parallelPackageParser.close();
}
public static void reportSettingsProblem(int priority, String msg) {
logCriticalInfo(priority, msg);
}
- private void collectCertificatesLI(PackageSetting ps, PackageParser.Package pkg, File srcFile,
- final int policyFlags) throws PackageManagerException {
+ private void collectCertificatesLI(PackageSetting ps, PackageParser.Package pkg,
+ final @ParseFlags int parseFlags) throws PackageManagerException {
// When upgrading from pre-N MR1, verify the package time stamp using the package
// directory and not the APK file.
final long lastModifiedTime = mIsPreNMR1Upgrade
- ? new File(pkg.codePath).lastModified() : getLastModifiedTime(pkg, srcFile);
+ ? new File(pkg.codePath).lastModified() : getLastModifiedTime(pkg);
if (ps != null
- && ps.codePath.equals(srcFile)
+ && ps.codePathString.equals(pkg.codePath)
&& ps.timeStamp == lastModifiedTime
&& !isCompatSignatureUpdateNeeded(pkg)
&& !isRecoverSignatureUpdateNeeded(pkg)) {
@@ -8098,12 +8156,12 @@
Slog.w(TAG, "PackageSetting for " + ps.name
+ " is missing signatures. Collecting certs again to recover them.");
} else {
- Slog.i(TAG, srcFile.toString() + " changed; collecting certs");
+ Slog.i(TAG, toString() + " changed; collecting certs");
}
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates");
- PackageParser.collectCertificates(pkg, policyFlags);
+ PackageParser.collectCertificates(pkg, parseFlags);
} catch (PackageParserException e) {
throw PackageManagerException.from(e);
} finally {
@@ -8138,10 +8196,6 @@
pp.setDisplayMetrics(mMetrics);
pp.setCallback(mPackageParserCallback);
- if ((scanFlags & SCAN_TRUSTED_OVERLAY) != 0) {
- parseFlags |= PackageParser.PARSE_TRUSTED_OVERLAY;
- }
-
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");
final PackageParser.Package pkg;
try {
@@ -8157,16 +8211,17 @@
renameStaticSharedLibraryPackage(pkg);
}
- return scanPackageLI(pkg, scanFile, parseFlags, scanFlags, currentTime, user);
+ return scanPackageChildLI(pkg, parseFlags, scanFlags, currentTime, user);
}
/**
* Scans a package and returns the newly parsed package.
* @throws PackageManagerException on a parse error.
*/
- private PackageParser.Package scanPackageLI(PackageParser.Package pkg, File scanFile,
- final int policyFlags, int scanFlags, long currentTime, @Nullable UserHandle user)
- throws PackageManagerException {
+ private PackageParser.Package scanPackageChildLI(PackageParser.Package pkg,
+ final @ParseFlags int parseFlags, @ScanFlags int scanFlags, long currentTime,
+ @Nullable UserHandle user)
+ throws PackageManagerException {
// If the package has children and this is the first dive in the function
// we scan the package with the SCAN_CHECK_ONLY flag set to see whether all
// packages (parent and children) would be successfully scanned before the
@@ -8181,20 +8236,20 @@
}
// Scan the parent
- PackageParser.Package scannedPkg = scanPackageInternalLI(pkg, scanFile, policyFlags,
+ PackageParser.Package scannedPkg = scanPackageInternalLI(pkg, parseFlags,
scanFlags, currentTime, user);
// Scan the children
final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
for (int i = 0; i < childCount; i++) {
PackageParser.Package childPackage = pkg.childPackages.get(i);
- scanPackageInternalLI(childPackage, scanFile, policyFlags, scanFlags,
+ scanPackageInternalLI(childPackage, parseFlags, scanFlags,
currentTime, user);
}
if ((scanFlags & SCAN_CHECK_ONLY) != 0) {
- return scanPackageLI(pkg, scanFile, policyFlags, scanFlags, currentTime, user);
+ return scanPackageChildLI(pkg, parseFlags, scanFlags, currentTime, user);
}
return scannedPkg;
@@ -8204,11 +8259,12 @@
* Scans a package and returns the newly parsed package.
* @throws PackageManagerException on a parse error.
*/
- private PackageParser.Package scanPackageInternalLI(PackageParser.Package pkg, File scanFile,
- int policyFlags, int scanFlags, long currentTime, @Nullable UserHandle user)
- throws PackageManagerException {
+ private PackageParser.Package scanPackageInternalLI(PackageParser.Package pkg,
+ @ParseFlags int parseFlags, @ScanFlags int scanFlags, long currentTime,
+ @Nullable UserHandle user)
+ throws PackageManagerException {
PackageSetting ps = null;
- PackageSetting updatedPkg;
+ PackageSetting updatedPs;
// reader
synchronized (mPackages) {
// Look to see if we already know about this package.
@@ -8225,14 +8281,14 @@
// Check to see if this package could be hiding/updating a system
// package. Must look for it either under the original or real
// package name depending on our state.
- updatedPkg = mSettings.getDisabledSystemPkgLPr(ps != null ? ps.name : pkg.packageName);
- if (DEBUG_INSTALL && updatedPkg != null) Slog.d(TAG, "updatedPkg = " + updatedPkg);
+ updatedPs = mSettings.getDisabledSystemPkgLPr(ps != null ? ps.name : pkg.packageName);
+ if (DEBUG_INSTALL && updatedPs != null) Slog.d(TAG, "updatedPkg = " + updatedPs);
// If this is a package we don't know about on the system partition, we
// may need to remove disabled child packages on the system partition
// or may need to not add child packages if the parent apk is updated
// on the data partition and no longer defines this child package.
- if ((policyFlags & PackageParser.PARSE_IS_SYSTEM) != 0) {
+ if ((scanFlags & SCAN_AS_SYSTEM) != 0) {
// If this is a parent package for an updated system app and this system
// app got an OTA update which no longer defines some of the child packages
// we have to prune them from the disabled system packages.
@@ -8260,28 +8316,27 @@
}
}
- final boolean isUpdatedPkg = updatedPkg != null;
- final boolean isUpdatedSystemPkg = isUpdatedPkg
- && (policyFlags & PackageParser.PARSE_IS_SYSTEM) != 0;
+ final boolean isUpdatedPkg = updatedPs != null;
+ final boolean isUpdatedSystemPkg = isUpdatedPkg && (scanFlags & SCAN_AS_SYSTEM) != 0;
boolean isUpdatedPkgBetter = false;
// First check if this is a system package that may involve an update
if (isUpdatedSystemPkg) {
// If new package is not located in "/system/priv-app" (e.g. due to an OTA),
// it needs to drop FLAG_PRIVILEGED.
- if (locationIsPrivileged(scanFile)) {
- updatedPkg.pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
+ if (locationIsPrivileged(pkg.codePath)) {
+ updatedPs.pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
} else {
- updatedPkg.pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
+ updatedPs.pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
}
// If new package is not located in "/oem" (e.g. due to an OTA),
// it needs to drop FLAG_OEM.
- if (locationIsOem(scanFile)) {
- updatedPkg.pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_OEM;
+ if (locationIsOem(pkg.codePath)) {
+ updatedPs.pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_OEM;
} else {
- updatedPkg.pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_OEM;
+ updatedPs.pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_OEM;
}
- if (ps != null && !ps.codePath.equals(scanFile)) {
+ if (ps != null && !ps.codePathString.equals(pkg.codePath)) {
// The path has changed from what was last scanned... check the
// version of the new path against what we have stored to determine
// what to do.
@@ -8289,26 +8344,27 @@
if (pkg.mVersionCode <= ps.versionCode) {
// The system package has been updated and the code path does not match
// Ignore entry. Skip it.
- if (DEBUG_INSTALL) Slog.i(TAG, "Package " + ps.name + " at " + scanFile
+ if (DEBUG_INSTALL) Slog.i(TAG, "Package " + ps.name + " at " + pkg.codePath
+ " ignored: updated version " + ps.versionCode
+ " better than this " + pkg.mVersionCode);
- if (!updatedPkg.codePath.equals(scanFile)) {
+ if (!updatedPs.codePathString.equals(pkg.codePath)) {
Slog.w(PackageManagerService.TAG, "Code path for hidden system pkg "
- + ps.name + " changing from " + updatedPkg.codePathString
- + " to " + scanFile);
- updatedPkg.codePath = scanFile;
- updatedPkg.codePathString = scanFile.toString();
- updatedPkg.resourcePath = scanFile;
- updatedPkg.resourcePathString = scanFile.toString();
+ + ps.name + " changing from " + updatedPs.codePathString
+ + " to " + pkg.codePath);
+ final File codePath = new File(pkg.codePath);
+ updatedPs.codePath = codePath;
+ updatedPs.codePathString = pkg.codePath;
+ updatedPs.resourcePath = codePath;
+ updatedPs.resourcePathString = pkg.codePath;
}
- updatedPkg.pkg = pkg;
- updatedPkg.versionCode = pkg.mVersionCode;
+ updatedPs.pkg = pkg;
+ updatedPs.versionCode = pkg.mVersionCode;
// Update the disabled system child packages to point to the package too.
- final int childCount = updatedPkg.childPackageNames != null
- ? updatedPkg.childPackageNames.size() : 0;
+ final int childCount = updatedPs.childPackageNames != null
+ ? updatedPs.childPackageNames.size() : 0;
for (int i = 0; i < childCount; i++) {
- String childPackageName = updatedPkg.childPackageNames.get(i);
+ String childPackageName = updatedPs.childPackageNames.get(i);
PackageSetting updatedChildPkg = mSettings.getDisabledSystemPkgLPr(
childPackageName);
if (updatedChildPkg != null) {
@@ -8329,7 +8385,7 @@
mPackages.remove(ps.name);
}
- logCriticalInfo(Log.WARN, "Package " + ps.name + " at " + scanFile
+ logCriticalInfo(Log.WARN, "Package " + ps.name + " at " + pkg.codePath
+ " reverting from " + ps.codePathString
+ ": new version " + pkg.mVersionCode
+ " better than installed " + ps.versionCode);
@@ -8349,7 +8405,7 @@
String resourcePath = null;
String baseResourcePath = null;
- if ((policyFlags & PackageParser.PARSE_FORWARD_LOCK) != 0 && !isUpdatedPkgBetter) {
+ if ((parseFlags & PackageParser.PARSE_FORWARD_LOCK) != 0 && !isUpdatedPkgBetter) {
if (ps != null && ps.resourcePathString != null) {
resourcePath = ps.resourcePathString;
baseResourcePath = ps.resourcePathString;
@@ -8376,39 +8432,38 @@
if (isUpdatedSystemPkg && !isUpdatedPkgBetter) {
// Set CPU Abis to application info.
if ((scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0) {
- final String cpuAbiOverride = deriveAbiOverride(pkg.cpuAbiOverride, updatedPkg);
- derivePackageAbi(pkg, scanFile, cpuAbiOverride, false, mAppLib32InstallDir);
+ final String cpuAbiOverride = deriveAbiOverride(pkg.cpuAbiOverride, updatedPs);
+ derivePackageAbi(pkg, cpuAbiOverride, false, mAppLib32InstallDir);
} else {
- pkg.applicationInfo.primaryCpuAbi = updatedPkg.primaryCpuAbiString;
- pkg.applicationInfo.secondaryCpuAbi = updatedPkg.secondaryCpuAbiString;
+ pkg.applicationInfo.primaryCpuAbi = updatedPs.primaryCpuAbiString;
+ pkg.applicationInfo.secondaryCpuAbi = updatedPs.secondaryCpuAbiString;
}
- pkg.mExtras = updatedPkg;
+ pkg.mExtras = updatedPs;
throw new PackageManagerException(Log.WARN, "Package " + ps.name + " at "
- + scanFile + " ignored: updated version " + ps.versionCode
+ + pkg.codePath + " ignored: updated version " + ps.versionCode
+ " better than this " + pkg.mVersionCode);
}
if (isUpdatedPkg) {
- // An updated system app will not have the PARSE_IS_SYSTEM flag set
- // initially
- policyFlags |= PackageParser.PARSE_IS_SYSTEM;
+ // updated system applications don't initially have the SCAN_AS_SYSTEM flag set
+ scanFlags |= SCAN_AS_SYSTEM;
- // An updated privileged app will not have the PARSE_IS_PRIVILEGED
+ // An updated privileged application will not have the PARSE_IS_PRIVILEGED
// flag set initially
- if ((updatedPkg.pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
- policyFlags |= PackageParser.PARSE_IS_PRIVILEGED;
+ if ((updatedPs.pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
+ scanFlags |= SCAN_AS_PRIVILEGED;
}
// An updated OEM app will not have the PARSE_IS_OEM
// flag set initially
- if ((updatedPkg.pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0) {
- policyFlags |= PackageParser.PARSE_IS_OEM;
+ if ((updatedPs.pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0) {
+ scanFlags |= SCAN_AS_OEM;
}
}
// Verify certificates against what was last scanned
- collectCertificatesLI(ps, pkg, scanFile, policyFlags);
+ collectCertificatesLI(ps, pkg, parseFlags);
/*
* A new system app appeared, but we already had a non-system one of the
@@ -8416,7 +8471,7 @@
*/
boolean shouldHideSystemApp = false;
if (!isUpdatedPkg && ps != null
- && (policyFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0 && !isSystemApp(ps)) {
+ && (parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0 && !isSystemApp(ps)) {
/*
* Check to make sure the signatures match first. If they don't,
* wipe the installed application and its data.
@@ -8438,7 +8493,7 @@
*/
if (pkg.mVersionCode <= ps.versionCode) {
shouldHideSystemApp = true;
- logCriticalInfo(Log.INFO, "Package " + ps.name + " appeared at " + scanFile
+ logCriticalInfo(Log.INFO, "Package " + ps.name + " appeared at " + pkg.codePath
+ " but new version " + pkg.mVersionCode + " better than installed "
+ ps.versionCode + "; hiding system");
} else {
@@ -8448,7 +8503,7 @@
* already-installed application and replace it with our own
* while keeping the application data.
*/
- logCriticalInfo(Log.WARN, "Package " + ps.name + " at " + scanFile
+ logCriticalInfo(Log.WARN, "Package " + ps.name + " at " + pkg.codePath
+ " reverting from " + ps.codePathString + ": new version "
+ pkg.mVersionCode + " better than installed " + ps.versionCode);
InstallArgs args = createInstallArgsForExisting(packageFlagsToInstallFlags(ps),
@@ -8464,9 +8519,9 @@
// are kept in different files. (except for app in either system or
// vendor path).
// TODO grab this value from PackageSettings
- if ((policyFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
+ if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
if (ps != null && !ps.codePath.equals(ps.resourcePath)) {
- policyFlags |= PackageParser.PARSE_FORWARD_LOCK;
+ parseFlags |= PackageParser.PARSE_FORWARD_LOCK;
}
}
@@ -8479,7 +8534,7 @@
}
// Note that we invoke the following method only if we are about to unpack an application
- PackageParser.Package scannedPkg = scanPackageLI(pkg, policyFlags, scanFlags
+ PackageParser.Package scannedPkg = scanPackageLI(pkg, parseFlags, scanFlags
| SCAN_UPDATE_SIGNATURE, currentTime, user);
/*
@@ -8985,11 +9040,11 @@
* Execute the background dexopt job immediately.
*/
@Override
- public boolean runBackgroundDexoptJob() {
+ public boolean runBackgroundDexoptJob(@Nullable List<String> packageNames) {
if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
return false;
}
- return BackgroundDexOptService.runIdleOptimizationsNow(this, mContext);
+ return BackgroundDexOptService.runIdleOptimizationsNow(this, mContext, packageNames);
}
List<PackageParser.Package> findSharedNonSystemLibraries(PackageParser.Package p) {
@@ -9481,8 +9536,8 @@
}
private PackageParser.Package scanPackageTracedLI(PackageParser.Package pkg,
- final int policyFlags, int scanFlags, long currentTime, @Nullable UserHandle user)
- throws PackageManagerException {
+ final @ParseFlags int parseFlags, @ScanFlags int scanFlags, long currentTime,
+ @Nullable UserHandle user) throws PackageManagerException {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanPackage");
// If the package has children and this is the first dive in the function
// we recursively scan the package with the SCAN_CHECK_ONLY flag set to see
@@ -9500,12 +9555,12 @@
final PackageParser.Package scannedPkg;
try {
// Scan the parent
- scannedPkg = scanPackageLI(pkg, policyFlags, scanFlags, currentTime, user);
+ scannedPkg = scanPackageLI(pkg, parseFlags, scanFlags, currentTime, user);
// Scan the children
final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
for (int i = 0; i < childCount; i++) {
PackageParser.Package childPkg = pkg.childPackages.get(i);
- scanPackageLI(childPkg, policyFlags,
+ scanPackageLI(childPkg, parseFlags,
scanFlags, currentTime, user);
}
} finally {
@@ -9513,18 +9568,18 @@
}
if ((scanFlags & SCAN_CHECK_ONLY) != 0) {
- return scanPackageTracedLI(pkg, policyFlags, scanFlags, currentTime, user);
+ return scanPackageTracedLI(pkg, parseFlags, scanFlags, currentTime, user);
}
return scannedPkg;
}
- private PackageParser.Package scanPackageLI(PackageParser.Package pkg, final int policyFlags,
- int scanFlags, long currentTime, @Nullable UserHandle user)
- throws PackageManagerException {
+ private PackageParser.Package scanPackageLI(PackageParser.Package pkg,
+ final @ParseFlags int parseFlags, final @ScanFlags int scanFlags, long currentTime,
+ @Nullable UserHandle user) throws PackageManagerException {
boolean success = false;
try {
- final PackageParser.Package res = scanPackageDirtyLI(pkg, policyFlags, scanFlags,
+ final PackageParser.Package res = scanPackageDirtyLI(pkg, parseFlags, scanFlags,
currentTime, user);
success = true;
return res;
@@ -9587,16 +9642,17 @@
}
private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg,
- final int policyFlags, final int scanFlags, long currentTime, @Nullable UserHandle user)
+ final @ParseFlags int parseFlags, final @ScanFlags int scanFlags, long currentTime,
+ @Nullable UserHandle user)
throws PackageManagerException {
if (DEBUG_PACKAGE_SCANNING) {
- if ((policyFlags & PackageParser.PARSE_CHATTY) != 0)
+ if ((parseFlags & PackageParser.PARSE_CHATTY) != 0)
Log.d(TAG, "Scanning package " + pkg.packageName);
}
- applyPolicy(pkg, policyFlags);
+ applyPolicy(pkg, parseFlags, scanFlags);
- assertPackageIsValid(pkg, policyFlags, scanFlags);
+ assertPackageIsValid(pkg, parseFlags, scanFlags);
if (Build.IS_DEBUGGABLE &&
pkg.isPrivileged() &&
@@ -9629,7 +9685,7 @@
suid = mSettings.getSharedUserLPw(
pkg.mSharedUserId, 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/, true /*create*/);
if (DEBUG_PACKAGE_SCANNING) {
- if ((policyFlags & PackageParser.PARSE_CHATTY) != 0)
+ if ((parseFlags & PackageParser.PARSE_CHATTY) != 0)
Log.d(TAG, "Shared UserID " + pkg.mSharedUserId + " (uid=" + suid.userId
+ "): packages=" + suid.packages);
}
@@ -9797,7 +9853,7 @@
}
if ((scanFlags & SCAN_BOOTING) == 0
- && (policyFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
+ && (parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
// Check all shared libraries and map to their actual file path.
// We only do this here for apps not on a system dir, because those
// are the only ones that can fail an install due to this. We
@@ -9834,7 +9890,7 @@
// over the latest parsed certs.
pkgSetting.signatures.mSignatures = pkg.mSignatures;
} else {
- if ((policyFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
+ if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
"Package " + pkg.packageName + " upgrade keys do not match the "
+ "previously installed version");
@@ -9861,7 +9917,7 @@
// over the latest parsed certs.
pkgSetting.signatures.mSignatures = pkg.mSignatures;
} catch (PackageManagerException e) {
- if ((policyFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
+ if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
throw e;
}
// The signature has changed, but this package is in the system
@@ -9921,8 +9977,7 @@
if ((scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "derivePackageAbi");
final boolean extractNativeLibs = !pkg.isLibrary();
- derivePackageAbi(pkg, scanFile, cpuAbiOverride, extractNativeLibs,
- mAppLib32InstallDir);
+ derivePackageAbi(pkg, cpuAbiOverride, extractNativeLibs, mAppLib32InstallDir);
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
// Some system apps still use directory structure for native libraries
@@ -10029,7 +10084,7 @@
}
// Take care of first install / last update times.
- final long scanFileTime = getLastModifiedTime(pkg, scanFile);
+ final long scanFileTime = getLastModifiedTime(pkg);
if (currentTime != 0) {
if (pkgSetting.firstInstallTime == 0) {
pkgSetting.firstInstallTime = pkgSetting.lastUpdateTime = currentTime;
@@ -10039,7 +10094,7 @@
} else if (pkgSetting.firstInstallTime == 0) {
// We need *something*. Take time time stamp of the file.
pkgSetting.firstInstallTime = pkgSetting.lastUpdateTime = scanFileTime;
- } else if ((policyFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0) {
+ } else if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0) {
if (scanFileTime != pkgSetting.timeStamp) {
// A package on the system image has changed; consider this
// to be an update.
@@ -10058,7 +10113,7 @@
final int userId = user == null ? 0 : user.getIdentifier();
// Modify state for the given package setting
commitPackageSettings(pkg, pkgSetting, user, scanFlags,
- (policyFlags & PackageParser.PARSE_CHATTY) != 0 /*chatty*/);
+ (parseFlags & PackageParser.PARSE_CHATTY) != 0 /*chatty*/);
if (pkgSetting.getInstantApp(userId)) {
mInstantAppRegistry.addInstantAppLPw(userId, pkgSetting.appId);
}
@@ -10073,8 +10128,9 @@
* Implementation detail: This method must NOT have any side effect. It would
* ideally be static, but, it requires locks to read system state.
*/
- private void applyPolicy(PackageParser.Package pkg, int policyFlags) {
- if ((policyFlags&PackageParser.PARSE_IS_SYSTEM) != 0) {
+ private void applyPolicy(PackageParser.Package pkg, final @ParseFlags int parseFlags,
+ final @ScanFlags int scanFlags) {
+ if ((scanFlags & SCAN_AS_SYSTEM) != 0) {
pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
if (pkg.applicationInfo.isDirectBootAware()) {
// we're direct boot aware; set for all components
@@ -10095,21 +10151,60 @@
pkg.isStub = true;
}
} else {
- // Only allow system apps to be flagged as core apps.
+ // non system apps can't be flagged as core
pkg.coreApp = false;
// clear flags not applicable to regular apps
+ pkg.applicationInfo.flags &=
+ ~ApplicationInfo.FLAG_PERSISTENT;
pkg.applicationInfo.privateFlags &=
~ApplicationInfo.PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE;
pkg.applicationInfo.privateFlags &=
~ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE;
+ // clear protected broadcasts
+ pkg.protectedBroadcasts = null;
+ // cap permission priorities
+ if (pkg.permissionGroups != null && pkg.permissionGroups.size() > 0) {
+ for (int i = pkg.permissionGroups.size() - 1; i >= 0; --i) {
+ pkg.permissionGroups.get(i).info.priority = 0;
+ }
+ }
}
- pkg.mTrustedOverlay = (policyFlags&PackageParser.PARSE_TRUSTED_OVERLAY) != 0;
+ if ((scanFlags & SCAN_AS_PRIVILEGED) == 0) {
+ // ignore export request for single user receivers
+ if (pkg.receivers != null) {
+ for (int i = pkg.receivers.size() - 1; i >= 0; --i) {
+ final PackageParser.Activity receiver = pkg.receivers.get(i);
+ if ((receiver.info.flags & ActivityInfo.FLAG_SINGLE_USER) != 0) {
+ receiver.info.exported = false;
+ }
+ }
+ }
+ // ignore export request for single user services
+ if (pkg.services != null) {
+ for (int i = pkg.services.size() - 1; i >= 0; --i) {
+ final PackageParser.Service service = pkg.services.get(i);
+ if ((service.info.flags & ServiceInfo.FLAG_SINGLE_USER) != 0) {
+ service.info.exported = false;
+ }
+ }
+ }
+ // ignore export request for single user providers
+ if (pkg.providers != null) {
+ for (int i = pkg.providers.size() - 1; i >= 0; --i) {
+ final PackageParser.Provider provider = pkg.providers.get(i);
+ if ((provider.info.flags & ProviderInfo.FLAG_SINGLE_USER) != 0) {
+ provider.info.exported = false;
+ }
+ }
+ }
+ }
+ pkg.mTrustedOverlay = (scanFlags & SCAN_TRUSTED_OVERLAY) != 0;
- if ((policyFlags&PackageParser.PARSE_IS_PRIVILEGED) != 0) {
+ if ((scanFlags & SCAN_AS_PRIVILEGED) != 0) {
pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
}
- if ((policyFlags&PackageParser.PARSE_IS_OEM) != 0) {
+ if ((scanFlags & SCAN_AS_OEM) != 0) {
pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_OEM;
}
@@ -10130,9 +10225,10 @@
*
* @throws PackageManagerException If the package fails any of the validation checks
*/
- private void assertPackageIsValid(PackageParser.Package pkg, int policyFlags, int scanFlags)
- throws PackageManagerException {
- if ((policyFlags & PackageParser.PARSE_ENFORCE_CODE) != 0) {
+ private void assertPackageIsValid(PackageParser.Package pkg, final @ParseFlags int parseFlags,
+ final @ScanFlags int scanFlags)
+ throws PackageManagerException {
+ if ((parseFlags & PackageParser.PARSE_ENFORCE_CODE) != 0) {
assertCodePolicy(pkg);
}
@@ -10290,7 +10386,7 @@
// Only privileged apps and updated privileged apps can add child packages.
if (pkg.childPackages != null && !pkg.childPackages.isEmpty()) {
- if ((policyFlags & PARSE_IS_PRIVILEGED) == 0) {
+ if ((scanFlags & SCAN_AS_PRIVILEGED) == 0) {
throw new PackageManagerException("Only privileged apps can add child "
+ "packages. Ignoring package " + pkg.packageName);
}
@@ -10416,7 +10512,8 @@
* be available for query, resolution, etc...
*/
private void commitPackageSettings(PackageParser.Package pkg, PackageSetting pkgSetting,
- UserHandle user, int scanFlags, boolean chatty) throws PackageManagerException {
+ UserHandle user, final @ScanFlags int scanFlags, boolean chatty)
+ throws PackageManagerException {
final String pkgName = pkg.packageName;
if (mCustomResolverComponentName != null &&
mCustomResolverComponentName.getPackageName().equals(pkg.packageName)) {
@@ -10767,10 +10864,9 @@
*
* If {@code extractLibs} is true, native libraries are extracted from the app if required.
*/
- private static void derivePackageAbi(PackageParser.Package pkg, File scanFile,
- String cpuAbiOverride, boolean extractLibs,
- File appLib32InstallDir)
- throws PackageManagerException {
+ private static void derivePackageAbi(PackageParser.Package pkg, String cpuAbiOverride,
+ boolean extractLibs, File appLib32InstallDir)
+ throws PackageManagerException {
// Give ourselves some initial paths; we'll come back for another
// pass once we've determined ABI below.
setNativeLibraryPaths(pkg, appLib32InstallDir);
@@ -14903,7 +14999,12 @@
resourceFile = afterCodeFile;
// Reflect the rename in scanned details
- pkg.setCodePath(afterCodeFile.getAbsolutePath());
+ try {
+ pkg.setCodePath(afterCodeFile.getCanonicalPath());
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to get path: " + afterCodeFile, e);
+ return false;
+ }
pkg.setBaseCodePath(FileUtils.rewriteAfterRename(beforeCodeFile,
afterCodeFile, pkg.baseCodePath));
pkg.setSplitCodePaths(FileUtils.rewriteAfterRename(beforeCodeFile,
@@ -15248,9 +15349,9 @@
/*
* Install a non-existing package.
*/
- private void installNewPackageLIF(PackageParser.Package pkg, final int policyFlags,
- int scanFlags, UserHandle user, String installerPackageName, String volumeUuid,
- PackageInstalledInfo res, int installReason) {
+ private void installNewPackageLIF(PackageParser.Package pkg, final @ParseFlags int parseFlags,
+ final @ScanFlags int scanFlags, UserHandle user, String installerPackageName,
+ String volumeUuid, PackageInstalledInfo res, int installReason) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installNewPackage");
// Remember this for later, in case we need to rollback this install
@@ -15279,7 +15380,7 @@
}
try {
- PackageParser.Package newPackage = scanPackageTracedLI(pkg, policyFlags, scanFlags,
+ PackageParser.Package newPackage = scanPackageTracedLI(pkg, parseFlags, scanFlags,
System.currentTimeMillis(), user);
updateSettingsLI(newPackage, installerPackageName, null, res, user, installReason);
@@ -15307,9 +15408,9 @@
}
}
- private void replacePackageLIF(PackageParser.Package pkg, final int policyFlags, int scanFlags,
- UserHandle user, String installerPackageName, PackageInstalledInfo res,
- int installReason) {
+ private void replacePackageLIF(PackageParser.Package pkg, final @ParseFlags int parseFlags,
+ final @ScanFlags int scanFlags, UserHandle user, String installerPackageName,
+ PackageInstalledInfo res, int installReason) {
final boolean isInstantApp = (scanFlags & SCAN_AS_INSTANT_APP) != 0;
final PackageParser.Package oldPackage;
@@ -15329,7 +15430,7 @@
== android.os.Build.VERSION_CODES.CUR_DEVELOPMENT;
if (oldTargetsPreRelease
&& !newTargetsPreRelease
- && ((policyFlags & PackageParser.PARSE_FORCE_SDK) == 0)) {
+ && ((parseFlags & PackageParser.PARSE_FORCE_SDK) == 0)) {
Slog.w(TAG, "Can't install package targeting released sdk");
res.setReturnCode(PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE);
return;
@@ -15480,15 +15581,16 @@
final boolean oem =
(oldPackage.applicationInfo.privateFlags
& ApplicationInfo.PRIVATE_FLAG_OEM) != 0;
- final int systemPolicyFlags = policyFlags
- | PackageParser.PARSE_IS_SYSTEM
- | (privileged ? PARSE_IS_PRIVILEGED : 0)
- | (oem ? PARSE_IS_OEM : 0);
+ final @ParseFlags int systemParseFlags = parseFlags;
+ final @ScanFlags int systemScanFlags = scanFlags
+ | SCAN_AS_SYSTEM
+ | (privileged ? SCAN_AS_PRIVILEGED : 0)
+ | (oem ? SCAN_AS_OEM : 0);
- replaceSystemPackageLIF(oldPackage, pkg, systemPolicyFlags, scanFlags,
+ replaceSystemPackageLIF(oldPackage, pkg, systemParseFlags, systemScanFlags,
user, allUsers, installerPackageName, res, installReason);
} else {
- replaceNonSystemPackageLIF(oldPackage, pkg, policyFlags, scanFlags,
+ replaceNonSystemPackageLIF(oldPackage, pkg, parseFlags, scanFlags,
user, allUsers, installerPackageName, res, installReason);
}
}
@@ -15510,9 +15612,9 @@
}
private void replaceNonSystemPackageLIF(PackageParser.Package deletedPackage,
- PackageParser.Package pkg, final int policyFlags, int scanFlags, UserHandle user,
- int[] allUsers, String installerPackageName, PackageInstalledInfo res,
- int installReason) {
+ PackageParser.Package pkg, final @ParseFlags int parseFlags,
+ final @ScanFlags int scanFlags, UserHandle user, int[] allUsers,
+ String installerPackageName, PackageInstalledInfo res, int installReason) {
if (DEBUG_INSTALL) Slog.d(TAG, "replaceNonSystemPackageLI: new=" + pkg + ", old="
+ deletedPackage);
@@ -15553,7 +15655,7 @@
clearAppProfilesLIF(deletedPackage, UserHandle.USER_ALL);
try {
- final PackageParser.Package newPackage = scanPackageTracedLI(pkg, policyFlags,
+ final PackageParser.Package newPackage = scanPackageTracedLI(pkg, parseFlags,
scanFlags | SCAN_UPDATE_TIME, System.currentTimeMillis(), user);
updateSettingsLI(newPackage, installerPackageName, allUsers, res, user,
installReason);
@@ -15659,7 +15761,8 @@
}
private void replaceSystemPackageLIF(PackageParser.Package deletedPackage,
- PackageParser.Package pkg, final int policyFlags, int scanFlags, UserHandle user,
+ PackageParser.Package pkg, final @ParseFlags int parseFlags,
+ final @ScanFlags int scanFlags, UserHandle user,
int[] allUsers, String installerPackageName, PackageInstalledInfo res,
int installReason) {
if (DEBUG_INSTALL) Slog.d(TAG, "replaceSystemPackageLI: new=" + pkg
@@ -15697,7 +15800,7 @@
PackageParser.Package newPackage = null;
try {
// Add the package to the internal data structures
- newPackage = scanPackageTracedLI(pkg, policyFlags, scanFlags, 0, user);
+ newPackage = scanPackageTracedLI(pkg, parseFlags, scanFlags, 0, user);
// Set the update and install times
PackageSetting deletedPkgSetting = (PackageSetting) deletedPackage.mExtras;
@@ -15754,7 +15857,7 @@
}
// Add back the old system package
try {
- scanPackageTracedLI(deletedPackage, policyFlags, SCAN_UPDATE_SIGNATURE, 0, user);
+ scanPackageTracedLI(deletedPackage, parseFlags, SCAN_UPDATE_SIGNATURE, 0, user);
} catch (PackageManagerException e) {
Slog.e(TAG, "Failed to restore original package: " + e.getMessage());
}
@@ -16008,7 +16111,7 @@
final boolean virtualPreload =
((installFlags & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0);
boolean replace = false;
- int scanFlags = SCAN_NEW_INSTALL | SCAN_UPDATE_SIGNATURE;
+ @ScanFlags int scanFlags = SCAN_NEW_INSTALL | SCAN_UPDATE_SIGNATURE;
if (args.move != null) {
// moving a complete application; perform an initial scan on the new install location
scanFlags |= SCAN_INITIAL;
@@ -16041,7 +16144,7 @@
}
// Retrieve PackageSettings and parse package
- final int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY
+ @ParseFlags final int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY
| PackageParser.PARSE_ENFORCE_CODE
| (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0)
| (onExternal ? PackageParser.PARSE_EXTERNAL_STORAGE : 0)
@@ -16383,8 +16486,7 @@
String abiOverride = (TextUtils.isEmpty(pkg.cpuAbiOverride) ?
args.abiOverride : pkg.cpuAbiOverride);
final boolean extractNativeLibs = !pkg.isLibrary();
- derivePackageAbi(pkg, new File(pkg.codePath), abiOverride,
- extractNativeLibs, mAppLib32InstallDir);
+ derivePackageAbi(pkg, abiOverride, extractNativeLibs, mAppLib32InstallDir);
} catch (PackageManagerException pme) {
Slog.e(TAG, "Error deriving application ABI", pme);
res.setError(INSTALL_FAILED_INTERNAL_ERROR, "Error deriving application ABI");
@@ -16473,7 +16575,7 @@
return;
}
}
- replacePackageLIF(pkg, parseFlags, scanFlags | SCAN_REPLACING, args.user,
+ replacePackageLIF(pkg, parseFlags, scanFlags, args.user,
installerPackageName, res, args.installReason);
} else {
installNewPackageLIF(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES,
@@ -17099,7 +17201,7 @@
try (PackageFreezer freezer = freezePackageForDelete(packageName, freezeUser,
deleteFlags, "deletePackageX")) {
res = deletePackageLIF(packageName, UserHandle.of(removeUser), true, allUsers,
- deleteFlags | FLAGS_REMOVE_CHATTY, info, true, null);
+ deleteFlags | PackageManager.DELETE_CHATTY, info, true, null);
}
synchronized (mPackages) {
if (res) {
@@ -17291,7 +17393,7 @@
}
}
- removePackageLI(ps, (flags & FLAGS_REMOVE_CHATTY) != 0);
+ removePackageLI(ps, (flags & PackageManager.DELETE_CHATTY) != 0);
if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) {
final PackageParser.Package resolvedPkg;
@@ -17388,21 +17490,19 @@
}
}
- static boolean locationIsPrivileged(File path) {
+ static boolean locationIsPrivileged(String path) {
try {
- final String privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app")
- .getCanonicalPath();
- return path.getCanonicalPath().startsWith(privilegedAppDir);
+ final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app");
+ return path.startsWith(privilegedAppDir.getCanonicalPath());
} catch (IOException e) {
Slog.e(TAG, "Unable to access code path " + path);
}
return false;
}
- static boolean locationIsOem(File path) {
+ static boolean locationIsOem(String path) {
try {
- return path.getCanonicalPath().startsWith(
- Environment.getOemDirectory().getCanonicalPath());
+ return path.startsWith(Environment.getOemDirectory().getCanonicalPath());
} catch (IOException e) {
Slog.e(TAG, "Unable to access code path " + path);
}
@@ -17498,7 +17598,7 @@
// Install the system package
if (DEBUG_REMOVE) Slog.d(TAG, "Re-installing system package: " + disabledPs);
try {
- installPackageFromSystemLIF(disabledPs.codePath, false /*isPrivileged*/, allUserHandles,
+ installPackageFromSystemLIF(disabledPs.codePathString, false, allUserHandles,
outInfo.origUsers, deletedPs.getPermissionsState(), writeSettings);
} catch (PackageManagerException e) {
Slog.w(TAG, "Failed to restore system package:" + deletedPkg.packageName + ": "
@@ -17515,23 +17615,25 @@
/**
* Installs a package that's already on the system partition.
*/
- private PackageParser.Package installPackageFromSystemLIF(@NonNull File codePath,
+ private PackageParser.Package installPackageFromSystemLIF(@NonNull String codePathString,
boolean isPrivileged, @Nullable int[] allUserHandles, @Nullable int[] origUserHandles,
@Nullable PermissionsState origPermissionState, boolean writeSettings)
throws PackageManagerException {
- int parseFlags = mDefParseFlags
+ @ParseFlags int parseFlags =
+ mDefParseFlags
| PackageParser.PARSE_MUST_BE_APK
- | PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR;
- if (isPrivileged || locationIsPrivileged(codePath)) {
- parseFlags |= PackageParser.PARSE_IS_PRIVILEGED;
+ @ScanFlags int scanFlags = SCAN_AS_SYSTEM;
+ if (isPrivileged || locationIsPrivileged(codePathString)) {
+ scanFlags |= SCAN_AS_PRIVILEGED;
}
- if (locationIsOem(codePath)) {
- parseFlags |= PackageParser.PARSE_IS_OEM;
+ if (locationIsOem(codePathString)) {
+ scanFlags |= SCAN_AS_OEM;
}
+ final File codePath = new File(codePathString);
final PackageParser.Package pkg =
- scanPackageTracedLI(codePath, parseFlags, 0 /*scanFlags*/, 0 /*currentTime*/, null);
+ scanPackageTracedLI(codePath, parseFlags, scanFlags, 0 /*currentTime*/, null);
try {
// update shared libraries for the newly re-installed system package
@@ -19587,9 +19689,8 @@
pp.setCallback(mPackageParserCallback);
final PackageParser.Package tmpPkg;
try {
- final int parseFlags = mDefParseFlags
+ final @ParseFlags int parseFlags = mDefParseFlags
| PackageParser.PARSE_MUST_BE_APK
- | PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR;
tmpPkg = pp.parsePackage(codePath, parseFlags);
} catch (PackageParserException e) {
@@ -19642,7 +19743,7 @@
// until we can disable the package later.
enableSystemPackageLPw(deletedPkg);
}
- installPackageFromSystemLIF(new File(deletedPkg.codePath),
+ installPackageFromSystemLIF(deletedPkg.codePath,
false /*isPrivileged*/, null /*allUserHandles*/,
null /*origUserHandles*/, null /*origPermissionsState*/,
true /*writeSettings*/);
@@ -22607,6 +22708,12 @@
}
@Override
+ public int getPackageUid(String packageName, int flags, int userId) {
+ return PackageManagerService.this
+ .getPackageUid(packageName, flags, userId);
+ }
+
+ @Override
public ApplicationInfo getApplicationInfo(
String packageName, int flags, int filterCallingUid, int userId) {
return PackageManagerService.this
diff --git a/com/android/server/pm/PackageManagerServiceUtils.java b/com/android/server/pm/PackageManagerServiceUtils.java
index 758abd7..20ec9b5 100644
--- a/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/com/android/server/pm/PackageManagerServiceUtils.java
@@ -295,19 +295,20 @@
return false;
}
- public static long getLastModifiedTime(PackageParser.Package pkg, File srcFile) {
- if (srcFile.isDirectory()) {
- final File baseFile = new File(pkg.baseCodePath);
- long maxModifiedTime = baseFile.lastModified();
- if (pkg.splitCodePaths != null) {
- for (int i = pkg.splitCodePaths.length - 1; i >=0; --i) {
- final File splitFile = new File(pkg.splitCodePaths[i]);
- maxModifiedTime = Math.max(maxModifiedTime, splitFile.lastModified());
- }
- }
- return maxModifiedTime;
+ public static long getLastModifiedTime(PackageParser.Package pkg) {
+ final File srcFile = new File(pkg.codePath);
+ if (!srcFile.isDirectory()) {
+ return srcFile.lastModified();
}
- return srcFile.lastModified();
+ final File baseFile = new File(pkg.baseCodePath);
+ long maxModifiedTime = baseFile.lastModified();
+ if (pkg.splitCodePaths != null) {
+ for (int i = pkg.splitCodePaths.length - 1; i >=0; --i) {
+ final File splitFile = new File(pkg.splitCodePaths[i]);
+ maxModifiedTime = Math.max(maxModifiedTime, splitFile.lastModified());
+ }
+ }
+ return maxModifiedTime;
}
/**
@@ -570,7 +571,7 @@
if (!match) {
throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
"Package " + packageName +
- " signatures don't match previously installed version; ignoring!");
+ " signatures do not match previously installed version; ignoring!");
}
}
// Check for shared user signatures
@@ -578,16 +579,16 @@
// Already existing package. Make sure signatures match
boolean match = compareSignatures(pkgSetting.sharedUser.signatures.mSignatures,
parsedSignatures) == PackageManager.SIGNATURE_MATCH;
- if (!match) {
+ if (!match && compareCompat) {
match = matchSignaturesCompat(
packageName, pkgSetting.sharedUser.signatures, parsedSignatures);
}
- if (!match && compareCompat) {
+ if (!match && compareRecover) {
match = matchSignaturesRecover(
packageName, pkgSetting.sharedUser.signatures.mSignatures, parsedSignatures);
compatMatch |= match;
}
- if (!match && compareRecover) {
+ if (!match) {
throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,
"Package " + packageName
+ " has no signatures that match those in shared user "
diff --git a/com/android/server/pm/PackageManagerShellCommand.java b/com/android/server/pm/PackageManagerShellCommand.java
index 807eb1a..44f36d1 100644
--- a/com/android/server/pm/PackageManagerShellCommand.java
+++ b/com/android/server/pm/PackageManagerShellCommand.java
@@ -264,7 +264,7 @@
PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null,
null, null);
params.sessionParams.setSize(PackageHelper.calculateInstalledSize(
- pkgLite, params.sessionParams.abiOverride));
+ pkgLite, params.sessionParams.abiOverride, fd.getFileDescriptor()));
} catch (PackageParserException | IOException e) {
getErrPrintWriter().println("Error: Failed to parse APK file: " + inPath);
throw new IllegalArgumentException(
@@ -1169,11 +1169,17 @@
}
List<String> failedPackages = new ArrayList<>();
+ int index = 0;
for (String packageName : packageNames) {
if (clearProfileData) {
mInterface.clearApplicationProfileData(packageName);
}
+ if (allPackages) {
+ pw.println(++index + "/" + packageNames.size() + ": " + packageName);
+ pw.flush();
+ }
+
boolean result = secondaryDex
? mInterface.performDexOptSecondary(packageName,
targetCompilerFilter, forceCompilation)
@@ -1219,7 +1225,13 @@
}
private int runDexoptJob() throws RemoteException {
- boolean result = mInterface.runBackgroundDexoptJob();
+ String arg;
+ List<String> packageNames = new ArrayList<>();
+ while ((arg = getNextArg()) != null) {
+ packageNames.add(arg);
+ }
+ boolean result = mInterface.runBackgroundDexoptJob(packageNames.isEmpty() ? null :
+ packageNames);
return result ? 0 : -1;
}
diff --git a/com/android/server/pm/Settings.java b/com/android/server/pm/Settings.java
index 7077fd1..ddad677 100644
--- a/com/android/server/pm/Settings.java
+++ b/com/android/server/pm/Settings.java
@@ -3572,11 +3572,10 @@
int pkgFlags = 0;
int pkgPrivateFlags = 0;
pkgFlags |= ApplicationInfo.FLAG_SYSTEM;
- final File codePathFile = new File(codePathStr);
- if (PackageManagerService.locationIsPrivileged(codePathFile)) {
+ if (PackageManagerService.locationIsPrivileged(codePathStr)) {
pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
}
- PackageSetting ps = new PackageSetting(name, realName, codePathFile,
+ PackageSetting ps = new PackageSetting(name, realName, new File(codePathStr),
new File(resourcePathStr), legacyNativeLibraryPathStr, primaryCpuAbiStr,
secondaryCpuAbiStr, cpuAbiOverrideStr, versionCode, pkgFlags, pkgPrivateFlags,
parentPackageName, null /*childPackageNames*/, 0 /*sharedUserId*/, null, null);
diff --git a/com/android/server/pm/UserManagerService.java b/com/android/server/pm/UserManagerService.java
index 1152310..dbf413f 100644
--- a/com/android/server/pm/UserManagerService.java
+++ b/com/android/server/pm/UserManagerService.java
@@ -717,6 +717,19 @@
}
}
+ @Override
+ public int getProfileParentId(int userHandle) {
+ checkManageUsersPermission("get the profile parent");
+ synchronized (mUsersLock) {
+ UserInfo profileParent = getProfileParentLU(userHandle);
+ if (profileParent == null) {
+ return userHandle;
+ }
+
+ return profileParent.id;
+ }
+ }
+
private UserInfo getProfileParentLU(int userHandle) {
UserInfo profile = getUserInfoLU(userHandle);
if (profile == null) {
diff --git a/com/android/server/pm/dex/DexLogger.java b/com/android/server/pm/dex/DexLogger.java
new file mode 100644
index 0000000..88d9e52
--- /dev/null
+++ b/com/android/server/pm/dex/DexLogger.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2017 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.server.pm.dex;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.os.RemoteException;
+
+import android.util.ArraySet;
+import android.util.ByteStringUtils;
+import android.util.EventLog;
+import android.util.PackageUtils;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.pm.Installer;
+import com.android.server.pm.Installer.InstallerException;
+
+import java.io.File;
+import java.util.Set;
+
+import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
+
+/**
+ * This class is responsible for logging data about secondary dex files.
+ * The data logged includes hashes of the name and content of each file.
+ */
+public class DexLogger implements DexManager.Listener {
+ private static final String TAG = "DexLogger";
+
+ // Event log tag & subtag used for SafetyNet logging of dynamic
+ // code loading (DCL) - see b/63927552.
+ private static final int SNET_TAG = 0x534e4554;
+ private static final String DCL_SUBTAG = "dcl";
+
+ private final IPackageManager mPackageManager;
+ private final Object mInstallLock;
+ @GuardedBy("mInstallLock")
+ private final Installer mInstaller;
+
+ public static DexManager.Listener getListener(IPackageManager pms,
+ Installer installer, Object installLock) {
+ return new DexLogger(pms, installer, installLock);
+ }
+
+ @VisibleForTesting
+ /*package*/ DexLogger(IPackageManager pms, Installer installer, Object installLock) {
+ mPackageManager = pms;
+ mInstaller = installer;
+ mInstallLock = installLock;
+ }
+
+ /**
+ * Compute and log hashes of the name and content of a secondary dex file.
+ */
+ @Override
+ public void onReconcileSecondaryDexFile(ApplicationInfo appInfo, DexUseInfo dexUseInfo,
+ String dexPath, int storageFlags) {
+ int ownerUid = appInfo.uid;
+
+ byte[] hash = null;
+ synchronized(mInstallLock) {
+ try {
+ hash = mInstaller.hashSecondaryDexFile(dexPath, appInfo.packageName,
+ ownerUid, appInfo.volumeUuid, storageFlags);
+ } catch (InstallerException e) {
+ Slog.e(TAG, "Got InstallerException when hashing dex " + dexPath +
+ " : " + e.getMessage());
+ }
+ }
+ if (hash == null) {
+ return;
+ }
+
+ String dexFileName = new File(dexPath).getName();
+ String message = PackageUtils.computeSha256Digest(dexFileName.getBytes());
+ // Valid SHA256 will be 256 bits, 32 bytes.
+ if (hash.length == 32) {
+ message = message + ' ' + ByteStringUtils.toHexString(hash);
+ }
+
+ writeDclEvent(ownerUid, message);
+
+ if (dexUseInfo.isUsedByOtherApps()) {
+ Set<String> otherPackages = dexUseInfo.getLoadingPackages();
+ Set<Integer> otherUids = new ArraySet<>(otherPackages.size());
+ for (String otherPackageName : otherPackages) {
+ try {
+ int otherUid = mPackageManager.getPackageUid(
+ otherPackageName, /*flags*/0, dexUseInfo.getOwnerUserId());
+ if (otherUid != -1 && otherUid != ownerUid) {
+ otherUids.add(otherUid);
+ }
+ } catch (RemoteException ignore) {
+ // Can't happen, we're local.
+ }
+ }
+ for (int otherUid : otherUids) {
+ writeDclEvent(otherUid, message);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ /*package*/ void writeDclEvent(int uid, String message) {
+ EventLog.writeEvent(SNET_TAG, DCL_SUBTAG, uid, message);
+ }
+}
diff --git a/com/android/server/pm/dex/DexManager.java b/com/android/server/pm/dex/DexManager.java
index 6274754..0e2730c 100644
--- a/com/android/server/pm/dex/DexManager.java
+++ b/com/android/server/pm/dex/DexManager.java
@@ -76,6 +76,7 @@
private final Object mInstallLock;
@GuardedBy("mInstallLock")
private final Installer mInstaller;
+ private final Listener mListener;
// Possible outcomes of a dex search.
private static int DEX_SEARCH_NOT_FOUND = 0; // dex file not found
@@ -96,14 +97,24 @@
*/
private final static PackageUseInfo DEFAULT_USE_INFO = new PackageUseInfo();
+ public interface Listener {
+ /**
+ * Invoked just before the secondary dex file {@code dexPath} for the specified application
+ * is reconciled.
+ */
+ void onReconcileSecondaryDexFile(ApplicationInfo appInfo, DexUseInfo dexUseInfo,
+ String dexPath, int storageFlags);
+ }
+
public DexManager(IPackageManager pms, PackageDexOptimizer pdo,
- Installer installer, Object installLock) {
+ Installer installer, Object installLock, Listener listener) {
mPackageCodeLocationsCache = new HashMap<>();
mPackageDexUsage = new PackageDexUsage();
mPackageManager = pms;
mPackageDexOptimizer = pdo;
mInstaller = installer;
mInstallLock = installLock;
+ mListener = listener;
}
/**
@@ -389,7 +400,7 @@
: mPackageDexOptimizer;
String packageName = options.getPackageName();
PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName);
- if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) {
+ if (useInfo.getDexUseInfoMap().isEmpty()) {
if (DEBUG) {
Slog.d(TAG, "No secondary dex use for package:" + packageName);
}
@@ -433,7 +444,7 @@
*/
public void reconcileSecondaryDexFiles(String packageName) {
PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName);
- if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) {
+ if (useInfo.getDexUseInfoMap().isEmpty()) {
if (DEBUG) {
Slog.d(TAG, "No secondary dex use for package:" + packageName);
}
@@ -481,12 +492,16 @@
continue;
}
+ if (mListener != null) {
+ mListener.onReconcileSecondaryDexFile(info, dexUseInfo, dexPath, flags);
+ }
+
boolean dexStillExists = true;
synchronized(mInstallLock) {
try {
String[] isas = dexUseInfo.getLoaderIsas().toArray(new String[0]);
dexStillExists = mInstaller.reconcileSecondaryDexFile(dexPath, packageName,
- pkg.applicationInfo.uid, isas, pkg.applicationInfo.volumeUuid, flags);
+ info.uid, isas, info.volumeUuid, flags);
} catch (InstallerException e) {
Slog.e(TAG, "Got InstallerException when reconciling dex " + dexPath +
" : " + e.getMessage());
diff --git a/com/android/server/policy/BarController.java b/com/android/server/policy/BarController.java
index b179235..10d9565 100644
--- a/com/android/server/policy/BarController.java
+++ b/com/android/server/policy/BarController.java
@@ -24,9 +24,9 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
-import android.view.WindowManagerPolicy.WindowState;
import com.android.server.LocalServices;
+import com.android.server.policy.WindowManagerPolicy.WindowState;
import com.android.server.statusbar.StatusBarManagerInternal;
import java.io.PrintWriter;
diff --git a/com/android/server/policy/GlobalActions.java b/com/android/server/policy/GlobalActions.java
index 3707a5e..108b6b2 100644
--- a/com/android/server/policy/GlobalActions.java
+++ b/com/android/server/policy/GlobalActions.java
@@ -14,14 +14,14 @@
package com.android.server.policy;
-import com.android.server.LocalServices;
-import com.android.server.statusbar.StatusBarManagerInternal;
-import com.android.server.statusbar.StatusBarManagerInternal.GlobalActionsListener;
-
import android.content.Context;
import android.os.Handler;
import android.util.Slog;
-import android.view.WindowManagerPolicy.WindowManagerFuncs;
+
+import com.android.server.LocalServices;
+import com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs;
+import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.statusbar.StatusBarManagerInternal.GlobalActionsListener;
class GlobalActions implements GlobalActionsListener {
diff --git a/com/android/server/policy/LegacyGlobalActions.java b/com/android/server/policy/LegacyGlobalActions.java
index 8eb6d06..96d062d 100644
--- a/com/android/server/policy/LegacyGlobalActions.java
+++ b/com/android/server/policy/LegacyGlobalActions.java
@@ -25,6 +25,7 @@
import com.android.internal.telephony.TelephonyProperties;
import com.android.internal.R;
import com.android.internal.widget.LockPatternUtils;
+import com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs;
import android.app.ActivityManager;
import android.app.Dialog;
@@ -64,7 +65,6 @@
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
-import android.view.WindowManagerPolicy.WindowManagerFuncs;
import android.view.accessibility.AccessibilityEvent;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
@@ -919,7 +919,7 @@
/**
* @param enabledIconResId The icon for when this action is on.
* @param disabledIconResid The icon for when this action is off.
- * @param essage The general information message, e.g 'Silent Mode'
+ * @param message The general information message, e.g 'Silent Mode'
* @param enabledStatusMessageResId The on status message, e.g 'sound disabled'
* @param disabledStatusMessageResId The off status message, e.g. 'sound enabled'
*/
diff --git a/com/android/server/policy/PhoneWindowManager.java b/com/android/server/policy/PhoneWindowManager.java
index 9162a97..7415ec3 100644
--- a/com/android/server/policy/PhoneWindowManager.java
+++ b/com/android/server/policy/PhoneWindowManager.java
@@ -119,12 +119,13 @@
import static android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION;
import static android.view.WindowManagerGlobal.ADD_OKAY;
import static android.view.WindowManagerGlobal.ADD_PERMISSION_DENIED;
-import static android.view.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVERED;
-import static android.view.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVER_ABSENT;
-import static android.view.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_UNCOVERED;
-import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT;
-import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_CLOSED;
-import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN;
+
+import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVERED;
+import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVER_ABSENT;
+import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_UNCOVERED;
+import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT;
+import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_CLOSED;
+import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -210,7 +211,6 @@
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
-import android.view.DisplayFrames;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.IApplicationToken;
@@ -230,9 +230,6 @@
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.view.WindowManagerGlobal;
-import android.view.WindowManagerInternal;
-import android.view.WindowManagerInternal.AppTransitionListener;
-import android.view.WindowManagerPolicy;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.Animation;
@@ -243,6 +240,7 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.internal.policy.IShortcutService;
@@ -259,6 +257,9 @@
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.vr.VrManagerInternal;
import com.android.server.wm.AppTransition;
+import com.android.server.wm.DisplayFrames;
+import com.android.server.wm.WindowManagerInternal;
+import com.android.server.wm.WindowManagerInternal.AppTransitionListener;
import java.io.File;
import java.io.FileReader;
@@ -304,6 +305,10 @@
static final int LONG_PRESS_POWER_GLOBAL_ACTIONS = 1;
static final int LONG_PRESS_POWER_SHUT_OFF = 2;
static final int LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM = 3;
+ static final int LONG_PRESS_POWER_GO_TO_VOICE_ASSIST = 4;
+
+ static final int VERY_LONG_PRESS_POWER_NOTHING = 0;
+ static final int VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS = 1;
static final int MULTI_PRESS_POWER_NOTHING = 0;
static final int MULTI_PRESS_POWER_THEATER_MODE = 1;
@@ -569,6 +574,7 @@
boolean mLidControlsSleep;
int mShortPressOnPowerBehavior;
int mLongPressOnPowerBehavior;
+ int mVeryLongPressOnPowerBehavior;
int mDoublePressOnPowerBehavior;
int mTriplePressOnPowerBehavior;
int mLongPressOnBackBehavior;
@@ -586,6 +592,7 @@
boolean mHasSoftInput = false;
boolean mTranslucentDecorEnabled = true;
boolean mUseTvRouting;
+ int mVeryLongPressTimeout;
private boolean mHandleVolumeKeysInWM;
@@ -796,6 +803,7 @@
private static final int MSG_HANDLE_ALL_APPS = 26;
private static final int MSG_LAUNCH_ASSIST = 27;
private static final int MSG_LAUNCH_ASSIST_LONG_PRESS = 28;
+ private static final int MSG_POWER_VERY_LONG_PRESS = 29;
private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS = 0;
private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_NAVIGATION = 1;
@@ -855,6 +863,9 @@
case MSG_POWER_LONG_PRESS:
powerLongPress();
break;
+ case MSG_POWER_VERY_LONG_PRESS:
+ powerVeryLongPress();
+ break;
case MSG_UPDATE_DREAMING_SLEEP_TOKEN:
updateDreamingSleepToken(msg.arg1 != 0);
break;
@@ -1043,7 +1054,8 @@
private ImmersiveModeConfirmation mImmersiveModeConfirmation;
- private SystemGesturesPointerEventListener mSystemGestures;
+ @VisibleForTesting
+ SystemGesturesPointerEventListener mSystemGestures;
IStatusBarService getStatusBarService() {
synchronized (mServiceAquireLock) {
@@ -1299,6 +1311,12 @@
msg.setAsynchronous(true);
mHandler.sendMessageDelayed(msg,
ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());
+
+ if (hasVeryLongPressOnPowerBehavior()) {
+ Message longMsg = mHandler.obtainMessage(MSG_POWER_VERY_LONG_PRESS);
+ longMsg.setAsynchronous(true);
+ mHandler.sendMessageDelayed(longMsg, mVeryLongPressTimeout);
+ }
}
} else {
wakeUpFromPowerKey(event.getDownTime());
@@ -1308,6 +1326,13 @@
msg.setAsynchronous(true);
mHandler.sendMessageDelayed(msg,
ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());
+
+ if (hasVeryLongPressOnPowerBehavior()) {
+ Message longMsg = mHandler.obtainMessage(MSG_POWER_VERY_LONG_PRESS);
+ longMsg.setAsynchronous(true);
+ mHandler.sendMessageDelayed(longMsg, mVeryLongPressTimeout);
+ }
+
mBeganFromNonInteractive = true;
} else {
final int maxCount = getMaxMultiPressPowerCount();
@@ -1369,6 +1394,9 @@
mPowerKeyHandled = true;
mHandler.removeMessages(MSG_POWER_LONG_PRESS);
}
+ if (hasVeryLongPressOnPowerBehavior()) {
+ mHandler.removeMessages(MSG_POWER_VERY_LONG_PRESS);
+ }
}
private void cancelPendingBackKeyAction() {
@@ -1516,6 +1544,29 @@
sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);
mWindowManagerFuncs.shutdown(behavior == LONG_PRESS_POWER_SHUT_OFF);
break;
+ case LONG_PRESS_POWER_GO_TO_VOICE_ASSIST:
+ mPowerKeyHandled = true;
+ performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
+ final boolean keyguardActive = mKeyguardDelegate == null
+ ? false
+ : mKeyguardDelegate.isShowing();
+ if (!keyguardActive) {
+ Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST);
+ startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF);
+ }
+ break;
+ }
+ }
+
+ private void powerVeryLongPress() {
+ switch (mVeryLongPressOnPowerBehavior) {
+ case VERY_LONG_PRESS_POWER_NOTHING:
+ break;
+ case VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS:
+ mPowerKeyHandled = true;
+ performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
+ showGlobalActionsInternal();
+ break;
}
}
@@ -1574,6 +1625,10 @@
return getResolvedLongPressOnPowerBehavior() != LONG_PRESS_POWER_NOTHING;
}
+ private boolean hasVeryLongPressOnPowerBehavior() {
+ return mVeryLongPressOnPowerBehavior != VERY_LONG_PRESS_POWER_NOTHING;
+ }
+
private boolean hasLongPressOnBackBehavior() {
return mLongPressOnBackBehavior != LONG_PRESS_BACK_NOTHING;
}
@@ -1979,12 +2034,16 @@
com.android.internal.R.integer.config_shortPressOnPowerBehavior);
mLongPressOnPowerBehavior = mContext.getResources().getInteger(
com.android.internal.R.integer.config_longPressOnPowerBehavior);
+ mVeryLongPressOnPowerBehavior = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_veryLongPressOnPowerBehavior);
mDoublePressOnPowerBehavior = mContext.getResources().getInteger(
com.android.internal.R.integer.config_doublePressOnPowerBehavior);
mTriplePressOnPowerBehavior = mContext.getResources().getInteger(
com.android.internal.R.integer.config_triplePressOnPowerBehavior);
mShortPressOnSleepBehavior = mContext.getResources().getInteger(
com.android.internal.R.integer.config_shortPressOnSleepBehavior);
+ mVeryLongPressTimeout = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_veryLongPressTimeout);
mUseTvRouting = AudioSystem.getPlatformType(mContext) == AudioSystem.PLATFORM_TELEVISION;
@@ -2607,17 +2666,21 @@
// The status bar is the only window allowed to exhibit keyguard behavior.
attrs.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
}
+ }
+ private int getImpliedSysUiFlagsForLayout(LayoutParams attrs) {
+ int impliedFlags = 0;
if ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0) {
- attrs.subtreeSystemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
+ impliedFlags |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
}
final boolean forceWindowDrawsStatusBarBackground =
(attrs.privateFlags & PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND) != 0;
if ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
|| forceWindowDrawsStatusBarBackground
&& attrs.height == MATCH_PARENT && attrs.width == MATCH_PARENT) {
- attrs.subtreeSystemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+ impliedFlags |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
}
+ return impliedFlags;
}
void readLidState() {
@@ -2667,7 +2730,7 @@
@Override
public void onConfigurationChanged() {
// TODO(multi-display): Define policy for secondary displays.
- Context uiContext = ActivityThread.currentActivityThread().getSystemUiContext();
+ Context uiContext = getSystemUiContext();
final Resources res = uiContext.getResources();
mStatusBarHeight =
@@ -2708,6 +2771,11 @@
}
}
+ @VisibleForTesting
+ Context getSystemUiContext() {
+ return ActivityThread.currentActivityThread().getSystemUiContext();
+ }
+
@Override
public int getMaxWallpaperLayer() {
return getWindowLayerFromTypeLw(TYPE_STATUS_BAR);
@@ -4768,7 +4836,8 @@
final int fl = PolicyControl.getWindowFlags(win, attrs);
final int pfl = attrs.privateFlags;
final int sim = attrs.softInputMode;
- final int sysUiFl = PolicyControl.getSystemUiVisibility(win, null);
+ final int requestedSysUiFl = PolicyControl.getSystemUiVisibility(win, null);
+ final int sysUiFl = requestedSysUiFl | getImpliedSysUiFlagsForLayout(attrs);
final Rect pf = mTmpParentFrame;
final Rect df = mTmpDisplayFrame;
@@ -8062,19 +8131,6 @@
}
@Override
- public boolean canMagnifyWindow(int windowType) {
- switch (windowType) {
- case WindowManager.LayoutParams.TYPE_INPUT_METHOD:
- case WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG:
- case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR:
- case WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY: {
- return false;
- }
- }
- return true;
- }
-
- @Override
public boolean isTopLevelWindow(int windowType) {
if (windowType >= WindowManager.LayoutParams.FIRST_SUB_WINDOW
&& windowType <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
@@ -8206,6 +8262,9 @@
pw.print("mLongPressOnPowerBehavior=");
pw.println(longPressOnPowerBehaviorToString(mLongPressOnPowerBehavior));
pw.print(prefix);
+ pw.print("mVeryLongPressOnPowerBehavior=");
+ pw.println(veryLongPressOnPowerBehaviorToString(mVeryLongPressOnPowerBehavior));
+ pw.print(prefix);
pw.print("mDoublePressOnPowerBehavior=");
pw.println(multiPressOnPowerBehaviorToString(mDoublePressOnPowerBehavior));
pw.print(prefix);
@@ -8458,6 +8517,18 @@
return Integer.toString(behavior);
}
}
+
+ private static String veryLongPressOnPowerBehaviorToString(int behavior) {
+ switch (behavior) {
+ case VERY_LONG_PRESS_POWER_NOTHING:
+ return "VERY_LONG_PRESS_POWER_NOTHING";
+ case VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS:
+ return "VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS";
+ default:
+ return Integer.toString(behavior);
+ }
+ }
+
private static String multiPressOnPowerBehaviorToString(int behavior) {
switch (behavior) {
case MULTI_PRESS_POWER_NOTHING:
diff --git a/com/android/server/policy/PolicyControl.java b/com/android/server/policy/PolicyControl.java
index dbafc42..3f26d86 100644
--- a/com/android/server/policy/PolicyControl.java
+++ b/com/android/server/policy/PolicyControl.java
@@ -25,7 +25,8 @@
import android.view.View;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
-import android.view.WindowManagerPolicy.WindowState;
+
+import com.android.server.policy.WindowManagerPolicy.WindowState;
import java.io.PrintWriter;
import java.io.StringWriter;
@@ -36,7 +37,7 @@
* This includes forcing immersive mode behavior for one or both system bars (based on a package
* list) and permanently disabling immersive mode confirmations for specific packages.
*
- * Control by setting {@link Settings.Global.POLICY_CONTROL} to one or more name-value pairs.
+ * Control by setting {@link Settings.Global#POLICY_CONTROL} to one or more name-value pairs.
* e.g.
* to force immersive mode everywhere:
* "immersive.full=*"
diff --git a/com/android/server/policy/SplashScreenSurface.java b/com/android/server/policy/SplashScreenSurface.java
index 37d6c0b..b9202c3 100644
--- a/com/android/server/policy/SplashScreenSurface.java
+++ b/com/android/server/policy/SplashScreenSurface.java
@@ -23,11 +23,10 @@
import android.util.Slog;
import android.view.View;
import android.view.WindowManager;
-import android.view.WindowManagerPolicy;
-import android.view.WindowManagerPolicy.StartingSurface;
import com.android.internal.policy.DecorView;
import com.android.internal.policy.PhoneWindow;
+import com.android.server.policy.WindowManagerPolicy.StartingSurface;
/**
* Holds the contents of a splash screen starting window, i.e. the {@link DecorView} of a
diff --git a/com/android/server/policy/StatusBarController.java b/com/android/server/policy/StatusBarController.java
index ecc88b5..af7e91c 100644
--- a/com/android/server/policy/StatusBarController.java
+++ b/com/android/server/policy/StatusBarController.java
@@ -18,7 +18,7 @@
import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
import static android.view.WindowManager.LayoutParams.MATCH_PARENT;
-import static android.view.WindowManagerInternal.AppTransitionListener;
+import static com.android.server.wm.WindowManagerInternal.AppTransitionListener;
import android.app.StatusBarManager;
import android.os.IBinder;
diff --git a/com/android/server/policy/SystemGesturesPointerEventListener.java b/com/android/server/policy/SystemGesturesPointerEventListener.java
index 598c58e..d3cc8ef 100644
--- a/com/android/server/policy/SystemGesturesPointerEventListener.java
+++ b/com/android/server/policy/SystemGesturesPointerEventListener.java
@@ -24,7 +24,7 @@
import android.view.GestureDetector;
import android.view.InputDevice;
import android.view.MotionEvent;
-import android.view.WindowManagerPolicy.PointerEventListener;
+import android.view.WindowManagerPolicyConstants.PointerEventListener;
import android.widget.OverScroller;
/*
diff --git a/com/android/server/policy/WindowManagerPolicy.java b/com/android/server/policy/WindowManagerPolicy.java
new file mode 100644
index 0000000..5f067d4
--- /dev/null
+++ b/com/android/server/policy/WindowManagerPolicy.java
@@ -0,0 +1,1690 @@
+/*
+ * Copyright (C) 2017 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.server.policy;
+
+import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_BOOT_PROGRESS;
+import static android.view.WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+import static android.view.WindowManager.LayoutParams.TYPE_DRAG;
+import static android.view.WindowManager.LayoutParams.TYPE_DREAM;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_PHONE;
+import static android.view.WindowManager.LayoutParams.TYPE_POINTER;
+import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION;
+import static android.view.WindowManager.LayoutParams.TYPE_PRIORITY_PHONE;
+import static android.view.WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION;
+import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
+import static android.view.WindowManager.LayoutParams.TYPE_SEARCH_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
+import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
+import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION_STARTING;
+import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType;
+
+import android.Manifest;
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+import android.view.Display;
+import android.view.IApplicationToken;
+import android.view.IWindowManager;
+import android.view.InputEventReceiver;
+import android.view.KeyEvent;
+import android.view.Surface;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+import android.view.WindowManagerPolicyConstants;
+import android.view.animation.Animation;
+
+import com.android.internal.policy.IKeyguardDismissCallback;
+import com.android.internal.policy.IShortcutService;
+import com.android.server.wm.DisplayFrames;
+
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This interface supplies all UI-specific behavior of the window manager. An
+ * instance of it is created by the window manager when it starts up, and allows
+ * customization of window layering, special window types, key dispatching, and
+ * layout.
+ *
+ * <p>Because this provides deep interaction with the system window manager,
+ * specific methods on this interface can be called from a variety of contexts
+ * with various restrictions on what they can do. These are encoded through
+ * a suffixes at the end of a method encoding the thread the method is called
+ * from and any locks that are held when it is being called; if no suffix
+ * is attached to a method, then it is not called with any locks and may be
+ * called from the main window manager thread or another thread calling into
+ * the window manager.
+ *
+ * <p>The current suffixes are:
+ *
+ * <dl>
+ * <dt> Ti <dd> Called from the input thread. This is the thread that
+ * collects pending input events and dispatches them to the appropriate window.
+ * It may block waiting for events to be processed, so that the input stream is
+ * properly serialized.
+ * <dt> Tq <dd> Called from the low-level input queue thread. This is the
+ * thread that reads events out of the raw input devices and places them
+ * into the global input queue that is read by the <var>Ti</var> thread.
+ * This thread should not block for a long period of time on anything but the
+ * key driver.
+ * <dt> Lw <dd> Called with the main window manager lock held. Because the
+ * window manager is a very low-level system service, there are few other
+ * system services you can call with this lock held. It is explicitly okay to
+ * make calls into the package manager and power manager; it is explicitly not
+ * okay to make calls into the activity manager or most other services. Note that
+ * {@link android.content.Context#checkPermission(String, int, int)} and
+ * variations require calling into the activity manager.
+ * <dt> Li <dd> Called with the input thread lock held. This lock can be
+ * acquired by the window manager while it holds the window lock, so this is
+ * even more restrictive than <var>Lw</var>.
+ * </dl>
+ */
+public interface WindowManagerPolicy extends WindowManagerPolicyConstants {
+ // Navigation bar position values
+ int NAV_BAR_LEFT = 1 << 0;
+ int NAV_BAR_RIGHT = 1 << 1;
+ int NAV_BAR_BOTTOM = 1 << 2;
+
+ /**
+ * Pass this event to the user / app. To be returned from
+ * {@link #interceptKeyBeforeQueueing}.
+ */
+ int ACTION_PASS_TO_USER = 0x00000001;
+ /** Layout state may have changed (so another layout will be performed) */
+ int FINISH_LAYOUT_REDO_LAYOUT = 0x0001;
+ /** Configuration state may have changed */
+ int FINISH_LAYOUT_REDO_CONFIG = 0x0002;
+ /** Wallpaper may need to move */
+ int FINISH_LAYOUT_REDO_WALLPAPER = 0x0004;
+ /** Need to recompute animations */
+ int FINISH_LAYOUT_REDO_ANIM = 0x0008;
+
+ /**
+ * Register shortcuts for window manager to dispatch.
+ * Shortcut code is packed as (metaState << Integer.SIZE) | keyCode
+ * @hide
+ */
+ void registerShortcutKey(long shortcutCode, IShortcutService shortcutKeyReceiver)
+ throws RemoteException;
+
+ /**
+ * Called when the Keyguard occluded state changed.
+ * @param occluded Whether Keyguard is currently occluded or not.
+ */
+ void onKeyguardOccludedChangedLw(boolean occluded);
+
+ /**
+ * Interface to the Window Manager state associated with a particular
+ * window. You can hold on to an instance of this interface from the call
+ * to prepareAddWindow() until removeWindow().
+ */
+ public interface WindowState {
+ /**
+ * Return the uid of the app that owns this window.
+ */
+ int getOwningUid();
+
+ /**
+ * Return the package name of the app that owns this window.
+ */
+ String getOwningPackage();
+
+ /**
+ * Perform standard frame computation. The result can be obtained with
+ * getFrame() if so desired. Must be called with the window manager
+ * lock held.
+ *
+ * @param parentFrame The frame of the parent container this window
+ * is in, used for computing its basic position.
+ * @param displayFrame The frame of the overall display in which this
+ * window can appear, used for constraining the overall dimensions
+ * of the window.
+ * @param overlayFrame The frame within the display that is inside
+ * of the overlay region.
+ * @param contentFrame The frame within the display in which we would
+ * like active content to appear. This will cause windows behind to
+ * be resized to match the given content frame.
+ * @param visibleFrame The frame within the display that the window
+ * is actually visible, used for computing its visible insets to be
+ * given to windows behind.
+ * This can be used as a hint for scrolling (avoiding resizing)
+ * the window to make certain that parts of its content
+ * are visible.
+ * @param decorFrame The decor frame specified by policy specific to this window,
+ * to use for proper cropping during animation.
+ * @param stableFrame The frame around which stable system decoration is positioned.
+ * @param outsetFrame The frame that includes areas that aren't part of the surface but we
+ * want to treat them as such.
+ */
+ public void computeFrameLw(Rect parentFrame, Rect displayFrame,
+ Rect overlayFrame, Rect contentFrame, Rect visibleFrame, Rect decorFrame,
+ Rect stableFrame, @Nullable Rect outsetFrame);
+
+ /**
+ * Retrieve the current frame of the window that has been assigned by
+ * the window manager. Must be called with the window manager lock held.
+ *
+ * @return Rect The rectangle holding the window frame.
+ */
+ public Rect getFrameLw();
+
+ /**
+ * Retrieve the current position of the window that is actually shown.
+ * Must be called with the window manager lock held.
+ *
+ * @return Point The point holding the shown window position.
+ */
+ public Point getShownPositionLw();
+
+ /**
+ * Retrieve the frame of the display that this window was last
+ * laid out in. Must be called with the
+ * window manager lock held.
+ *
+ * @return Rect The rectangle holding the display frame.
+ */
+ public Rect getDisplayFrameLw();
+
+ /**
+ * Retrieve the frame of the area inside the overscan region of the
+ * display that this window was last laid out in. Must be called with the
+ * window manager lock held.
+ *
+ * @return Rect The rectangle holding the display overscan frame.
+ */
+ public Rect getOverscanFrameLw();
+
+ /**
+ * Retrieve the frame of the content area that this window was last
+ * laid out in. This is the area in which the content of the window
+ * should be placed. It will be smaller than the display frame to
+ * account for screen decorations such as a status bar or soft
+ * keyboard. Must be called with the
+ * window manager lock held.
+ *
+ * @return Rect The rectangle holding the content frame.
+ */
+ public Rect getContentFrameLw();
+
+ /**
+ * Retrieve the frame of the visible area that this window was last
+ * laid out in. This is the area of the screen in which the window
+ * will actually be fully visible. It will be smaller than the
+ * content frame to account for transient UI elements blocking it
+ * such as an input method's candidates UI. Must be called with the
+ * window manager lock held.
+ *
+ * @return Rect The rectangle holding the visible frame.
+ */
+ public Rect getVisibleFrameLw();
+
+ /**
+ * Returns true if this window is waiting to receive its given
+ * internal insets from the client app, and so should not impact the
+ * layout of other windows.
+ */
+ public boolean getGivenInsetsPendingLw();
+
+ /**
+ * Retrieve the insets given by this window's client for the content
+ * area of windows behind it. Must be called with the
+ * window manager lock held.
+ *
+ * @return Rect The left, top, right, and bottom insets, relative
+ * to the window's frame, of the actual contents.
+ */
+ public Rect getGivenContentInsetsLw();
+
+ /**
+ * Retrieve the insets given by this window's client for the visible
+ * area of windows behind it. Must be called with the
+ * window manager lock held.
+ *
+ * @return Rect The left, top, right, and bottom insets, relative
+ * to the window's frame, of the actual visible area.
+ */
+ public Rect getGivenVisibleInsetsLw();
+
+ /**
+ * Retrieve the current LayoutParams of the window.
+ *
+ * @return WindowManager.LayoutParams The window's internal LayoutParams
+ * instance.
+ */
+ public WindowManager.LayoutParams getAttrs();
+
+ /**
+ * Return whether this window needs the menu key shown. Must be called
+ * with window lock held, because it may need to traverse down through
+ * window list to determine the result.
+ * @param bottom The bottom-most window to consider when determining this.
+ */
+ public boolean getNeedsMenuLw(WindowState bottom);
+
+ /**
+ * Retrieve the current system UI visibility flags associated with
+ * this window.
+ */
+ public int getSystemUiVisibility();
+
+ /**
+ * Get the layer at which this window's surface will be Z-ordered.
+ */
+ public int getSurfaceLayer();
+
+ /**
+ * Retrieve the type of the top-level window.
+ *
+ * @return the base type of the parent window if attached or its own type otherwise
+ */
+ public int getBaseType();
+
+ /**
+ * Return the token for the application (actually activity) that owns
+ * this window. May return null for system windows.
+ *
+ * @return An IApplicationToken identifying the owning activity.
+ */
+ public IApplicationToken getAppToken();
+
+ /**
+ * Return true if this window is participating in voice interaction.
+ */
+ public boolean isVoiceInteraction();
+
+ /**
+ * Return true if, at any point, the application token associated with
+ * this window has actually displayed any windows. This is most useful
+ * with the "starting up" window to determine if any windows were
+ * displayed when it is closed.
+ *
+ * @return Returns true if one or more windows have been displayed,
+ * else false.
+ */
+ public boolean hasAppShownWindows();
+
+ /**
+ * Is this window visible? It is not visible if there is no
+ * surface, or we are in the process of running an exit animation
+ * that will remove the surface.
+ */
+ boolean isVisibleLw();
+
+ /**
+ * Is this window currently visible to the user on-screen? It is
+ * displayed either if it is visible or it is currently running an
+ * animation before no longer being visible. Must be called with the
+ * window manager lock held.
+ */
+ boolean isDisplayedLw();
+
+ /**
+ * Return true if this window (or a window it is attached to, but not
+ * considering its app token) is currently animating.
+ */
+ boolean isAnimatingLw();
+
+ /**
+ * @return Whether the window can affect SystemUI flags, meaning that SystemUI (system bars,
+ * for example) will be affected by the flags specified in this window. This is the
+ * case when the surface is on screen but not exiting.
+ */
+ boolean canAffectSystemUiFlags();
+
+ /**
+ * Is this window considered to be gone for purposes of layout?
+ */
+ boolean isGoneForLayoutLw();
+
+ /**
+ * Returns true if the window has a surface that it has drawn a
+ * complete UI in to. Note that this is different from {@link #hasDrawnLw()}
+ * in that it also returns true if the window is READY_TO_SHOW, but was not yet
+ * promoted to HAS_DRAWN.
+ */
+ boolean isDrawnLw();
+
+ /**
+ * Returns true if this window has been shown on screen at some time in
+ * the past. Must be called with the window manager lock held.
+ */
+ public boolean hasDrawnLw();
+
+ /**
+ * Can be called by the policy to force a window to be hidden,
+ * regardless of whether the client or window manager would like
+ * it shown. Must be called with the window manager lock held.
+ * Returns true if {@link #showLw} was last called for the window.
+ */
+ public boolean hideLw(boolean doAnimation);
+
+ /**
+ * Can be called to undo the effect of {@link #hideLw}, allowing a
+ * window to be shown as long as the window manager and client would
+ * also like it to be shown. Must be called with the window manager
+ * lock held.
+ * Returns true if {@link #hideLw} was last called for the window.
+ */
+ public boolean showLw(boolean doAnimation);
+
+ /**
+ * Check whether the process hosting this window is currently alive.
+ */
+ public boolean isAlive();
+
+ /**
+ * Check if window is on {@link Display#DEFAULT_DISPLAY}.
+ * @return true if window is on default display.
+ */
+ public boolean isDefaultDisplay();
+
+ /**
+ * Check whether the window is currently dimming.
+ */
+ public boolean isDimming();
+
+ /** @return the current windowing mode of this window. */
+ int getWindowingMode();
+
+ /**
+ * Returns true if the window is current in multi-windowing mode. i.e. it shares the
+ * screen with other application windows.
+ */
+ public boolean isInMultiWindowMode();
+
+ public int getRotationAnimationHint();
+
+ public boolean isInputMethodWindow();
+
+ public int getDisplayId();
+
+ /**
+ * Returns true if the window owner can add internal system windows.
+ * That is, they have {@link Manifest.permission#INTERNAL_SYSTEM_WINDOW}.
+ */
+ default boolean canAddInternalSystemWindow() {
+ return false;
+ }
+
+ /**
+ * Returns true if the window owner has the permission to acquire a sleep token when it's
+ * visible. That is, they have the permission {@link Manifest.permission#DEVICE_POWER}.
+ */
+ boolean canAcquireSleepToken();
+ }
+
+ /**
+ * Representation of a input consumer that the policy has added to the
+ * window manager to consume input events going to windows below it.
+ */
+ public interface InputConsumer {
+ /**
+ * Remove the input consumer from the window manager.
+ */
+ void dismiss();
+ }
+
+ /**
+ * Holds the contents of a starting window. {@link #addSplashScreen} needs to wrap the
+ * contents of the starting window into an class implementing this interface, which then will be
+ * held by WM and released with {@link #remove} when no longer needed.
+ */
+ interface StartingSurface {
+
+ /**
+ * Removes the starting window surface. Do not hold the window manager lock when calling
+ * this method!
+ */
+ void remove();
+ }
+
+ /**
+ * Interface for calling back in to the window manager that is private
+ * between it and the policy.
+ */
+ public interface WindowManagerFuncs {
+ public static final int LID_ABSENT = -1;
+ public static final int LID_CLOSED = 0;
+ public static final int LID_OPEN = 1;
+
+ public static final int CAMERA_LENS_COVER_ABSENT = -1;
+ public static final int CAMERA_LENS_UNCOVERED = 0;
+ public static final int CAMERA_LENS_COVERED = 1;
+
+ /**
+ * Ask the window manager to re-evaluate the system UI flags.
+ */
+ public void reevaluateStatusBarVisibility();
+
+ /**
+ * Add a input consumer which will consume all input events going to any window below it.
+ */
+ public InputConsumer createInputConsumer(Looper looper, String name,
+ InputEventReceiver.Factory inputEventReceiverFactory);
+
+ /**
+ * Returns a code that describes the current state of the lid switch.
+ */
+ public int getLidState();
+
+ /**
+ * Lock the device now.
+ */
+ public void lockDeviceNow();
+
+ /**
+ * Returns a code that descripbes whether the camera lens is covered or not.
+ */
+ public int getCameraLensCoverState();
+
+ /**
+ * Switch the input method, to be precise, input method subtype.
+ *
+ * @param forwardDirection {@code true} to rotate in a forward direction.
+ */
+ public void switchInputMethod(boolean forwardDirection);
+
+ public void shutdown(boolean confirm);
+ public void reboot(boolean confirm);
+ public void rebootSafeMode(boolean confirm);
+
+ /**
+ * Return the window manager lock needed to correctly call "Lw" methods.
+ */
+ public Object getWindowManagerLock();
+
+ /** Register a system listener for touch events */
+ void registerPointerEventListener(PointerEventListener listener);
+
+ /** Unregister a system listener for touch events */
+ void unregisterPointerEventListener(PointerEventListener listener);
+
+ /**
+ * @return The content insets of the docked divider window.
+ */
+ int getDockedDividerInsetsLw();
+
+ /**
+ * Retrieves the {@param outBounds} from the stack matching the {@param windowingMode} and
+ * {@param activityType}.
+ */
+ void getStackBounds(int windowingMode, int activityType, Rect outBounds);
+
+ /**
+ * Notifies window manager that {@link #isShowingDreamLw} has changed.
+ */
+ void notifyShowingDreamChanged();
+
+ /**
+ * @return The currently active input method window.
+ */
+ WindowState getInputMethodWindowLw();
+
+ /**
+ * Notifies window manager that {@link #isKeyguardTrustedLw} has changed.
+ */
+ void notifyKeyguardTrustedChanged();
+
+ /**
+ * Notifies the window manager that screen is being turned off.
+ *
+ * @param listener callback to call when display can be turned off
+ */
+ void screenTurningOff(ScreenOffListener listener);
+
+ /**
+ * Convert the lid state to a human readable format.
+ */
+ static String lidStateToString(int lid) {
+ switch (lid) {
+ case LID_ABSENT:
+ return "LID_ABSENT";
+ case LID_CLOSED:
+ return "LID_CLOSED";
+ case LID_OPEN:
+ return "LID_OPEN";
+ default:
+ return Integer.toString(lid);
+ }
+ }
+
+ /**
+ * Convert the camera lens state to a human readable format.
+ */
+ static String cameraLensStateToString(int lens) {
+ switch (lens) {
+ case CAMERA_LENS_COVER_ABSENT:
+ return "CAMERA_LENS_COVER_ABSENT";
+ case CAMERA_LENS_UNCOVERED:
+ return "CAMERA_LENS_UNCOVERED";
+ case CAMERA_LENS_COVERED:
+ return "CAMERA_LENS_COVERED";
+ default:
+ return Integer.toString(lens);
+ }
+ }
+ }
+
+ /** Window has been added to the screen. */
+ public static final int TRANSIT_ENTER = 1;
+ /** Window has been removed from the screen. */
+ public static final int TRANSIT_EXIT = 2;
+ /** Window has been made visible. */
+ public static final int TRANSIT_SHOW = 3;
+ /** Window has been made invisible.
+ * TODO: Consider removal as this is unused. */
+ public static final int TRANSIT_HIDE = 4;
+ /** The "application starting" preview window is no longer needed, and will
+ * animate away to show the real window. */
+ public static final int TRANSIT_PREVIEW_DONE = 5;
+
+ // NOTE: screen off reasons are in order of significance, with more
+ // important ones lower than less important ones.
+
+ /** @hide */
+ @IntDef({USER_ROTATION_FREE, USER_ROTATION_LOCKED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UserRotationMode {}
+
+ /** When not otherwise specified by the activity's screenOrientation, rotation should be
+ * determined by the system (that is, using sensors). */
+ public final int USER_ROTATION_FREE = 0;
+ /** When not otherwise specified by the activity's screenOrientation, rotation is set by
+ * the user. */
+ public final int USER_ROTATION_LOCKED = 1;
+
+ /**
+ * Perform initialization of the policy.
+ *
+ * @param context The system context we are running in.
+ */
+ public void init(Context context, IWindowManager windowManager,
+ WindowManagerFuncs windowManagerFuncs);
+
+ /**
+ * @return true if com.android.internal.R.bool#config_forceDefaultOrientation is true.
+ */
+ public boolean isDefaultOrientationForced();
+
+ /**
+ * Called by window manager once it has the initial, default native
+ * display dimensions.
+ */
+ public void setInitialDisplaySize(Display display, int width, int height, int density);
+
+ /**
+ * Check permissions when adding a window.
+ *
+ * @param attrs The window's LayoutParams.
+ * @param outAppOp First element will be filled with the app op corresponding to
+ * this window, or OP_NONE.
+ *
+ * @return {@link WindowManagerGlobal#ADD_OKAY} if the add can proceed;
+ * else an error code, usually
+ * {@link WindowManagerGlobal#ADD_PERMISSION_DENIED}, to abort the add.
+ */
+ public int checkAddPermission(WindowManager.LayoutParams attrs, int[] outAppOp);
+
+ /**
+ * Check permissions when adding a window.
+ *
+ * @param attrs The window's LayoutParams.
+ *
+ * @return True if the window may only be shown to the current user, false if the window can
+ * be shown on all users' windows.
+ */
+ public boolean checkShowToOwnerOnly(WindowManager.LayoutParams attrs);
+
+ /**
+ * Sanitize the layout parameters coming from a client. Allows the policy
+ * to do things like ensure that windows of a specific type can't take
+ * input focus.
+ *
+ * @param attrs The window layout parameters to be modified. These values
+ * are modified in-place.
+ */
+ public void adjustWindowParamsLw(WindowState win, WindowManager.LayoutParams attrs,
+ boolean hasStatusBarServicePermission);
+
+ /**
+ * After the window manager has computed the current configuration based
+ * on its knowledge of the display and input devices, it gives the policy
+ * a chance to adjust the information contained in it. If you want to
+ * leave it as-is, simply do nothing.
+ *
+ * <p>This method may be called by any thread in the window manager, but
+ * no internal locks in the window manager will be held.
+ *
+ * @param config The Configuration being computed, for you to change as
+ * desired.
+ * @param keyboardPresence Flags that indicate whether internal or external
+ * keyboards are present.
+ * @param navigationPresence Flags that indicate whether internal or external
+ * navigation devices are present.
+ */
+ public void adjustConfigurationLw(Configuration config, int keyboardPresence,
+ int navigationPresence);
+
+ /**
+ * Returns the layer assignment for the window state. Allows you to control how different
+ * kinds of windows are ordered on-screen.
+ *
+ * @param win The window state
+ * @return int An arbitrary integer used to order windows, with lower numbers below higher ones.
+ */
+ default int getWindowLayerLw(WindowState win) {
+ return getWindowLayerFromTypeLw(win.getBaseType(), win.canAddInternalSystemWindow());
+ }
+
+ /**
+ * Returns the layer assignment for the window type. Allows you to control how different
+ * kinds of windows are ordered on-screen.
+ *
+ * @param type The type of window being assigned.
+ * @return int An arbitrary integer used to order windows, with lower numbers below higher ones.
+ */
+ default int getWindowLayerFromTypeLw(int type) {
+ if (isSystemAlertWindowType(type)) {
+ throw new IllegalArgumentException("Use getWindowLayerFromTypeLw() or"
+ + " getWindowLayerLw() for alert window types");
+ }
+ return getWindowLayerFromTypeLw(type, false /* canAddInternalSystemWindow */);
+ }
+
+ /**
+ * Returns the layer assignment for the window type. Allows you to control how different
+ * kinds of windows are ordered on-screen.
+ *
+ * @param type The type of window being assigned.
+ * @param canAddInternalSystemWindow If the owner window associated with the type we are
+ * evaluating can add internal system windows. I.e they have
+ * {@link Manifest.permission#INTERNAL_SYSTEM_WINDOW}. If true, alert window
+ * types {@link android.view.WindowManager.LayoutParams#isSystemAlertWindowType(int)}
+ * can be assigned layers greater than the layer for
+ * {@link android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY} Else, their
+ * layers would be lesser.
+ * @return int An arbitrary integer used to order windows, with lower numbers below higher ones.
+ */
+ default int getWindowLayerFromTypeLw(int type, boolean canAddInternalSystemWindow) {
+ if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
+ return APPLICATION_LAYER;
+ }
+
+ switch (type) {
+ case TYPE_WALLPAPER:
+ // wallpaper is at the bottom, though the window manager may move it.
+ return 1;
+ case TYPE_PRESENTATION:
+ case TYPE_PRIVATE_PRESENTATION:
+ return APPLICATION_LAYER;
+ case TYPE_DOCK_DIVIDER:
+ return APPLICATION_LAYER;
+ case TYPE_QS_DIALOG:
+ return APPLICATION_LAYER;
+ case TYPE_PHONE:
+ return 3;
+ case TYPE_SEARCH_BAR:
+ case TYPE_VOICE_INTERACTION_STARTING:
+ return 4;
+ case TYPE_VOICE_INTERACTION:
+ // voice interaction layer is almost immediately above apps.
+ return 5;
+ case TYPE_INPUT_CONSUMER:
+ return 6;
+ case TYPE_SYSTEM_DIALOG:
+ return 7;
+ case TYPE_TOAST:
+ // toasts and the plugged-in battery thing
+ return 8;
+ case TYPE_PRIORITY_PHONE:
+ // SIM errors and unlock. Not sure if this really should be in a high layer.
+ return 9;
+ case TYPE_SYSTEM_ALERT:
+ // like the ANR / app crashed dialogs
+ return canAddInternalSystemWindow ? 11 : 10;
+ case TYPE_APPLICATION_OVERLAY:
+ return 12;
+ case TYPE_DREAM:
+ // used for Dreams (screensavers with TYPE_DREAM windows)
+ return 13;
+ case TYPE_INPUT_METHOD:
+ // on-screen keyboards and other such input method user interfaces go here.
+ return 14;
+ case TYPE_INPUT_METHOD_DIALOG:
+ // on-screen keyboards and other such input method user interfaces go here.
+ return 15;
+ case TYPE_STATUS_BAR:
+ return 17;
+ case TYPE_STATUS_BAR_PANEL:
+ return 18;
+ case TYPE_STATUS_BAR_SUB_PANEL:
+ return 19;
+ case TYPE_KEYGUARD_DIALOG:
+ return 20;
+ case TYPE_VOLUME_OVERLAY:
+ // the on-screen volume indicator and controller shown when the user
+ // changes the device volume
+ return 21;
+ case TYPE_SYSTEM_OVERLAY:
+ // the on-screen volume indicator and controller shown when the user
+ // changes the device volume
+ return canAddInternalSystemWindow ? 22 : 11;
+ case TYPE_NAVIGATION_BAR:
+ // the navigation bar, if available, shows atop most things
+ return 23;
+ case TYPE_NAVIGATION_BAR_PANEL:
+ // some panels (e.g. search) need to show on top of the navigation bar
+ return 24;
+ case TYPE_SCREENSHOT:
+ // screenshot selection layer shouldn't go above system error, but it should cover
+ // navigation bars at the very least.
+ return 25;
+ case TYPE_SYSTEM_ERROR:
+ // system-level error dialogs
+ return canAddInternalSystemWindow ? 26 : 10;
+ case TYPE_MAGNIFICATION_OVERLAY:
+ // used to highlight the magnified portion of a display
+ return 27;
+ case TYPE_DISPLAY_OVERLAY:
+ // used to simulate secondary display devices
+ return 28;
+ case TYPE_DRAG:
+ // the drag layer: input for drag-and-drop is associated with this window,
+ // which sits above all other focusable windows
+ return 29;
+ case TYPE_ACCESSIBILITY_OVERLAY:
+ // overlay put by accessibility services to intercept user interaction
+ return 30;
+ case TYPE_SECURE_SYSTEM_OVERLAY:
+ return 31;
+ case TYPE_BOOT_PROGRESS:
+ return 32;
+ case TYPE_POINTER:
+ // the (mouse) pointer layer
+ return 33;
+ default:
+ Slog.e("WindowManager", "Unknown window type: " + type);
+ return APPLICATION_LAYER;
+ }
+ }
+
+ /**
+ * Return how to Z-order sub-windows in relation to the window they are attached to.
+ * Return positive to have them ordered in front, negative for behind.
+ *
+ * @param type The sub-window type code.
+ *
+ * @return int Layer in relation to the attached window, where positive is
+ * above and negative is below.
+ */
+ default int getSubWindowLayerFromTypeLw(int type) {
+ switch (type) {
+ case TYPE_APPLICATION_PANEL:
+ case TYPE_APPLICATION_ATTACHED_DIALOG:
+ return APPLICATION_PANEL_SUBLAYER;
+ case TYPE_APPLICATION_MEDIA:
+ return APPLICATION_MEDIA_SUBLAYER;
+ case TYPE_APPLICATION_MEDIA_OVERLAY:
+ return APPLICATION_MEDIA_OVERLAY_SUBLAYER;
+ case TYPE_APPLICATION_SUB_PANEL:
+ return APPLICATION_SUB_PANEL_SUBLAYER;
+ case TYPE_APPLICATION_ABOVE_SUB_PANEL:
+ return APPLICATION_ABOVE_SUB_PANEL_SUBLAYER;
+ }
+ Slog.e("WindowManager", "Unknown sub-window type: " + type);
+ return 0;
+ }
+
+ /**
+ * Get the highest layer (actually one more than) that the wallpaper is
+ * allowed to be in.
+ */
+ public int getMaxWallpaperLayer();
+
+ /**
+ * Return the display width available after excluding any screen
+ * decorations that could never be removed in Honeycomb. That is, system bar or
+ * button bar.
+ */
+ public int getNonDecorDisplayWidth(int fullWidth, int fullHeight, int rotation,
+ int uiMode, int displayId);
+
+ /**
+ * Return the display height available after excluding any screen
+ * decorations that could never be removed in Honeycomb. That is, system bar or
+ * button bar.
+ */
+ public int getNonDecorDisplayHeight(int fullWidth, int fullHeight, int rotation,
+ int uiMode, int displayId);
+
+ /**
+ * Return the available screen width that we should report for the
+ * configuration. This must be no larger than
+ * {@link #getNonDecorDisplayWidth(int, int, int, int int, int)}; it may be smaller than
+ * that to account for more transient decoration like a status bar.
+ */
+ public int getConfigDisplayWidth(int fullWidth, int fullHeight, int rotation,
+ int uiMode, int displayId);
+
+ /**
+ * Return the available screen height that we should report for the
+ * configuration. This must be no larger than
+ * {@link #getNonDecorDisplayHeight(int, int, int, int, int)}; it may be smaller than
+ * that to account for more transient decoration like a status bar.
+ */
+ public int getConfigDisplayHeight(int fullWidth, int fullHeight, int rotation,
+ int uiMode, int displayId);
+
+ /**
+ * Return whether the given window can become the Keyguard window. Typically returns true for
+ * the StatusBar.
+ */
+ public boolean isKeyguardHostWindow(WindowManager.LayoutParams attrs);
+
+ /**
+ * @return whether {@param win} can be hidden by Keyguard
+ */
+ public boolean canBeHiddenByKeyguardLw(WindowState win);
+
+ /**
+ * Called when the system would like to show a UI to indicate that an
+ * application is starting. You can use this to add a
+ * APPLICATION_STARTING_TYPE window with the given appToken to the window
+ * manager (using the normal window manager APIs) that will be shown until
+ * the application displays its own window. This is called without the
+ * window manager locked so that you can call back into it.
+ *
+ * @param appToken Token of the application being started.
+ * @param packageName The name of the application package being started.
+ * @param theme Resource defining the application's overall visual theme.
+ * @param nonLocalizedLabel The default title label of the application if
+ * no data is found in the resource.
+ * @param labelRes The resource ID the application would like to use as its name.
+ * @param icon The resource ID the application would like to use as its icon.
+ * @param windowFlags Window layout flags.
+ * @param overrideConfig override configuration to consider when generating
+ * context to for resources.
+ * @param displayId Id of the display to show the splash screen at.
+ *
+ * @return The starting surface.
+ *
+ */
+ public StartingSurface addSplashScreen(IBinder appToken, String packageName, int theme,
+ CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, int icon,
+ int logo, int windowFlags, Configuration overrideConfig, int displayId);
+
+ /**
+ * Prepare for a window being added to the window manager. You can throw an
+ * exception here to prevent the window being added, or do whatever setup
+ * you need to keep track of the window.
+ *
+ * @param win The window being added.
+ * @param attrs The window's LayoutParams.
+ *
+ * @return {@link WindowManagerGlobal#ADD_OKAY} if the add can proceed, else an
+ * error code to abort the add.
+ */
+ public int prepareAddWindowLw(WindowState win,
+ WindowManager.LayoutParams attrs);
+
+ /**
+ * Called when a window is being removed from a window manager. Must not
+ * throw an exception -- clean up as much as possible.
+ *
+ * @param win The window being removed.
+ */
+ public void removeWindowLw(WindowState win);
+
+ /**
+ * Control the animation to run when a window's state changes. Return a
+ * non-0 number to force the animation to a specific resource ID, or 0
+ * to use the default animation.
+ *
+ * @param win The window that is changing.
+ * @param transit What is happening to the window: {@link #TRANSIT_ENTER},
+ * {@link #TRANSIT_EXIT}, {@link #TRANSIT_SHOW}, or
+ * {@link #TRANSIT_HIDE}.
+ *
+ * @return Resource ID of the actual animation to use, or 0 for none.
+ */
+ public int selectAnimationLw(WindowState win, int transit);
+
+ /**
+ * Determine the animation to run for a rotation transition based on the
+ * top fullscreen windows {@link WindowManager.LayoutParams#rotationAnimation}
+ * and whether it is currently fullscreen and frontmost.
+ *
+ * @param anim The exiting animation resource id is stored in anim[0], the
+ * entering animation resource id is stored in anim[1].
+ */
+ public void selectRotationAnimationLw(int anim[]);
+
+ /**
+ * Validate whether the current top fullscreen has specified the same
+ * {@link WindowManager.LayoutParams#rotationAnimation} value as that
+ * being passed in from the previous top fullscreen window.
+ *
+ * @param exitAnimId exiting resource id from the previous window.
+ * @param enterAnimId entering resource id from the previous window.
+ * @param forceDefault For rotation animations only, if true ignore the
+ * animation values and just return false.
+ * @return true if the previous values are still valid, false if they
+ * should be replaced with the default.
+ */
+ public boolean validateRotationAnimationLw(int exitAnimId, int enterAnimId,
+ boolean forceDefault);
+
+ /**
+ * Create and return an animation to re-display a window that was force hidden by Keyguard.
+ */
+ public Animation createHiddenByKeyguardExit(boolean onWallpaper,
+ boolean goingToNotificationShade);
+
+ /**
+ * Create and return an animation to let the wallpaper disappear after being shown behind
+ * Keyguard.
+ */
+ public Animation createKeyguardWallpaperExit(boolean goingToNotificationShade);
+
+ /**
+ * Called from the input reader thread before a key is enqueued.
+ *
+ * <p>There are some actions that need to be handled here because they
+ * affect the power state of the device, for example, the power keys.
+ * Generally, it's best to keep as little as possible in the queue thread
+ * because it's the most fragile.
+ * @param event The key event.
+ * @param policyFlags The policy flags associated with the key.
+ *
+ * @return Actions flags: may be {@link #ACTION_PASS_TO_USER}.
+ */
+ public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags);
+
+ /**
+ * Called from the input reader thread before a motion is enqueued when the device is in a
+ * non-interactive state.
+ *
+ * <p>There are some actions that need to be handled here because they
+ * affect the power state of the device, for example, waking on motions.
+ * Generally, it's best to keep as little as possible in the queue thread
+ * because it's the most fragile.
+ * @param policyFlags The policy flags associated with the motion.
+ *
+ * @return Actions flags: may be {@link #ACTION_PASS_TO_USER}.
+ */
+ public int interceptMotionBeforeQueueingNonInteractive(long whenNanos, int policyFlags);
+
+ /**
+ * Called from the input dispatcher thread before a key is dispatched to a window.
+ *
+ * <p>Allows you to define
+ * behavior for keys that can not be overridden by applications.
+ * This method is called from the input thread, with no locks held.
+ *
+ * @param win The window that currently has focus. This is where the key
+ * event will normally go.
+ * @param event The key event.
+ * @param policyFlags The policy flags associated with the key.
+ * @return 0 if the key should be dispatched immediately, -1 if the key should
+ * not be dispatched ever, or a positive value indicating the number of
+ * milliseconds by which the key dispatch should be delayed before trying
+ * again.
+ */
+ public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags);
+
+ /**
+ * Called from the input dispatcher thread when an application did not handle
+ * a key that was dispatched to it.
+ *
+ * <p>Allows you to define default global behavior for keys that were not handled
+ * by applications. This method is called from the input thread, with no locks held.
+ *
+ * @param win The window that currently has focus. This is where the key
+ * event will normally go.
+ * @param event The key event.
+ * @param policyFlags The policy flags associated with the key.
+ * @return Returns an alternate key event to redispatch as a fallback, or null to give up.
+ * The caller is responsible for recycling the key event.
+ */
+ public KeyEvent dispatchUnhandledKey(WindowState win, KeyEvent event, int policyFlags);
+
+ /**
+ * Called when layout of the windows is about to start.
+ *
+ * @param displayFrames frames of the display we are doing layout on.
+ * @param uiMode The current uiMode in configuration.
+ */
+ default void beginLayoutLw(DisplayFrames displayFrames, int uiMode) {}
+
+ /**
+ * Returns the bottom-most layer of the system decor, above which no policy decor should
+ * be applied.
+ */
+ public int getSystemDecorLayerLw();
+
+ /**
+ * Called for each window attached to the window manager as layout is proceeding. The
+ * implementation of this function must take care of setting the window's frame, either here or
+ * in finishLayout().
+ *
+ * @param win The window being positioned.
+ * @param attached For sub-windows, the window it is attached to; this
+ * window will already have had layoutWindow() called on it
+ * so you can use its Rect. Otherwise null.
+ * @param displayFrames The display frames.
+ */
+ default void layoutWindowLw(
+ WindowState win, WindowState attached, DisplayFrames displayFrames) {}
+
+
+ /**
+ * Return the insets for the areas covered by system windows. These values are computed on the
+ * most recent layout, so they are not guaranteed to be correct.
+ *
+ * @param attrs The LayoutParams of the window.
+ * @param taskBounds The bounds of the task this window is on or {@code null} if no task is
+ * associated with the window.
+ * @param displayFrames display frames.
+ * @param outContentInsets The areas covered by system windows, expressed as positive insets.
+ * @param outStableInsets The areas covered by stable system windows irrespective of their
+ * current visibility. Expressed as positive insets.
+ * @param outOutsets The areas that are not real display, but we would like to treat as such.
+ * @return Whether to always consume the navigation bar.
+ * See {@link #isNavBarForcedShownLw(WindowState)}.
+ */
+ default boolean getInsetHintLw(WindowManager.LayoutParams attrs, Rect taskBounds,
+ DisplayFrames displayFrames, Rect outContentInsets, Rect outStableInsets,
+ Rect outOutsets) {
+ return false;
+ }
+
+ /**
+ * Called following layout of all windows before each window has policy applied.
+ *
+ * @param displayWidth The current full width of the screen.
+ * @param displayHeight The current full height of the screen.
+ */
+ public void beginPostLayoutPolicyLw(int displayWidth, int displayHeight);
+
+ /**
+ * Called following layout of all window to apply policy to each window.
+ *
+ * @param win The window being positioned.
+ * @param attrs The LayoutParams of the window.
+ * @param attached For sub-windows, the window it is attached to. Otherwise null.
+ */
+ public void applyPostLayoutPolicyLw(WindowState win,
+ WindowManager.LayoutParams attrs, WindowState attached, WindowState imeTarget);
+
+ /**
+ * Called following layout of all windows and after policy has been applied
+ * to each window. If in this function you do
+ * something that may have modified the animation state of another window,
+ * be sure to return non-zero in order to perform another pass through layout.
+ *
+ * @return Return any bit set of {@link #FINISH_LAYOUT_REDO_LAYOUT},
+ * {@link #FINISH_LAYOUT_REDO_CONFIG}, {@link #FINISH_LAYOUT_REDO_WALLPAPER},
+ * or {@link #FINISH_LAYOUT_REDO_ANIM}.
+ */
+ public int finishPostLayoutPolicyLw();
+
+ /**
+ * Return true if it is okay to perform animations for an app transition
+ * that is about to occur. You may return false for this if, for example,
+ * the lock screen is currently displayed so the switch should happen
+ * immediately.
+ */
+ public boolean allowAppAnimationsLw();
+
+
+ /**
+ * A new window has been focused.
+ */
+ public int focusChangedLw(WindowState lastFocus, WindowState newFocus);
+
+ /**
+ * Called when the device has started waking up.
+ */
+ public void startedWakingUp();
+
+ /**
+ * Called when the device has finished waking up.
+ */
+ public void finishedWakingUp();
+
+ /**
+ * Called when the device has started going to sleep.
+ *
+ * @param why {@link #OFF_BECAUSE_OF_USER}, {@link #OFF_BECAUSE_OF_ADMIN},
+ * or {@link #OFF_BECAUSE_OF_TIMEOUT}.
+ */
+ public void startedGoingToSleep(int why);
+
+ /**
+ * Called when the device has finished going to sleep.
+ *
+ * @param why {@link #OFF_BECAUSE_OF_USER}, {@link #OFF_BECAUSE_OF_ADMIN},
+ * or {@link #OFF_BECAUSE_OF_TIMEOUT}.
+ */
+ public void finishedGoingToSleep(int why);
+
+ /**
+ * Called when the device is about to turn on the screen to show content.
+ * When waking up, this method will be called once after the call to wakingUp().
+ * When dozing, the method will be called sometime after the call to goingToSleep() and
+ * may be called repeatedly in the case where the screen is pulsing on and off.
+ *
+ * Must call back on the listener to tell it when the higher-level system
+ * is ready for the screen to go on (i.e. the lock screen is shown).
+ */
+ public void screenTurningOn(ScreenOnListener screenOnListener);
+
+ /**
+ * Called when the device has actually turned on the screen, i.e. the display power state has
+ * been set to ON and the screen is unblocked.
+ */
+ public void screenTurnedOn();
+
+ /**
+ * Called when the display would like to be turned off. This gives policy a chance to do some
+ * things before the display power state is actually changed to off.
+ *
+ * @param screenOffListener Must be called to tell that the display power state can actually be
+ * changed now after policy has done its work.
+ */
+ public void screenTurningOff(ScreenOffListener screenOffListener);
+
+ /**
+ * Called when the device has turned the screen off.
+ */
+ public void screenTurnedOff();
+
+ public interface ScreenOnListener {
+ void onScreenOn();
+ }
+
+ /**
+ * See {@link #screenTurnedOff}
+ */
+ public interface ScreenOffListener {
+ void onScreenOff();
+ }
+
+ /**
+ * Return whether the default display is on and not blocked by a black surface.
+ */
+ public boolean isScreenOn();
+
+ /**
+ * @return whether the device is currently allowed to animate.
+ *
+ * Note: this can be true even if it is not appropriate to animate for reasons that are outside
+ * of the policy's authority.
+ */
+ boolean okToAnimate();
+
+ /**
+ * Tell the policy that the lid switch has changed state.
+ * @param whenNanos The time when the change occurred in uptime nanoseconds.
+ * @param lidOpen True if the lid is now open.
+ */
+ public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen);
+
+ /**
+ * Tell the policy that the camera lens has been covered or uncovered.
+ * @param whenNanos The time when the change occurred in uptime nanoseconds.
+ * @param lensCovered True if the lens is covered.
+ */
+ public void notifyCameraLensCoverSwitchChanged(long whenNanos, boolean lensCovered);
+
+ /**
+ * Tell the policy if anyone is requesting that keyguard not come on.
+ *
+ * @param enabled Whether keyguard can be on or not. does not actually
+ * turn it on, unless it was previously disabled with this function.
+ *
+ * @see android.app.KeyguardManager.KeyguardLock#disableKeyguard()
+ * @see android.app.KeyguardManager.KeyguardLock#reenableKeyguard()
+ */
+ @SuppressWarnings("javadoc")
+ public void enableKeyguard(boolean enabled);
+
+ /**
+ * Callback used by {@link #exitKeyguardSecurely}
+ */
+ interface OnKeyguardExitResult {
+ void onKeyguardExitResult(boolean success);
+ }
+
+ /**
+ * Tell the policy if anyone is requesting the keyguard to exit securely
+ * (this would be called after the keyguard was disabled)
+ * @param callback Callback to send the result back.
+ * @see android.app.KeyguardManager#exitKeyguardSecurely(android.app.KeyguardManager.OnKeyguardExitResult)
+ */
+ @SuppressWarnings("javadoc")
+ void exitKeyguardSecurely(OnKeyguardExitResult callback);
+
+ /**
+ * isKeyguardLocked
+ *
+ * Return whether the keyguard is currently locked.
+ *
+ * @return true if in keyguard is locked.
+ */
+ public boolean isKeyguardLocked();
+
+ /**
+ * isKeyguardSecure
+ *
+ * Return whether the keyguard requires a password to unlock.
+ * @param userId
+ *
+ * @return true if in keyguard is secure.
+ */
+ public boolean isKeyguardSecure(int userId);
+
+ /**
+ * Return whether the keyguard is currently occluded.
+ *
+ * @return true if in keyguard is occluded, false otherwise
+ */
+ public boolean isKeyguardOccluded();
+
+ /**
+ * @return true if in keyguard is on and not occluded.
+ */
+ public boolean isKeyguardShowingAndNotOccluded();
+
+ /**
+ * @return whether Keyguard is in trusted state and can be dismissed without credentials
+ */
+ public boolean isKeyguardTrustedLw();
+
+ /**
+ * inKeyguardRestrictedKeyInputMode
+ *
+ * if keyguard screen is showing or in restricted key input mode (i.e. in
+ * keyguard password emergency screen). When in such mode, certain keys,
+ * such as the Home key and the right soft keys, don't work.
+ *
+ * @return true if in keyguard restricted input mode.
+ */
+ public boolean inKeyguardRestrictedKeyInputMode();
+
+ /**
+ * Ask the policy to dismiss the keyguard, if it is currently shown.
+ *
+ * @param callback Callback to be informed about the result.
+ */
+ public void dismissKeyguardLw(@Nullable IKeyguardDismissCallback callback);
+
+ /**
+ * Ask the policy whether the Keyguard has drawn. If the Keyguard is disabled, this method
+ * returns true as soon as we know that Keyguard is disabled.
+ *
+ * @return true if the keyguard has drawn.
+ */
+ public boolean isKeyguardDrawnLw();
+
+ public boolean isShowingDreamLw();
+
+ /**
+ * Given an orientation constant, returns the appropriate surface rotation,
+ * taking into account sensors, docking mode, rotation lock, and other factors.
+ *
+ * @param orientation An orientation constant, such as
+ * {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_LANDSCAPE}.
+ * @param lastRotation The most recently used rotation.
+ * @return The surface rotation to use.
+ */
+ public int rotationForOrientationLw(@ActivityInfo.ScreenOrientation int orientation,
+ int lastRotation);
+
+ /**
+ * Given an orientation constant and a rotation, returns true if the rotation
+ * has compatible metrics to the requested orientation. For example, if
+ * the application requested landscape and got seascape, then the rotation
+ * has compatible metrics; if the application requested portrait and got landscape,
+ * then the rotation has incompatible metrics; if the application did not specify
+ * a preference, then anything goes.
+ *
+ * @param orientation An orientation constant, such as
+ * {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_LANDSCAPE}.
+ * @param rotation The rotation to check.
+ * @return True if the rotation is compatible with the requested orientation.
+ */
+ public boolean rotationHasCompatibleMetricsLw(@ActivityInfo.ScreenOrientation int orientation,
+ int rotation);
+
+ /**
+ * Called by the window manager when the rotation changes.
+ *
+ * @param rotation The new rotation.
+ */
+ public void setRotationLw(int rotation);
+
+ /**
+ * Called when the system is mostly done booting to set whether
+ * the system should go into safe mode.
+ */
+ public void setSafeMode(boolean safeMode);
+
+ /**
+ * Called when the system is mostly done booting.
+ */
+ public void systemReady();
+
+ /**
+ * Called when the system is done booting to the point where the
+ * user can start interacting with it.
+ */
+ public void systemBooted();
+
+ /**
+ * Show boot time message to the user.
+ */
+ public void showBootMessage(final CharSequence msg, final boolean always);
+
+ /**
+ * Hide the UI for showing boot messages, never to be displayed again.
+ */
+ public void hideBootMessages();
+
+ /**
+ * Called when userActivity is signalled in the power manager.
+ * This is safe to call from any thread, with any window manager locks held or not.
+ */
+ public void userActivity();
+
+ /**
+ * Called when we have finished booting and can now display the home
+ * screen to the user. This will happen after systemReady(), and at
+ * this point the display is active.
+ */
+ public void enableScreenAfterBoot();
+
+ public void setCurrentOrientationLw(@ActivityInfo.ScreenOrientation int newOrientation);
+
+ /**
+ * Call from application to perform haptic feedback on its window.
+ */
+ public boolean performHapticFeedbackLw(WindowState win, int effectId, boolean always);
+
+ /**
+ * Called when we have started keeping the screen on because a window
+ * requesting this has become visible.
+ */
+ public void keepScreenOnStartedLw();
+
+ /**
+ * Called when we have stopped keeping the screen on because the last window
+ * requesting this is no longer visible.
+ */
+ public void keepScreenOnStoppedLw();
+
+ /**
+ * Gets the current user rotation mode.
+ *
+ * @return The rotation mode.
+ *
+ * @see #USER_ROTATION_LOCKED
+ * @see #USER_ROTATION_FREE
+ */
+ @UserRotationMode
+ public int getUserRotationMode();
+
+ /**
+ * Inform the policy that the user has chosen a preferred orientation ("rotation lock").
+ *
+ * @param mode One of {@link #USER_ROTATION_LOCKED} or {@link #USER_ROTATION_FREE}.
+ * @param rotation One of {@link Surface#ROTATION_0}, {@link Surface#ROTATION_90},
+ * {@link Surface#ROTATION_180}, {@link Surface#ROTATION_270}.
+ */
+ public void setUserRotationMode(@UserRotationMode int mode, @Surface.Rotation int rotation);
+
+ /**
+ * Called when a new system UI visibility is being reported, allowing
+ * the policy to adjust what is actually reported.
+ * @param visibility The raw visibility reported by the status bar.
+ * @return The new desired visibility.
+ */
+ public int adjustSystemUiVisibilityLw(int visibility);
+
+ /**
+ * Called by System UI to notify of changes to the visibility of Recents.
+ */
+ public void setRecentsVisibilityLw(boolean visible);
+
+ /**
+ * Called by System UI to notify of changes to the visibility of PIP.
+ */
+ void setPipVisibilityLw(boolean visible);
+
+ /**
+ * Specifies whether there is an on-screen navigation bar separate from the status bar.
+ */
+ public boolean hasNavigationBar();
+
+ /**
+ * Lock the device now.
+ */
+ public void lockNow(Bundle options);
+
+ /**
+ * Set the last used input method window state. This state is used to make IME transition
+ * smooth.
+ * @hide
+ */
+ public void setLastInputMethodWindowLw(WindowState ime, WindowState target);
+
+ /**
+ * An internal callback (from InputMethodManagerService) to notify a state change regarding
+ * whether the back key should dismiss the software keyboard (IME) or not.
+ *
+ * @param newValue {@code true} if the software keyboard is shown and the back key is expected
+ * to dismiss the software keyboard.
+ * @hide
+ */
+ default void setDismissImeOnBackKeyPressed(boolean newValue) {
+ // Default implementation does nothing.
+ }
+
+ /**
+ * Show the recents task list app.
+ * @hide
+ */
+ public void showRecentApps(boolean fromHome);
+
+ /**
+ * Show the global actions dialog.
+ * @hide
+ */
+ public void showGlobalActions();
+
+ /**
+ * Called when the current user changes. Guaranteed to be called before the broadcast
+ * of the new user id is made to all listeners.
+ *
+ * @param newUserId The id of the incoming user.
+ */
+ public void setCurrentUserLw(int newUserId);
+
+ /**
+ * For a given user-switch operation, this will be called once with switching=true before the
+ * user-switch and once with switching=false afterwards (or if the user-switch was cancelled).
+ * This gives the policy a chance to alter its behavior for the duration of a user-switch.
+ *
+ * @param switching true if a user-switch is in progress
+ */
+ void setSwitchingUser(boolean switching);
+
+ /**
+ * Print the WindowManagerPolicy's state into the given stream.
+ *
+ * @param prefix Text to print at the front of each line.
+ * @param writer The PrintWriter to which you should dump your state. This will be
+ * closed for you after you return.
+ * @param args additional arguments to the dump request.
+ */
+ public void dump(String prefix, PrintWriter writer, String[] args);
+
+ /**
+ * Write the WindowManagerPolicy's state into the protocol buffer.
+ * The message is described in {@link com.android.server.wm.proto.WindowManagerPolicyProto}
+ *
+ * @param proto The protocol buffer output stream to write to.
+ */
+ void writeToProto(ProtoOutputStream proto, long fieldId);
+
+ /**
+ * Returns whether a given window type is considered a top level one.
+ * A top level window does not have a container, i.e. attached window,
+ * or if it has a container it is laid out as a top-level window, not
+ * as a child of its container.
+ *
+ * @param windowType The window type.
+ * @return True if the window is a top level one.
+ */
+ public boolean isTopLevelWindow(int windowType);
+
+ /**
+ * Notifies the keyguard to start fading out.
+ *
+ * @param startTime the start time of the animation in uptime milliseconds
+ * @param fadeoutDuration the duration of the exit animation, in milliseconds
+ */
+ public void startKeyguardExitAnimation(long startTime, long fadeoutDuration);
+
+ /**
+ * Calculates the stable insets without running a layout.
+ *
+ * @param displayRotation the current display rotation
+ * @param displayWidth the current display width
+ * @param displayHeight the current display height
+ * @param outInsets the insets to return
+ */
+ public void getStableInsetsLw(int displayRotation, int displayWidth, int displayHeight,
+ Rect outInsets);
+
+
+ /**
+ * @return true if the navigation bar is forced to stay visible
+ */
+ public boolean isNavBarForcedShownLw(WindowState win);
+
+ /**
+ * @return The side of the screen where navigation bar is positioned.
+ * @see #NAV_BAR_LEFT
+ * @see #NAV_BAR_RIGHT
+ * @see #NAV_BAR_BOTTOM
+ */
+ int getNavBarPosition();
+
+ /**
+ * Calculates the insets for the areas that could never be removed in Honeycomb, i.e. system
+ * bar or button bar. See {@link #getNonDecorDisplayWidth}.
+ *
+ * @param displayRotation the current display rotation
+ * @param displayWidth the current display width
+ * @param displayHeight the current display height
+ * @param outInsets the insets to return
+ */
+ public void getNonDecorInsetsLw(int displayRotation, int displayWidth, int displayHeight,
+ Rect outInsets);
+
+ /**
+ * @return True if a specified {@param dockSide} is allowed on the current device, or false
+ * otherwise. It is guaranteed that at least one dock side for a particular orientation
+ * is allowed, so for example, if DOCKED_RIGHT is not allowed, DOCKED_LEFT is allowed.
+ */
+ public boolean isDockSideAllowed(int dockSide);
+
+ /**
+ * Called when the configuration has changed, and it's safe to load new values from resources.
+ */
+ public void onConfigurationChanged();
+
+ public boolean shouldRotateSeamlessly(int oldRotation, int newRotation);
+
+ /**
+ * Called when System UI has been started.
+ */
+ void onSystemUiStarted();
+
+ /**
+ * Checks whether the policy is ready for dismissing the boot animation and completing the boot.
+ *
+ * @return true if ready; false otherwise.
+ */
+ boolean canDismissBootAnimation();
+
+ /**
+ * Convert the user rotation mode to a human readable format.
+ */
+ static String userRotationModeToString(int mode) {
+ switch(mode) {
+ case USER_ROTATION_FREE:
+ return "USER_ROTATION_FREE";
+ case USER_ROTATION_LOCKED:
+ return "USER_ROTATION_LOCKED";
+ default:
+ return Integer.toString(mode);
+ }
+ }
+}
diff --git a/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
index 70cd54f..58002bc 100644
--- a/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
+++ b/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -15,14 +15,14 @@
import android.os.UserHandle;
import android.util.Log;
import android.util.Slog;
-import android.view.WindowManagerPolicy;
-import android.view.WindowManagerPolicy.OnKeyguardExitResult;
+import android.view.WindowManagerPolicyConstants;
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.internal.policy.IKeyguardDrawnCallback;
import com.android.internal.policy.IKeyguardExitCallback;
import com.android.internal.policy.IKeyguardService;
import com.android.server.UiThread;
+import com.android.server.policy.WindowManagerPolicy.OnKeyguardExitResult;
import java.io.PrintWriter;
@@ -419,7 +419,7 @@
pw.println(prefix + "deviceHasKeyguard=" + mKeyguardState.deviceHasKeyguard);
pw.println(prefix + "enabled=" + mKeyguardState.enabled);
pw.println(prefix + "offReason=" +
- WindowManagerPolicy.offReasonToString(mKeyguardState.offReason));
+ WindowManagerPolicyConstants.offReasonToString(mKeyguardState.offReason));
pw.println(prefix + "currentUser=" + mKeyguardState.currentUser);
pw.println(prefix + "bootCompleted=" + mKeyguardState.bootCompleted);
pw.println(prefix + "screenState=" + screenStateToString(mKeyguardState.screenState));
diff --git a/com/android/server/power/BatterySaverPolicy.java b/com/android/server/power/BatterySaverPolicy.java
index 15121b8..7c234f9 100644
--- a/com/android/server/power/BatterySaverPolicy.java
+++ b/com/android/server/power/BatterySaverPolicy.java
@@ -29,21 +29,26 @@
import android.util.KeyValueListParser;
import android.util.Slog;
+import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.R;
+import com.android.server.power.batterysaver.CpuFrequencies;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.List;
/**
* Class to decide whether to turn on battery saver mode for specific service
*
- * Test: atest BatterySaverPolicyTest
+ * Test:
+ atest ${ANDROID_BUILD_TOP}/frameworks/base/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
*/
public class BatterySaverPolicy extends ContentObserver {
private static final String TAG = "BatterySaverPolicy";
+ public static final boolean DEBUG = false; // DO NOT SUBMIT WITH TRUE.
+
// Value of batterySaverGpsMode such that GPS isn't affected by battery saver mode.
public static final int GPS_MODE_NO_CHANGE = 0;
// Value of batterySaverGpsMode such that GPS is disabled when battery saver mode
@@ -59,19 +64,27 @@
private static final String KEY_FIREWALL_DISABLED = "firewall_disabled";
private static final String KEY_ADJUST_BRIGHTNESS_DISABLED = "adjust_brightness_disabled";
private static final String KEY_DATASAVER_DISABLED = "datasaver_disabled";
+ private static final String KEY_LAUNCH_BOOST_DISABLED = "launch_boost_disabled";
private static final String KEY_ADJUST_BRIGHTNESS_FACTOR = "adjust_brightness_factor";
private static final String KEY_FULLBACKUP_DEFERRED = "fullbackup_deferred";
private static final String KEY_KEYVALUE_DEFERRED = "keyvaluebackup_deferred";
- private static final String KEY_FORCE_ALL_APPS_STANDBY_JOBS = "force_all_apps_standby_jobs";
- private static final String KEY_FORCE_ALL_APPS_STANDBY_ALARMS = "force_all_apps_standby_alarms";
+ private static final String KEY_FORCE_ALL_APPS_STANDBY = "force_all_apps_standby";
+ private static final String KEY_FORCE_BACKGROUND_CHECK = "force_background_check";
private static final String KEY_OPTIONAL_SENSORS_DISABLED = "optional_sensors_disabled";
- private static final String KEY_SCREEN_ON_FILE_PREFIX = "file-on:";
- private static final String KEY_SCREEN_OFF_FILE_PREFIX = "file-off:";
+ private static final String KEY_CPU_FREQ_INTERACTIVE = "cpufreq-i";
+ private static final String KEY_CPU_FREQ_NONINTERACTIVE = "cpufreq-n";
- private static String mSettings;
- private static String mDeviceSpecificSettings;
- private static String mDeviceSpecificSettingsSource; // For dump() only.
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private String mSettings;
+
+ @GuardedBy("mLock")
+ private String mDeviceSpecificSettings;
+
+ @GuardedBy("mLock")
+ private String mDeviceSpecificSettingsSource; // For dump() only.
/**
* {@code true} if vibration is disabled in battery saver mode.
@@ -79,6 +92,7 @@
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_VIBRATION_DISABLED
*/
+ @GuardedBy("mLock")
private boolean mVibrationDisabled;
/**
@@ -87,6 +101,7 @@
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_ANIMATION_DISABLED
*/
+ @GuardedBy("mLock")
private boolean mAnimationDisabled;
/**
@@ -96,6 +111,7 @@
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_SOUNDTRIGGER_DISABLED
*/
+ @GuardedBy("mLock")
private boolean mSoundTriggerDisabled;
/**
@@ -104,6 +120,7 @@
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_FULLBACKUP_DEFERRED
*/
+ @GuardedBy("mLock")
private boolean mFullBackupDeferred;
/**
@@ -112,6 +129,7 @@
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_KEYVALUE_DEFERRED
*/
+ @GuardedBy("mLock")
private boolean mKeyValueBackupDeferred;
/**
@@ -120,6 +138,7 @@
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_FIREWALL_DISABLED
*/
+ @GuardedBy("mLock")
private boolean mFireWallDisabled;
/**
@@ -128,6 +147,7 @@
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_ADJUST_BRIGHTNESS_DISABLED
*/
+ @GuardedBy("mLock")
private boolean mAdjustBrightnessDisabled;
/**
@@ -136,14 +156,22 @@
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_DATASAVER_DISABLED
*/
+ @GuardedBy("mLock")
private boolean mDataSaverDisabled;
/**
+ * {@code true} if launch boost should be disabled on battery saver.
+ */
+ @GuardedBy("mLock")
+ private boolean mLaunchBoostDisabled;
+
+ /**
* This is the flag to decide the gps mode in battery saver mode.
*
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_GPS_MODE
*/
+ @GuardedBy("mLock")
private int mGpsMode;
/**
@@ -153,25 +181,27 @@
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_ADJUST_BRIGHTNESS_FACTOR
*/
+ @GuardedBy("mLock")
private float mAdjustBrightnessFactor;
/**
- * Whether to put all apps in the stand-by mode or not for job scheduler.
+ * Whether to put all apps in the stand-by mode.
*/
- private boolean mForceAllAppsStandbyJobs;
+ @GuardedBy("mLock")
+ private boolean mForceAllAppsStandby;
/**
- * Whether to put all apps in the stand-by mode or not for alarms.
+ * Whether to put all apps in the stand-by mode.
*/
- private boolean mForceAllAppsStandbyAlarms;
+ @GuardedBy("mLock")
+ private boolean mForceBackgroundCheck;
/**
* Weather to show non-essential sensors (e.g. edge sensors) or not.
*/
+ @GuardedBy("mLock")
private boolean mOptionalSensorsDisabled;
- private final Object mLock = new Object();
-
@GuardedBy("mLock")
private Context mContext;
@@ -179,25 +209,25 @@
private ContentResolver mContentResolver;
@GuardedBy("mLock")
- private final ArrayList<BatterySaverPolicyListener> mListeners = new ArrayList<>();
+ private final List<BatterySaverPolicyListener> mListeners = new ArrayList<>();
/**
* List of [Filename -> content] that should be written when battery saver is activated
- * and the screen is on.
+ * and the device is interactive.
*
* We use this to change the max CPU frequencies.
*/
@GuardedBy("mLock")
- private ArrayMap<String, String> mScreenOnFiles;
+ private ArrayMap<String, String> mFilesForInteractive;
/**
* List of [Filename -> content] that should be written when battery saver is activated
- * and the screen is off.
+ * and the device is non-interactive.
*
* We use this to change the max CPU frequencies.
*/
@GuardedBy("mLock")
- private ArrayMap<String, String> mScreenOffFiles;
+ private ArrayMap<String, String> mFilesForNoninteractive;
public interface BatterySaverPolicyListener {
void onBatterySaverPolicyChanged(BatterySaverPolicy policy);
@@ -228,7 +258,11 @@
@VisibleForTesting
String getGlobalSetting(String key) {
- return Settings.Global.getString(mContentResolver, key);
+ final ContentResolver cr;
+ synchronized (mLock) {
+ cr = mContentResolver;
+ }
+ return Settings.Global.getString(cr, key);
}
@VisibleForTesting
@@ -236,11 +270,6 @@
return R.string.config_batterySaverDeviceSpecificConfig;
}
- @VisibleForTesting
- void onChangeForTest() {
- onChange(true, null);
- }
-
@Override
public void onChange(boolean selfChange, Uri uri) {
final BatterySaverPolicyListener[] listeners;
@@ -279,6 +308,11 @@
mSettings = setting;
mDeviceSpecificSettings = deviceSpecificSetting;
+ if (DEBUG) {
+ Slog.i(TAG, "mSettings=" + mSettings);
+ Slog.i(TAG, "mDeviceSpecificSettings=" + mDeviceSpecificSettings);
+ }
+
final KeyValueListParser parser = new KeyValueListParser(',');
// Non-device-specific parameters.
@@ -297,9 +331,9 @@
mAdjustBrightnessDisabled = parser.getBoolean(KEY_ADJUST_BRIGHTNESS_DISABLED, false);
mAdjustBrightnessFactor = parser.getFloat(KEY_ADJUST_BRIGHTNESS_FACTOR, 0.5f);
mDataSaverDisabled = parser.getBoolean(KEY_DATASAVER_DISABLED, true);
- mForceAllAppsStandbyJobs = parser.getBoolean(KEY_FORCE_ALL_APPS_STANDBY_JOBS, true);
- mForceAllAppsStandbyAlarms =
- parser.getBoolean(KEY_FORCE_ALL_APPS_STANDBY_ALARMS, true);
+ mLaunchBoostDisabled = parser.getBoolean(KEY_LAUNCH_BOOST_DISABLED, true);
+ mForceAllAppsStandby = parser.getBoolean(KEY_FORCE_ALL_APPS_STANDBY, true);
+ mForceBackgroundCheck = parser.getBoolean(KEY_FORCE_BACKGROUND_CHECK, true);
mOptionalSensorsDisabled = parser.getBoolean(KEY_OPTIONAL_SENSORS_DISABLED, true);
// Get default value from Settings.Secure
@@ -315,29 +349,11 @@
+ deviceSpecificSetting);
}
- mScreenOnFiles = collectParams(parser, KEY_SCREEN_ON_FILE_PREFIX);
- mScreenOffFiles = collectParams(parser, KEY_SCREEN_OFF_FILE_PREFIX);
- }
+ mFilesForInteractive = (new CpuFrequencies()).parseString(
+ parser.getString(KEY_CPU_FREQ_INTERACTIVE, "")).toSysFileMap();
- private static ArrayMap<String, String> collectParams(
- KeyValueListParser parser, String prefix) {
- final ArrayMap<String, String> ret = new ArrayMap<>();
-
- for (int i = parser.size() - 1; i >= 0; i--) {
- final String key = parser.keyAt(i);
- if (!key.startsWith(prefix)) {
- continue;
- }
- final String path = key.substring(prefix.length());
-
- if (!(path.startsWith("/sys/") || path.startsWith("/proc"))) {
- Slog.wtf(TAG, "Invalid path: " + path);
- continue;
- }
-
- ret.put(path, parser.getString(key, ""));
- }
- return ret;
+ mFilesForNoninteractive = (new CpuFrequencies()).parseString(
+ parser.getString(KEY_CPU_FREQ_NONINTERACTIVE, "")).toSysFileMap();
}
/**
@@ -387,11 +403,11 @@
case ServiceType.VIBRATION:
return builder.setBatterySaverEnabled(mVibrationDisabled)
.build();
- case ServiceType.FORCE_ALL_APPS_STANDBY_JOBS:
- return builder.setBatterySaverEnabled(mForceAllAppsStandbyJobs)
+ case ServiceType.FORCE_ALL_APPS_STANDBY:
+ return builder.setBatterySaverEnabled(mForceAllAppsStandby)
.build();
- case ServiceType.FORCE_ALL_APPS_STANDBY_ALARMS:
- return builder.setBatterySaverEnabled(mForceAllAppsStandbyAlarms)
+ case ServiceType.FORCE_BACKGROUND_CHECK:
+ return builder.setBatterySaverEnabled(mForceBackgroundCheck)
.build();
case ServiceType.OPTIONAL_SENSORS:
return builder.setBatterySaverEnabled(mOptionalSensorsDisabled)
@@ -403,9 +419,15 @@
}
}
- public ArrayMap<String, String> getFileValues(boolean screenOn) {
+ public ArrayMap<String, String> getFileValues(boolean interactive) {
synchronized (mLock) {
- return screenOn ? mScreenOnFiles : mScreenOffFiles;
+ return interactive ? mFilesForInteractive : mFilesForNoninteractive;
+ }
+ }
+
+ public boolean isLaunchBoostDisabled() {
+ synchronized (mLock) {
+ return mLaunchBoostDisabled;
}
}
@@ -413,10 +435,10 @@
synchronized (mLock) {
pw.println();
pw.println("Battery saver policy");
- pw.println(" Settings " + Settings.Global.BATTERY_SAVER_CONSTANTS);
- pw.println(" value: " + mSettings);
- pw.println(" Settings " + mDeviceSpecificSettingsSource);
- pw.println(" value: " + mDeviceSpecificSettings);
+ pw.println(" Settings: " + Settings.Global.BATTERY_SAVER_CONSTANTS);
+ pw.println(" value: " + mSettings);
+ pw.println(" Settings: " + mDeviceSpecificSettingsSource);
+ pw.println(" value: " + mDeviceSpecificSettings);
pw.println();
pw.println(" " + KEY_VIBRATION_DISABLED + "=" + mVibrationDisabled);
@@ -425,20 +447,21 @@
pw.println(" " + KEY_KEYVALUE_DEFERRED + "=" + mKeyValueBackupDeferred);
pw.println(" " + KEY_FIREWALL_DISABLED + "=" + mFireWallDisabled);
pw.println(" " + KEY_DATASAVER_DISABLED + "=" + mDataSaverDisabled);
+ pw.println(" " + KEY_LAUNCH_BOOST_DISABLED + "=" + mLaunchBoostDisabled);
pw.println(" " + KEY_ADJUST_BRIGHTNESS_DISABLED + "=" + mAdjustBrightnessDisabled);
pw.println(" " + KEY_ADJUST_BRIGHTNESS_FACTOR + "=" + mAdjustBrightnessFactor);
pw.println(" " + KEY_GPS_MODE + "=" + mGpsMode);
- pw.println(" " + KEY_FORCE_ALL_APPS_STANDBY_JOBS + "=" + mForceAllAppsStandbyJobs);
- pw.println(" " + KEY_FORCE_ALL_APPS_STANDBY_ALARMS + "=" + mForceAllAppsStandbyAlarms);
+ pw.println(" " + KEY_FORCE_ALL_APPS_STANDBY + "=" + mForceAllAppsStandby);
+ pw.println(" " + KEY_FORCE_BACKGROUND_CHECK + "=" + mForceBackgroundCheck);
pw.println(" " + KEY_OPTIONAL_SENSORS_DISABLED + "=" + mOptionalSensorsDisabled);
pw.println();
- pw.print(" Screen On Files:\n");
- dumpMap(pw, " ", mScreenOnFiles);
+ pw.print(" Interactive File values:\n");
+ dumpMap(pw, " ", mFilesForInteractive);
pw.println();
- pw.print(" Screen Off Files:\n");
- dumpMap(pw, " ", mScreenOffFiles);
+ pw.print(" Noninteractive File values:\n");
+ dumpMap(pw, " ", mFilesForNoninteractive);
pw.println();
}
}
diff --git a/com/android/server/power/Notifier.java b/com/android/server/power/Notifier.java
index 0ecf0e1..8ee26f2 100644
--- a/com/android/server/power/Notifier.java
+++ b/com/android/server/power/Notifier.java
@@ -25,6 +25,7 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
+import com.android.server.policy.WindowManagerPolicy;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -49,7 +50,6 @@
import android.provider.Settings;
import android.util.EventLog;
import android.util.Slog;
-import android.view.WindowManagerPolicy;
import android.view.inputmethod.InputMethodManagerInternal;
/**
diff --git a/com/android/server/power/PowerManagerService.java b/com/android/server/power/PowerManagerService.java
index a47b809..7f1a534 100644
--- a/com/android/server/power/PowerManagerService.java
+++ b/com/android/server/power/PowerManagerService.java
@@ -69,7 +69,6 @@
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
-import android.view.WindowManagerPolicy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IAppOpsService;
@@ -90,6 +89,7 @@
import com.android.server.am.BatteryStatsService;
import com.android.server.lights.Light;
import com.android.server.lights.LightsManager;
+import com.android.server.policy.WindowManagerPolicy;
import com.android.server.power.batterysaver.BatterySaverController;
import libcore.util.Objects;
@@ -1457,6 +1457,10 @@
case PowerManager.GO_TO_SLEEP_REASON_HDMI:
Slog.i(TAG, "Going to sleep due to HDMI standby (uid " + uid +")...");
break;
+ case PowerManager.GO_TO_SLEEP_REASON_ACCESSIBILITY:
+ Slog.i(TAG, "Going to sleep by an accessibility service request (uid "
+ + uid +")...");
+ break;
default:
Slog.i(TAG, "Going to sleep by application request (uid " + uid +")...");
reason = PowerManager.GO_TO_SLEEP_REASON_APPLICATION;
@@ -3105,7 +3109,16 @@
mIsVrModeEnabled = enabled;
}
- public static void powerHintInternal(int hintId, int data) {
+ private void powerHintInternal(int hintId, int data) {
+ // Maybe filter the event.
+ switch (hintId) {
+ case PowerHint.LAUNCH: // 1: activate launch boost 0: deactivate.
+ if (data == 1 && mBatterySaverController.isLaunchBoostDisabled()) {
+ return;
+ }
+ break;
+ }
+
nativeSendPowerHint(hintId, data);
}
diff --git a/com/android/server/power/ShutdownThread.java b/com/android/server/power/ShutdownThread.java
index 6bf725e..6fb345b 100644
--- a/com/android/server/power/ShutdownThread.java
+++ b/com/android/server/power/ShutdownThread.java
@@ -20,10 +20,7 @@
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.IActivityManager;
-import android.app.KeyguardManager;
import android.app.ProgressDialog;
-import android.app.WallpaperColors;
-import android.app.WallpaperManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.IBluetoothManager;
import android.content.BroadcastReceiver;
@@ -31,8 +28,6 @@
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
import android.media.AudioAttributes;
import android.os.FileUtils;
import android.os.Handler;
@@ -47,8 +42,6 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.os.Vibrator;
-import android.os.storage.IStorageManager;
-import android.os.storage.IStorageShutdownObserver;
import android.util.ArrayMap;
import android.util.Log;
import android.util.TimingsTraceLog;
@@ -123,7 +116,6 @@
private static String METRIC_RADIOS = "shutdown_radios";
private static String METRIC_BT = "shutdown_bt";
private static String METRIC_RADIO = "shutdown_radio";
- private static String METRIC_SM = "shutdown_storage_manager";
private final Object mActionDoneSync = new Object();
private boolean mActionDone;
@@ -526,54 +518,6 @@
shutdownTimingLog.traceEnd(); // ShutdownRadios
metricEnded(METRIC_RADIOS);
- // Shutdown StorageManagerService to ensure media is in a safe state
- IStorageShutdownObserver observer = new IStorageShutdownObserver.Stub() {
- public void onShutDownComplete(int statusCode) throws RemoteException {
- Log.w(TAG, "Result code " + statusCode + " from StorageManagerService.shutdown");
- actionDone();
- }
- };
-
- Log.i(TAG, "Shutting down StorageManagerService");
- shutdownTimingLog.traceBegin("ShutdownStorageManager");
- metricStarted(METRIC_SM);
-
- // Set initial variables and time out time.
- mActionDone = false;
- final long endShutTime = SystemClock.elapsedRealtime() + MAX_SHUTDOWN_WAIT_TIME;
- synchronized (mActionDoneSync) {
- try {
- final IStorageManager storageManager = IStorageManager.Stub.asInterface(
- ServiceManager.checkService("mount"));
- if (storageManager != null) {
- storageManager.shutdown(observer);
- } else {
- Log.w(TAG, "StorageManagerService unavailable for shutdown");
- }
- } catch (Exception e) {
- Log.e(TAG, "Exception during StorageManagerService shutdown", e);
- }
- while (!mActionDone) {
- long delay = endShutTime - SystemClock.elapsedRealtime();
- if (delay <= 0) {
- Log.w(TAG, "StorageManager shutdown wait timed out");
- break;
- } else if (mRebootHasProgressBar) {
- int status = (int)((MAX_SHUTDOWN_WAIT_TIME - delay) * 1.0 *
- (MOUNT_SERVICE_STOP_PERCENT - RADIO_STOP_PERCENT) /
- MAX_SHUTDOWN_WAIT_TIME);
- status += RADIO_STOP_PERCENT;
- sInstance.setRebootProgress(status, null);
- }
- try {
- mActionDoneSync.wait(Math.min(delay, ACTION_DONE_POLL_WAIT_MS));
- } catch (InterruptedException e) {
- }
- }
- }
- shutdownTimingLog.traceEnd(); // ShutdownStorageManager
- metricEnded(METRIC_SM);
-
if (mRebootHasProgressBar) {
sInstance.setRebootProgress(MOUNT_SERVICE_STOP_PERCENT, null);
@@ -585,6 +529,7 @@
shutdownTimingLog.traceEnd(); // SystemServerShutdown
metricEnded(METRIC_SYSTEM_SERVER);
saveMetrics(mReboot);
+ // Remaining work will be done by init, including vold shutdown
rebootOrShutdown(mContext, mReboot, mReason);
}
diff --git a/com/android/server/power/batterysaver/BatterySaverController.java b/com/android/server/power/batterysaver/BatterySaverController.java
index b3e8538..80bc935 100644
--- a/com/android/server/power/batterysaver/BatterySaverController.java
+++ b/com/android/server/power/batterysaver/BatterySaverController.java
@@ -16,6 +16,7 @@
package com.android.server.power.batterysaver;
import android.Manifest;
+import android.app.ActivityManagerInternal;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -25,6 +26,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
+import android.os.PowerManagerInternal;
import android.os.PowerManagerInternal.LowPowerModeListener;
import android.os.PowerSaveState;
import android.os.UserHandle;
@@ -33,7 +35,9 @@
import android.widget.Toast;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
+import com.android.server.LocalServices;
import com.android.server.power.BatterySaverPolicy;
import com.android.server.power.BatterySaverPolicy.BatterySaverPolicyListener;
import com.android.server.power.PowerManagerService;
@@ -46,7 +50,7 @@
public class BatterySaverController implements BatterySaverPolicyListener {
static final String TAG = "BatterySaverController";
- static final boolean DEBUG = false; // DO NOT MERGE WITH TRUE
+ static final boolean DEBUG = BatterySaverPolicy.DEBUG;
private final Object mLock = new Object();
private final Context mContext;
@@ -63,20 +67,17 @@
@GuardedBy("mLock")
private boolean mEnabled;
- /**
- * Keep track of the previous enabled state, which we use to decide when to send broadcasts,
- * which we don't want to send only when the screen state changes.
- */
- @GuardedBy("mLock")
- private boolean mWasEnabled;
-
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
switch (intent.getAction()) {
case Intent.ACTION_SCREEN_ON:
case Intent.ACTION_SCREEN_OFF:
- mHandler.postStateChanged();
+ if (!isEnabled()) {
+ return; // No need to send it if not enabled.
+ }
+ // Don't send the broadcast, because we never did so in this case.
+ mHandler.postStateChanged(/*sendBroadcast=*/ false);
break;
}
}
@@ -109,6 +110,9 @@
final IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
mContext.registerReceiver(mReceiver, filter);
+
+ mFileUpdater.systemReady(LocalServices.getService(ActivityManagerInternal.class)
+ .isRuntimeRestarted());
}
private PowerManager getPowerManager() {
@@ -121,25 +125,32 @@
@Override
public void onBatterySaverPolicyChanged(BatterySaverPolicy policy) {
- mHandler.postStateChanged();
+ if (!isEnabled()) {
+ return; // No need to send it if not enabled.
+ }
+ mHandler.postStateChanged(/*sendBroadcast=*/ true);
}
private class MyHandler extends Handler {
- private final int MSG_STATE_CHANGED = 1;
+ private static final int MSG_STATE_CHANGED = 1;
+
+ private static final int ARG_DONT_SEND_BROADCAST = 0;
+ private static final int ARG_SEND_BROADCAST = 1;
public MyHandler(Looper looper) {
super(looper);
}
- public void postStateChanged() {
- obtainMessage(MSG_STATE_CHANGED).sendToTarget();
+ public void postStateChanged(boolean sendBroadcast) {
+ obtainMessage(MSG_STATE_CHANGED, sendBroadcast ?
+ ARG_SEND_BROADCAST : ARG_DONT_SEND_BROADCAST, 0).sendToTarget();
}
@Override
public void dispatchMessage(Message msg) {
switch (msg.what) {
case MSG_STATE_CHANGED:
- handleBatterySaverStateChanged();
+ handleBatterySaverStateChanged(msg.arg1 == ARG_SEND_BROADCAST);
break;
}
}
@@ -155,53 +166,75 @@
}
mEnabled = enable;
- mHandler.postStateChanged();
+ mHandler.postStateChanged(/*sendBroadcast=*/ true);
}
}
+ /** @return whether battery saver is enabled or not. */
+ boolean isEnabled() {
+ synchronized (mLock) {
+ return mEnabled;
+ }
+ }
+
+ /**
+ * @return true if launch boost should currently be disabled.
+ */
+ public boolean isLaunchBoostDisabled() {
+ return isEnabled() && mBatterySaverPolicy.isLaunchBoostDisabled();
+ }
+
/**
* Dispatch power save events to the listeners.
*
- * This is always called on the handler thread.
+ * This method is always called on the handler thread.
+ *
+ * This method is called only in the following cases:
+ * - When battery saver becomes activated.
+ * - When battery saver becomes deactivated.
+ * - When battery saver is on the interactive state changes.
+ * - When battery saver is on the battery saver policy changes.
*/
- void handleBatterySaverStateChanged() {
+ void handleBatterySaverStateChanged(boolean sendBroadcast) {
final LowPowerModeListener[] listeners;
- final boolean wasEnabled;
final boolean enabled;
- final boolean isScreenOn = getPowerManager().isInteractive();
+ final boolean isInteractive = getPowerManager().isInteractive();
final ArrayMap<String, String> fileValues;
synchronized (mLock) {
- Slog.i(TAG, "Battery saver enabled: screen on=" + isScreenOn);
+ Slog.i(TAG, "Battery saver " + (mEnabled ? "enabled" : "disabled")
+ + ": isInteractive=" + isInteractive);
listeners = mListeners.toArray(new LowPowerModeListener[mListeners.size()]);
- wasEnabled = mWasEnabled;
enabled = mEnabled;
if (enabled) {
- fileValues = mBatterySaverPolicy.getFileValues(isScreenOn);
+ fileValues = mBatterySaverPolicy.getFileValues(isInteractive);
} else {
fileValues = null;
}
}
- PowerManagerService.powerHintInternal(PowerHint.LOW_POWER, enabled ? 1 : 0);
-
- if (enabled) {
- // STOPSHIP Remove the toast.
- Toast.makeText(mContext,
- com.android.internal.R.string.battery_saver_warning,
- Toast.LENGTH_LONG).show();
+ final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class);
+ if (pmi != null) {
+ pmi.powerHint(PowerHint.LOW_POWER, enabled ? 1 : 0);
}
- if (fileValues == null || fileValues.size() == 0) {
+ if (ArrayUtils.isEmpty(fileValues)) {
mFileUpdater.restoreDefault();
} else {
mFileUpdater.writeFiles(fileValues);
}
- if (enabled != wasEnabled) {
+ if (sendBroadcast) {
+ if (enabled) {
+ // STOPSHIP Remove the toast.
+ Toast.makeText(mContext,
+ com.android.internal.R.string.battery_saver_warning,
+ Toast.LENGTH_LONG).show();
+ }
+
if (DEBUG) {
Slog.i(TAG, "Sending broadcasts for mode: " + enabled);
}
@@ -231,9 +264,5 @@
listener.onLowPowerModeChanged(result);
}
}
-
- synchronized (mLock) {
- mWasEnabled = enabled;
- }
}
}
diff --git a/com/android/server/power/batterysaver/CpuFrequencies.java b/com/android/server/power/batterysaver/CpuFrequencies.java
new file mode 100644
index 0000000..1629486
--- /dev/null
+++ b/com/android/server/power/batterysaver/CpuFrequencies.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2017 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.server.power.batterysaver;
+
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Map;
+
+
+/**
+ * Helper to parse a list of "core-number:frequency" pairs concatenated with / as a separator,
+ * and convert them into a map of "filename -> value" that should be written to
+ * /sys/.../scaling_max_freq.
+ *
+ * Example input: "0:1900800/4:2500000", which will be converted into:
+ * "/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq" "1900800"
+ * "/sys/devices/system/cpu/cpu4/cpufreq/scaling_max_freq" "2500000"
+ *
+ * Test:
+ atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/CpuFrequenciesTest.java
+ */
+public class CpuFrequencies {
+ private static final String TAG = "CpuFrequencies";
+
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private final ArrayMap<Integer, Long> mCoreAndFrequencies = new ArrayMap<>();
+
+ public CpuFrequencies() {
+ }
+
+ /**
+ * Parse a string.
+ */
+ public CpuFrequencies parseString(String cpuNumberAndFrequencies) {
+ synchronized (mLock) {
+ mCoreAndFrequencies.clear();
+ try {
+ for (String pair : cpuNumberAndFrequencies.split("/")) {
+ final String[] coreAndFreq = pair.split(":", 2);
+
+ if (coreAndFreq.length != 2) {
+ throw new IllegalArgumentException("Wrong format");
+ }
+ final int core = Integer.parseInt(coreAndFreq[0]);
+ final long freq = Long.parseLong(coreAndFreq[1]);
+
+ mCoreAndFrequencies.put(core, freq);
+ }
+ } catch (IllegalArgumentException e) {
+ Slog.wtf(TAG, "Invalid configuration: " + cpuNumberAndFrequencies, e);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Return a new map containing the filename-value pairs.
+ */
+ public ArrayMap<String, String> toSysFileMap() {
+ final ArrayMap<String, String> map = new ArrayMap<>();
+ addToSysFileMap(map);
+ return map;
+ }
+
+ /**
+ * Add the filename-value pairs to an existing map.
+ */
+ public void addToSysFileMap(Map<String, String> map) {
+ synchronized (mLock) {
+ final int size = mCoreAndFrequencies.size();
+
+ for (int i = 0; i < size; i++) {
+ final int core = mCoreAndFrequencies.keyAt(i);
+ final long freq = mCoreAndFrequencies.valueAt(i);
+
+ final String file = "/sys/devices/system/cpu/cpu" + Integer.toString(core) +
+ "/cpufreq/scaling_max_freq";
+
+ map.put(file, Long.toString(freq));
+ }
+ }
+ }
+}
diff --git a/com/android/server/power/batterysaver/FileUpdater.java b/com/android/server/power/batterysaver/FileUpdater.java
index cfe8fc4..e0ab9e9 100644
--- a/com/android/server/power/batterysaver/FileUpdater.java
+++ b/com/android/server/power/batterysaver/FileUpdater.java
@@ -16,40 +16,389 @@
package com.android.server.power.batterysaver;
import android.content.Context;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemProperties;
import android.util.ArrayMap;
+import android.util.AtomicFile;
import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
+import com.android.server.IoThread;
+
+import libcore.io.IoUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Map;
/**
* Used by {@link BatterySaverController} to write values to /sys/ (and possibly /proc/ too) files
- * with retry and to restore the original values.
+ * with retries. It also support restoring to the file original values.
*
- * TODO Implement it
+ * Retries are needed because writing to "/sys/.../scaling_max_freq" returns EIO when the current
+ * frequency happens to be above the new max frequency.
+ *
+ * Test:
+ atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java
*/
public class FileUpdater {
private static final String TAG = BatterySaverController.TAG;
private static final boolean DEBUG = BatterySaverController.DEBUG;
+ /**
+ * If this system property is set to 1, it'll skip all file writes. This can be used when
+ * one needs to change max CPU frequency for benchmarking, for example.
+ */
+ private static final String PROP_SKIP_WRITE = "debug.batterysaver.no_write_files";
+
+ private static final String TAG_DEFAULT_ROOT = "defaults";
+
+ // Don't do disk access with this lock held.
private final Object mLock = new Object();
+
private final Context mContext;
+ private final Handler mHandler;
+
+ /**
+ * Filename -> value map that holds pending writes.
+ */
+ @GuardedBy("mLock")
+ private final ArrayMap<String, String> mPendingWrites = new ArrayMap<>();
+
+ /**
+ * Filename -> value that holds the original value of each file.
+ */
+ @GuardedBy("mLock")
+ private final ArrayMap<String, String> mDefaultValues = new ArrayMap<>();
+
+ /** Number of retries. We give up on writing after {@link #MAX_RETRIES} retries. */
+ @GuardedBy("mLock")
+ private int mRetries = 0;
+
+ private final int MAX_RETRIES;
+
+ private final long RETRY_INTERVAL_MS;
+
+ /**
+ * "Official" constructor. Don't use the other constructor in the production code.
+ */
public FileUpdater(Context context) {
- mContext = context;
+ this(context, IoThread.get().getLooper(), 10, 5000);
}
- public void writeFiles(ArrayMap<String, String> fileValues) {
- if (DEBUG) {
- final int size = fileValues.size();
- for (int i = 0; i < size; i++) {
- Slog.d(TAG, "Writing '" + fileValues.valueAt(i)
- + "' to '" + fileValues.keyAt(i) + "'");
+ /**
+ * Constructor for test.
+ */
+ @VisibleForTesting
+ FileUpdater(Context context, Looper looper, int maxRetries, int retryIntervalMs) {
+ mContext = context;
+ mHandler = new Handler(looper);
+
+ MAX_RETRIES = maxRetries;
+ RETRY_INTERVAL_MS = retryIntervalMs;
+ }
+
+ public void systemReady(boolean runtimeRestarted) {
+ synchronized (mLock) {
+ if (runtimeRestarted) {
+ // If it runtime restarted, read the original values from the disk and apply.
+ if (loadDefaultValuesLocked()) {
+ Slog.d(TAG, "Default values loaded after runtime restart; writing them...");
+ restoreDefault();
+ }
+ } else {
+ // Delete it, without checking the result. (file-not-exist is not an exception.)
+ injectDefaultValuesFilename().delete();
}
}
}
- public void restoreDefault() {
- if (DEBUG) {
- Slog.d(TAG, "Resetting file default values");
+ /**
+ * Write values to files. (Note the actual writes happen ASAP but asynchronously.)
+ */
+ public void writeFiles(ArrayMap<String, String> fileValues) {
+ synchronized (mLock) {
+ for (int i = fileValues.size() - 1; i >= 0; i--) {
+ final String file = fileValues.keyAt(i);
+ final String value = fileValues.valueAt(i);
+
+ if (DEBUG) {
+ Slog.d(TAG, "Scheduling write: '" + value + "' to '" + file + "'");
+ }
+
+ mPendingWrites.put(file, value);
+
+ }
+ mRetries = 0;
+
+ mHandler.removeCallbacks(mHandleWriteOnHandlerRunnable);
+ mHandler.post(mHandleWriteOnHandlerRunnable);
}
}
+
+ /**
+ * Restore the default values.
+ */
+ public void restoreDefault() {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slog.d(TAG, "Resetting file default values.");
+ }
+ mPendingWrites.clear();
+
+ writeFiles(mDefaultValues);
+ }
+ }
+
+ private Runnable mHandleWriteOnHandlerRunnable = () -> handleWriteOnHandler();
+
+ /** Convert map keys into a single string for debug messages. */
+ private String getKeysString(Map<String, String> source) {
+ return new ArrayList<>(source.keySet()).toString();
+ }
+
+ /** Clone an ArrayMap. */
+ private ArrayMap<String, String> cloneMap(ArrayMap<String, String> source) {
+ return new ArrayMap<>(source);
+ }
+
+ /**
+ * Called on the handler and writes {@link #mPendingWrites} to the disk.
+ *
+ * When it about to write to each file for the first time, it'll read the file and store
+ * the original value in {@link #mDefaultValues}.
+ */
+ private void handleWriteOnHandler() {
+ // We don't want to access the disk with the lock held, so copy the pending writes to
+ // a local map.
+ final ArrayMap<String, String> writes;
+ synchronized (mLock) {
+ if (mPendingWrites.size() == 0) {
+ return;
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG, "Writing files: (# retries=" + mRetries + ") " +
+ getKeysString(mPendingWrites));
+ }
+
+ writes = cloneMap(mPendingWrites);
+ }
+
+ // Then write.
+
+ boolean needRetry = false;
+
+ final int size = writes.size();
+ for (int i = 0; i < size; i++) {
+ final String file = writes.keyAt(i);
+ final String value = writes.valueAt(i);
+
+ // Make sure the default value is loaded.
+ if (!ensureDefaultLoaded(file)) {
+ continue;
+ }
+
+ // Write to the file. When succeeded, remove it from the pending list.
+ // Otherwise, schedule a retry.
+ try {
+ injectWriteToFile(file, value);
+
+ removePendingWrite(file);
+ } catch (IOException e) {
+ needRetry = true;
+ }
+ }
+ if (needRetry) {
+ scheduleRetry();
+ }
+ }
+
+ private void removePendingWrite(String file) {
+ synchronized (mLock) {
+ mPendingWrites.remove(file);
+ }
+ }
+
+ private void scheduleRetry() {
+ synchronized (mLock) {
+ if (mPendingWrites.size() == 0) {
+ return; // Shouldn't happen but just in case.
+ }
+
+ mRetries++;
+ if (mRetries > MAX_RETRIES) {
+ doWtf("Gave up writing files: " + getKeysString(mPendingWrites));
+ return;
+ }
+
+ mHandler.removeCallbacks(mHandleWriteOnHandlerRunnable);
+ mHandler.postDelayed(mHandleWriteOnHandlerRunnable, RETRY_INTERVAL_MS);
+ }
+ }
+
+ /**
+ * Make sure {@link #mDefaultValues} has the default value loaded for {@code file}.
+ *
+ * @return true if the default value is loaded. false if the file cannot be read.
+ */
+ private boolean ensureDefaultLoaded(String file) {
+ // Has the default already?
+ synchronized (mLock) {
+ if (mDefaultValues.containsKey(file)) {
+ return true;
+ }
+ }
+ final String originalValue;
+ try {
+ originalValue = injectReadFromFileTrimmed(file);
+ } catch (IOException e) {
+ // If the file is not readable, assume can't write too.
+ injectWtf("Unable to read from file", e);
+
+ removePendingWrite(file);
+ return false;
+ }
+ synchronized (mLock) {
+ mDefaultValues.put(file, originalValue);
+ saveDefaultValuesLocked();
+ }
+ return true;
+ }
+
+ @VisibleForTesting
+ String injectReadFromFileTrimmed(String file) throws IOException {
+ return IoUtils.readFileAsString(file).trim();
+ }
+
+ @VisibleForTesting
+ void injectWriteToFile(String file, String value) throws IOException {
+ if (injectShouldSkipWrite()) {
+ Slog.i(TAG, "Skipped writing to '" + file + "'");
+ return;
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "Writing: '" + value + "' to '" + file + "'");
+ }
+ try (FileWriter out = new FileWriter(file)) {
+ out.write(value);
+ } catch (IOException | RuntimeException e) {
+ Slog.w(TAG, "Failed writing '" + value + "' to '" + file + "': " + e.getMessage());
+ throw e;
+ }
+ }
+
+ private void saveDefaultValuesLocked() {
+ final AtomicFile file = new AtomicFile(injectDefaultValuesFilename());
+
+ FileOutputStream outs = null;
+ try {
+ file.getBaseFile().getParentFile().mkdirs();
+ outs = file.startWrite();
+
+ // Write to XML
+ XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(outs, StandardCharsets.UTF_8.name());
+ out.startDocument(null, true);
+ out.startTag(null, TAG_DEFAULT_ROOT);
+
+ XmlUtils.writeMapXml(mDefaultValues, out, null);
+
+ // Epilogue.
+ out.endTag(null, TAG_DEFAULT_ROOT);
+ out.endDocument();
+
+ // Close.
+ file.finishWrite(outs);
+ } catch (IOException | XmlPullParserException | RuntimeException e) {
+ Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
+ file.failWrite(outs);
+ }
+ }
+
+ @VisibleForTesting
+ boolean loadDefaultValuesLocked() {
+ final AtomicFile file = new AtomicFile(injectDefaultValuesFilename());
+ if (DEBUG) {
+ Slog.d(TAG, "Loading from " + file.getBaseFile());
+ }
+ Map<String, String> read = null;
+ try (FileInputStream in = file.openRead()) {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(in, StandardCharsets.UTF_8.name());
+
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+ final int depth = parser.getDepth();
+ // Check the root tag
+ final String tag = parser.getName();
+ if (depth == 1) {
+ if (!TAG_DEFAULT_ROOT.equals(tag)) {
+ Slog.e(TAG, "Invalid root tag: " + tag);
+ return false;
+ }
+ continue;
+ }
+ final String[] tagName = new String[1];
+ read = (ArrayMap<String, String>) XmlUtils.readThisArrayMapXml(parser,
+ TAG_DEFAULT_ROOT, tagName, null);
+ }
+ } catch (FileNotFoundException e) {
+ read = null;
+ } catch (IOException | XmlPullParserException | RuntimeException e) {
+ Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
+ }
+ if (read != null) {
+ mDefaultValues.clear();
+ mDefaultValues.putAll(read);
+ return true;
+ }
+ return false;
+ }
+
+ private void doWtf(String message) {
+ injectWtf(message, null);
+ }
+
+ @VisibleForTesting
+ void injectWtf(String message, Throwable e) {
+ Slog.wtf(TAG, message, e);
+ }
+
+ File injectDefaultValuesFilename() {
+ final File dir = new File(Environment.getDataSystemDirectory(), "battery-saver");
+ dir.mkdirs();
+ return new File(dir, "default-values.xml");
+ }
+
+ @VisibleForTesting
+ boolean injectShouldSkipWrite() {
+ return SystemProperties.getBoolean(PROP_SKIP_WRITE, false);
+ }
+
+ @VisibleForTesting
+ ArrayMap<String, String> getDefaultValuesForTest() {
+ return mDefaultValues;
+ }
}
diff --git a/com/android/server/stats/StatsCompanionService.java b/com/android/server/stats/StatsCompanionService.java
index dafab77..e240ec5 100644
--- a/com/android/server/stats/StatsCompanionService.java
+++ b/com/android/server/stats/StatsCompanionService.java
@@ -75,6 +75,7 @@
private final PendingIntent mPullingAlarmIntent;
private final BroadcastReceiver mAppUpdateReceiver;
private final BroadcastReceiver mUserUpdateReceiver;
+ private final ShutdownEventReceiver mShutdownEventReceiver;
private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader();
private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
private final KernelCpuSpeedReader[] mKernelCpuSpeedReaders;
@@ -109,6 +110,7 @@
}
}
};
+ mShutdownEventReceiver = new ShutdownEventReceiver();
Slog.w(TAG, "Registered receiver for ACTION_PACKAGE_REPLACE AND ADDED.");
PowerProfile powerProfile = new PowerProfile(context);
final int numClusters = powerProfile.getNumCpuClusters();
@@ -239,20 +241,45 @@
Slog.d(TAG, "Time to poll something.");
synchronized (sStatsdLock) {
if (sStatsd == null) {
- Slog.w(TAG, "Could not access statsd to inform it of pulling alarm firing");
+ Slog.w(TAG, "Could not access statsd to inform it of pulling alarm firing.");
return;
}
try {
// Two-way call to statsd to retain AlarmManager wakelock
sStatsd.informPollAlarmFired();
} catch (RemoteException e) {
- Slog.w(TAG, "Failed to inform statsd of pulling alarm firing", e);
+ Slog.w(TAG, "Failed to inform statsd of pulling alarm firing.", e);
}
}
// AlarmManager releases its own wakelock here.
}
}
+ public final static class ShutdownEventReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ /**
+ * Skip immediately if intent is not relevant to device shutdown.
+ */
+ if (!intent.getAction().equals(Intent.ACTION_REBOOT)
+ && !intent.getAction().equals(Intent.ACTION_SHUTDOWN)) {
+ return;
+ }
+ Slog.i(TAG, "StatsCompanionService noticed a shutdown.");
+ synchronized (sStatsdLock) {
+ if (sStatsd == null) {
+ Slog.w(TAG, "Could not access statsd to inform it of a shutdown event.");
+ return;
+ }
+ try {
+ sStatsd.writeDataToDisk();
+ } catch (Exception e) {
+ Slog.w(TAG, "Failed to inform statsd of a shutdown event.", e);
+ }
+ }
+ }
+ }
+
@Override // Binder call
public void setAnomalyAlarm(long timestampMs) {
enforceCallingPermission();
@@ -496,6 +523,18 @@
sayHiToStatsd(); // tell statsd that we're ready too and link to it
}
+ @Override
+ public void triggerUidSnapshot() {
+ enforceCallingPermission();
+ synchronized (sStatsdLock) {
+ try {
+ informAllUidsLocked(mContext);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to trigger uid snapshot.", e);
+ }
+ }
+ }
+
private void enforceCallingPermission() {
if (Binder.getCallingPid() == Process.myPid()) {
return;
@@ -572,7 +611,7 @@
Slog.e(TAG, "linkToDeath(StatsdDeathRecipient) failed", e);
forgetEverything();
}
- // Setup broadcast receiver for updates
+ // Setup broadcast receiver for updates.
IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REPLACED);
filter.addAction(Intent.ACTION_PACKAGE_ADDED);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
@@ -587,6 +626,12 @@
mContext.registerReceiverAsUser(mUserUpdateReceiver, UserHandle.ALL,
filter, null, null);
+ // Setup receiver for device reboots or shutdowns.
+ filter = new IntentFilter(Intent.ACTION_REBOOT);
+ filter.addAction(Intent.ACTION_SHUTDOWN);
+ mContext.registerReceiverAsUser(
+ mShutdownEventReceiver, UserHandle.ALL, filter, null, null);
+
// Pull the latest state of UID->app name, version mapping when statsd starts.
informAllUidsLocked(mContext);
} catch (RemoteException e) {
@@ -609,6 +654,7 @@
sStatsd = null;
mContext.unregisterReceiver(mAppUpdateReceiver);
mContext.unregisterReceiver(mUserUpdateReceiver);
+ mContext.unregisterReceiver(mShutdownEventReceiver);
cancelAnomalyAlarm();
cancelPullingAlarms();
}
diff --git a/com/android/server/statusbar/StatusBarManagerInternal.java b/com/android/server/statusbar/StatusBarManagerInternal.java
index b07fe98..3792bc6 100644
--- a/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -90,6 +90,13 @@
boolean showShutdownUi(boolean isReboot, String requestString);
+ /**
+ * Show a rotation suggestion that a user may approve to rotate the screen.
+ *
+ * @param rotation rotation suggestion
+ */
+ void onProposedRotationChanged(int rotation);
+
public interface GlobalActionsListener {
/**
* Called when sysui starts and connects its status bar, or when the status bar binder
diff --git a/com/android/server/statusbar/StatusBarManagerService.java b/com/android/server/statusbar/StatusBarManagerService.java
index c78a340..c7c03b4 100644
--- a/com/android/server/statusbar/StatusBarManagerService.java
+++ b/com/android/server/statusbar/StatusBarManagerService.java
@@ -406,6 +406,15 @@
}
return false;
}
+
+ @Override
+ public void onProposedRotationChanged(int rotation) {
+ if (mBar != null){
+ try {
+ mBar.onProposedRotationChanged(rotation);
+ } catch (RemoteException ex) {}
+ }
+ }
};
// ================================================================================
diff --git a/com/android/server/usage/AppIdleHistory.java b/com/android/server/usage/AppIdleHistory.java
index c5ca330..ee11241 100644
--- a/com/android/server/usage/AppIdleHistory.java
+++ b/com/android/server/usage/AppIdleHistory.java
@@ -91,8 +91,6 @@
private long mScreenOnSnapshot; // Elapsed time snapshot when last write of mScreenOnDuration
private long mScreenOnDuration; // Total screen on duration since device was "born"
- private long mElapsedTimeThreshold;
- private long mScreenOnTimeThreshold;
private final File mStorageDir;
private boolean mScreenOn;
@@ -113,11 +111,6 @@
readScreenOnTime();
}
- public void setThresholds(long elapsedTimeThreshold, long screenOnTimeThreshold) {
- mElapsedTimeThreshold = elapsedTimeThreshold;
- mScreenOnTimeThreshold = screenOnTimeThreshold;
- }
-
public void updateDisplay(boolean screenOn, long elapsedRealtime) {
if (screenOn == mScreenOn) return;
@@ -186,7 +179,7 @@
writeScreenOnTime();
}
- public void reportUsage(String packageName, int userId, long elapsedRealtime) {
+ public int reportUsage(String packageName, int userId, long elapsedRealtime) {
ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
elapsedRealtime, true);
@@ -197,12 +190,33 @@
+ (elapsedRealtime - mElapsedSnapshot);
appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime);
appUsageHistory.recent[HISTORY_SIZE - 1] = FLAG_LAST_STATE | FLAG_PARTIAL_ACTIVE;
- appUsageHistory.currentBucket = AppStandby.STANDBY_BUCKET_ACTIVE;
- appUsageHistory.bucketingReason = AppStandby.REASON_USAGE;
- if (DEBUG) {
- Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket
- + ", reason=" + appUsageHistory.bucketingReason);
+ if (appUsageHistory.currentBucket > STANDBY_BUCKET_ACTIVE) {
+ appUsageHistory.currentBucket = STANDBY_BUCKET_ACTIVE;
+ if (DEBUG) {
+ Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket
+ + ", reason=" + appUsageHistory.bucketingReason);
+ }
}
+ appUsageHistory.bucketingReason = AppStandby.REASON_USAGE;
+
+ return appUsageHistory.currentBucket;
+ }
+
+ public int reportMildUsage(String packageName, int userId, long elapsedRealtime) {
+ ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+ AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
+ elapsedRealtime, true);
+ if (appUsageHistory.currentBucket > STANDBY_BUCKET_WORKING_SET) {
+ appUsageHistory.currentBucket = STANDBY_BUCKET_WORKING_SET;
+ if (DEBUG) {
+ Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket
+ + ", reason=" + appUsageHistory.bucketingReason);
+ }
+ }
+ // TODO: Should this be a different reason for partial usage?
+ appUsageHistory.bucketingReason = AppStandby.REASON_USAGE;
+
+ return appUsageHistory.currentBucket;
}
public void setIdle(String packageName, int userId, long elapsedRealtime) {
@@ -313,7 +327,8 @@
return (elapsedRealtime - mElapsedSnapshot + mElapsedDuration);
}
- public void setIdle(String packageName, int userId, boolean idle, long elapsedRealtime) {
+ /* Returns the new standby bucket the app is assigned to */
+ public int setIdle(String packageName, int userId, boolean idle, long elapsedRealtime) {
ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
elapsedRealtime, true);
@@ -325,6 +340,7 @@
// This is to pretend that the app was just used, don't freeze the state anymore.
appUsageHistory.bucketingReason = REASON_USAGE;
}
+ return appUsageHistory.currentBucket;
}
public void clearUsage(String packageName, int userId) {
diff --git a/com/android/server/usage/AppStandbyController.java b/com/android/server/usage/AppStandbyController.java
index 5623a68..3c099c2 100644
--- a/com/android/server/usage/AppStandbyController.java
+++ b/com/android/server/usage/AppStandbyController.java
@@ -91,14 +91,14 @@
0,
0,
COMPRESS_TIME ? 120 * 1000 : 1 * ONE_HOUR,
- COMPRESS_TIME ? 240 * 1000 : 8 * ONE_HOUR
+ COMPRESS_TIME ? 240 * 1000 : 2 * ONE_HOUR
};
static final long[] ELAPSED_TIME_THRESHOLDS = {
0,
COMPRESS_TIME ? 1 * ONE_MINUTE : 12 * ONE_HOUR,
- COMPRESS_TIME ? 4 * ONE_MINUTE : 2 * ONE_DAY,
- COMPRESS_TIME ? 16 * ONE_MINUTE : 8 * ONE_DAY
+ COMPRESS_TIME ? 4 * ONE_MINUTE : 24 * ONE_HOUR,
+ COMPRESS_TIME ? 16 * ONE_MINUTE : 48 * ONE_HOUR
};
static final int[] THRESHOLD_BUCKETS = {
@@ -140,9 +140,7 @@
static final int MSG_PAROLE_STATE_CHANGED = 9;
static final int MSG_ONE_TIME_CHECK_IDLE_STATES = 10;
- long mAppIdleScreenThresholdMillis;
long mCheckIdleIntervalMillis;
- long mAppIdleWallclockThresholdMillis;
long mAppIdleParoleIntervalMillis;
long mAppIdleParoleDurationMillis;
long[] mAppStandbyScreenThresholds = SCREEN_TIME_THRESHOLDS;
@@ -156,7 +154,7 @@
private volatile boolean mPendingOneTimeCheckIdleStates;
- private final Handler mHandler;
+ private final AppStandbyHandler mHandler;
private final Context mContext;
// TODO: Provide a mechanism to set an external bucketing service
@@ -229,6 +227,7 @@
// Get sync adapters for the authority
String[] packages = ContentResolver.getSyncAdapterPackagesForAuthorityAsUser(
authority, userId);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
for (String packageName: packages) {
// Only force the sync adapters to active if the provider is not in the same package and
// the sync adapter is a system package.
@@ -239,7 +238,12 @@
continue;
}
if (!packageName.equals(providerPkgName)) {
- setAppIdleAsync(packageName, false, userId);
+ synchronized (mAppIdleLock) {
+ int newBucket = mAppIdleHistory.reportMildUsage(packageName, userId,
+ elapsedRealtime);
+ maybeInformListeners(packageName, userId, elapsedRealtime,
+ newBucket);
+ }
}
} catch (PackageManager.NameNotFoundException e) {
// Shouldn't happen
@@ -408,6 +412,7 @@
private void maybeInformListeners(String packageName, int userId,
long elapsedRealtime, int bucket) {
synchronized (mAppIdleLock) {
+ // TODO: fold these into one call + lookup for efficiency if needed
if (mAppIdleHistory.shouldInformListeners(packageName, userId,
elapsedRealtime, bucket)) {
mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS,
@@ -493,11 +498,21 @@
if ((event.mEventType == UsageEvents.Event.MOVE_TO_FOREGROUND
|| event.mEventType == UsageEvents.Event.MOVE_TO_BACKGROUND
|| event.mEventType == UsageEvents.Event.SYSTEM_INTERACTION
- || event.mEventType == UsageEvents.Event.USER_INTERACTION)) {
- mAppIdleHistory.reportUsage(event.mPackage, userId, elapsedRealtime);
+ || event.mEventType == UsageEvents.Event.USER_INTERACTION
+ || event.mEventType == UsageEvents.Event.NOTIFICATION_SEEN)) {
+
+ final int newBucket;
+ if (event.mEventType == UsageEvents.Event.NOTIFICATION_SEEN) {
+ newBucket = mAppIdleHistory.reportMildUsage(event.mPackage, userId,
+ elapsedRealtime);
+ } else {
+ newBucket = mAppIdleHistory.reportUsage(event.mPackage, userId,
+ elapsedRealtime);
+ }
+
+ maybeInformListeners(event.mPackage, userId, elapsedRealtime,
+ newBucket);
if (previouslyIdle) {
- maybeInformListeners(event.mPackage, userId, elapsedRealtime,
- AppStandby.STANDBY_BUCKET_ACTIVE);
notifyBatteryStats(event.mPackage, userId, false);
}
}
@@ -519,15 +534,15 @@
final boolean previouslyIdle = isAppIdleFiltered(packageName, appId,
userId, elapsedRealtime);
+ final int standbyBucket;
synchronized (mAppIdleLock) {
- mAppIdleHistory.setIdle(packageName, userId, idle, elapsedRealtime);
+ standbyBucket = mAppIdleHistory.setIdle(packageName, userId, idle, elapsedRealtime);
}
final boolean stillIdle = isAppIdleFiltered(packageName, appId,
userId, elapsedRealtime);
// Inform listeners if necessary
if (previouslyIdle != stillIdle) {
- mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, userId,
- /* idle = */ stillIdle ? 1 : 0, packageName));
+ maybeInformListeners(packageName, userId, elapsedRealtime, standbyBucket);
if (!stillIdle) {
notifyBatteryStats(packageName, userId, idle);
}
@@ -723,7 +738,7 @@
.sendToTarget();
}
- @StandbyBuckets int getAppStandbyBucket(String packageName, int userId,
+ @StandbyBuckets public int getAppStandbyBucket(String packageName, int userId,
long elapsedRealtime, boolean shouldObfuscateInstantApps) {
if (shouldObfuscateInstantApps &&
mInjector.isPackageEphemeral(userId, packageName)) {
@@ -737,6 +752,8 @@
String reason, long elapsedRealtime) {
mAppIdleHistory.setAppStandbyBucket(packageName, userId, elapsedRealtime, newBucket,
reason);
+ maybeInformListeners(packageName, userId, elapsedRealtime,
+ newBucket);
}
private boolean isActiveDeviceAdmin(String packageName, int userId) {
@@ -896,14 +913,6 @@
pw.println();
pw.println("Settings:");
- pw.print(" mAppIdleDurationMillis=");
- TimeUtils.formatDuration(mAppIdleScreenThresholdMillis, pw);
- pw.println();
-
- pw.print(" mAppIdleWallclockThresholdMillis=");
- TimeUtils.formatDuration(mAppIdleWallclockThresholdMillis, pw);
- pw.println();
-
pw.print(" mCheckIdleIntervalMillis=");
TimeUtils.formatDuration(mCheckIdleIntervalMillis, pw);
pw.println();
@@ -1033,6 +1042,11 @@
int userId) {
return appWidgetManager.isBoundWidgetPackage(packageName, userId);
}
+
+ String getAppIdleSettings() {
+ return Settings.Global.getString(mContext.getContentResolver(),
+ Settings.Global.APP_IDLE_CONSTANTS);
+ }
}
class AppStandbyHandler extends Handler {
@@ -1165,31 +1179,18 @@
// Look at global settings for this.
// TODO: Maybe apply different thresholds for different users.
try {
- mParser.setString(Settings.Global.getString(mContext.getContentResolver(),
- Settings.Global.APP_IDLE_CONSTANTS));
+ mParser.setString(mInjector.getAppIdleSettings());
} catch (IllegalArgumentException e) {
Slog.e(TAG, "Bad value for app idle settings: " + e.getMessage());
// fallthrough, mParser is empty and all defaults will be returned.
}
- // Default: 12 hours of screen-on time sans dream-time
- mAppIdleScreenThresholdMillis = mParser.getLong(KEY_IDLE_DURATION,
- COMPRESS_TIME ? ONE_MINUTE * 4 : 12 * 60 * ONE_MINUTE);
-
- mAppIdleWallclockThresholdMillis = mParser.getLong(KEY_WALLCLOCK_THRESHOLD,
- COMPRESS_TIME ? ONE_MINUTE * 8 : 2L * 24 * 60 * ONE_MINUTE); // 2 days
-
- mCheckIdleIntervalMillis = Math.min(mAppIdleScreenThresholdMillis / 4,
- COMPRESS_TIME ? ONE_MINUTE : 4 * 60 * ONE_MINUTE); // 4 hours
-
// Default: 24 hours between paroles
mAppIdleParoleIntervalMillis = mParser.getLong(KEY_PAROLE_INTERVAL,
COMPRESS_TIME ? ONE_MINUTE * 10 : 24 * 60 * ONE_MINUTE);
mAppIdleParoleDurationMillis = mParser.getLong(KEY_PAROLE_DURATION,
COMPRESS_TIME ? ONE_MINUTE : 10 * ONE_MINUTE); // 10 minutes
- mAppIdleHistory.setThresholds(mAppIdleWallclockThresholdMillis,
- mAppIdleScreenThresholdMillis);
String screenThresholdsValue = mParser.getString(KEY_SCREEN_TIME_THRESHOLDS, null);
mAppStandbyScreenThresholds = parseLongArray(screenThresholdsValue,
@@ -1199,6 +1200,9 @@
null);
mAppStandbyElapsedThresholds = parseLongArray(elapsedThresholdsValue,
ELAPSED_TIME_THRESHOLDS);
+ mCheckIdleIntervalMillis = Math.min(mAppStandbyElapsedThresholds[1] / 4,
+ COMPRESS_TIME ? ONE_MINUTE : 4 * 60 * ONE_MINUTE); // 4 hours
+
}
}
diff --git a/com/android/server/usage/UsageStatsService.java b/com/android/server/usage/UsageStatsService.java
index 44e6a6c..65c1cef 100644
--- a/com/android/server/usage/UsageStatsService.java
+++ b/com/android/server/usage/UsageStatsService.java
@@ -24,6 +24,7 @@
import android.app.usage.ConfigurationStats;
import android.app.usage.IUsageStatsManager;
import android.app.usage.UsageEvents;
+import android.app.usage.AppStandby.StandbyBuckets;
import android.app.usage.UsageEvents.Event;
import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManagerInternal;
@@ -867,6 +868,12 @@
}
@Override
+ @StandbyBuckets public int getAppStandbyBucket(String packageName, int userId,
+ long nowElapsed) {
+ return mAppStandby.getAppStandbyBucket(packageName, userId, nowElapsed, false);
+ }
+
+ @Override
public int[] getIdleUidsForUser(int userId) {
return mAppStandby.getIdleUidsForUser(userId);
}
diff --git a/com/android/server/usage/UserUsageStatsService.java b/com/android/server/usage/UserUsageStatsService.java
index 0b10590..f02221c 100644
--- a/com/android/server/usage/UserUsageStatsService.java
+++ b/com/android/server/usage/UserUsageStatsService.java
@@ -643,6 +643,8 @@
return "SHORTCUT_INVOCATION";
case UsageEvents.Event.CHOOSER_ACTION:
return "CHOOSER_ACTION";
+ case UsageEvents.Event.NOTIFICATION_SEEN:
+ return "NOTIFICATION_SEEN";
default:
return "UNKNOWN";
}
diff --git a/com/android/server/usb/UsbAlsaManager.java b/com/android/server/usb/UsbAlsaManager.java
index d359b70..7bea8a1 100644
--- a/com/android/server/usb/UsbAlsaManager.java
+++ b/com/android/server/usb/UsbAlsaManager.java
@@ -255,6 +255,7 @@
}
private void alsaFileAdded(String name) {
+ Slog.i(TAG, "alsaFileAdded(" + name + ")");
int type = AlsaDevice.TYPE_UNKNOWN;
int card = -1, device = -1;
diff --git a/com/android/server/usb/UsbHostManager.java b/com/android/server/usb/UsbHostManager.java
index 095fdc6..dd2e192 100644
--- a/com/android/server/usb/UsbHostManager.java
+++ b/com/android/server/usb/UsbHostManager.java
@@ -19,13 +19,8 @@
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
-import android.hardware.usb.UsbConfiguration;
import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
-import android.hardware.usb.UsbDeviceConnection;
-import android.hardware.usb.UsbEndpoint;
-import android.hardware.usb.UsbInterface;
-import android.hardware.usb.UsbManager;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.text.TextUtils;
@@ -37,7 +32,6 @@
import com.android.server.usb.descriptors.report.TextReportCanvas;
import com.android.server.usb.descriptors.tree.UsbDescriptorsTree;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@@ -50,28 +44,23 @@
private final Context mContext;
- // contains all connected USB devices
- private final HashMap<String, UsbDevice> mDevices = new HashMap<>();
-
// USB busses to exclude from USB host support
private final String[] mHostBlacklist;
- private final Object mLock = new Object();
-
- private UsbDevice mNewDevice;
- private UsbConfiguration mNewConfiguration;
- private UsbInterface mNewInterface;
- private ArrayList<UsbConfiguration> mNewConfigurations;
- private ArrayList<UsbInterface> mNewInterfaces;
- private ArrayList<UsbEndpoint> mNewEndpoints;
-
private final UsbAlsaManager mUsbAlsaManager;
private final UsbSettingsManager mSettingsManager;
+ private final Object mLock = new Object();
@GuardedBy("mLock")
+ // contains all connected USB devices
+ private final HashMap<String, UsbDevice> mDevices = new HashMap<>();
+
+ private Object mSettingsLock = new Object();
+ @GuardedBy("mSettingsLock")
private UsbProfileGroupSettingsManager mCurrentSettings;
- @GuardedBy("mLock")
+ private Object mHandlerLock = new Object();
+ @GuardedBy("mHandlerLock")
private ComponentName mUsbDeviceConnectionHandler;
public UsbHostManager(Context context, UsbAlsaManager alsaManager,
@@ -91,33 +80,33 @@
}
public void setCurrentUserSettings(UsbProfileGroupSettingsManager settings) {
- synchronized (mLock) {
+ synchronized (mSettingsLock) {
mCurrentSettings = settings;
}
}
private UsbProfileGroupSettingsManager getCurrentUserSettings() {
- synchronized (mLock) {
+ synchronized (mSettingsLock) {
return mCurrentSettings;
}
}
public void setUsbDeviceConnectionHandler(@Nullable ComponentName usbDeviceConnectionHandler) {
- synchronized (mLock) {
+ synchronized (mHandlerLock) {
mUsbDeviceConnectionHandler = usbDeviceConnectionHandler;
}
}
private @Nullable ComponentName getUsbDeviceConnectionHandler() {
- synchronized (mLock) {
+ synchronized (mHandlerLock) {
return mUsbDeviceConnectionHandler;
}
}
- private boolean isBlackListed(String deviceName) {
+ private boolean isBlackListed(String deviceAddress) {
int count = mHostBlacklist.length;
for (int i = 0; i < count; i++) {
- if (deviceName.startsWith(mHostBlacklist[i])) {
+ if (deviceAddress.startsWith(mHostBlacklist[i])) {
return true;
}
}
@@ -136,166 +125,73 @@
}
/* Called from JNI in monitorUsbHostBus() to report new USB devices
- Returns true if successful, in which case the JNI code will continue adding configurations,
- interfaces and endpoints, and finally call endUsbDeviceAdded after all descriptors
- have been processed
+ Returns true if successful, i.e. the USB Audio device descriptors are
+ correctly parsed and the unique device is added to the audio device list.
*/
@SuppressWarnings("unused")
- private boolean beginUsbDeviceAdded(String deviceName, int vendorID, int productID,
- int deviceClass, int deviceSubclass, int deviceProtocol,
- String manufacturerName, String productName, int version, String serialNumber) {
-
+ private boolean usbDeviceAdded(String deviceAddress, int deviceClass, int deviceSubclass,
+ byte[] descriptors) {
if (DEBUG) {
- Slog.d(TAG, "usb:UsbHostManager.beginUsbDeviceAdded(" + deviceName + ")");
- // Audio Class Codes:
- // Audio: 0x01
- // Audio Subclass Codes:
- // undefined: 0x00
- // audio control: 0x01
- // audio streaming: 0x02
- // midi streaming: 0x03
-
- // some useful debugging info
- Slog.d(TAG, "usb: nm:" + deviceName + " vnd:" + vendorID + " prd:" + productID + " cls:"
- + deviceClass + " sub:" + deviceSubclass + " proto:" + deviceProtocol);
+ Slog.d(TAG, "usbDeviceAdded(" + deviceAddress + ") - start");
}
- // OK this is non-obvious, but true. One can't tell if the device being attached is even
- // potentially an audio device without parsing the interface descriptors, so punt on any
- // such test until endUsbDeviceAdded() when we have that info.
-
- if (isBlackListed(deviceName) ||
- isBlackListed(deviceClass, deviceSubclass)) {
+ // check class/subclass first as it is more likely to be blacklisted
+ if (isBlackListed(deviceClass, deviceSubclass) || isBlackListed(deviceAddress)) {
+ if (DEBUG) {
+ Slog.d(TAG, "device is black listed");
+ }
return false;
}
synchronized (mLock) {
- if (mDevices.get(deviceName) != null) {
- Slog.w(TAG, "device already on mDevices list: " + deviceName);
+ if (mDevices.get(deviceAddress) != null) {
+ Slog.w(TAG, "device already on mDevices list: " + deviceAddress);
+ //TODO If this is the same peripheral as is being connected, replace
+ // it with the new connection.
return false;
}
- if (mNewDevice != null) {
- Slog.e(TAG, "mNewDevice is not null in endUsbDeviceAdded");
- return false;
- }
+ UsbDescriptorParser parser = new UsbDescriptorParser(deviceAddress);
+ if (parser.parseDescriptors(descriptors)) {
- // Create version string in "%.%" format
- String versionString = Integer.toString(version >> 8) + "." + (version & 0xFF);
-
- mNewDevice = new UsbDevice(deviceName, vendorID, productID,
- deviceClass, deviceSubclass, deviceProtocol,
- manufacturerName, productName, versionString, serialNumber);
-
- mNewConfigurations = new ArrayList<>();
- mNewInterfaces = new ArrayList<>();
- mNewEndpoints = new ArrayList<>();
- }
-
- return true;
- }
-
- /* Called from JNI in monitorUsbHostBus() to report new USB configuration for the device
- currently being added. Returns true if successful, false in case of error.
- */
- @SuppressWarnings("unused")
- private void addUsbConfiguration(int id, String name, int attributes, int maxPower) {
- if (mNewConfiguration != null) {
- mNewConfiguration.setInterfaces(
- mNewInterfaces.toArray(new UsbInterface[mNewInterfaces.size()]));
- mNewInterfaces.clear();
- }
-
- mNewConfiguration = new UsbConfiguration(id, name, attributes, maxPower);
- mNewConfigurations.add(mNewConfiguration);
- }
-
- /* Called from JNI in monitorUsbHostBus() to report new USB interface for the device
- currently being added. Returns true if successful, false in case of error.
- */
- @SuppressWarnings("unused")
- private void addUsbInterface(int id, String name, int altSetting,
- int Class, int subClass, int protocol) {
- if (mNewInterface != null) {
- mNewInterface.setEndpoints(
- mNewEndpoints.toArray(new UsbEndpoint[mNewEndpoints.size()]));
- mNewEndpoints.clear();
- }
-
- mNewInterface = new UsbInterface(id, altSetting, name, Class, subClass, protocol);
- mNewInterfaces.add(mNewInterface);
- }
-
- /* Called from JNI in monitorUsbHostBus() to report new USB endpoint for the device
- currently being added. Returns true if successful, false in case of error.
- */
- @SuppressWarnings("unused")
- private void addUsbEndpoint(int address, int attributes, int maxPacketSize, int interval) {
- mNewEndpoints.add(new UsbEndpoint(address, attributes, maxPacketSize, interval));
- }
-
- /* Called from JNI in monitorUsbHostBus() to finish adding a new device */
- @SuppressWarnings("unused")
- private void endUsbDeviceAdded() {
- if (DEBUG) {
- Slog.d(TAG, "usb:UsbHostManager.endUsbDeviceAdded()");
- }
- if (mNewInterface != null) {
- mNewInterface.setEndpoints(
- mNewEndpoints.toArray(new UsbEndpoint[mNewEndpoints.size()]));
- }
- if (mNewConfiguration != null) {
- mNewConfiguration.setInterfaces(
- mNewInterfaces.toArray(new UsbInterface[mNewInterfaces.size()]));
- }
-
-
- synchronized (mLock) {
- if (mNewDevice != null) {
- mNewDevice.setConfigurations(
- mNewConfigurations.toArray(
- new UsbConfiguration[mNewConfigurations.size()]));
- mDevices.put(mNewDevice.getDeviceName(), mNewDevice);
- Slog.d(TAG, "Added device " + mNewDevice);
+ UsbDevice newDevice = parser.toAndroidUsbDevice();
+ mDevices.put(deviceAddress, newDevice);
// It is fine to call this only for the current user as all broadcasts are sent to
// all profiles of the user and the dialogs should only show once.
ComponentName usbDeviceConnectionHandler = getUsbDeviceConnectionHandler();
if (usbDeviceConnectionHandler == null) {
- getCurrentUserSettings().deviceAttached(mNewDevice);
+ getCurrentUserSettings().deviceAttached(newDevice);
} else {
- getCurrentUserSettings().deviceAttachedForFixedHandler(mNewDevice,
+ getCurrentUserSettings().deviceAttachedForFixedHandler(newDevice,
usbDeviceConnectionHandler);
}
- // deviceName is something like: "/dev/bus/usb/001/001"
- UsbDescriptorParser parser = new UsbDescriptorParser();
- boolean isInputHeadset = false;
- boolean isOutputHeadset = false;
- if (parser.parseDevice(mNewDevice.getDeviceName())) {
- isInputHeadset = parser.isInputHeadset();
- isOutputHeadset = parser.isOutputHeadset();
- Slog.i(TAG, "---- isHeadset[in: " + isInputHeadset
- + " , out: " + isOutputHeadset + "]");
- }
- mUsbAlsaManager.usbDeviceAdded(mNewDevice,
- isInputHeadset, isOutputHeadset);
+
+ // Headset?
+ boolean isInputHeadset = parser.isInputHeadset();
+ boolean isOutputHeadset = parser.isOutputHeadset();
+ Slog.i(TAG, "---- isHeadset[in: " + isInputHeadset
+ + " , out: " + isOutputHeadset + "]");
+
+ mUsbAlsaManager.usbDeviceAdded(newDevice, isInputHeadset, isOutputHeadset);
} else {
- Slog.e(TAG, "mNewDevice is null in endUsbDeviceAdded");
+ Slog.e(TAG, "Error parsing USB device descriptors for " + deviceAddress);
+ return false;
}
- mNewDevice = null;
- mNewConfigurations = null;
- mNewInterfaces = null;
- mNewEndpoints = null;
- mNewConfiguration = null;
- mNewInterface = null;
}
+
+ if (DEBUG) {
+ Slog.d(TAG, "beginUsbDeviceAdded(" + deviceAddress + ") end");
+ }
+
+ return true;
}
/* Called from JNI in monitorUsbHostBus to report USB device removal */
@SuppressWarnings("unused")
- private void usbDeviceRemoved(String deviceName) {
+ private void usbDeviceRemoved(String deviceAddress) {
synchronized (mLock) {
- UsbDevice device = mDevices.remove(deviceName);
+ UsbDevice device = mDevices.remove(deviceAddress);
if (device != null) {
mUsbAlsaManager.usbDeviceRemoved(device);
mSettingsManager.usbDeviceRemoved(device);
@@ -323,31 +219,35 @@
}
/* Opens the specified USB device */
- public ParcelFileDescriptor openDevice(String deviceName, UsbUserSettingsManager settings) {
+ public ParcelFileDescriptor openDevice(String deviceAddress, UsbUserSettingsManager settings,
+ String packageName, int uid) {
synchronized (mLock) {
- if (isBlackListed(deviceName)) {
+ if (isBlackListed(deviceAddress)) {
throw new SecurityException("USB device is on a restricted bus");
}
- UsbDevice device = mDevices.get(deviceName);
+ UsbDevice device = mDevices.get(deviceAddress);
if (device == null) {
// if it is not in mDevices, it either does not exist or is blacklisted
throw new IllegalArgumentException(
- "device " + deviceName + " does not exist or is restricted");
+ "device " + deviceAddress + " does not exist or is restricted");
}
- settings.checkPermission(device);
- return nativeOpenDevice(deviceName);
+
+ settings.checkPermission(device, packageName, uid);
+ return nativeOpenDevice(deviceAddress);
}
}
public void dump(IndentingPrintWriter pw) {
- synchronized (mLock) {
- pw.println("USB Host State:");
- for (String name : mDevices.keySet()) {
- pw.println(" " + name + ": " + mDevices.get(name));
- }
+ pw.println("USB Host State:");
+ synchronized (mHandlerLock) {
if (mUsbDeviceConnectionHandler != null) {
pw.println("Default USB Host Connection handler: " + mUsbDeviceConnectionHandler);
}
+ }
+ synchronized (mLock) {
+ for (String name : mDevices.keySet()) {
+ pw.println(" " + name + ": " + mDevices.get(name));
+ }
Collection<UsbDevice> devices = mDevices.values();
if (devices.size() != 0) {
@@ -355,17 +255,12 @@
for (UsbDevice device : devices) {
StringBuilder stringBuilder = new StringBuilder();
- UsbDescriptorParser parser = new UsbDescriptorParser();
- if (parser.parseDevice(device.getDeviceName())) {
+ UsbDescriptorParser parser = new UsbDescriptorParser(device.getDeviceName());
+ if (parser.parseDevice()) {
UsbDescriptorsTree descriptorTree = new UsbDescriptorsTree();
descriptorTree.parse(parser);
- UsbManager usbManager =
- (UsbManager) mContext.getSystemService(Context.USB_SERVICE);
- UsbDeviceConnection connection = usbManager.openDevice(device);
-
- descriptorTree.report(new TextReportCanvas(connection, stringBuilder));
- connection.close();
+ descriptorTree.report(new TextReportCanvas(parser, stringBuilder));
stringBuilder.append("isHeadset[in: " + parser.isInputHeadset()
+ " , out: " + parser.isOutputHeadset() + "]");
@@ -381,5 +276,5 @@
}
private native void monitorUsbHostBus();
- private native ParcelFileDescriptor nativeOpenDevice(String deviceName);
+ private native ParcelFileDescriptor nativeOpenDevice(String deviceAddress);
}
diff --git a/com/android/server/usb/UsbService.java b/com/android/server/usb/UsbService.java
index e4fcea7..17de83f 100644
--- a/com/android/server/usb/UsbService.java
+++ b/com/android/server/usb/UsbService.java
@@ -232,7 +232,7 @@
/* Opens the specified USB device (host mode) */
@Override
- public ParcelFileDescriptor openDevice(String deviceName) {
+ public ParcelFileDescriptor openDevice(String deviceName, String packageName) {
ParcelFileDescriptor fd = null;
if (mHostManager != null) {
@@ -242,7 +242,8 @@
boolean isCurrentUser = isCallerInCurrentUserProfileGroupLocked();
if (isCurrentUser) {
- fd = mHostManager.openDevice(deviceName, getSettingsForUser(userIdInt));
+ fd = mHostManager.openDevice(deviceName, getSettingsForUser(userIdInt),
+ packageName, Binder.getCallingUid());
} else {
Slog.w(TAG, "Cannot open " + deviceName + " for user " + userIdInt +
" as user is not active.");
@@ -308,9 +309,10 @@
}
@Override
- public boolean hasDevicePermission(UsbDevice device) {
+ public boolean hasDevicePermission(UsbDevice device, String packageName) {
final int userId = UserHandle.getCallingUserId();
- return getSettingsForUser(userId).hasPermission(device);
+ return getSettingsForUser(userId).hasPermission(device, packageName,
+ Binder.getCallingUid());
}
@Override
@@ -322,7 +324,8 @@
@Override
public void requestDevicePermission(UsbDevice device, String packageName, PendingIntent pi) {
final int userId = UserHandle.getCallingUserId();
- getSettingsForUser(userId).requestPermission(device, packageName, pi);
+ getSettingsForUser(userId).requestPermission(device, packageName, pi,
+ Binder.getCallingUid());
}
@Override
diff --git a/com/android/server/usb/UsbUserSettingsManager.java b/com/android/server/usb/UsbUserSettingsManager.java
index 96c5211..11e43e3 100644
--- a/com/android/server/usb/UsbUserSettingsManager.java
+++ b/com/android/server/usb/UsbUserSettingsManager.java
@@ -26,6 +26,8 @@
import android.content.pm.PackageManager.NameNotFoundException;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbInterface;
+import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbManager;
import android.os.Binder;
import android.os.Process;
@@ -95,10 +97,70 @@
}
}
+ /**
+ * Check whether a particular device or any of its interfaces
+ * is of class VIDEO.
+ *
+ * @param device The device that needs to get scanned
+ * @return True in case a VIDEO device or interface is present,
+ * False otherwise.
+ */
+ private boolean isCameraDevicePresent(UsbDevice device) {
+ if (device.getDeviceClass() == UsbConstants.USB_CLASS_VIDEO) {
+ return true;
+ }
- public boolean hasPermission(UsbDevice device) {
+ for (int i = 0; i < device.getInterfaceCount(); i++) {
+ UsbInterface iface = device.getInterface(i);
+ if (iface.getInterfaceClass() == UsbConstants.USB_CLASS_VIDEO) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Check for camera permission of the calling process.
+ *
+ * @param packageName Package name of the caller.
+ * @param uid Linux uid of the calling process.
+ *
+ * @return True in case camera permission is available, False otherwise.
+ */
+ private boolean isCameraPermissionGranted(String packageName, int uid) {
+ int targetSdkVersion = android.os.Build.VERSION_CODES.P;
+ try {
+ ApplicationInfo aInfo = mPackageManager.getApplicationInfo(packageName, 0);
+ // compare uid with packageName to foil apps pretending to be someone else
+ if (aInfo.uid != uid) {
+ Slog.i(TAG, "Package " + packageName + " does not match caller's uid " + uid);
+ return false;
+ }
+ targetSdkVersion = aInfo.targetSdkVersion;
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.i(TAG, "Package not found, likely due to invalid package name!");
+ return false;
+ }
+
+ if (targetSdkVersion >= android.os.Build.VERSION_CODES.P) {
+ int allowed = mUserContext.checkCallingPermission(android.Manifest.permission.CAMERA);
+ if (android.content.pm.PackageManager.PERMISSION_DENIED == allowed) {
+ Slog.i(TAG, "Camera permission required for USB video class devices");
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public boolean hasPermission(UsbDevice device, String packageName, int uid) {
synchronized (mLock) {
- int uid = Binder.getCallingUid();
+ if (isCameraDevicePresent(device)) {
+ if (!isCameraPermissionGranted(packageName, uid)) {
+ return false;
+ }
+ }
if (uid == Process.SYSTEM_UID || mDisablePermissionDialogs) {
return true;
}
@@ -124,8 +186,8 @@
}
}
- public void checkPermission(UsbDevice device) {
- if (!hasPermission(device)) {
+ public void checkPermission(UsbDevice device, String packageName, int uid) {
+ if (!hasPermission(device, packageName, uid)) {
throw new SecurityException("User has not given permission to device " + device);
}
}
@@ -166,11 +228,11 @@
}
}
- public void requestPermission(UsbDevice device, String packageName, PendingIntent pi) {
+ public void requestPermission(UsbDevice device, String packageName, PendingIntent pi, int uid) {
Intent intent = new Intent();
// respond immediately if permission has already been granted
- if (hasPermission(device)) {
+ if (hasPermission(device, packageName, uid)) {
intent.putExtra(UsbManager.EXTRA_DEVICE, device);
intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true);
try {
@@ -180,6 +242,18 @@
}
return;
}
+ if (isCameraDevicePresent(device)) {
+ if (!isCameraPermissionGranted(packageName, uid)) {
+ intent.putExtra(UsbManager.EXTRA_DEVICE, device);
+ intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false);
+ try {
+ pi.send(mUserContext, 0, intent);
+ } catch (PendingIntent.CanceledException e) {
+ if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled");
+ }
+ return;
+ }
+ }
// start UsbPermissionActivity so user can choose an activity
intent.putExtra(UsbManager.EXTRA_DEVICE, device);
diff --git a/com/android/server/usb/descriptors/Usb10ACHeader.java b/com/android/server/usb/descriptors/Usb10ACHeader.java
index a35b463..7763150 100644
--- a/com/android/server/usb/descriptors/Usb10ACHeader.java
+++ b/com/android/server/usb/descriptors/Usb10ACHeader.java
@@ -32,7 +32,7 @@
// numbers associate with this endpoint
private byte mControls; // Vers 2.0 thing
- public Usb10ACHeader(int length, byte type, byte subtype, byte subclass, int spec) {
+ public Usb10ACHeader(int length, byte type, byte subtype, int subclass, int spec) {
super(length, type, subtype, subclass, spec);
}
diff --git a/com/android/server/usb/descriptors/Usb10ACInputTerminal.java b/com/android/server/usb/descriptors/Usb10ACInputTerminal.java
index 2363c4d..75531d1 100644
--- a/com/android/server/usb/descriptors/Usb10ACInputTerminal.java
+++ b/com/android/server/usb/descriptors/Usb10ACInputTerminal.java
@@ -32,7 +32,7 @@
private byte mChannelNames; // 10:1 Unused (0x00)
private byte mTerminal; // 11:1 Unused (0x00)
- public Usb10ACInputTerminal(int length, byte type, byte subtype, byte subclass) {
+ public Usb10ACInputTerminal(int length, byte type, byte subtype, int subclass) {
super(length, type, subtype, subclass);
}
diff --git a/com/android/server/usb/descriptors/Usb10ACMixerUnit.java b/com/android/server/usb/descriptors/Usb10ACMixerUnit.java
index d348664..c7634ba 100644
--- a/com/android/server/usb/descriptors/Usb10ACMixerUnit.java
+++ b/com/android/server/usb/descriptors/Usb10ACMixerUnit.java
@@ -30,7 +30,7 @@
private byte[] mControls; // bitmasks of which controls are present for each channel
private byte mNameID; // string descriptor ID of mixer name
- public Usb10ACMixerUnit(int length, byte type, byte subtype, byte subClass) {
+ public Usb10ACMixerUnit(int length, byte type, byte subtype, int subClass) {
super(length, type, subtype, subClass);
}
diff --git a/com/android/server/usb/descriptors/Usb10ACOutputTerminal.java b/com/android/server/usb/descriptors/Usb10ACOutputTerminal.java
index 9f2f09e..468ae57 100644
--- a/com/android/server/usb/descriptors/Usb10ACOutputTerminal.java
+++ b/com/android/server/usb/descriptors/Usb10ACOutputTerminal.java
@@ -28,7 +28,7 @@
private byte mSourceID; // 7:1 From Input Terminal. (0x01)
private byte mTerminal; // 8:1 Unused.
- public Usb10ACOutputTerminal(int length, byte type, byte subtype, byte subClass) {
+ public Usb10ACOutputTerminal(int length, byte type, byte subtype, int subClass) {
super(length, type, subtype, subClass);
}
diff --git a/com/android/server/usb/descriptors/Usb10ASFormatI.java b/com/android/server/usb/descriptors/Usb10ASFormatI.java
index 1523bb5..1d8498a 100644
--- a/com/android/server/usb/descriptors/Usb10ASFormatI.java
+++ b/com/android/server/usb/descriptors/Usb10ASFormatI.java
@@ -33,7 +33,7 @@
// min & max rates otherwise mSamFreqType rates.
// All 3-byte values. All rates in Hz
- public Usb10ASFormatI(int length, byte type, byte subtype, byte formatType, byte subclass) {
+ public Usb10ASFormatI(int length, byte type, byte subtype, byte formatType, int subclass) {
super(length, type, subtype, formatType, subclass);
}
diff --git a/com/android/server/usb/descriptors/Usb10ASFormatII.java b/com/android/server/usb/descriptors/Usb10ASFormatII.java
index b1e7680..3c45790 100644
--- a/com/android/server/usb/descriptors/Usb10ASFormatII.java
+++ b/com/android/server/usb/descriptors/Usb10ASFormatII.java
@@ -38,7 +38,7 @@
// the min & max rates. otherwise mSamFreqType rates.
// All 3-byte values. All rates in Hz
- public Usb10ASFormatII(int length, byte type, byte subtype, byte formatType, byte subclass) {
+ public Usb10ASFormatII(int length, byte type, byte subtype, byte formatType, int subclass) {
super(length, type, subtype, formatType, subclass);
}
diff --git a/com/android/server/usb/descriptors/Usb10ASGeneral.java b/com/android/server/usb/descriptors/Usb10ASGeneral.java
index 2d4f604..4fbbb21 100644
--- a/com/android/server/usb/descriptors/Usb10ASGeneral.java
+++ b/com/android/server/usb/descriptors/Usb10ASGeneral.java
@@ -34,7 +34,7 @@
private int mFormatTag; // 5:2 The Audio Data Format that has to be used to communicate
// with this interface.
- public Usb10ASGeneral(int length, byte type, byte subtype, byte subclass) {
+ public Usb10ASGeneral(int length, byte type, byte subtype, int subclass) {
super(length, type, subtype, subclass);
}
diff --git a/com/android/server/usb/descriptors/Usb20ACHeader.java b/com/android/server/usb/descriptors/Usb20ACHeader.java
index eefae3d..fe1b502 100644
--- a/com/android/server/usb/descriptors/Usb20ACHeader.java
+++ b/com/android/server/usb/descriptors/Usb20ACHeader.java
@@ -29,7 +29,7 @@
// See audio20.pdf Appendix A.7, “Audio Function Category Codes.”
private byte mControls; // 8:1 See audio20.pdf Table 4-5.
- public Usb20ACHeader(int length, byte type, byte subtype, byte subclass, int spec) {
+ public Usb20ACHeader(int length, byte type, byte subtype, int subclass, int spec) {
super(length, type, subtype, subclass, spec);
}
diff --git a/com/android/server/usb/descriptors/Usb20ACInputTerminal.java b/com/android/server/usb/descriptors/Usb20ACInputTerminal.java
index 3e2ac39..ee1b32c 100644
--- a/com/android/server/usb/descriptors/Usb20ACInputTerminal.java
+++ b/com/android/server/usb/descriptors/Usb20ACInputTerminal.java
@@ -39,7 +39,7 @@
private byte mTerminalName; // 16:1 - Index of a string descriptor, describing the
// Input Terminal.
- public Usb20ACInputTerminal(int length, byte type, byte subtype, byte subclass) {
+ public Usb20ACInputTerminal(int length, byte type, byte subtype, int subclass) {
super(length, type, subtype, subclass);
}
diff --git a/com/android/server/usb/descriptors/Usb20ACMixerUnit.java b/com/android/server/usb/descriptors/Usb20ACMixerUnit.java
index 1b267a6..ab96585 100644
--- a/com/android/server/usb/descriptors/Usb20ACMixerUnit.java
+++ b/com/android/server/usb/descriptors/Usb20ACMixerUnit.java
@@ -33,7 +33,7 @@
private byte mNameID; // 12+p+N:1 Index of a string descriptor, describing the
// Mixer Unit.
- public Usb20ACMixerUnit(int length, byte type, byte subtype, byte subClass) {
+ public Usb20ACMixerUnit(int length, byte type, byte subtype, int subClass) {
super(length, type, subtype, subClass);
}
diff --git a/com/android/server/usb/descriptors/Usb20ACOutputTerminal.java b/com/android/server/usb/descriptors/Usb20ACOutputTerminal.java
index 67478aa..20a97af 100644
--- a/com/android/server/usb/descriptors/Usb20ACOutputTerminal.java
+++ b/com/android/server/usb/descriptors/Usb20ACOutputTerminal.java
@@ -34,7 +34,7 @@
private int mControls; // 9:2 - see Audio20.pdf Table 4-10
private byte mTerminalID; // 11:1 - Index of a string descriptor, describing the
- public Usb20ACOutputTerminal(int length, byte type, byte subtype, byte subClass) {
+ public Usb20ACOutputTerminal(int length, byte type, byte subtype, int subClass) {
super(length, type, subtype, subClass);
}
diff --git a/com/android/server/usb/descriptors/Usb20ASFormatI.java b/com/android/server/usb/descriptors/Usb20ASFormatI.java
index c031996..7310a3e 100644
--- a/com/android/server/usb/descriptors/Usb20ASFormatI.java
+++ b/com/android/server/usb/descriptors/Usb20ASFormatI.java
@@ -31,7 +31,7 @@
private byte mBitResolution; // 5:1 The number of effectively used bits from
// the available bits in an audio subslot.
- public Usb20ASFormatI(int length, byte type, byte subtype, byte formatType, byte subclass) {
+ public Usb20ASFormatI(int length, byte type, byte subtype, byte formatType, int subclass) {
super(length, type, subtype, formatType, subclass);
}
diff --git a/com/android/server/usb/descriptors/Usb20ASFormatII.java b/com/android/server/usb/descriptors/Usb20ASFormatII.java
index dc44ff0..fe88743 100644
--- a/com/android/server/usb/descriptors/Usb20ASFormatII.java
+++ b/com/android/server/usb/descriptors/Usb20ASFormatII.java
@@ -34,7 +34,7 @@
/**
* TBD
*/
- public Usb20ASFormatII(int length, byte type, byte subtype, byte formatType, byte subclass) {
+ public Usb20ASFormatII(int length, byte type, byte subtype, byte formatType, int subclass) {
super(length, type, subtype, formatType, subclass);
}
diff --git a/com/android/server/usb/descriptors/Usb20ASFormatIII.java b/com/android/server/usb/descriptors/Usb20ASFormatIII.java
index b44a216..b0ba02f 100644
--- a/com/android/server/usb/descriptors/Usb20ASFormatIII.java
+++ b/com/android/server/usb/descriptors/Usb20ASFormatIII.java
@@ -31,7 +31,7 @@
private byte mBitResolution; // 5:1 The number of effectively used bits from
// the available bits in an audio subframe.
- public Usb20ASFormatIII(int length, byte type, byte subtype, byte formatType, byte subclass) {
+ public Usb20ASFormatIII(int length, byte type, byte subtype, byte formatType, int subclass) {
super(length, type, subtype, formatType, subclass);
}
diff --git a/com/android/server/usb/descriptors/Usb20ASGeneral.java b/com/android/server/usb/descriptors/Usb20ASGeneral.java
index 18d48a0..de20738 100644
--- a/com/android/server/usb/descriptors/Usb20ASGeneral.java
+++ b/com/android/server/usb/descriptors/Usb20ASGeneral.java
@@ -41,7 +41,7 @@
private byte mChannelNames; // 15:1 Index of a string descriptor, describing the
// name of the first physical channel.
- public Usb20ASGeneral(int length, byte type, byte subtype, byte subclass) {
+ public Usb20ASGeneral(int length, byte type, byte subtype, int subclass) {
super(length, type, subtype, subclass);
}
diff --git a/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java b/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java
index 6e1ce07..409e605 100644
--- a/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java
+++ b/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java
@@ -38,7 +38,7 @@
static final byte ATTRIBSMASK_SYNC = 0x0C;
static final byte ATTRIBMASK_TRANS = 0x03;
- public UsbACAudioControlEndpoint(int length, byte type, byte subclass) {
+ public UsbACAudioControlEndpoint(int length, byte type, int subclass) {
super(length, type, subclass);
}
diff --git a/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java b/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java
index d351902..e63bb74 100644
--- a/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java
+++ b/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java
@@ -24,7 +24,7 @@
private static final String TAG = "UsbACAudioStreamEndpoint";
//TODO data fields...
- public UsbACAudioStreamEndpoint(int length, byte type, byte subclass) {
+ public UsbACAudioStreamEndpoint(int length, byte type, int subclass) {
super(length, type, subclass);
}
diff --git a/com/android/server/usb/descriptors/UsbACEndpoint.java b/com/android/server/usb/descriptors/UsbACEndpoint.java
index 4a6839d..7ebccf3 100644
--- a/com/android/server/usb/descriptors/UsbACEndpoint.java
+++ b/com/android/server/usb/descriptors/UsbACEndpoint.java
@@ -25,16 +25,16 @@
abstract class UsbACEndpoint extends UsbDescriptor {
private static final String TAG = "UsbACEndpoint";
- protected final byte mSubclass; // from the mSubclass member of the "enclosing"
- // Interface Descriptor, not the stream.
- protected byte mSubtype; // 2:1 HEADER descriptor subtype
+ protected final int mSubclass; // from the mSubclass member of the "enclosing"
+ // Interface Descriptor, not the stream.
+ protected byte mSubtype; // 2:1 HEADER descriptor subtype
- UsbACEndpoint(int length, byte type, byte subclass) {
+ UsbACEndpoint(int length, byte type, int subclass) {
super(length, type);
mSubclass = subclass;
}
- public byte getSubclass() {
+ public int getSubclass() {
return mSubclass;
}
@@ -52,7 +52,7 @@
public static UsbDescriptor allocDescriptor(UsbDescriptorParser parser,
int length, byte type) {
UsbInterfaceDescriptor interfaceDesc = parser.getCurInterface();
- byte subClass = interfaceDesc.getUsbSubclass();
+ int subClass = interfaceDesc.getUsbSubclass();
switch (subClass) {
case AUDIO_AUDIOCONTROL:
return new UsbACAudioControlEndpoint(length, type, subClass);
diff --git a/com/android/server/usb/descriptors/UsbACFeatureUnit.java b/com/android/server/usb/descriptors/UsbACFeatureUnit.java
index ab3903b..2c7ef79 100644
--- a/com/android/server/usb/descriptors/UsbACFeatureUnit.java
+++ b/com/android/server/usb/descriptors/UsbACFeatureUnit.java
@@ -46,7 +46,7 @@
// logical channel
private byte mUnitName; // ?:1 Index of a string descriptor, describing this Feature Unit.
- public UsbACFeatureUnit(int length, byte type, byte subtype, byte subClass) {
+ public UsbACFeatureUnit(int length, byte type, byte subtype, int subClass) {
super(length, type, subtype, subClass);
}
diff --git a/com/android/server/usb/descriptors/UsbACHeaderInterface.java b/com/android/server/usb/descriptors/UsbACHeaderInterface.java
index 01a355e..88d026e 100644
--- a/com/android/server/usb/descriptors/UsbACHeaderInterface.java
+++ b/com/android/server/usb/descriptors/UsbACHeaderInterface.java
@@ -31,7 +31,7 @@
// of this descriptor header and all Unit and Terminal descriptors.
public UsbACHeaderInterface(
- int length, byte type, byte subtype, byte subclass, int adcRelease) {
+ int length, byte type, byte subtype, int subclass, int adcRelease) {
super(length, type, subtype, subclass);
mADCRelease = adcRelease;
}
diff --git a/com/android/server/usb/descriptors/UsbACInterface.java b/com/android/server/usb/descriptors/UsbACInterface.java
index df6c53f..38c12a1 100644
--- a/com/android/server/usb/descriptors/UsbACInterface.java
+++ b/com/android/server/usb/descriptors/UsbACInterface.java
@@ -78,10 +78,10 @@
public static final int FORMAT_III_IEC1937_MPEG2_Layer1LS = 0x2005;
protected final byte mSubtype; // 2:1 HEADER descriptor subtype
- protected final byte mSubclass; // from the mSubclass member of the
+ protected final int mSubclass; // from the mSubclass member of the
// "enclosing" Interface Descriptor
- public UsbACInterface(int length, byte type, byte subtype, byte subclass) {
+ public UsbACInterface(int length, byte type, byte subtype, int subclass) {
super(length, type);
mSubtype = subtype;
mSubclass = subclass;
@@ -91,12 +91,12 @@
return mSubtype;
}
- public byte getSubclass() {
+ public int getSubclass() {
return mSubclass;
}
private static UsbDescriptor allocAudioControlDescriptor(UsbDescriptorParser parser,
- ByteStream stream, int length, byte type, byte subtype, byte subClass) {
+ ByteStream stream, int length, byte type, byte subtype, int subClass) {
switch (subtype) {
case ACI_HEADER:
{
@@ -157,7 +157,7 @@
}
private static UsbDescriptor allocAudioStreamingDescriptor(UsbDescriptorParser parser,
- ByteStream stream, int length, byte type, byte subtype, byte subClass) {
+ ByteStream stream, int length, byte type, byte subtype, int subClass) {
//int spec = parser.getUsbSpec();
int acInterfaceSpec = parser.getACInterfaceSpec();
switch (subtype) {
@@ -182,7 +182,7 @@
}
private static UsbDescriptor allocMidiStreamingDescriptor(int length, byte type,
- byte subtype, byte subClass) {
+ byte subtype, int subClass) {
switch (subtype) {
case MSI_HEADER:
return new UsbMSMidiHeader(length, type, subtype, subClass);
@@ -212,7 +212,7 @@
int length, byte type) {
byte subtype = stream.getByte();
UsbInterfaceDescriptor interfaceDesc = parser.getCurInterface();
- byte subClass = interfaceDesc.getUsbSubclass();
+ int subClass = interfaceDesc.getUsbSubclass();
switch (subClass) {
case AUDIO_AUDIOCONTROL:
return allocAudioControlDescriptor(
@@ -236,7 +236,7 @@
public void report(ReportCanvas canvas) {
super.report(canvas);
- byte subClass = getSubclass();
+ int subClass = getSubclass();
String subClassName = UsbStrings.getACInterfaceSubclassName(subClass);
byte subtype = getSubtype();
diff --git a/com/android/server/usb/descriptors/UsbACInterfaceUnparsed.java b/com/android/server/usb/descriptors/UsbACInterfaceUnparsed.java
index 9e00a79..bd027ae 100644
--- a/com/android/server/usb/descriptors/UsbACInterfaceUnparsed.java
+++ b/com/android/server/usb/descriptors/UsbACInterfaceUnparsed.java
@@ -22,7 +22,7 @@
public final class UsbACInterfaceUnparsed extends UsbACInterface {
private static final String TAG = "UsbACInterfaceUnparsed";
- public UsbACInterfaceUnparsed(int length, byte type, byte subtype, byte subClass) {
+ public UsbACInterfaceUnparsed(int length, byte type, byte subtype, int subClass) {
super(length, type, subtype, subClass);
}
}
diff --git a/com/android/server/usb/descriptors/UsbACMidiEndpoint.java b/com/android/server/usb/descriptors/UsbACMidiEndpoint.java
index 9c31457..42ee889 100644
--- a/com/android/server/usb/descriptors/UsbACMidiEndpoint.java
+++ b/com/android/server/usb/descriptors/UsbACMidiEndpoint.java
@@ -28,7 +28,7 @@
private byte mNumJacks;
private byte[] mJackIds;
- public UsbACMidiEndpoint(int length, byte type, byte subclass) {
+ public UsbACMidiEndpoint(int length, byte type, int subclass) {
super(length, type, subclass);
}
diff --git a/com/android/server/usb/descriptors/UsbACMixerUnit.java b/com/android/server/usb/descriptors/UsbACMixerUnit.java
index 88faed9..606fa2b 100644
--- a/com/android/server/usb/descriptors/UsbACMixerUnit.java
+++ b/com/android/server/usb/descriptors/UsbACMixerUnit.java
@@ -24,7 +24,7 @@
// are connected.
protected byte mNumOutputs; // The number of output channels
- public UsbACMixerUnit(int length, byte type, byte subtype, byte subClass) {
+ public UsbACMixerUnit(int length, byte type, byte subtype, int subClass) {
super(length, type, subtype, subClass);
}
diff --git a/com/android/server/usb/descriptors/UsbACSelectorUnit.java b/com/android/server/usb/descriptors/UsbACSelectorUnit.java
index b16bc57..4644fe1 100644
--- a/com/android/server/usb/descriptors/UsbACSelectorUnit.java
+++ b/com/android/server/usb/descriptors/UsbACSelectorUnit.java
@@ -32,7 +32,7 @@
// Input Pin of this Selector Unit is connected.
private byte mNameIndex; // Index of a string descriptor, describing the Selector Unit.
- public UsbACSelectorUnit(int length, byte type, byte subtype, byte subClass) {
+ public UsbACSelectorUnit(int length, byte type, byte subtype, int subClass) {
super(length, type, subtype, subClass);
}
diff --git a/com/android/server/usb/descriptors/UsbACTerminal.java b/com/android/server/usb/descriptors/UsbACTerminal.java
index 2836508..36139d6 100644
--- a/com/android/server/usb/descriptors/UsbACTerminal.java
+++ b/com/android/server/usb/descriptors/UsbACTerminal.java
@@ -32,7 +32,7 @@
protected int mTerminalType; // 4:2 USB Streaming. (0x0101)
protected byte mAssocTerminal; // 6:1 Unused (0x00)
- public UsbACTerminal(int length, byte type, byte subtype, byte subclass) {
+ public UsbACTerminal(int length, byte type, byte subtype, int subclass) {
super(length, type, subtype, subclass);
}
diff --git a/com/android/server/usb/descriptors/UsbASFormat.java b/com/android/server/usb/descriptors/UsbASFormat.java
index 7a92f9e..5e515a1 100644
--- a/com/android/server/usb/descriptors/UsbASFormat.java
+++ b/com/android/server/usb/descriptors/UsbASFormat.java
@@ -40,7 +40,7 @@
public static final byte EXT_FORMAT_TYPE_II = (byte) 0x82;
public static final byte EXT_FORMAT_TYPE_III = (byte) 0x83;
- public UsbASFormat(int length, byte type, byte subtype, byte formatType, byte mSubclass) {
+ public UsbASFormat(int length, byte type, byte subtype, byte formatType, int mSubclass) {
super(length, type, subtype, mSubclass);
mFormatType = formatType;
}
@@ -66,8 +66,8 @@
* stream.
*/
public static UsbDescriptor allocDescriptor(UsbDescriptorParser parser,
- ByteStream stream, int length, byte type,
- byte subtype, byte subclass) {
+ ByteStream stream, int length, byte type,
+ byte subtype, int subclass) {
byte formatType = stream.getByte();
int acInterfaceSpec = parser.getACInterfaceSpec();
diff --git a/com/android/server/usb/descriptors/UsbConfigDescriptor.java b/com/android/server/usb/descriptors/UsbConfigDescriptor.java
index 75279c6..993778f 100644
--- a/com/android/server/usb/descriptors/UsbConfigDescriptor.java
+++ b/com/android/server/usb/descriptors/UsbConfigDescriptor.java
@@ -15,8 +15,13 @@
*/
package com.android.server.usb.descriptors;
+import android.hardware.usb.UsbConfiguration;
+import android.hardware.usb.UsbInterface;
+
import com.android.server.usb.descriptors.report.ReportCanvas;
+import java.util.ArrayList;
+
/**
* @hide
* An USB Config Descriptor.
@@ -25,15 +30,18 @@
public final class UsbConfigDescriptor extends UsbDescriptor {
private static final String TAG = "UsbConfigDescriptor";
- private int mTotalLength; // 2:2 Total length in bytes of data returned
+ private int mTotalLength; // 2:2 Total length in bytes of data returned
private byte mNumInterfaces; // 4:1 Number of Interfaces
- private byte mConfigValue; // 5:1 Value to use as an argument to select this configuration
- private byte mConfigIndex; // 6:1 Index of String Descriptor describing this configuration
- private byte mAttribs; // 7:1 D7 Reserved, set to 1. (USB 1.0 Bus Powered)
- // D6 Self Powered
- // D5 Remote Wakeup
- // D4..0 Reserved, set to 0.
- private byte mMaxPower; // 8:1 Maximum Power Consumption in 2mA units
+ private int mConfigValue; // 5:1 Value to use as an argument to select this configuration
+ private byte mConfigIndex; // 6:1 Index of String Descriptor describing this configuration
+ private int mAttribs; // 7:1 D7 Reserved, set to 1. (USB 1.0 Bus Powered)
+ // D6 Self Powered
+ // D5 Remote Wakeup
+ // D4..0 Reserved, set to 0.
+ private int mMaxPower; // 8:1 Maximum Power Consumption in 2mA units
+
+ private ArrayList<UsbInterfaceDescriptor> mInterfaceDescriptors =
+ new ArrayList<UsbInterfaceDescriptor>();
UsbConfigDescriptor(int length, byte type) {
super(length, type);
@@ -48,7 +56,7 @@
return mNumInterfaces;
}
- public byte getConfigValue() {
+ public int getConfigValue() {
return mConfigValue;
}
@@ -56,22 +64,38 @@
return mConfigIndex;
}
- public byte getAttribs() {
+ public int getAttribs() {
return mAttribs;
}
- public byte getMaxPower() {
+ public int getMaxPower() {
return mMaxPower;
}
+ void addInterfaceDescriptor(UsbInterfaceDescriptor interfaceDesc) {
+ mInterfaceDescriptors.add(interfaceDesc);
+ }
+
+ UsbConfiguration toAndroid(UsbDescriptorParser parser) {
+ String name = parser.getDescriptorString(mConfigIndex);
+ UsbConfiguration config = new
+ UsbConfiguration(mConfigValue, name, mAttribs, mMaxPower);
+ UsbInterface[] interfaces = new UsbInterface[mInterfaceDescriptors.size()];
+ for (int index = 0; index < mInterfaceDescriptors.size(); index++) {
+ interfaces[index] = mInterfaceDescriptors.get(index).toAndroid(parser);
+ }
+ config.setInterfaces(interfaces);
+ return config;
+ }
+
@Override
public int parseRawDescriptors(ByteStream stream) {
mTotalLength = stream.unpackUsbShort();
mNumInterfaces = stream.getByte();
- mConfigValue = stream.getByte();
+ mConfigValue = stream.getUnsignedByte();
mConfigIndex = stream.getByte();
- mAttribs = stream.getByte();
- mMaxPower = stream.getByte();
+ mAttribs = stream.getUnsignedByte();
+ mMaxPower = stream.getUnsignedByte();
return mLength;
}
diff --git a/com/android/server/usb/descriptors/UsbDescriptor.java b/com/android/server/usb/descriptors/UsbDescriptor.java
index 8c7565b..3fc5fe3 100644
--- a/com/android/server/usb/descriptors/UsbDescriptor.java
+++ b/com/android/server/usb/descriptors/UsbDescriptor.java
@@ -85,36 +85,36 @@
public static final byte DESCRIPTORTYPE_ENDPOINT_COMPANION = 0x30; // 48
// Class IDs
- public static final byte CLASSID_DEVICE = 0x00;
- public static final byte CLASSID_AUDIO = 0x01;
- public static final byte CLASSID_COM = 0x02;
- public static final byte CLASSID_HID = 0x03;
- // public static final byte CLASSID_??? = 0x04;
- public static final byte CLASSID_PHYSICAL = 0x05;
- public static final byte CLASSID_IMAGE = 0x06;
- public static final byte CLASSID_PRINTER = 0x07;
- public static final byte CLASSID_STORAGE = 0x08;
- public static final byte CLASSID_HUB = 0x09;
- public static final byte CLASSID_CDC_CONTROL = 0x0A;
- public static final byte CLASSID_SMART_CARD = 0x0B;
- //public static final byte CLASSID_??? = 0x0C;
- public static final byte CLASSID_SECURITY = 0x0D;
- public static final byte CLASSID_VIDEO = 0x0E;
- public static final byte CLASSID_HEALTHCARE = 0x0F;
- public static final byte CLASSID_AUDIOVIDEO = 0x10;
- public static final byte CLASSID_BILLBOARD = 0x11;
- public static final byte CLASSID_TYPECBRIDGE = 0x12;
- public static final byte CLASSID_DIAGNOSTIC = (byte) 0xDC;
- public static final byte CLASSID_WIRELESS = (byte) 0xE0;
- public static final byte CLASSID_MISC = (byte) 0xEF;
- public static final byte CLASSID_APPSPECIFIC = (byte) 0xFE;
- public static final byte CLASSID_VENDSPECIFIC = (byte) 0xFF;
+ public static final int CLASSID_DEVICE = 0x00;
+ public static final int CLASSID_AUDIO = 0x01;
+ public static final int CLASSID_COM = 0x02;
+ public static final int CLASSID_HID = 0x03;
+ // public static final int CLASSID_??? = 0x04;
+ public static final int CLASSID_PHYSICAL = 0x05;
+ public static final int CLASSID_IMAGE = 0x06;
+ public static final int CLASSID_PRINTER = 0x07;
+ public static final int CLASSID_STORAGE = 0x08;
+ public static final int CLASSID_HUB = 0x09;
+ public static final int CLASSID_CDC_CONTROL = 0x0A;
+ public static final int CLASSID_SMART_CARD = 0x0B;
+ //public static final int CLASSID_??? = 0x0C;
+ public static final int CLASSID_SECURITY = 0x0D;
+ public static final int CLASSID_VIDEO = 0x0E;
+ public static final int CLASSID_HEALTHCARE = 0x0F;
+ public static final int CLASSID_AUDIOVIDEO = 0x10;
+ public static final int CLASSID_BILLBOARD = 0x11;
+ public static final int CLASSID_TYPECBRIDGE = 0x12;
+ public static final int CLASSID_DIAGNOSTIC = 0xDC;
+ public static final int CLASSID_WIRELESS = 0xE0;
+ public static final int CLASSID_MISC = 0xEF;
+ public static final int CLASSID_APPSPECIFIC = 0xFE;
+ public static final int CLASSID_VENDSPECIFIC = 0xFF;
// Audio Subclass codes
- public static final byte AUDIO_SUBCLASS_UNDEFINED = 0x00;
- public static final byte AUDIO_AUDIOCONTROL = 0x01;
- public static final byte AUDIO_AUDIOSTREAMING = 0x02;
- public static final byte AUDIO_MIDISTREAMING = 0x03;
+ public static final int AUDIO_SUBCLASS_UNDEFINED = 0x00;
+ public static final int AUDIO_AUDIOCONTROL = 0x01;
+ public static final int AUDIO_AUDIOSTREAMING = 0x02;
+ public static final int AUDIO_MIDISTREAMING = 0x03;
// Request IDs
public static final int REQUEST_GET_STATUS = 0x00;
diff --git a/com/android/server/usb/descriptors/UsbDescriptorParser.java b/com/android/server/usb/descriptors/UsbDescriptorParser.java
index ad7bde5..6c6bd01 100644
--- a/com/android/server/usb/descriptors/UsbDescriptorParser.java
+++ b/com/android/server/usb/descriptors/UsbDescriptorParser.java
@@ -15,6 +15,7 @@
*/
package com.android.server.usb.descriptors;
+import android.hardware.usb.UsbDevice;
import android.util.Log;
import java.util.ArrayList;
@@ -25,11 +26,16 @@
*/
public final class UsbDescriptorParser {
private static final String TAG = "UsbDescriptorParser";
+ private static final boolean DEBUG = false;
+
+ private final String mDeviceAddr;
// Descriptor Objects
+ private static final int DESCRIPTORS_ALLOC_SIZE = 128;
private ArrayList<UsbDescriptor> mDescriptors = new ArrayList<UsbDescriptor>();
private UsbDeviceDescriptor mDeviceDescriptor;
+ private UsbConfigDescriptor mCurConfigDescriptor;
private UsbInterfaceDescriptor mCurInterfaceDescriptor;
// The AudioClass spec implemented by the AudioClass Interfaces
@@ -37,7 +43,13 @@
// Obtained from the first AudioClass Header descriptor.
private int mACInterfacesSpec = UsbDeviceDescriptor.USBSPEC_1_0;
- public UsbDescriptorParser() {}
+ public UsbDescriptorParser(String deviceAddr) {
+ mDeviceAddr = deviceAddr;
+ }
+
+ public String getDeviceAddr() {
+ return mDeviceAddr;
+ }
/**
* @return the USB Spec value associated with the Device descriptor for the
@@ -60,6 +72,18 @@
public int getACInterfaceSpec() {
return mACInterfacesSpec;
}
+
+ private class UsbDescriptorsStreamFormatException extends Exception {
+ String mMessage;
+ UsbDescriptorsStreamFormatException(String message) {
+ mMessage = message;
+ }
+
+ public String toString() {
+ return "Descriptor Stream Format Exception: " + mMessage;
+ }
+ }
+
/**
* The probability (as returned by getHeadsetProbability() at which we conclude
* the peripheral is a headset.
@@ -67,7 +91,8 @@
private static final float IN_HEADSET_TRIGGER = 0.75f;
private static final float OUT_HEADSET_TRIGGER = 0.75f;
- private UsbDescriptor allocDescriptor(ByteStream stream) {
+ private UsbDescriptor allocDescriptor(ByteStream stream)
+ throws UsbDescriptorsStreamFormatException {
stream.resetReadCount();
int length = stream.getUnsignedByte();
@@ -83,15 +108,38 @@
break;
case UsbDescriptor.DESCRIPTORTYPE_CONFIG:
- descriptor = new UsbConfigDescriptor(length, type);
+ descriptor = mCurConfigDescriptor = new UsbConfigDescriptor(length, type);
+ if (mDeviceDescriptor != null) {
+ mDeviceDescriptor.addConfigDescriptor(mCurConfigDescriptor);
+ } else {
+ Log.e(TAG, "Config Descriptor found with no associated Device Descriptor!");
+ throw new UsbDescriptorsStreamFormatException(
+ "Config Descriptor found with no associated Device Descriptor!");
+ }
break;
case UsbDescriptor.DESCRIPTORTYPE_INTERFACE:
descriptor = mCurInterfaceDescriptor = new UsbInterfaceDescriptor(length, type);
+ if (mCurConfigDescriptor != null) {
+ mCurConfigDescriptor.addInterfaceDescriptor(mCurInterfaceDescriptor);
+ } else {
+ Log.e(TAG, "Interface Descriptor found with no associated Config Descriptor!");
+ throw new UsbDescriptorsStreamFormatException(
+ "Interface Descriptor found with no associated Config Descriptor!");
+ }
break;
case UsbDescriptor.DESCRIPTORTYPE_ENDPOINT:
descriptor = new UsbEndpointDescriptor(length, type);
+ if (mCurInterfaceDescriptor != null) {
+ mCurInterfaceDescriptor.addEndpointDescriptor(
+ (UsbEndpointDescriptor) descriptor);
+ } else {
+ Log.e(TAG,
+ "Endpoint Descriptor found with no associated Interface Descriptor!");
+ throw new UsbDescriptorsStreamFormatException(
+ "Endpoint Descriptor found with no associated Interface Descriptor!");
+ }
break;
/*
@@ -144,8 +192,12 @@
/**
* @hide
*/
- public void parseDescriptors(byte[] descriptors) {
- mDescriptors.clear();
+ public boolean parseDescriptors(byte[] descriptors) {
+ if (DEBUG) {
+ Log.d(TAG, "parseDescriptors() - start");
+ }
+ // This will allow us to (probably) alloc mDescriptors just once.
+ mDescriptors = new ArrayList<UsbDescriptor>(DESCRIPTORS_ALLOC_SIZE);
ByteStream stream = new ByteStream(descriptors);
while (stream.available() > 0) {
@@ -173,21 +225,36 @@
}
}
}
+ if (DEBUG) {
+ Log.d(TAG, "parseDescriptors() - end " + mDescriptors.size() + " descriptors.");
+ }
+ return true;
}
/**
* @hide
*/
- public boolean parseDevice(String deviceAddr) {
- byte[] rawDescriptors = getRawDescriptors(deviceAddr);
- if (rawDescriptors != null) {
- parseDescriptors(rawDescriptors);
- return true;
- }
- return false;
+ public boolean parseDevice() {
+ byte[] rawDescriptors = getRawDescriptors();
+
+ return rawDescriptors != null
+ ? parseDescriptors(rawDescriptors) : false;
}
- private native byte[] getRawDescriptors(String deviceAddr);
+ private byte[] getRawDescriptors() {
+ return getRawDescriptors_native(mDeviceAddr);
+ }
+
+ private native byte[] getRawDescriptors_native(String deviceAddr);
+
+ /**
+ * @hide
+ */
+ public String getDescriptorString(int stringId) {
+ return getDescriptorString_native(mDeviceAddr, stringId);
+ }
+
+ private native String getDescriptorString_native(String deviceAddr, int stringId);
public int getParsingSpec() {
return mDeviceDescriptor != null ? mDeviceDescriptor.getSpec() : 0;
@@ -200,6 +267,17 @@
/**
* @hide
*/
+ public UsbDevice toAndroidUsbDevice() {
+ if (mDeviceDescriptor == null) {
+ return null;
+ }
+
+ return mDeviceDescriptor.toAndroid(this);
+ }
+
+ /**
+ * @hide
+ */
public ArrayList<UsbDescriptor> getDescriptors(byte type) {
ArrayList<UsbDescriptor> list = new ArrayList<UsbDescriptor>();
for (UsbDescriptor descriptor : mDescriptors) {
@@ -213,7 +291,7 @@
/**
* @hide
*/
- public ArrayList<UsbDescriptor> getInterfaceDescriptorsForClass(byte usbClass) {
+ public ArrayList<UsbDescriptor> getInterfaceDescriptorsForClass(int usbClass) {
ArrayList<UsbDescriptor> list = new ArrayList<UsbDescriptor>();
for (UsbDescriptor descriptor : mDescriptors) {
// ensure that this isn't an unrecognized DESCRIPTORTYPE_INTERFACE
@@ -235,7 +313,7 @@
/**
* @hide
*/
- public ArrayList<UsbDescriptor> getACInterfaceDescriptors(byte subtype, byte subclass) {
+ public ArrayList<UsbDescriptor> getACInterfaceDescriptors(byte subtype, int subclass) {
ArrayList<UsbDescriptor> list = new ArrayList<UsbDescriptor>();
for (UsbDescriptor descriptor : mDescriptors) {
if (descriptor.getType() == UsbDescriptor.DESCRIPTORTYPE_AUDIO_INTERFACE) {
@@ -355,8 +433,6 @@
* to count on the peripheral being a headset.
*/
public boolean isInputHeadset() {
- // TEMP
- Log.i(TAG, "---- isInputHeadset() prob:" + (getInputHeadsetProbability() * 100f) + "%");
return getInputHeadsetProbability() >= IN_HEADSET_TRIGGER;
}
@@ -410,8 +486,6 @@
* to count on the peripheral being a headset.
*/
public boolean isOutputHeadset() {
- // TEMP
- Log.i(TAG, "---- isOutputHeadset() prob:" + (getOutputHeadsetProbability() * 100f) + "%");
return getOutputHeadsetProbability() >= OUT_HEADSET_TRIGGER;
}
diff --git a/com/android/server/usb/descriptors/UsbDeviceDescriptor.java b/com/android/server/usb/descriptors/UsbDeviceDescriptor.java
index d5cb89e..8e7f0fd 100644
--- a/com/android/server/usb/descriptors/UsbDeviceDescriptor.java
+++ b/com/android/server/usb/descriptors/UsbDeviceDescriptor.java
@@ -15,9 +15,14 @@
*/
package com.android.server.usb.descriptors;
+import android.hardware.usb.UsbConfiguration;
+import android.hardware.usb.UsbDevice;
+
import com.android.server.usb.descriptors.report.ReportCanvas;
import com.android.server.usb.descriptors.report.UsbStrings;
+import java.util.ArrayList;
+
/**
* @hide
* A USB Device Descriptor.
@@ -31,9 +36,9 @@
public static final int USBSPEC_2_0 = 0x0200;
private int mSpec; // 2:2 bcdUSB 2 BCD USB Specification Number - BCD
- private byte mDevClass; // 4:1 class code
- private byte mDevSubClass; // 5:1 subclass code
- private byte mProtocol; // 6:1 protocol
+ private int mDevClass; // 4:1 class code
+ private int mDevSubClass; // 5:1 subclass code
+ private int mProtocol; // 6:1 protocol
private byte mPacketSize; // 7:1 Maximum Packet Size for Zero Endpoint.
// Valid Sizes are 8, 16, 32, 64
private int mVendorID; // 8:2 vendor ID
@@ -44,6 +49,9 @@
private byte mSerialNum; // 16:1 Index of Serial Number String Descriptor
private byte mNumConfigs; // 17:1 Number of Possible Configurations
+ private ArrayList<UsbConfigDescriptor> mConfigDescriptors =
+ new ArrayList<UsbConfigDescriptor>();
+
UsbDeviceDescriptor(int length, byte type) {
super(length, type);
mHierarchyLevel = 1;
@@ -53,15 +61,15 @@
return mSpec;
}
- public byte getDevClass() {
+ public int getDevClass() {
return mDevClass;
}
- public byte getDevSubClass() {
+ public int getDevSubClass() {
return mDevSubClass;
}
- public byte getProtocol() {
+ public int getProtocol() {
return mProtocol;
}
@@ -97,12 +105,41 @@
return mNumConfigs;
}
+ void addConfigDescriptor(UsbConfigDescriptor config) {
+ mConfigDescriptors.add(config);
+ }
+
+ /**
+ * @hide
+ */
+ public UsbDevice toAndroid(UsbDescriptorParser parser) {
+ String mfgName = parser.getDescriptorString(mMfgIndex);
+ String prodName = parser.getDescriptorString(mProductIndex);
+
+ // Create version string in "%.%" format
+ String versionString =
+ Integer.toString(mDeviceRelease >> 8) + "." + (mDeviceRelease & 0xFF);
+ String serialStr = parser.getDescriptorString(mSerialNum);
+
+ UsbDevice device = new UsbDevice(parser.getDeviceAddr(), mVendorID, mProductID,
+ mDevClass, mDevSubClass,
+ mProtocol, mfgName, prodName,
+ versionString, serialStr);
+ UsbConfiguration[] configs = new UsbConfiguration[mConfigDescriptors.size()];
+ for (int index = 0; index < mConfigDescriptors.size(); index++) {
+ configs[index] = mConfigDescriptors.get(index).toAndroid(parser);
+ }
+ device.setConfigurations(configs);
+
+ return device;
+ }
+
@Override
public int parseRawDescriptors(ByteStream stream) {
mSpec = stream.unpackUsbShort();
- mDevClass = stream.getByte();
- mDevSubClass = stream.getByte();
- mProtocol = stream.getByte();
+ mDevClass = stream.getUnsignedByte();
+ mDevSubClass = stream.getUnsignedByte();
+ mProtocol = stream.getUnsignedByte();
mPacketSize = stream.getByte();
mVendorID = stream.unpackUsbShort();
mProductID = stream.unpackUsbShort();
@@ -124,9 +161,9 @@
int spec = getSpec();
canvas.writeListItem("Spec: " + ReportCanvas.getBCDString(spec));
- byte devClass = getDevClass();
+ int devClass = getDevClass();
String classStr = UsbStrings.getClassName(devClass);
- byte devSubClass = getDevSubClass();
+ int devSubClass = getDevSubClass();
String subClasStr = UsbStrings.getClassName(devSubClass);
canvas.writeListItem("Class " + devClass + ": " + classStr + " Subclass"
+ devSubClass + ": " + subClasStr);
@@ -134,12 +171,11 @@
+ " Product ID: " + ReportCanvas.getHexString(getProductID())
+ " Product Release: " + ReportCanvas.getBCDString(getDeviceRelease()));
+ UsbDescriptorParser parser = canvas.getParser();
byte mfgIndex = getMfgIndex();
- String manufacturer =
- UsbDescriptor.getUsbDescriptorString(canvas.getConnection(), mfgIndex);
+ String manufacturer = parser.getDescriptorString(mfgIndex);
byte productIndex = getProductIndex();
- String product =
- UsbDescriptor.getUsbDescriptorString(canvas.getConnection(), productIndex);
+ String product = parser.getDescriptorString(productIndex);
canvas.writeListItem("Manufacturer " + mfgIndex + ": " + manufacturer
+ " Product " + productIndex + ": " + product);
diff --git a/com/android/server/usb/descriptors/UsbEndpointDescriptor.java b/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
index 6322fbe..1130238 100644
--- a/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
+++ b/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
@@ -15,6 +15,8 @@
*/
package com.android.server.usb.descriptors;
+import android.hardware.usb.UsbEndpoint;
+
import com.android.server.usb.descriptors.report.ReportCanvas;
/**
@@ -25,16 +27,16 @@
public class UsbEndpointDescriptor extends UsbDescriptor {
private static final String TAG = "UsbEndpointDescriptor";
- public static final byte MASK_ENDPOINT_ADDRESS = 0b0001111;
- public static final byte MASK_ENDPOINT_DIRECTION = (byte) 0b10000000;
- public static final byte DIRECTION_OUTPUT = 0x00;
- public static final byte DIRECTION_INPUT = (byte) 0x80;
+ public static final int MASK_ENDPOINT_ADDRESS = 0b000000000001111;
+ public static final int MASK_ENDPOINT_DIRECTION = (byte) 0b0000000010000000;
+ public static final int DIRECTION_OUTPUT = 0x0000;
+ public static final int DIRECTION_INPUT = (byte) 0x0080;
- public static final byte MASK_ATTRIBS_TRANSTYPE = 0b00000011;
- public static final byte TRANSTYPE_CONTROL = 0x00;
- public static final byte TRANSTYPE_ISO = 0x01;
- public static final byte TRANSTYPE_BULK = 0x02;
- public static final byte TRANSTYPE_INTERRUPT = 0x03;
+ public static final int MASK_ATTRIBS_TRANSTYPE = 0b00000011;
+ public static final int TRANSTYPE_CONTROL = 0x00;
+ public static final int TRANSTYPE_ISO = 0x01;
+ public static final int TRANSTYPE_BULK = 0x02;
+ public static final int TRANSTYPE_INTERRUPT = 0x03;
public static final byte MASK_ATTRIBS_SYNCTYPE = 0b00001100;
public static final byte SYNCTYPE_NONE = 0b00000000;
@@ -42,18 +44,18 @@
public static final byte SYNCTYPE_ADAPTSYNC = 0b00001000;
public static final byte SYNCTYPE_RESERVED = 0b00001100;
- public static final byte MASK_ATTRIBS_USEAGE = 0b00110000;
- public static final byte USEAGE_DATA = 0b00000000;
- public static final byte USEAGE_FEEDBACK = 0b00010000;
- public static final byte USEAGE_EXPLICIT = 0b00100000;
- public static final byte USEAGE_RESERVED = 0b00110000;
+ public static final int MASK_ATTRIBS_USEAGE = 0b00110000;
+ public static final int USEAGE_DATA = 0b00000000;
+ public static final int USEAGE_FEEDBACK = 0b00010000;
+ public static final int USEAGE_EXPLICIT = 0b00100000;
+ public static final int USEAGE_RESERVED = 0b00110000;
- private byte mEndpointAddress; // 2:1 Endpoint Address
+ private int mEndpointAddress; // 2:1 Endpoint Address
// Bits 0..3b Endpoint Number.
// Bits 4..6b Reserved. Set to Zero
// Bits 7 Direction 0 = Out, 1 = In
// (Ignored for Control Endpoints)
- private byte mAttributes; // 3:1 Various flags
+ private int mAttributes; // 3:1 Various flags
// Bits 0..1 Transfer Type:
// 00 = Control, 01 = Isochronous, 10 = Bulk, 11 = Interrupt
// Bits 2..7 are reserved. If Isochronous endpoint,
@@ -69,7 +71,7 @@
// 11: Reserved
private int mPacketSize; // 4:2 Maximum Packet Size this endpoint is capable of
// sending or receiving
- private byte mInterval; // 6:1 Interval for polling endpoint data transfers. Value in
+ private int mInterval; // 6:1 Interval for polling endpoint data transfers. Value in
// frame counts.
// Ignored for Bulk & Control Endpoints. Isochronous must equal
// 1 and field may range from 1 to 255 for interrupt endpoints.
@@ -81,11 +83,11 @@
mHierarchyLevel = 4;
}
- public byte getEndpointAddress() {
+ public int getEndpointAddress() {
return mEndpointAddress;
}
- public byte getAttributes() {
+ public int getAttributes() {
return mAttributes;
}
@@ -93,7 +95,7 @@
return mPacketSize;
}
- public byte getInterval() {
+ public int getInterval() {
return mInterval;
}
@@ -105,12 +107,16 @@
return mSyncAddress;
}
+ /* package */ UsbEndpoint toAndroid(UsbDescriptorParser parser) {
+ return new UsbEndpoint(mEndpointAddress, mAttributes, mPacketSize, mInterval);
+ }
+
@Override
public int parseRawDescriptors(ByteStream stream) {
- mEndpointAddress = stream.getByte();
- mAttributes = stream.getByte();
+ mEndpointAddress = stream.getUnsignedByte();
+ mAttributes = stream.getUnsignedByte();
mPacketSize = stream.unpackUsbShort();
- mInterval = stream.getByte();
+ mInterval = stream.getUnsignedByte();
if (mLength == 9) {
mRefresh = stream.getByte();
mSyncAddress = stream.getByte();
@@ -124,13 +130,13 @@
canvas.openList();
- byte address = getEndpointAddress();
+ int address = getEndpointAddress();
canvas.writeListItem("Address: "
+ ReportCanvas.getHexString(address & UsbEndpointDescriptor.MASK_ENDPOINT_ADDRESS)
+ ((address & UsbEndpointDescriptor.MASK_ENDPOINT_DIRECTION)
== UsbEndpointDescriptor.DIRECTION_OUTPUT ? " [out]" : " [in]"));
- byte attributes = getAttributes();
+ int attributes = getAttributes();
canvas.openListItem();
canvas.write("Attributes: " + ReportCanvas.getHexString(attributes) + " ");
switch (attributes & UsbEndpointDescriptor.MASK_ATTRIBS_TRANSTYPE) {
diff --git a/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java b/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
index 4eef6ca..d87b1af 100644
--- a/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
+++ b/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
@@ -15,9 +15,14 @@
*/
package com.android.server.usb.descriptors;
+import android.hardware.usb.UsbEndpoint;
+import android.hardware.usb.UsbInterface;
+
import com.android.server.usb.descriptors.report.ReportCanvas;
import com.android.server.usb.descriptors.report.UsbStrings;
+import java.util.ArrayList;
+
/**
* @hide
* A common super-class for all USB Interface Descritor subtypes.
@@ -26,14 +31,17 @@
public class UsbInterfaceDescriptor extends UsbDescriptor {
private static final String TAG = "UsbInterfaceDescriptor";
- protected byte mInterfaceNumber; // 2:1 Number of Interface
+ protected int mInterfaceNumber; // 2:1 Number of Interface
protected byte mAlternateSetting; // 3:1 Value used to select alternative setting
protected byte mNumEndpoints; // 4:1 Number of Endpoints used for this interface
- protected byte mUsbClass; // 5:1 Class Code
- protected byte mUsbSubclass; // 6:1 Subclass Code
- protected byte mProtocol; // 7:1 Protocol Code
+ protected int mUsbClass; // 5:1 Class Code
+ protected int mUsbSubclass; // 6:1 Subclass Code
+ protected int mProtocol; // 7:1 Protocol Code
protected byte mDescrIndex; // 8:1 Index of String Descriptor Describing this interface
+ private ArrayList<UsbEndpointDescriptor> mEndpointDescriptors =
+ new ArrayList<UsbEndpointDescriptor>();
+
UsbInterfaceDescriptor(int length, byte type) {
super(length, type);
mHierarchyLevel = 3;
@@ -41,18 +49,18 @@
@Override
public int parseRawDescriptors(ByteStream stream) {
- mInterfaceNumber = stream.getByte();
+ mInterfaceNumber = stream.getUnsignedByte();
mAlternateSetting = stream.getByte();
mNumEndpoints = stream.getByte();
- mUsbClass = stream.getByte();
- mUsbSubclass = stream.getByte();
- mProtocol = stream.getByte();
+ mUsbClass = stream.getUnsignedByte();
+ mUsbSubclass = stream.getUnsignedByte();
+ mProtocol = stream.getUnsignedByte();
mDescrIndex = stream.getByte();
return mLength;
}
- public byte getInterfaceNumber() {
+ public int getInterfaceNumber() {
return mInterfaceNumber;
}
@@ -64,15 +72,15 @@
return mNumEndpoints;
}
- public byte getUsbClass() {
+ public int getUsbClass() {
return mUsbClass;
}
- public byte getUsbSubclass() {
+ public int getUsbSubclass() {
return mUsbSubclass;
}
- public byte getProtocol() {
+ public int getProtocol() {
return mProtocol;
}
@@ -80,13 +88,29 @@
return mDescrIndex;
}
+ void addEndpointDescriptor(UsbEndpointDescriptor endpoint) {
+ mEndpointDescriptors.add(endpoint);
+ }
+
+ UsbInterface toAndroid(UsbDescriptorParser parser) {
+ String name = parser.getDescriptorString(mDescrIndex);
+ UsbInterface ntrface = new UsbInterface(
+ mInterfaceNumber, mAlternateSetting, name, mUsbClass, mUsbSubclass, mProtocol);
+ UsbEndpoint[] endpoints = new UsbEndpoint[mEndpointDescriptors.size()];
+ for (int index = 0; index < mEndpointDescriptors.size(); index++) {
+ endpoints[index] = mEndpointDescriptors.get(index).toAndroid(parser);
+ }
+ ntrface.setEndpoints(endpoints);
+ return ntrface;
+ }
+
@Override
public void report(ReportCanvas canvas) {
super.report(canvas);
- byte usbClass = getUsbClass();
- byte usbSubclass = getUsbSubclass();
- byte protocol = getProtocol();
+ int usbClass = getUsbClass();
+ int usbSubclass = getUsbSubclass();
+ int protocol = getProtocol();
String className = UsbStrings.getClassName(usbClass);
String subclassName = "";
if (usbClass == UsbDescriptor.CLASSID_AUDIO) {
diff --git a/com/android/server/usb/descriptors/UsbMSMidiHeader.java b/com/android/server/usb/descriptors/UsbMSMidiHeader.java
index 85a3e68..d0ca6db 100644
--- a/com/android/server/usb/descriptors/UsbMSMidiHeader.java
+++ b/com/android/server/usb/descriptors/UsbMSMidiHeader.java
@@ -25,7 +25,7 @@
public final class UsbMSMidiHeader extends UsbACInterface {
private static final String TAG = "UsbMSMidiHeader";
- public UsbMSMidiHeader(int length, byte type, byte subtype, byte subclass) {
+ public UsbMSMidiHeader(int length, byte type, byte subtype, int subclass) {
super(length, type, subtype, subclass);
}
diff --git a/com/android/server/usb/descriptors/UsbMSMidiInputJack.java b/com/android/server/usb/descriptors/UsbMSMidiInputJack.java
index 1d5cbf2..7df7cfc 100644
--- a/com/android/server/usb/descriptors/UsbMSMidiInputJack.java
+++ b/com/android/server/usb/descriptors/UsbMSMidiInputJack.java
@@ -25,7 +25,7 @@
public final class UsbMSMidiInputJack extends UsbACInterface {
private static final String TAG = "UsbMSMidiInputJack";
- UsbMSMidiInputJack(int length, byte type, byte subtype, byte subclass) {
+ UsbMSMidiInputJack(int length, byte type, byte subtype, int subclass) {
super(length, type, subtype, subclass);
}
diff --git a/com/android/server/usb/descriptors/UsbMSMidiOutputJack.java b/com/android/server/usb/descriptors/UsbMSMidiOutputJack.java
index 9f50240..1879ac0 100644
--- a/com/android/server/usb/descriptors/UsbMSMidiOutputJack.java
+++ b/com/android/server/usb/descriptors/UsbMSMidiOutputJack.java
@@ -25,7 +25,7 @@
public final class UsbMSMidiOutputJack extends UsbACInterface {
private static final String TAG = "UsbMSMidiOutputJack";
- public UsbMSMidiOutputJack(int length, byte type, byte subtype, byte subclass) {
+ public UsbMSMidiOutputJack(int length, byte type, byte subtype, int subclass) {
super(length, type, subtype, subclass);
}
diff --git a/com/android/server/usb/descriptors/report/HTMLReportCanvas.java b/com/android/server/usb/descriptors/report/HTMLReportCanvas.java
index 99ebcca..adfc514 100644
--- a/com/android/server/usb/descriptors/report/HTMLReportCanvas.java
+++ b/com/android/server/usb/descriptors/report/HTMLReportCanvas.java
@@ -15,7 +15,7 @@
*/
package com.android.server.usb.descriptors.report;
-import android.hardware.usb.UsbDeviceConnection;
+import com.android.server.usb.descriptors.UsbDescriptorParser;
/**
* @hide
@@ -32,8 +32,8 @@
* from the USB device.
* @param stringBuilder Generated output gets written into this object.
*/
- public HTMLReportCanvas(UsbDeviceConnection connection, StringBuilder stringBuilder) {
- super(connection);
+ public HTMLReportCanvas(UsbDescriptorParser parser, StringBuilder stringBuilder) {
+ super(parser);
mStringBuilder = stringBuilder;
}
diff --git a/com/android/server/usb/descriptors/report/ReportCanvas.java b/com/android/server/usb/descriptors/report/ReportCanvas.java
index 9e0adf5..c34dc98 100644
--- a/com/android/server/usb/descriptors/report/ReportCanvas.java
+++ b/com/android/server/usb/descriptors/report/ReportCanvas.java
@@ -15,7 +15,7 @@
*/
package com.android.server.usb.descriptors.report;
-import android.hardware.usb.UsbDeviceConnection;
+import com.android.server.usb.descriptors.UsbDescriptorParser;
/**
* @hide
@@ -24,22 +24,19 @@
public abstract class ReportCanvas {
private static final String TAG = "ReportCanvas";
- private final UsbDeviceConnection mConnection;
+ private final UsbDescriptorParser mParser;
/**
* Constructor.
* @param connection The USB connection object used to retrieve strings
* from the USB device.
*/
- public ReportCanvas(UsbDeviceConnection connection) {
- mConnection = connection;
+ public ReportCanvas(UsbDescriptorParser parser) {
+ mParser = parser;
}
- /**
- * @returns the UsbDeviceConnection member (mConnection).
- */
- public UsbDeviceConnection getConnection() {
- return mConnection;
+ public UsbDescriptorParser getParser() {
+ return mParser;
}
/**
diff --git a/com/android/server/usb/descriptors/report/TextReportCanvas.java b/com/android/server/usb/descriptors/report/TextReportCanvas.java
index a43569d..1e19ea1 100644
--- a/com/android/server/usb/descriptors/report/TextReportCanvas.java
+++ b/com/android/server/usb/descriptors/report/TextReportCanvas.java
@@ -15,7 +15,7 @@
*/
package com.android.server.usb.descriptors.report;
-import android.hardware.usb.UsbDeviceConnection;
+import com.android.server.usb.descriptors.UsbDescriptorParser;
/**
* @hide
@@ -34,8 +34,8 @@
* from the USB device.
* @param stringBuilder Generated output gets written into this object.
*/
- public TextReportCanvas(UsbDeviceConnection connection, StringBuilder stringBuilder) {
- super(connection);
+ public TextReportCanvas(UsbDescriptorParser parser, StringBuilder stringBuilder) {
+ super(parser);
mStringBuilder = stringBuilder;
}
diff --git a/com/android/server/usb/descriptors/report/UsbStrings.java b/com/android/server/usb/descriptors/report/UsbStrings.java
index 64ecebc..fb4576a 100644
--- a/com/android/server/usb/descriptors/report/UsbStrings.java
+++ b/com/android/server/usb/descriptors/report/UsbStrings.java
@@ -32,8 +32,8 @@
private static HashMap<Byte, String> sDescriptorNames;
private static HashMap<Byte, String> sACControlInterfaceNames;
private static HashMap<Byte, String> sACStreamingInterfaceNames;
- private static HashMap<Byte, String> sClassNames;
- private static HashMap<Byte, String> sAudioSubclassNames;
+ private static HashMap<Integer, String> sClassNames;
+ private static HashMap<Integer, String> sAudioSubclassNames;
private static HashMap<Integer, String> sAudioEncodingNames;
private static HashMap<Integer, String> sTerminalNames;
private static HashMap<Integer, String> sFormatNames;
@@ -92,7 +92,7 @@
}
private static void initClassNames() {
- sClassNames = new HashMap<Byte, String>();
+ sClassNames = new HashMap<Integer, String>();
sClassNames.put(UsbDescriptor.CLASSID_DEVICE, "Device");
sClassNames.put(UsbDescriptor.CLASSID_AUDIO, "Audio");
sClassNames.put(UsbDescriptor.CLASSID_COM, "Communications");
@@ -118,7 +118,7 @@
}
private static void initAudioSubclassNames() {
- sAudioSubclassNames = new HashMap<Byte, String>();
+ sAudioSubclassNames = new HashMap<Integer, String>();
sAudioSubclassNames.put(UsbDescriptor.AUDIO_SUBCLASS_UNDEFINED, "Undefinded");
sAudioSubclassNames.put(UsbDescriptor.AUDIO_AUDIOCONTROL, "Audio Control");
sAudioSubclassNames.put(UsbDescriptor.AUDIO_AUDIOSTREAMING, "Audio Streaming");
@@ -300,7 +300,7 @@
/**
* Retrieves the name for the specified USB class ID.
*/
- public static String getClassName(byte classID) {
+ public static String getClassName(int classID) {
String name = sClassNames.get(classID);
int iClassID = classID & 0xFF;
return name != null
@@ -312,7 +312,7 @@
/**
* Retrieves the name for the specified USB audio subclass ID.
*/
- public static String getAudioSubclassName(byte subClassID) {
+ public static String getAudioSubclassName(int subClassID) {
String name = sAudioSubclassNames.get(subClassID);
int iSubclassID = subClassID & 0xFF;
return name != null
@@ -335,7 +335,7 @@
/**
* Retrieves the name for the specified USB audio interface subclass ID.
*/
- public static String getACInterfaceSubclassName(byte subClassID) {
+ public static String getACInterfaceSubclassName(int subClassID) {
return subClassID == UsbDescriptor.AUDIO_AUDIOCONTROL ? "AC Control" : "AC Streaming";
}
}
diff --git a/com/android/server/utils/AppInstallerUtil.java b/com/android/server/utils/AppInstallerUtil.java
new file mode 100644
index 0000000..af7ff41
--- /dev/null
+++ b/com/android/server/utils/AppInstallerUtil.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2017 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.server.utils;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.util.Log;
+
+public class AppInstallerUtil {
+ private static final String LOG_TAG = "AppInstallerUtil";
+
+ private static Intent resolveIntent(Context context, Intent i) {
+ ResolveInfo result = context.getPackageManager().resolveActivity(i, 0);
+ return result != null ? new Intent(i.getAction())
+ .setClassName(result.activityInfo.packageName, result.activityInfo.name) : null;
+ }
+
+ /**
+ * Returns the package name of the app which installed a given packageName, if available.
+ */
+ public static String getInstallerPackageName(Context context, String packageName) {
+ String installerPackageName = null;
+ try {
+ installerPackageName =
+ context.getPackageManager().getInstallerPackageName(packageName);
+ } catch (IllegalArgumentException e) {
+ Log.e(LOG_TAG, "Exception while retrieving the package installer of " + packageName, e);
+ }
+ if (installerPackageName == null) {
+ return null;
+ }
+ return installerPackageName;
+ }
+
+ /**
+ * Returns an intent to launcher the installer for a given package name.
+ */
+ public static Intent createIntent(Context context, String installerPackageName,
+ String packageName) {
+ Intent intent = new Intent(Intent.ACTION_SHOW_APP_INFO).setPackage(installerPackageName);
+ final Intent result = resolveIntent(context, intent);
+ if (result != null) {
+ result.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
+ return result;
+ }
+ return null;
+ }
+
+ /**
+ * Convenience method that looks up the installerPackageName.
+ */
+ public static Intent createIntent(Context context, String packageName) {
+ String installerPackageName = getInstallerPackageName(context, packageName);
+ return createIntent(context, installerPackageName, packageName);
+ }
+}
diff --git a/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 44e5314..2d93da9 100644
--- a/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -427,8 +427,11 @@
if (hasComponent) {
mShortcutServiceInternal.setShortcutHostPackage(TAG,
serviceComponent.getPackageName(), mCurUser);
+ mAmInternal.setAllowAppSwitches(TAG,
+ serviceInfo.applicationInfo.uid, mCurUser);
} else {
mShortcutServiceInternal.setShortcutHostPackage(TAG, null, mCurUser);
+ mAmInternal.setAllowAppSwitches(TAG, -1, mCurUser);
}
}
diff --git a/com/android/server/vr/Vr2dDisplay.java b/com/android/server/vr/Vr2dDisplay.java
index 95d03d4..7866e7d 100644
--- a/com/android/server/vr/Vr2dDisplay.java
+++ b/com/android/server/vr/Vr2dDisplay.java
@@ -24,9 +24,8 @@
import android.service.vr.IVrManager;
import android.util.Log;
import android.view.Surface;
-import android.view.WindowManagerInternal;
-import com.android.server.vr.VrManagerService;
+import com.android.server.wm.WindowManagerInternal;
/**
* Creates a 2D Virtual Display while VR Mode is enabled. This display will be used to run and
diff --git a/com/android/server/vr/VrManagerService.java b/com/android/server/vr/VrManagerService.java
index 5493207..1f4e64e 100644
--- a/com/android/server/vr/VrManagerService.java
+++ b/com/android/server/vr/VrManagerService.java
@@ -58,7 +58,7 @@
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
-import android.view.WindowManagerInternal;
+import com.android.server.wm.WindowManagerInternal;
import com.android.internal.R;
import com.android.internal.util.DumpUtils;
diff --git a/com/android/server/wifi/HalDeviceManager.java b/com/android/server/wifi/HalDeviceManager.java
index 0cc735a..ffc7113 100644
--- a/com/android/server/wifi/HalDeviceManager.java
+++ b/com/android/server/wifi/HalDeviceManager.java
@@ -16,6 +16,8 @@
package com.android.server.wifi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.hardware.wifi.V1_0.IWifi;
import android.hardware.wifi.V1_0.IWifiApIface;
import android.hardware.wifi.V1_0.IWifiChip;
@@ -34,7 +36,6 @@
import android.hidl.manager.V1_0.IServiceNotification;
import android.os.Handler;
import android.os.HwRemoteBinder;
-import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
import android.util.LongSparseArray;
@@ -102,13 +103,14 @@
* single copy kept.
*
* @param listener ManagerStatusListener listener object.
- * @param handler Handler on which to dispatch listener. Null implies a new Handler based on
- * the current looper.
+ * @param handler Handler on which to dispatch listener. Null implies the listener will be
+ * invoked synchronously from the context of the client which triggered the
+ * state change.
*/
- public void registerStatusListener(ManagerStatusListener listener, Handler handler) {
+ public void registerStatusListener(@NonNull ManagerStatusListener listener,
+ @Nullable Handler handler) {
synchronized (mLock) {
- if (!mManagerStatusListeners.add(new ManagerStatusListenerProxy(listener,
- handler == null ? new Handler(Looper.myLooper()) : handler))) {
+ if (!mManagerStatusListeners.add(new ManagerStatusListenerProxy(listener, handler))) {
Log.w(TAG, "registerStatusListener: duplicate registration ignored");
}
}
@@ -197,36 +199,37 @@
* @param destroyedListener Optional (nullable) listener to call when the allocated interface
* is removed. Will only be registered and used if an interface is
* created successfully.
- * @param handler The Handler on which to dispatch the listener. A null implies a new Handler
- * based on the current looper.
+ * @param handler Handler on which to dispatch listener. Null implies the listener will be
+ * invoked synchronously from the context of the client which triggered the
+ * iface destruction.
* @return A newly created interface - or null if the interface could not be created.
*/
- public IWifiStaIface createStaIface(InterfaceDestroyedListener destroyedListener,
- Handler handler) {
+ public IWifiStaIface createStaIface(@Nullable InterfaceDestroyedListener destroyedListener,
+ @Nullable Handler handler) {
return (IWifiStaIface) createIface(IfaceType.STA, destroyedListener, handler);
}
/**
* Create AP interface if possible (see createStaIface doc).
*/
- public IWifiApIface createApIface(InterfaceDestroyedListener destroyedListener,
- Handler handler) {
+ public IWifiApIface createApIface(@Nullable InterfaceDestroyedListener destroyedListener,
+ @Nullable Handler handler) {
return (IWifiApIface) createIface(IfaceType.AP, destroyedListener, handler);
}
/**
* Create P2P interface if possible (see createStaIface doc).
*/
- public IWifiP2pIface createP2pIface(InterfaceDestroyedListener destroyedListener,
- Handler handler) {
+ public IWifiP2pIface createP2pIface(@Nullable InterfaceDestroyedListener destroyedListener,
+ @Nullable Handler handler) {
return (IWifiP2pIface) createIface(IfaceType.P2P, destroyedListener, handler);
}
/**
* Create NAN interface if possible (see createStaIface doc).
*/
- public IWifiNanIface createNanIface(InterfaceDestroyedListener destroyedListener,
- Handler handler) {
+ public IWifiNanIface createNanIface(@Nullable InterfaceDestroyedListener destroyedListener,
+ @Nullable Handler handler) {
return (IWifiNanIface) createIface(IfaceType.NAN, destroyedListener, handler);
}
@@ -268,11 +271,16 @@
* and false on failure. This listener is in addition to the one registered when the interface
* was created - allowing non-creators to monitor interface status.
*
- * Listener called-back on the specified handler - or on the current looper if a null is passed.
+ * @param destroyedListener Listener to call when the allocated interface is removed.
+ * Will only be registered and used if an interface is created
+ * successfully.
+ * @param handler Handler on which to dispatch listener. Null implies the listener will be
+ * invoked synchronously from the context of the client which triggered the
+ * iface destruction.
*/
public boolean registerDestroyedListener(IWifiIface iface,
- InterfaceDestroyedListener destroyedListener,
- Handler handler) {
+ @NonNull InterfaceDestroyedListener destroyedListener,
+ @Nullable Handler handler) {
String name = getName(iface);
if (DBG) Log.d(TAG, "registerDestroyedListener: iface(name)=" + name);
@@ -284,7 +292,7 @@
}
return cacheEntry.destroyedListeners.add(
- new InterfaceDestroyedListenerProxy(destroyedListener, handler));
+ new InterfaceDestroyedListenerProxy(name, destroyedListener, handler));
}
}
@@ -303,11 +311,12 @@
* @param ifaceType The interface type (IfaceType) to be monitored.
* @param listener Listener to call when an interface of the requested
* type could be created
- * @param handler The Handler on which to dispatch the listener. A null implies a new Handler
- * on the current looper.
+ * @param handler Handler on which to dispatch listener. Null implies the listener will be
+ * invoked synchronously from the context of the client which triggered the
+ * mode change.
*/
public void registerInterfaceAvailableForRequestListener(int ifaceType,
- InterfaceAvailableForRequestListener listener, Handler handler) {
+ @NonNull InterfaceAvailableForRequestListener listener, @Nullable Handler handler) {
if (DBG) Log.d(TAG, "registerInterfaceAvailableForRequestListener: ifaceType=" + ifaceType);
synchronized (mLock) {
@@ -382,8 +391,10 @@
*
* Can be registered when the interface is requested with createXxxIface() - will
* only be valid if the interface creation was successful - i.e. a non-null was returned.
+ *
+ * @param ifaceName Name of the interface that was destroyed.
*/
- void onDestroyed();
+ void onDestroyed(@NonNull String ifaceName);
}
/**
@@ -1212,9 +1223,8 @@
private class ManagerStatusListenerProxy extends
ListenerProxy<ManagerStatusListener> {
- ManagerStatusListenerProxy(ManagerStatusListener statusListener,
- Handler handler) {
- super(statusListener, handler, true, "ManagerStatusListenerProxy");
+ ManagerStatusListenerProxy(ManagerStatusListener statusListener, Handler handler) {
+ super(statusListener, handler, "ManagerStatusListenerProxy");
}
@Override
@@ -1346,7 +1356,8 @@
cacheEntry.type = ifaceType;
if (destroyedListener != null) {
cacheEntry.destroyedListeners.add(
- new InterfaceDestroyedListenerProxy(destroyedListener, handler));
+ new InterfaceDestroyedListenerProxy(
+ cacheEntry.name, destroyedListener, handler));
}
cacheEntry.creationTime = mClock.getUptimeSinceBootMillis();
@@ -1898,11 +1909,8 @@
}
private abstract class ListenerProxy<LISTENER> {
- private static final int LISTENER_TRIGGERED = 0;
-
protected LISTENER mListener;
private Handler mHandler;
- private boolean mFrontOfQueue;
// override equals & hash to make sure that the container HashSet is unique with respect to
// the contained listener
@@ -1917,37 +1925,36 @@
}
void trigger() {
- if (mFrontOfQueue) {
- mHandler.postAtFrontOfQueue(() -> {
- action();
- });
- } else {
+ if (mHandler != null) {
mHandler.post(() -> {
action();
});
+ } else {
+ action();
}
}
protected abstract void action();
- ListenerProxy(LISTENER listener, Handler handler, boolean frontOfQueue, String tag) {
+ ListenerProxy(LISTENER listener, Handler handler, String tag) {
mListener = listener;
mHandler = handler;
- mFrontOfQueue = frontOfQueue;
}
}
private class InterfaceDestroyedListenerProxy extends
ListenerProxy<InterfaceDestroyedListener> {
- InterfaceDestroyedListenerProxy(InterfaceDestroyedListener destroyedListener,
- Handler handler) {
- super(destroyedListener, handler == null ? new Handler(Looper.myLooper()) : handler,
- true, "InterfaceDestroyedListenerProxy");
+ private final String mIfaceName;
+ InterfaceDestroyedListenerProxy(@NonNull String ifaceName,
+ InterfaceDestroyedListener destroyedListener,
+ Handler handler) {
+ super(destroyedListener, handler, "InterfaceDestroyedListenerProxy");
+ mIfaceName = ifaceName;
}
@Override
protected void action() {
- mListener.onDestroyed();
+ mListener.onDestroyed(mIfaceName);
}
}
@@ -1955,8 +1962,7 @@
ListenerProxy<InterfaceAvailableForRequestListener> {
InterfaceAvailableForRequestListenerProxy(
InterfaceAvailableForRequestListener destroyedListener, Handler handler) {
- super(destroyedListener, handler == null ? new Handler(Looper.myLooper()) : handler,
- false, "InterfaceAvailableForRequestListenerProxy");
+ super(destroyedListener, handler, "InterfaceAvailableForRequestListenerProxy");
}
@Override
diff --git a/com/android/server/wifi/SoftApManager.java b/com/android/server/wifi/SoftApManager.java
index e045c39..cec9887 100644
--- a/com/android/server/wifi/SoftApManager.java
+++ b/com/android/server/wifi/SoftApManager.java
@@ -25,9 +25,7 @@
import android.content.Intent;
import android.net.InterfaceConfiguration;
import android.net.wifi.IApInterface;
-import android.net.wifi.IApInterfaceEventCallback;
import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiConfiguration.KeyMgmt;
import android.net.wifi.WifiManager;
import android.os.INetworkManagementService;
import android.os.Looper;
@@ -41,9 +39,9 @@
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import com.android.server.net.BaseNetworkObserver;
+import com.android.server.wifi.WifiNative.SoftApListener;
import com.android.server.wifi.util.ApConfigUtil;
-import java.nio.charset.StandardCharsets;
import java.util.Locale;
/**
@@ -76,16 +74,14 @@
private int mNumAssociatedStations = 0;
- /**
- * Listener for AP Interface events.
- */
- public class ApInterfaceListener extends IApInterfaceEventCallback.Stub {
+ private final SoftApListener mSoftApListener = new SoftApListener() {
@Override
public void onNumAssociatedStationsChanged(int numStations) {
mStateMachine.sendMessage(
SoftApStateMachine.CMD_NUM_ASSOCIATED_STATIONS_CHANGED, numStations);
}
- }
+ };
+
/**
* Listener for soft AP state changes.
@@ -227,71 +223,24 @@
return ERROR_GENERIC;
}
}
-
- int encryptionType = getIApInterfaceEncryptionType(localConfig);
-
if (localConfig.hiddenSSID) {
Log.d(TAG, "SoftAP is a hidden network");
}
-
- try {
- // Note that localConfig.SSID is intended to be either a hex string or "double quoted".
- // However, it seems that whatever is handing us these configurations does not obey
- // this convention.
- boolean success = mApInterface.writeHostapdConfig(
- localConfig.SSID.getBytes(StandardCharsets.UTF_8), localConfig.hiddenSSID,
- localConfig.apChannel, encryptionType,
- (localConfig.preSharedKey != null)
- ? localConfig.preSharedKey.getBytes(StandardCharsets.UTF_8)
- : new byte[0]);
- if (!success) {
- Log.e(TAG, "Failed to write hostapd configuration");
- return ERROR_GENERIC;
- }
-
- success = mApInterface.startHostapd(new ApInterfaceListener());
- if (!success) {
- Log.e(TAG, "Failed to start hostapd.");
- return ERROR_GENERIC;
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Exception in starting soft AP: " + e);
+ if (!mWifiNative.startSoftAp(localConfig, mSoftApListener)) {
+ Log.e(TAG, "Soft AP start failed");
+ return ERROR_GENERIC;
}
-
Log.d(TAG, "Soft AP is started");
return SUCCESS;
}
- private static int getIApInterfaceEncryptionType(WifiConfiguration localConfig) {
- int encryptionType;
- switch (localConfig.getAuthType()) {
- case KeyMgmt.NONE:
- encryptionType = IApInterface.ENCRYPTION_TYPE_NONE;
- break;
- case KeyMgmt.WPA_PSK:
- encryptionType = IApInterface.ENCRYPTION_TYPE_WPA;
- break;
- case KeyMgmt.WPA2_PSK:
- encryptionType = IApInterface.ENCRYPTION_TYPE_WPA2;
- break;
- default:
- // We really shouldn't default to None, but this was how NetworkManagementService
- // used to do this.
- encryptionType = IApInterface.ENCRYPTION_TYPE_NONE;
- break;
- }
- return encryptionType;
- }
-
/**
* Teardown soft AP.
*/
private void stopSoftAp() {
- try {
- mApInterface.stopHostapd();
- } catch (RemoteException e) {
- Log.e(TAG, "Exception in stopping soft AP: " + e);
+ if (!mWifiNative.stopSoftAp()) {
+ Log.d(TAG, "Soft AP stop failed");
return;
}
Log.d(TAG, "Soft AP is stopped");
diff --git a/com/android/server/wifi/WakeupController.java b/com/android/server/wifi/WakeupController.java
new file mode 100644
index 0000000..a3c095a
--- /dev/null
+++ b/com/android/server/wifi/WakeupController.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2017 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.server.wifi;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * WakeupController is responsible managing Auto Wifi.
+ *
+ * <p>It determines if and when to re-enable wifi after it has been turned off by the user.
+ */
+public class WakeupController {
+
+ // TODO(b/69624403) propagate this to Settings
+ private static final boolean USE_PLATFORM_WIFI_WAKE = false;
+
+ private final Context mContext;
+ private final Handler mHandler;
+ private final FrameworkFacade mFrameworkFacade;
+ private final ContentObserver mContentObserver;
+
+ /** Whether this feature is enabled in Settings. */
+ private boolean mWifiWakeupEnabled;
+
+ public WakeupController(
+ Context context,
+ Looper looper,
+ FrameworkFacade frameworkFacade) {
+ mContext = context;
+ mHandler = new Handler(looper);
+ mFrameworkFacade = frameworkFacade;
+ mContentObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ mWifiWakeupEnabled = mFrameworkFacade.getIntegerSetting(
+ mContext, Settings.Global.WIFI_WAKEUP_ENABLED, 0) == 1;
+ }
+ };
+ mFrameworkFacade.registerContentObserver(mContext, Settings.Global.getUriFor(
+ Settings.Global.WIFI_WAKEUP_ENABLED), true, mContentObserver);
+ mContentObserver.onChange(false /* selfChange */);
+ }
+
+ /**
+ * Whether the feature is enabled in settings.
+ *
+ * <p>Note: This method is only used to determine whether or not to actually enable wifi. All
+ * other aspects of the WakeupController lifecycle operate normally irrespective of this.
+ */
+ @VisibleForTesting
+ boolean isEnabled() {
+ return mWifiWakeupEnabled;
+ }
+}
diff --git a/com/android/server/wifi/WifiInjector.java b/com/android/server/wifi/WifiInjector.java
index cc55934..f14a57f 100644
--- a/com/android/server/wifi/WifiInjector.java
+++ b/com/android/server/wifi/WifiInjector.java
@@ -122,6 +122,7 @@
private final WifiStateTracker mWifiStateTracker;
private final Runtime mJavaRuntime;
private final SelfRecovery mSelfRecovery;
+ private final WakeupController mWakeupController;
private final boolean mUseRealLogger;
@@ -230,6 +231,8 @@
mWifiConfigManager, mWifiConfigStore, mWifiStateMachine,
new OpenNetworkRecommender(),
new ConnectToNetworkNotificationBuilder(mContext, mFrameworkFacade));
+ mWakeupController = new WakeupController(mContext,
+ mWifiStateMachineHandlerThread.getLooper(), mFrameworkFacade);
mLockManager = new WifiLockManager(mContext, BatteryStatsService.getService());
mWifiController = new WifiController(mContext, mWifiStateMachine, mSettingsStore,
mLockManager, mWifiServiceHandlerThread.getLooper(), mFrameworkFacade);
@@ -349,6 +352,10 @@
return mPasspointManager;
}
+ public WakeupController getWakeupController() {
+ return mWakeupController;
+ }
+
public TelephonyManager makeTelephonyManager() {
// may not be available when WiFi starts
return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
diff --git a/com/android/server/wifi/WifiNative.java b/com/android/server/wifi/WifiNative.java
index cda1cf6..cf80d22 100644
--- a/com/android/server/wifi/WifiNative.java
+++ b/com/android/server/wifi/WifiNative.java
@@ -273,6 +273,36 @@
return mWificondControl.stopPnoScan();
}
+ /**
+ * Callbacks for SoftAp interface.
+ */
+ public interface SoftApListener {
+ /**
+ * Invoked when the number of associated stations changes.
+ */
+ void onNumAssociatedStationsChanged(int numStations);
+ }
+
+ /**
+ * Start Soft AP operation using the provided configuration.
+ *
+ * @param config Configuration to use for the soft ap created.
+ * @param listener Callback for AP events.
+ * @return true on success, false otherwise.
+ */
+ public boolean startSoftAp(WifiConfiguration config, SoftApListener listener) {
+ return mWificondControl.startSoftAp(config, listener);
+ }
+
+ /**
+ * Stop the ongoing Soft AP operation.
+ *
+ * @return true on success, false otherwise.
+ */
+ public boolean stopSoftAp() {
+ return mWificondControl.stopSoftAp();
+ }
+
/********************************************************
* Supplicant operations
********************************************************/
diff --git a/com/android/server/wifi/WifiServiceImpl.java b/com/android/server/wifi/WifiServiceImpl.java
index e86bd54..8db180f 100644
--- a/com/android/server/wifi/WifiServiceImpl.java
+++ b/com/android/server/wifi/WifiServiceImpl.java
@@ -16,7 +16,6 @@
package com.android.server.wifi;
-import static android.app.AppOpsManager.MODE_IGNORED;
import static android.net.wifi.WifiManager.EXTRA_PREVIOUS_WIFI_AP_STATE;
import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_FAILURE_REASON;
import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME;
@@ -43,7 +42,6 @@
import static com.android.server.wifi.WifiController.CMD_WIFI_TOGGLED;
import android.Manifest;
-import android.annotation.CheckResult;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.AppOpsManager;
@@ -75,6 +73,7 @@
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.LocalOnlyHotspotCallback;
import android.net.wifi.WifiScanner;
+import android.net.wifi.hotspot2.IProvisioningCallback;
import android.net.wifi.hotspot2.OsuProvider;
import android.net.wifi.hotspot2.PasspointConfiguration;
import android.os.AsyncTask;
@@ -143,11 +142,6 @@
private static final boolean DBG = true;
private static final boolean VDBG = false;
- // Dumpsys argument to enable/disable disconnect on IP reachability failures.
- private static final String DUMP_ARG_SET_IPREACH_DISCONNECT = "set-ipreach-disconnect";
- private static final String DUMP_ARG_SET_IPREACH_DISCONNECT_ENABLED = "enabled";
- private static final String DUMP_ARG_SET_IPREACH_DISCONNECT_DISABLED = "disabled";
-
// Default scan background throttling interval if not overriden in settings
private static final long DEFAULT_SCAN_BACKGROUND_THROTTLE_INTERVAL_MS = 30 * 60 * 1000;
@@ -591,9 +585,7 @@
*/
@Override
public void startScan(ScanSettings settings, WorkSource workSource, String packageName) {
- if (enforceChangePermission(packageName) == MODE_IGNORED) {
- return;
- }
+ enforceChangePermission();
mLog.info("startScan uid=%").c(Binder.getCallingUid()).flush();
// Check and throttle background apps for wifi scan.
@@ -737,21 +729,9 @@
"WifiService");
}
- /**
- * Checks whether the caller can change the wifi state.
- * Possible results:
- * 1. Operation is allowed. No exception thrown, and AppOpsManager.MODE_ALLOWED returned.
- * 2. Operation is not allowed, and caller must be told about this. SecurityException is thrown.
- * 3. Operation is not allowed, and caller must not be told about this (i.e. must silently
- * ignore the operation). No exception is thrown, and AppOpsManager.MODE_IGNORED returned.
- */
- @CheckResult
- private int enforceChangePermission(String callingPackage) {
+ private void enforceChangePermission() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_WIFI_STATE,
"WifiService");
-
- return mAppOps.noteOp(AppOpsManager.OP_CHANGE_WIFI_STATE, Binder.getCallingUid(),
- callingPackage);
}
private void enforceLocationHardwarePermission() {
@@ -795,10 +775,7 @@
@Override
public synchronized boolean setWifiEnabled(String packageName, boolean enable)
throws RemoteException {
- if (enforceChangePermission(packageName) == MODE_IGNORED) {
- return false;
- }
-
+ enforceChangePermission();
Slog.d(TAG, "setWifiEnabled: " + enable + " pid=" + Binder.getCallingPid()
+ ", uid=" + Binder.getCallingUid() + ", package=" + packageName);
mLog.info("setWifiEnabled package=% uid=% enable=%").c(packageName)
@@ -1192,9 +1169,7 @@
final int uid = Binder.getCallingUid();
final int pid = Binder.getCallingPid();
- if (enforceChangePermission(packageName) == MODE_IGNORED) {
- return LocalOnlyHotspotCallback.ERROR_GENERIC;
- }
+ enforceChangePermission();
enforceLocationPermission(packageName, uid);
// also need to verify that Locations services are enabled.
if (mSettingsStore.getLocationModeSetting(mContext) == Settings.Secure.LOCATION_MODE_OFF) {
@@ -1266,12 +1241,9 @@
* Hotspot.
*/
@Override
- public void stopLocalOnlyHotspot(String packageName) {
+ public void stopLocalOnlyHotspot() {
// first check if the caller has permission to stop a local only hotspot
- if (enforceChangePermission(packageName) == MODE_IGNORED) {
- // As this step is about cleaning up previously allocated resources, we'll allow the
- // app to do this cleanup even if the op is configured to be ignored.
- }
+ enforceChangePermission();
final int uid = Binder.getCallingUid();
final int pid = Binder.getCallingPid();
@@ -1373,10 +1345,8 @@
* @throws SecurityException if the caller does not have permission to write the sotap config
*/
@Override
- public void setWifiApConfiguration(WifiConfiguration wifiConfig, String packageName) {
- if (enforceChangePermission(packageName) == MODE_IGNORED) {
- return;
- }
+ public void setWifiApConfiguration(WifiConfiguration wifiConfig) {
+ enforceChangePermission();
int uid = Binder.getCallingUid();
// only allow Settings UI to write the stored SoftApConfig
if (!mWifiPermissionsUtil.checkConfigOverridePermission(uid)) {
@@ -1408,10 +1378,8 @@
* see {@link android.net.wifi.WifiManager#disconnect()}
*/
@Override
- public void disconnect(String packageName) {
- if (enforceChangePermission(packageName) == MODE_IGNORED) {
- return;
- }
+ public void disconnect() {
+ enforceChangePermission();
mLog.info("disconnect uid=%").c(Binder.getCallingUid()).flush();
mWifiStateMachine.disconnectCommand();
}
@@ -1420,10 +1388,8 @@
* see {@link android.net.wifi.WifiManager#reconnect()}
*/
@Override
- public void reconnect(String packageName) {
- if (enforceChangePermission(packageName) == MODE_IGNORED) {
- return;
- }
+ public void reconnect() {
+ enforceChangePermission();
mLog.info("reconnect uid=%").c(Binder.getCallingUid()).flush();
mWifiStateMachine.reconnectCommand(new WorkSource(Binder.getCallingUid()));
}
@@ -1432,10 +1398,8 @@
* see {@link android.net.wifi.WifiManager#reassociate()}
*/
@Override
- public void reassociate(String packageName) {
- if (enforceChangePermission(packageName) == MODE_IGNORED) {
- return;
- }
+ public void reassociate() {
+ enforceChangePermission();
mLog.info("reassociate uid=%").c(Binder.getCallingUid()).flush();
mWifiStateMachine.reassociateCommand();
}
@@ -1636,10 +1600,8 @@
* network if the operation succeeds, or {@code -1} if it fails
*/
@Override
- public int addOrUpdateNetwork(WifiConfiguration config, String packageName) {
- if (enforceChangePermission(packageName) == MODE_IGNORED) {
- return -1;
- }
+ public int addOrUpdateNetwork(WifiConfiguration config) {
+ enforceChangePermission();
mLog.info("addOrUpdateNetwork uid=%").c(Binder.getCallingUid()).flush();
// Previously, this API is overloaded for installing Passpoint profiles. Now
@@ -1658,7 +1620,7 @@
config.enterpriseConfig.getClientCertificateChain());
passpointConfig.getCredential().setClientPrivateKey(
config.enterpriseConfig.getClientPrivateKey());
- if (!addOrUpdatePasspointConfiguration(passpointConfig, packageName)) {
+ if (!addOrUpdatePasspointConfiguration(passpointConfig)) {
Slog.e(TAG, "Failed to add Passpoint profile");
return -1;
}
@@ -1709,10 +1671,8 @@
* @return {@code true} if the operation succeeded
*/
@Override
- public boolean removeNetwork(int netId, String packageName) {
- if (enforceChangePermission(packageName) == MODE_IGNORED) {
- return false;
- }
+ public boolean removeNetwork(int netId) {
+ enforceChangePermission();
mLog.info("removeNetwork uid=%").c(Binder.getCallingUid()).flush();
// TODO Add private logging for netId b/33807876
if (mWifiStateMachineChannel != null) {
@@ -1731,10 +1691,8 @@
* @return {@code true} if the operation succeeded
*/
@Override
- public boolean enableNetwork(int netId, boolean disableOthers, String packageName) {
- if (enforceChangePermission(packageName) == MODE_IGNORED) {
- return false;
- }
+ public boolean enableNetwork(int netId, boolean disableOthers) {
+ enforceChangePermission();
// TODO b/33807876 Log netId
mLog.info("enableNetwork uid=% disableOthers=%")
.c(Binder.getCallingUid())
@@ -1756,10 +1714,8 @@
* @return {@code true} if the operation succeeded
*/
@Override
- public boolean disableNetwork(int netId, String packageName) {
- if (enforceChangePermission(packageName) == MODE_IGNORED) {
- return false;
- }
+ public boolean disableNetwork(int netId) {
+ enforceChangePermission();
// TODO b/33807876 Log netId
mLog.info("disableNetwork uid=%").c(Binder.getCallingUid()).flush();
@@ -1817,11 +1773,8 @@
* @return true on success or false on failure
*/
@Override
- public boolean addOrUpdatePasspointConfiguration(
- PasspointConfiguration config, String packageName) {
- if (enforceChangePermission(packageName) == MODE_IGNORED) {
- return false;
- }
+ public boolean addOrUpdatePasspointConfiguration(PasspointConfiguration config) {
+ enforceChangePermission();
mLog.info("addorUpdatePasspointConfiguration uid=%").c(Binder.getCallingUid()).flush();
if (!mContext.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_WIFI_PASSPOINT)) {
@@ -1838,10 +1791,8 @@
* @return true on success or false on failure
*/
@Override
- public boolean removePasspointConfiguration(String fqdn, String packageName) {
- if (enforceChangePermission(packageName) == MODE_IGNORED) {
- return false;
- }
+ public boolean removePasspointConfiguration(String fqdn) {
+ enforceChangePermission();
mLog.info("removePasspointConfiguration uid=%").c(Binder.getCallingUid()).flush();
if (!mContext.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_WIFI_PASSPOINT)) {
@@ -1913,10 +1864,8 @@
* TODO: deprecate this
*/
@Override
- public boolean saveConfiguration(String packageName) {
- if (enforceChangePermission(packageName) == MODE_IGNORED) {
- return false;
- }
+ public boolean saveConfiguration() {
+ enforceChangePermission();
mLog.info("saveConfiguration uid=%").c(Binder.getCallingUid()).flush();
if (mWifiStateMachineChannel != null) {
return mWifiStateMachine.syncSaveConfig(mWifiStateMachineChannel);
@@ -2112,13 +2061,9 @@
* an AsyncChannel communication with WifiService
*/
@Override
- public Messenger getWifiServiceMessenger(String packageName) throws RemoteException {
+ public Messenger getWifiServiceMessenger() {
enforceAccessPermission();
- if (enforceChangePermission(packageName) == MODE_IGNORED) {
- // We don't have a good way of creating a fake Messenger, and returning null would
- // immediately break callers.
- throw new RemoteException("Could not create wifi service messenger");
- }
+ enforceChangePermission();
mLog.info("getWifiServiceMessenger uid=%").c(Binder.getCallingUid()).flush();
return new Messenger(mClientHandler);
}
@@ -2127,11 +2072,9 @@
* Disable an ephemeral network, i.e. network that is created thru a WiFi Scorer
*/
@Override
- public void disableEphemeralNetwork(String SSID, String packageName) {
+ public void disableEphemeralNetwork(String SSID) {
enforceAccessPermission();
- if (enforceChangePermission(packageName) == MODE_IGNORED) {
- return;
- }
+ enforceChangePermission();
mLog.info("disableEphemeralNetwork uid=%").c(Binder.getCallingUid()).flush();
mWifiStateMachine.disableEphemeralNetwork(SSID);
}
@@ -2485,10 +2428,8 @@
}
@Override
- public boolean setEnableAutoJoinWhenAssociated(boolean enabled, String packageName) {
- if (enforceChangePermission(packageName) == MODE_IGNORED) {
- return false;
- }
+ public boolean setEnableAutoJoinWhenAssociated(boolean enabled) {
+ enforceChangePermission();
mLog.info("setEnableAutoJoinWhenAssociated uid=% enabled=%")
.c(Binder.getCallingUid())
.c(enabled).flush();
@@ -2517,7 +2458,7 @@
}
@Override
- public void factoryReset(String packageName) {
+ public void factoryReset() {
enforceConnectivityInternalPermission();
mLog.info("factoryReset uid=%").c(Binder.getCallingUid()).flush();
if (mUserManager.hasUserRestriction(UserManager.DISALLOW_NETWORK_RESET)) {
@@ -2543,9 +2484,9 @@
Binder.getCallingUid(), mWifiStateMachineChannel);
if (networks != null) {
for (WifiConfiguration config : networks) {
- removeNetwork(config.networkId, packageName);
+ removeNetwork(config.networkId);
}
- saveConfiguration(packageName);
+ saveConfiguration();
}
}
}
@@ -2719,4 +2660,33 @@
restoreNetworks(wifiConfigurations);
Slog.d(TAG, "Restored supplicant backup data");
}
+
+ /**
+ * Starts subscription provisioning with a provider
+ *
+ * @param provider {@link OsuProvider} the provider to provision with
+ * @param callback {@link IProvisoningCallback} the callback object to inform status
+ */
+ @Override
+ public void startSubscriptionProvisioning(OsuProvider provider,
+ IProvisioningCallback callback) {
+ if (provider == null) {
+ throw new IllegalArgumentException("Provider must not be null");
+ }
+ if (callback == null) {
+ throw new IllegalArgumentException("Callback must not be null");
+ }
+ enforceNetworkSettingsPermission();
+ if (!mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_WIFI_PASSPOINT)) {
+ throw new UnsupportedOperationException("Passpoint not enabled");
+ }
+ final int uid = Binder.getCallingUid();
+ mLog.trace("startSubscriptionProvisioning uid=%").c(uid).flush();
+ if (mWifiStateMachine.syncStartSubscriptionProvisioning(uid, provider,
+ callback, mWifiStateMachineChannel)) {
+ mLog.trace("Subscription provisioning started with %")
+ .c(provider.toString()).flush();
+ }
+ }
}
diff --git a/com/android/server/wifi/WifiStateMachine.java b/com/android/server/wifi/WifiStateMachine.java
index 2c8c0b7..b005923 100644
--- a/com/android/server/wifi/WifiStateMachine.java
+++ b/com/android/server/wifi/WifiStateMachine.java
@@ -78,6 +78,7 @@
import android.net.wifi.WpsInfo;
import android.net.wifi.WpsResult;
import android.net.wifi.WpsResult.Status;
+import android.net.wifi.hotspot2.IProvisioningCallback;
import android.net.wifi.hotspot2.OsuProvider;
import android.net.wifi.hotspot2.PasspointConfiguration;
import android.net.wifi.p2p.IWifiP2pManager;
@@ -181,6 +182,7 @@
private static final String EXTRA_OSU_ICON_QUERY_BSSID = "BSSID";
private static final String EXTRA_OSU_ICON_QUERY_FILENAME = "FILENAME";
+ private static final String EXTRA_OSU_PROVIDER = "OsuProvider";
private boolean mVerboseLoggingEnabled = false;
@@ -740,6 +742,9 @@
/* Used to set the tx power limit for SAR during the start of a phone call. */
private static final int CMD_SELECT_TX_POWER_SCENARIO = BASE + 253;
+ // Start subscription provisioning with a given provider
+ private static final int CMD_START_SUBSCRIPTION_PROVISIONING = BASE + 254;
+
// For message logging.
private static final Class[] sMessageClasses = {
AsyncChannel.class, WifiStateMachine.class, DhcpClient.class };
@@ -1246,6 +1251,7 @@
mWifiNative.enableVerboseLogging(verbose);
mWifiConfigManager.enableVerboseLogging(verbose);
mSupplicantStateTracker.enableVerboseLogging(verbose);
+ mPasspointManager.enableVerboseLogging(verbose);
}
private static final String SYSTEM_PROPERTY_LOG_CONTROL_WIFIHAL = "log.tag.WifiHAL";
@@ -2011,6 +2017,25 @@
}
/**
+ * Start subscription provisioning synchronously
+ *
+ * @param provider {@link OsuProvider} the provider to provision with
+ * @param callback {@link IProvisioningCallback} callback for provisioning status
+ * @return boolean true indicates provisioning was started, false otherwise
+ */
+ public boolean syncStartSubscriptionProvisioning(int callingUid, OsuProvider provider,
+ IProvisioningCallback callback, AsyncChannel channel) {
+ Message msg = Message.obtain();
+ msg.what = CMD_START_SUBSCRIPTION_PROVISIONING;
+ msg.arg1 = callingUid;
+ msg.obj = callback;
+ msg.getData().putParcelable(EXTRA_OSU_PROVIDER, provider);
+ Message resultMsg = channel.sendMessageSynchronously(msg);
+ boolean result = resultMsg.arg1 != 0;
+ resultMsg.recycle();
+ return result;
+ }
+ /**
* Get connection statistics synchronously
*
* @param channel
@@ -3896,6 +3921,8 @@
break;
case CMD_INITIALIZE:
ok = mWifiNative.initializeVendorHal(mVendorHalDeathRecipient);
+ mPasspointManager.initializeProvisioner(
+ mWifiInjector.getWifiServiceHandlerThread().getLooper());
replyToMessage(message, message.what, ok ? SUCCESS : FAILURE);
break;
case CMD_BOOT_COMPLETED:
@@ -4028,6 +4055,9 @@
case CMD_GET_MATCHING_OSU_PROVIDERS:
replyToMessage(message, message.what, new ArrayList<OsuProvider>());
break;
+ case CMD_START_SUBSCRIPTION_PROVISIONING:
+ replyToMessage(message, message.what, 0);
+ break;
case CMD_IP_CONFIGURATION_SUCCESSFUL:
case CMD_IP_CONFIGURATION_LOST:
case CMD_IP_REACHABILITY_LOST:
@@ -5156,6 +5186,14 @@
replyToMessage(message, message.what,
mPasspointManager.getMatchingOsuProviders((ScanResult) message.obj));
break;
+ case CMD_START_SUBSCRIPTION_PROVISIONING:
+ IProvisioningCallback callback = (IProvisioningCallback) message.obj;
+ OsuProvider provider =
+ (OsuProvider) message.getData().getParcelable(EXTRA_OSU_PROVIDER);
+ int res = mPasspointManager.startSubscriptionProvisioning(
+ message.arg1, provider, callback) ? 1 : 0;
+ replyToMessage(message, message.what, res);
+ break;
case CMD_RECONNECT:
WorkSource workSource = (WorkSource) message.obj;
mWifiConnectivityManager.forceConnectivityScan(workSource);
diff --git a/com/android/server/wifi/WificondControl.java b/com/android/server/wifi/WificondControl.java
index aa723d6..3dd6311 100644
--- a/com/android/server/wifi/WificondControl.java
+++ b/com/android/server/wifi/WificondControl.java
@@ -18,18 +18,21 @@
import android.annotation.NonNull;
import android.net.wifi.IApInterface;
+import android.net.wifi.IApInterfaceEventCallback;
import android.net.wifi.IClientInterface;
import android.net.wifi.IPnoScanEvent;
import android.net.wifi.IScanEvent;
import android.net.wifi.IWifiScannerImpl;
import android.net.wifi.IWificond;
import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiScanner;
import android.net.wifi.WifiSsid;
import android.os.Binder;
import android.os.RemoteException;
import android.util.Log;
+import com.android.server.wifi.WifiNative.SoftApListener;
import com.android.server.wifi.hotspot2.NetworkDetail;
import com.android.server.wifi.util.InformationElementUtil;
import com.android.server.wifi.util.NativeUtil;
@@ -41,6 +44,7 @@
import com.android.server.wifi.wificond.PnoSettings;
import com.android.server.wifi.wificond.SingleScanSettings;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Set;
@@ -70,6 +74,7 @@
private IWifiScannerImpl mWificondScanner;
private IScanEvent mScanEventHandler;
private IPnoScanEvent mPnoScanEventHandler;
+ private IApInterfaceEventCallback mApInterfaceListener;
private String mClientInterfaceName;
@@ -122,6 +127,22 @@
}
}
+ /**
+ * Listener for AP Interface events.
+ */
+ private class ApInterfaceEventCallback extends IApInterfaceEventCallback.Stub {
+ private SoftApListener mSoftApListener;
+
+ ApInterfaceEventCallback(SoftApListener listener) {
+ mSoftApListener = listener;
+ }
+
+ @Override
+ public void onNumAssociatedStationsChanged(int numStations) {
+ mSoftApListener.onNumAssociatedStationsChanged(numStations);
+ }
+ }
+
/** Enable or disable verbose logging of WificondControl.
* @param enable True to enable verbose logging. False to disable verbose logging.
*/
@@ -251,12 +272,12 @@
* @return Returns true on success.
*/
public boolean disableSupplicant() {
- if (mClientInterface == null) {
- Log.e(TAG, "No valid wificond client interface handler");
+ if (mWificond == null) {
+ Log.e(TAG, "No valid handler");
return false;
}
try {
- return mClientInterface.disableSupplicant();
+ return mWificond.disableSupplicant();
} catch (RemoteException e) {
Log.e(TAG, "Failed to disable supplicant due to remote exception");
}
@@ -268,13 +289,13 @@
* @return Returns true on success.
*/
public boolean enableSupplicant() {
- if (mClientInterface == null) {
- Log.e(TAG, "No valid wificond client interface handler");
+ if (mWificond == null) {
+ Log.e(TAG, "No valid wificond handler");
return false;
}
try {
- return mClientInterface.enableSupplicant();
+ return mWificond.enableSupplicant();
} catch (RemoteException e) {
Log.e(TAG, "Failed to enable supplicant due to remote exception");
}
@@ -526,7 +547,6 @@
}
}
-
/**
* Query the list of valid frequencies for the provided band.
* The result depends on the on the country code that has been set.
@@ -560,4 +580,90 @@
}
return null;
}
+
+ /**
+ * Start Soft AP operation using the provided configuration.
+ *
+ * @param config Configuration to use for the soft ap created.
+ * @param listener Callback for AP events.
+ * @return true on success, false otherwise.
+ */
+ public boolean startSoftAp(WifiConfiguration config, SoftApListener listener) {
+ if (mApInterface == null) {
+ Log.e(TAG, "No valid ap interface handler");
+ return false;
+ }
+ int encryptionType = getIApInterfaceEncryptionType(config);
+ try {
+ // TODO(b/67745880) Note that config.SSID is intended to be either a
+ // hex string or "double quoted".
+ // However, it seems that whatever is handing us these configurations does not obey
+ // this convention.
+ boolean success = mApInterface.writeHostapdConfig(
+ config.SSID.getBytes(StandardCharsets.UTF_8), config.hiddenSSID,
+ config.apChannel, encryptionType,
+ (config.preSharedKey != null)
+ ? config.preSharedKey.getBytes(StandardCharsets.UTF_8)
+ : new byte[0]);
+ if (!success) {
+ Log.e(TAG, "Failed to write hostapd configuration");
+ return false;
+ }
+ mApInterfaceListener = new ApInterfaceEventCallback(listener);
+ success = mApInterface.startHostapd(mApInterfaceListener);
+ if (!success) {
+ Log.e(TAG, "Failed to start hostapd.");
+ return false;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception in starting soft AP: " + e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Stop the ongoing Soft AP operation.
+ *
+ * @return true on success, false otherwise.
+ */
+ public boolean stopSoftAp() {
+ if (mApInterface == null) {
+ Log.e(TAG, "No valid ap interface handler");
+ return false;
+ }
+ try {
+ boolean success = mApInterface.stopHostapd();
+ if (!success) {
+ Log.e(TAG, "Failed to stop hostapd.");
+ return false;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception in stopping soft AP: " + e);
+ return false;
+ }
+ mApInterfaceListener = null;
+ return true;
+ }
+
+ private static int getIApInterfaceEncryptionType(WifiConfiguration localConfig) {
+ int encryptionType;
+ switch (localConfig.getAuthType()) {
+ case WifiConfiguration.KeyMgmt.NONE:
+ encryptionType = IApInterface.ENCRYPTION_TYPE_NONE;
+ break;
+ case WifiConfiguration.KeyMgmt.WPA_PSK:
+ encryptionType = IApInterface.ENCRYPTION_TYPE_WPA;
+ break;
+ case WifiConfiguration.KeyMgmt.WPA2_PSK:
+ encryptionType = IApInterface.ENCRYPTION_TYPE_WPA2;
+ break;
+ default:
+ // We really shouldn't default to None, but this was how NetworkManagementService
+ // used to do this.
+ encryptionType = IApInterface.ENCRYPTION_TYPE_NONE;
+ break;
+ }
+ return encryptionType;
+ }
}
diff --git a/com/android/server/wifi/aware/WifiAwareDiscoverySessionState.java b/com/android/server/wifi/aware/WifiAwareDiscoverySessionState.java
index 86f4e37..3358a4a 100644
--- a/com/android/server/wifi/aware/WifiAwareDiscoverySessionState.java
+++ b/com/android/server/wifi/aware/WifiAwareDiscoverySessionState.java
@@ -244,13 +244,20 @@
* (usually not used in the match decisions).
* @param matchFilter The filter from the discovery advertisement (which was
* used in the match decision).
+ * @param rangingIndication Bit mask indicating the type of ranging event triggered.
+ * @param rangeMm The range to the peer in mm (valid if rangingIndication specifies ingress
+ * or egress events - i.e. non-zero).
*/
public void onMatch(int requestorInstanceId, byte[] peerMac, byte[] serviceSpecificInfo,
- byte[] matchFilter) {
+ byte[] matchFilter, int rangingIndication, int rangeMm) {
int peerId = getPeerIdOrAddIfNew(requestorInstanceId, peerMac);
try {
- mCallback.onMatch(peerId, serviceSpecificInfo, matchFilter);
+ if (rangingIndication == 0) {
+ mCallback.onMatch(peerId, serviceSpecificInfo, matchFilter);
+ } else {
+ mCallback.onMatchWithDistance(peerId, serviceSpecificInfo, matchFilter, rangeMm);
+ }
} catch (RemoteException e) {
Log.w(TAG, "onMatch: RemoteException (FYI): " + e);
}
diff --git a/com/android/server/wifi/aware/WifiAwareNativeApi.java b/com/android/server/wifi/aware/WifiAwareNativeApi.java
index a6e724f..c8b5fce 100644
--- a/com/android/server/wifi/aware/WifiAwareNativeApi.java
+++ b/com/android/server/wifi/aware/WifiAwareNativeApi.java
@@ -26,6 +26,7 @@
import android.hardware.wifi.V1_0.NanInitiateDataPathRequest;
import android.hardware.wifi.V1_0.NanMatchAlg;
import android.hardware.wifi.V1_0.NanPublishRequest;
+import android.hardware.wifi.V1_0.NanRangingIndication;
import android.hardware.wifi.V1_0.NanRespondToDataPathIndicationRequest;
import android.hardware.wifi.V1_0.NanSubscribeRequest;
import android.hardware.wifi.V1_0.NanTransmitFollowupRequest;
@@ -430,11 +431,13 @@
req.baseConfigs.disableMatchExpirationIndication = true;
req.baseConfigs.disableFollowupReceivedIndication = false;
- // TODO: configure ranging and security
- req.baseConfigs.securityConfig.securityType = NanDataPathSecurityType.OPEN;
- req.baseConfigs.rangingRequired = false;
req.autoAcceptDataPathRequests = false;
+ req.baseConfigs.rangingRequired = publishConfig.mEnableRanging;
+
+ // TODO: configure security
+ req.baseConfigs.securityConfig.securityType = NanDataPathSecurityType.OPEN;
+
req.publishType = publishConfig.mPublishType;
req.txType = NanTxType.BROADCAST;
@@ -493,9 +496,23 @@
req.baseConfigs.disableMatchExpirationIndication = true;
req.baseConfigs.disableFollowupReceivedIndication = false;
- // TODO: configure ranging and security
+ req.baseConfigs.rangingRequired =
+ subscribeConfig.mMinDistanceMmSet || subscribeConfig.mMaxDistanceMmSet;
+ req.baseConfigs.configRangingIndications = 0;
+ // TODO: b/69428593 remove correction factors once HAL converted from CM to MM
+ if (subscribeConfig.mMinDistanceMmSet) {
+ req.baseConfigs.distanceIngressCm = (short) Math.min(
+ subscribeConfig.mMinDistanceMm / 10, Short.MAX_VALUE);
+ req.baseConfigs.configRangingIndications |= NanRangingIndication.INGRESS_MET_MASK;
+ }
+ if (subscribeConfig.mMaxDistanceMmSet) {
+ req.baseConfigs.distanceEgressCm = (short) Math.min(subscribeConfig.mMaxDistanceMm / 10,
+ Short.MAX_VALUE);
+ req.baseConfigs.configRangingIndications |= NanRangingIndication.EGRESS_MET_MASK;
+ }
+
+ // TODO: configure security
req.baseConfigs.securityConfig.securityType = NanDataPathSecurityType.OPEN;
- req.baseConfigs.rangingRequired = false;
req.subscribeType = subscribeConfig.mSubscribeType;
diff --git a/com/android/server/wifi/aware/WifiAwareNativeCallback.java b/com/android/server/wifi/aware/WifiAwareNativeCallback.java
index 2121160..b45978b 100644
--- a/com/android/server/wifi/aware/WifiAwareNativeCallback.java
+++ b/com/android/server/wifi/aware/WifiAwareNativeCallback.java
@@ -405,13 +405,17 @@
+ (event.serviceSpecificInfo == null ? 0 : event.serviceSpecificInfo.size())
+ ", matchFilter=" + Arrays.toString(
convertArrayListToNativeByteArray(event.matchFilter)) + ", mf.size()=" + (
- event.matchFilter == null ? 0 : event.matchFilter.size()));
+ event.matchFilter == null ? 0 : event.matchFilter.size())
+ + ", rangingIndicationType=" + event.rangingIndicationType
+ + ", rangingMeasurementInCm=" + event.rangingMeasurementInCm);
}
incrementCbCount(CB_EV_MATCH);
+ // TODO: b/69428593 get rid of conversion once HAL moves from CM to MM
mWifiAwareStateManager.onMatchNotification(event.discoverySessionId, event.peerId,
event.addr, convertArrayListToNativeByteArray(event.serviceSpecificInfo),
- convertArrayListToNativeByteArray(event.matchFilter));
+ convertArrayListToNativeByteArray(event.matchFilter), event.rangingIndicationType,
+ event.rangingMeasurementInCm * 10);
}
@Override
diff --git a/com/android/server/wifi/aware/WifiAwareNativeManager.java b/com/android/server/wifi/aware/WifiAwareNativeManager.java
index 8659a77..d6bec5f 100644
--- a/com/android/server/wifi/aware/WifiAwareNativeManager.java
+++ b/com/android/server/wifi/aware/WifiAwareNativeManager.java
@@ -16,6 +16,7 @@
package com.android.server.wifi.aware;
+import android.annotation.NonNull;
import android.hardware.wifi.V1_0.IWifiNanIface;
import android.hardware.wifi.V1_0.IfaceType;
import android.hardware.wifi.V1_0.WifiStatus;
@@ -150,7 +151,7 @@
private class InterfaceDestroyedListener implements
HalDeviceManager.InterfaceDestroyedListener {
@Override
- public void onDestroyed() {
+ public void onDestroyed(@NonNull String ifaceName) {
if (DBG) Log.d(TAG, "Interface was destroyed");
awareIsDown();
}
diff --git a/com/android/server/wifi/aware/WifiAwareStateManager.java b/com/android/server/wifi/aware/WifiAwareStateManager.java
index 0efe736..89d9a90 100644
--- a/com/android/server/wifi/aware/WifiAwareStateManager.java
+++ b/com/android/server/wifi/aware/WifiAwareStateManager.java
@@ -183,6 +183,8 @@
private static final String MESSAGE_BUNDLE_KEY_PMK = "pmk";
private static final String MESSAGE_BUNDLE_KEY_PASSPHRASE = "passphrase";
private static final String MESSAGE_BUNDLE_KEY_OOB = "out_of_band";
+ private static final String MESSAGE_RANGING_INDICATION = "ranging_indication";
+ private static final String MESSAGE_RANGE_MM = "range_mm";
private WifiAwareNativeApi mWifiAwareNativeApi;
private WifiAwareNativeManager mWifiAwareNativeManager;
@@ -944,7 +946,7 @@
* matching service (to the one we were looking for).
*/
public void onMatchNotification(int pubSubId, int requestorInstanceId, byte[] peerMac,
- byte[] serviceSpecificInfo, byte[] matchFilter) {
+ byte[] serviceSpecificInfo, byte[] matchFilter, int rangingIndication, int rangeMm) {
Message msg = mSm.obtainMessage(MESSAGE_TYPE_NOTIFICATION);
msg.arg1 = NOTIFICATION_TYPE_MATCH;
msg.arg2 = pubSubId;
@@ -952,6 +954,8 @@
msg.getData().putByteArray(MESSAGE_BUNDLE_KEY_MAC_ADDRESS, peerMac);
msg.getData().putByteArray(MESSAGE_BUNDLE_KEY_SSI_DATA, serviceSpecificInfo);
msg.getData().putByteArray(MESSAGE_BUNDLE_KEY_FILTER_DATA, matchFilter);
+ msg.getData().putInt(MESSAGE_RANGING_INDICATION, rangingIndication);
+ msg.getData().putInt(MESSAGE_RANGE_MM, rangeMm);
mSm.sendMessage(msg);
}
@@ -1255,9 +1259,11 @@
byte[] serviceSpecificInfo = msg.getData()
.getByteArray(MESSAGE_BUNDLE_KEY_SSI_DATA);
byte[] matchFilter = msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_FILTER_DATA);
+ int rangingIndication = msg.getData().getInt(MESSAGE_RANGING_INDICATION);
+ int rangeMm = msg.getData().getInt(MESSAGE_RANGE_MM);
onMatchLocal(pubSubId, requestorInstanceId, peerMac, serviceSpecificInfo,
- matchFilter);
+ matchFilter, rangingIndication, rangeMm);
break;
}
case NOTIFICATION_TYPE_SESSION_TERMINATED: {
@@ -2788,13 +2794,14 @@
}
private void onMatchLocal(int pubSubId, int requestorInstanceId, byte[] peerMac,
- byte[] serviceSpecificInfo, byte[] matchFilter) {
+ byte[] serviceSpecificInfo, byte[] matchFilter, int rangingIndication, int rangeMm) {
if (VDBG) {
Log.v(TAG,
"onMatch: pubSubId=" + pubSubId + ", requestorInstanceId=" + requestorInstanceId
+ ", peerDiscoveryMac=" + String.valueOf(HexEncoding.encode(peerMac))
+ ", serviceSpecificInfo=" + Arrays.toString(serviceSpecificInfo)
- + ", matchFilter=" + Arrays.toString(matchFilter));
+ + ", matchFilter=" + Arrays.toString(matchFilter)
+ + ", rangingIndication=" + rangingIndication + ", rangeMm=" + rangeMm);
}
Pair<WifiAwareClientState, WifiAwareDiscoverySessionState> data =
@@ -2804,7 +2811,8 @@
return;
}
- data.second.onMatch(requestorInstanceId, peerMac, serviceSpecificInfo, matchFilter);
+ data.second.onMatch(requestorInstanceId, peerMac, serviceSpecificInfo, matchFilter,
+ rangingIndication, rangeMm);
}
private void onSessionTerminatedLocal(int pubSubId, boolean isPublish, int reason) {
diff --git a/com/android/server/wifi/hotspot2/OsuNetworkConnection.java b/com/android/server/wifi/hotspot2/OsuNetworkConnection.java
new file mode 100644
index 0000000..f1bf117
--- /dev/null
+++ b/com/android/server/wifi/hotspot2/OsuNetworkConnection.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright 2017 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.server.wifi.hotspot2;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Network;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiSsid;
+import android.os.Handler;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * Responsible for setup/monitor on Wi-Fi state and connection to the OSU AP.
+ */
+public class OsuNetworkConnection {
+ private static final String TAG = "OsuNetworkConnection";
+
+ private final Context mContext;
+
+ private boolean mVerboseLoggingEnabled = false;
+ private WifiManager mWifiManager;
+ private Callbacks mCallbacks;
+ private boolean mConnected = false;
+ private int mNetworkId = -1;
+ private boolean mWifiEnabled = false;
+
+ /**
+ * Callbacks on Wi-Fi connection state changes.
+ */
+ public interface Callbacks {
+ /**
+ * Invoked when network connection is established with IP connectivity.
+ *
+ * @param network {@link Network} associated with the connected network.
+ */
+ void onConnected(Network network);
+
+ /**
+ * Invoked when the targeted network is disconnected.
+ */
+ void onDisconnected();
+
+ /**
+ * Invoked when a timer tracking connection request is not reset by successfull connection.
+ */
+ void onTimeOut();
+
+ /**
+ * Invoked when Wifi is enabled.
+ */
+ void onWifiEnabled();
+
+ /**
+ * Invoked when Wifi is disabled.
+ */
+ void onWifiDisabled();
+ }
+
+ /**
+ * Create an instance of {@link NetworkConnection} for the specified Wi-Fi network.
+ * @param context The application context
+ */
+ public OsuNetworkConnection(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Called to initialize tracking of wifi state and network events by registering for the
+ * corresponding intents.
+ */
+ public void init(Handler handler) {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+ filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
+ BroadcastReceiver receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+ handleNetworkStateChanged(
+ intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO),
+ intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO));
+ } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
+ int state = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
+ WifiManager.WIFI_STATE_UNKNOWN);
+ handleWifiStateChanged(state);
+ }
+ }
+ };
+ mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+ mContext.registerReceiver(receiver, filter, null, handler);
+ mWifiEnabled = mWifiManager.isWifiEnabled();
+ }
+
+ /**
+ * Disconnect, if required in the two cases
+ * - still connected to the OSU AP
+ * - connection to OSU AP was requested and in progress
+ */
+ public void disconnectIfNeeded() {
+ if (mNetworkId < 0) {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "No connection to tear down");
+ }
+ return;
+ }
+ mWifiManager.removeNetwork(mNetworkId);
+ mNetworkId = -1;
+ mConnected = false;
+ if (mCallbacks != null) {
+ mCallbacks.onDisconnected();
+ }
+ }
+
+ /**
+ * Register for network and Wifi state events
+ * @param callbacks The callbacks to be invoked on network change events
+ */
+ public void setEventCallback(Callbacks callbacks) {
+ mCallbacks = callbacks;
+ }
+
+ /**
+ * Connect to a OSU Wi-Fi network specified by the given SSID. The security type of the Wi-Fi
+ * network is either open or OSEN (OSU Server-only authenticated layer 2 Encryption Network).
+ * When network access identifier is provided, OSEN is used.
+ *
+ * @param ssid The SSID to connect to
+ * @param nai Network access identifier of the network
+ *
+ * @return boolean true if connection was successfully initiated
+ */
+ public boolean connect(WifiSsid ssid, String nai) {
+ if (mConnected) {
+ if (mVerboseLoggingEnabled) {
+ // Already connected
+ Log.v(TAG, "Connect called twice");
+ }
+ return true;
+ }
+ if (!mWifiManager.isWifiEnabled()) {
+ Log.w(TAG, "Wifi is not enabled");
+ return false;
+ }
+ WifiConfiguration config = new WifiConfiguration();
+ config.SSID = "\"" + ssid.toString() + "\"";
+ if (TextUtils.isEmpty(nai)) {
+ config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+ } else {
+ // TODO(sohanirao): Handle OSEN.
+ Log.w(TAG, "OSEN not supported");
+ return false;
+ }
+ mNetworkId = mWifiManager.addNetwork(config);
+ if (mNetworkId < 0) {
+ Log.e(TAG, "Unable to add network");
+ return false;
+ }
+ if (!mWifiManager.enableNetwork(mNetworkId, true)) {
+ Log.e(TAG, "Unable to enable network " + mNetworkId);
+ disconnectIfNeeded();
+ return false;
+ }
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "Current network ID " + mNetworkId);
+ }
+ // TODO(sohanirao): setup alarm to time out the connection attempt.
+ return true;
+ }
+
+ /**
+ * Method to update logging level in this class
+ * @param verbose more than 0 enables verbose logging
+ */
+ public void enableVerboseLogging(int verbose) {
+ mVerboseLoggingEnabled = verbose > 0 ? true : false;
+ }
+
+ /**
+ * Handle network state changed events.
+ *
+ * @param networkInfo {@link NetworkInfo} indicating the current network state
+ * @param wifiInfo {@link WifiInfo} associated with the current network when connected
+ */
+ private void handleNetworkStateChanged(NetworkInfo networkInfo, WifiInfo wifiInfo) {
+ if (networkInfo == null) {
+ Log.w(TAG, "NetworkInfo not provided for network state changed event");
+ return;
+ }
+ switch (networkInfo.getDetailedState()) {
+ case CONNECTED:
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "Connected event received");
+ }
+ if (wifiInfo == null) {
+ Log.w(TAG, "WifiInfo not provided for network state changed event");
+ return;
+ }
+ handleConnectedEvent(wifiInfo);
+ break;
+ case DISCONNECTED:
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "Disconnected event received");
+ }
+ disconnectIfNeeded();
+ break;
+ default:
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "Ignore uninterested state: " + networkInfo.getDetailedState());
+ }
+ break;
+ }
+ }
+
+ /**
+ * Handle network connected event.
+ *
+ * @param wifiInfo {@link WifiInfo} associated with the current connection
+ */
+ private void handleConnectedEvent(WifiInfo wifiInfo) {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "handleConnectedEvent " + wifiInfo.getNetworkId());
+ }
+ if (wifiInfo.getNetworkId() != mNetworkId) {
+ disconnectIfNeeded();
+ return;
+ }
+ if (!mConnected) {
+ mConnected = true;
+ if (mCallbacks != null) {
+ mCallbacks.onConnected(mWifiManager.getCurrentNetwork());
+ }
+ }
+ }
+
+ /**
+ * Handle Wifi state change event
+ */
+ private void handleWifiStateChanged(int state) {
+ if (state == WifiManager.WIFI_STATE_DISABLED && mWifiEnabled) {
+ mWifiEnabled = false;
+ if (mCallbacks != null) mCallbacks.onWifiDisabled();
+ }
+ if (state == WifiManager.WIFI_STATE_ENABLED && !mWifiEnabled) {
+ mWifiEnabled = true;
+ if (mCallbacks != null) mCallbacks.onWifiEnabled();
+ }
+ }
+}
diff --git a/com/android/server/wifi/hotspot2/PasspointManager.java b/com/android/server/wifi/hotspot2/PasspointManager.java
index 3580c83..fec3dd8 100644
--- a/com/android/server/wifi/hotspot2/PasspointManager.java
+++ b/com/android/server/wifi/hotspot2/PasspointManager.java
@@ -33,8 +33,10 @@
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiEnterpriseConfig;
+import android.net.wifi.hotspot2.IProvisioningCallback;
import android.net.wifi.hotspot2.OsuProvider;
import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.os.Looper;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
@@ -99,6 +101,7 @@
private final WifiConfigManager mWifiConfigManager;
private final CertificateVerifier mCertVerifier;
private final WifiMetrics mWifiMetrics;
+ private final PasspointProvisioner mPasspointProvisioner;
// Counter used for assigning unique identifier to each provider.
private long mProviderIndex;
@@ -212,10 +215,27 @@
mProviderIndex = 0;
wifiConfigStore.registerStoreData(objectFactory.makePasspointConfigStoreData(
mKeyStore, mSimAccessor, new DataSourceHandler()));
+ mPasspointProvisioner = objectFactory.makePasspointProvisioner(context,
+ objectFactory.makeOsuNetworkConnection(context));
sPasspointManager = this;
}
/**
+ * Initializes the provisioning flow with a looper
+ */
+ public void initializeProvisioner(Looper looper) {
+ mPasspointProvisioner.init(looper);
+ }
+
+ /**
+ * Enable verbose logging
+ * @param verbose more than 0 enables verbose logging
+ */
+ public void enableVerboseLogging(int verbose) {
+ mPasspointProvisioner.enableVerboseLogging(verbose);
+ }
+
+ /**
* Add or update a Passpoint provider with the given configuration.
*
* Each provider is uniquely identified by its FQDN (Fully Qualified Domain Name).
@@ -704,4 +724,16 @@
mProviders.put(passpointConfig.getHomeSp().getFqdn(), provider);
return true;
}
+
+ /**
+ * Start the subscription provisioning flow with a provider.
+ * @param callingUid integer indicating the uid of the caller
+ * @param provider {@link OsuProvider} the provider to subscribe to
+ * @param callback {@link IProvisioningCallback} callback to update status to the caller
+ * @return boolean return value from the provisioning method
+ */
+ public boolean startSubscriptionProvisioning(int callingUid, OsuProvider provider,
+ IProvisioningCallback callback) {
+ return mPasspointProvisioner.startSubscriptionProvisioning(callingUid, provider, callback);
+ }
}
diff --git a/com/android/server/wifi/hotspot2/PasspointObjectFactory.java b/com/android/server/wifi/hotspot2/PasspointObjectFactory.java
index c41c49a..71fc47a 100644
--- a/com/android/server/wifi/hotspot2/PasspointObjectFactory.java
+++ b/com/android/server/wifi/hotspot2/PasspointObjectFactory.java
@@ -16,6 +16,7 @@
package com.android.server.wifi.hotspot2;
+import android.content.Context;
import android.net.wifi.hotspot2.PasspointConfiguration;
import com.android.server.wifi.Clock;
@@ -95,4 +96,25 @@
public CertificateVerifier makeCertificateVerifier() {
return new CertificateVerifier();
}
+
+ /**
+ * Create an instance of {@link PasspointProvisioner}.
+ *
+ * @param context
+ * @return {@link PasspointProvisioner}
+ */
+ public PasspointProvisioner makePasspointProvisioner(Context context,
+ OsuNetworkConnection osuNetworkConnection) {
+ return new PasspointProvisioner(context, osuNetworkConnection);
+ }
+
+ /**
+ * Create an instance of {@link OsuNetworkConnection}.
+ *
+ * @param context
+ * @return {@link OsuNetworkConnection}
+ */
+ public OsuNetworkConnection makeOsuNetworkConnection(Context context) {
+ return new OsuNetworkConnection(context);
+ }
}
diff --git a/com/android/server/wifi/hotspot2/PasspointProvisioner.java b/com/android/server/wifi/hotspot2/PasspointProvisioner.java
new file mode 100644
index 0000000..d3bb773
--- /dev/null
+++ b/com/android/server/wifi/hotspot2/PasspointProvisioner.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright 2017 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.server.wifi.hotspot2;
+
+import android.content.Context;
+import android.net.Network;
+import android.net.wifi.hotspot2.IProvisioningCallback;
+import android.net.wifi.hotspot2.OsuProvider;
+import android.net.wifi.hotspot2.ProvisioningCallback;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * Provides methods to carry out provisioning flow
+ */
+public class PasspointProvisioner {
+ private static final String TAG = "PasspointProvisioner";
+
+ private static final int PROVISIONING_STATUS = 0;
+ private static final int PROVISIONING_FAILURE = 1;
+
+ private final Context mContext;
+ private final ProvisioningStateMachine mProvisioningStateMachine;
+ private final OsuNetworkCallbacks mOsuNetworkCallbacks;
+ private final OsuNetworkConnection mOsuNetworkConnection;
+
+ private int mCallingUid;
+ private boolean mVerboseLoggingEnabled = false;
+
+ PasspointProvisioner(Context context, OsuNetworkConnection osuNetworkConnection) {
+ mContext = context;
+ mOsuNetworkConnection = osuNetworkConnection;
+ mProvisioningStateMachine = new ProvisioningStateMachine();
+ mOsuNetworkCallbacks = new OsuNetworkCallbacks();
+ }
+
+ /**
+ * Sets up for provisioning
+ * @param looper Looper on which the Provisioning state machine will run
+ */
+ public void init(Looper looper) {
+ mProvisioningStateMachine.start(new Handler(looper));
+ mOsuNetworkConnection.init(mProvisioningStateMachine.getHandler());
+ }
+
+ /**
+ * Enable verbose logging to help debug failures
+ * @param level integer indicating verbose logging enabled if > 0
+ */
+ public void enableVerboseLogging(int level) {
+ mVerboseLoggingEnabled = (level > 0) ? true : false;
+ mOsuNetworkConnection.enableVerboseLogging(level);
+ }
+
+ /**
+ * Start provisioning flow with a given provider.
+ * @param callingUid calling uid.
+ * @param provider {@link OsuProvider} to provision with.
+ * @param callback {@link IProvisioningCallback} to provide provisioning status.
+ * @return boolean value, true if provisioning was started, false otherwise.
+ *
+ * Implements HS2.0 provisioning flow with a given HS2.0 provider.
+ */
+ public boolean startSubscriptionProvisioning(int callingUid, OsuProvider provider,
+ IProvisioningCallback callback) {
+ mCallingUid = callingUid;
+
+ Log.v(TAG, "Provisioning started with " + provider.toString());
+
+ mProvisioningStateMachine.getHandler().post(() -> {
+ mProvisioningStateMachine.startProvisioning(provider, callback);
+ });
+
+ return true;
+ }
+
+ /**
+ * Handles the provisioning flow state transitions
+ */
+ class ProvisioningStateMachine {
+ private static final String TAG = "ProvisioningStateMachine";
+
+ private static final int DEFAULT_STATE = 0;
+ private static final int INITIAL_STATE = 1;
+ private static final int WAITING_TO_CONNECT = 2;
+ private static final int OSU_AP_CONNECTED = 3;
+ private static final int FAILED_STATE = 4;
+
+ private OsuProvider mOsuProvider;
+ private IProvisioningCallback mProvisioningCallback;
+ private Handler mHandler;
+ private int mState;
+
+ ProvisioningStateMachine() {
+ mState = DEFAULT_STATE;
+ }
+
+ /**
+ * Initializes and starts the state machine with a handler to handle incoming events
+ */
+ public void start(Handler handler) {
+ mHandler = handler;
+ changeState(INITIAL_STATE);
+ }
+
+ /**
+ * Returns the handler on which a runnable can be posted
+ * @return Handler State Machine's handler
+ */
+ public Handler getHandler() {
+ return mHandler;
+ }
+
+ /**
+ * Start Provisioning with the Osuprovider and invoke callbacks
+ * @param provider OsuProvider to provision with
+ * @param callback IProvisioningCallback to invoke callbacks on
+ */
+ public void startProvisioning(OsuProvider provider, IProvisioningCallback callback) {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "startProvisioning received in state=" + mState);
+ }
+ if (mState != INITIAL_STATE) {
+ Log.v(TAG, "State Machine needs to be reset before starting provisioning");
+ resetStateMachine();
+ }
+ mProvisioningCallback = callback;
+ mOsuProvider = provider;
+
+ // Register for network and wifi state events during provisioning flow
+ mOsuNetworkConnection.setEventCallback(mOsuNetworkCallbacks);
+
+ if (mOsuNetworkConnection.connect(mOsuProvider.getOsuSsid(),
+ mOsuProvider.getNetworkAccessIdentifier())) {
+ invokeProvisioningCallback(PROVISIONING_STATUS,
+ ProvisioningCallback.OSU_STATUS_AP_CONNECTING);
+ changeState(WAITING_TO_CONNECT);
+ } else {
+ invokeProvisioningCallback(PROVISIONING_FAILURE,
+ ProvisioningCallback.OSU_FAILURE_AP_CONNECTION);
+ enterFailedState();
+ }
+ }
+
+ /**
+ * Handle Wifi Disable event
+ */
+ public void handleWifiDisabled() {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "Wifi Disabled in state=" + mState);
+ }
+ if (mState == INITIAL_STATE || mState == FAILED_STATE) {
+ Log.w(TAG, "Wifi Disable unhandled in state=" + mState);
+ return;
+ }
+ invokeProvisioningCallback(PROVISIONING_FAILURE,
+ ProvisioningCallback.OSU_FAILURE_AP_CONNECTION);
+ enterFailedState();
+ }
+
+ private void resetStateMachine() {
+ // Set to null so that no callbacks are invoked during reset
+ mProvisioningCallback = null;
+ if (mState != INITIAL_STATE || mState != FAILED_STATE) {
+ // Transition through Failed state to clean up
+ enterFailedState();
+ }
+ changeState(INITIAL_STATE);
+ }
+
+ /**
+ * Connected event received
+ * @param network Network object for this connection
+ */
+ public void handleConnectedEvent(Network network) {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "Connected event received in state=" + mState);
+ }
+ if (mState != WAITING_TO_CONNECT) {
+ // Not waiting for a connection
+ Log.w(TAG, "Connection event unhandled in state=" + mState);
+ return;
+ }
+ invokeProvisioningCallback(PROVISIONING_STATUS,
+ ProvisioningCallback.OSU_STATUS_AP_CONNECTED);
+ changeState(OSU_AP_CONNECTED);
+ }
+
+ /**
+ * Disconnect event received
+ */
+ public void handleDisconnect() {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "Connection failed in state=" + mState);
+ }
+ if (mState == INITIAL_STATE || mState == FAILED_STATE) {
+ Log.w(TAG, "Disconnect event unhandled in state=" + mState);
+ return;
+ }
+ invokeProvisioningCallback(PROVISIONING_FAILURE,
+ ProvisioningCallback.OSU_FAILURE_AP_CONNECTION);
+ enterFailedState();
+ }
+
+ private void changeState(int nextState) {
+ if (nextState != mState) {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "Changing state from " + mState + " -> " + nextState);
+ }
+ mState = nextState;
+ }
+ }
+
+ private void invokeProvisioningCallback(int callbackType, int status) {
+ if (mProvisioningCallback == null) {
+ Log.e(TAG, "Provisioning callback " + callbackType + " with status " + status
+ + " not invoked, callback is null");
+ return;
+ }
+ try {
+ if (callbackType == PROVISIONING_STATUS) {
+ mProvisioningCallback.onProvisioningStatus(status);
+ } else {
+ mProvisioningCallback.onProvisioningFailure(status);
+ }
+ } catch (RemoteException e) {
+ if (callbackType == PROVISIONING_STATUS) {
+ Log.e(TAG, "Remote Exception while posting Provisioning status " + status);
+ } else {
+ Log.e(TAG, "Remote Exception while posting Provisioning failure " + status);
+ }
+ }
+ }
+
+ private void enterFailedState() {
+ changeState(FAILED_STATE);
+ mOsuNetworkConnection.setEventCallback(null);
+ mOsuNetworkConnection.disconnectIfNeeded();
+ }
+ }
+
+ /**
+ * Callbacks for network and wifi events
+ */
+ class OsuNetworkCallbacks implements OsuNetworkConnection.Callbacks {
+
+ OsuNetworkCallbacks() {
+ }
+
+ @Override
+ public void onConnected(Network network) {
+ Log.v(TAG, "onConnected to " + network);
+ if (network == null) {
+ mProvisioningStateMachine.getHandler().post(() -> {
+ mProvisioningStateMachine.handleDisconnect();
+ });
+ } else {
+ mProvisioningStateMachine.getHandler().post(() -> {
+ mProvisioningStateMachine.handleConnectedEvent(network);
+ });
+ }
+ }
+
+ @Override
+ public void onDisconnected() {
+ Log.v(TAG, "onDisconnected");
+ mProvisioningStateMachine.getHandler().post(() -> {
+ mProvisioningStateMachine.handleDisconnect();
+ });
+ }
+
+ @Override
+ public void onTimeOut() {
+ Log.v(TAG, "Timed out waiting for connection to OSU AP");
+ mProvisioningStateMachine.getHandler().post(() -> {
+ mProvisioningStateMachine.handleDisconnect();
+ });
+ }
+
+ @Override
+ public void onWifiEnabled() {
+ Log.v(TAG, "onWifiEnabled");
+ }
+
+ @Override
+ public void onWifiDisabled() {
+ Log.v(TAG, "onWifiDisabled");
+ mProvisioningStateMachine.getHandler().post(() -> {
+ mProvisioningStateMachine.handleWifiDisabled();
+ });
+ }
+ }
+}
diff --git a/com/android/server/wifi/p2p/WifiP2pServiceImpl.java b/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
index da3da7c..751737f 100644
--- a/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
+++ b/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
@@ -16,6 +16,7 @@
package com.android.server.wifi.p2p;
+import android.annotation.NonNull;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
@@ -524,7 +525,7 @@
}
mHalDeviceManager = mWifiInjector.getHalDeviceManager();
}
- mIWifiP2pIface = mHalDeviceManager.createP2pIface(() -> {
+ mIWifiP2pIface = mHalDeviceManager.createP2pIface((@NonNull String ifaceName) -> {
if (DBG) Log.d(TAG, "IWifiP2pIface destroyedListener");
synchronized (mLock) {
mIWifiP2pIface = null;
diff --git a/com/android/server/wifi/rtt/RttNative.java b/com/android/server/wifi/rtt/RttNative.java
index fe1829f..dd54c24 100644
--- a/com/android/server/wifi/rtt/RttNative.java
+++ b/com/android/server/wifi/rtt/RttNative.java
@@ -46,7 +46,7 @@
*/
public class RttNative extends IWifiRttControllerEventCallback.Stub {
private static final String TAG = "RttNative";
- private static final boolean VDBG = true; // STOPSHIP if true
+ private static final boolean VDBG = false; // STOPSHIP if true
private final RttServiceImpl mRttService;
private final HalDeviceManager mHalDeviceManager;
diff --git a/com/android/server/wifi/rtt/RttService.java b/com/android/server/wifi/rtt/RttService.java
index 0790dee..5c2cec1 100644
--- a/com/android/server/wifi/rtt/RttService.java
+++ b/com/android/server/wifi/rtt/RttService.java
@@ -66,7 +66,8 @@
Context.WIFI_AWARE_SERVICE);
RttNative rttNative = new RttNative(mImpl, halDeviceManager);
- mImpl.start(handlerThread.getLooper(), awareBinder, rttNative, wifiPermissionsUtil);
+ mImpl.start(handlerThread.getLooper(), wifiInjector.getClock(), awareBinder, rttNative,
+ wifiPermissionsUtil);
}
}
}
diff --git a/com/android/server/wifi/rtt/RttServiceImpl.java b/com/android/server/wifi/rtt/RttServiceImpl.java
index 36caab7..401daab 100644
--- a/com/android/server/wifi/rtt/RttServiceImpl.java
+++ b/com/android/server/wifi/rtt/RttServiceImpl.java
@@ -16,6 +16,7 @@
package com.android.server.wifi.rtt;
+import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -39,13 +40,13 @@
import android.os.Looper;
import android.os.PowerManager;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.os.WorkSource;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.WakeupMessage;
+import com.android.server.wifi.Clock;
import com.android.server.wifi.util.NativeUtil;
import com.android.server.wifi.util.WifiPermissionsUtil;
@@ -66,12 +67,14 @@
*/
public class RttServiceImpl extends IWifiRttManager.Stub {
private static final String TAG = "RttServiceImpl";
- private static final boolean VDBG = true; // STOPSHIP if true
+ private static final boolean VDBG = false; // STOPSHIP if true
private final Context mContext;
+ private Clock mClock;
private IWifiAwareManager mAwareBinder;
private RttNative mRttNative;
private WifiPermissionsUtil mWifiPermissionsUtil;
+ private ActivityManager mActivityManager;
private PowerManager mPowerManager;
private RttServiceSynchronized mRttServiceSynchronized;
@@ -79,7 +82,10 @@
@VisibleForTesting
public static final String HAL_RANGING_TIMEOUT_TAG = TAG + " HAL Ranging Timeout";
- private static final long HAL_RANGING_TIMEOUT_MS = 5_000;
+ private static final long HAL_RANGING_TIMEOUT_MS = 5_000; // 5 sec
+
+ // TODO: b/69323456 convert to a settable value
+ /* package */ static final long BACKGROUND_PROCESS_EXEC_GAP_MS = 1_800_000; // 30 min
public RttServiceImpl(Context context) {
mContext = context;
@@ -93,17 +99,21 @@
* Initializes the RTT service (usually with objects from an injector).
*
* @param looper The looper on which to synchronize operations.
+ * @param clock A mockable clock.
* @param awareBinder The Wi-Fi Aware service (binder) if supported on the system.
* @param rttNative The Native interface to the HAL.
* @param wifiPermissionsUtil Utility for permission checks.
*/
- public void start(Looper looper, IWifiAwareManager awareBinder, RttNative rttNative,
+ public void start(Looper looper, Clock clock, IWifiAwareManager awareBinder,
+ RttNative rttNative,
WifiPermissionsUtil wifiPermissionsUtil) {
+ mClock = clock;
mAwareBinder = awareBinder;
mRttNative = rttNative;
mWifiPermissionsUtil = wifiPermissionsUtil;
mRttServiceSynchronized = new RttServiceSynchronized(looper, rttNative);
+ mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
mPowerManager = mContext.getSystemService(PowerManager.class);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
@@ -323,6 +333,7 @@
private RttNative mRttNative;
private int mNextCommandId = 1000;
+ private Map<Integer, RttRequesterInfo> mRttRequesterInfo = new HashMap<>();
private List<RttRequestInfo> mRttRequestQueue = new LinkedList<>();
private WakeupMessage mRangingTimeoutMessage = null;
@@ -540,10 +551,23 @@
return;
}
+ if (!preExecThrottleCheck(nextRequest.workSource)) {
+ Log.w(TAG, "RttServiceSynchronized.startRanging: execution throttled - nextRequest="
+ + nextRequest + ", mRttRequesterInfo=" + mRttRequesterInfo);
+ try {
+ nextRequest.callback.onRangingFailure(RangingResultCallback.STATUS_CODE_FAIL);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RttServiceSynchronized.startRanging: throttled, callback failed -- "
+ + e);
+ }
+ executeNextRangingRequestIfPossible(true);
+ return;
+ }
+
nextRequest.cmdId = mNextCommandId++;
if (mRttNative.rangeRequest(nextRequest.cmdId, nextRequest.request)) {
mRangingTimeoutMessage.schedule(
- SystemClock.elapsedRealtime() + HAL_RANGING_TIMEOUT_MS);
+ mClock.getElapsedSinceBootMillis() + HAL_RANGING_TIMEOUT_MS);
} else {
Log.w(TAG, "RttServiceSynchronized.startRanging: native rangeRequest call failed");
try {
@@ -558,6 +582,64 @@
}
/**
+ * Perform pre-execution throttling checks:
+ * - If all uids in ws are in background then check last execution and block if request is
+ * more frequent than permitted
+ * - If executing (i.e. permitted) then update execution time
+ *
+ * Returns true to permit execution, false to abort it.
+ */
+ private boolean preExecThrottleCheck(WorkSource ws) {
+ if (VDBG) Log.v(TAG, "preExecThrottleCheck: ws=" + ws);
+
+ // are all UIDs running in the background or is at least 1 in the foreground?
+ boolean allUidsInBackground = true;
+ for (int i = 0; i < ws.size(); ++i) {
+ int uidImportance = mActivityManager.getUidImportance(ws.get(i));
+ if (VDBG) {
+ Log.v(TAG, "preExecThrottleCheck: uid=" + ws.get(i) + " -> importance="
+ + uidImportance);
+ }
+ if (uidImportance
+ <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE) {
+ allUidsInBackground = false;
+ break;
+ }
+ }
+
+ // if all UIDs are in background then check timestamp since last execution and see if
+ // any is permitted (infrequent enough)
+ boolean allowExecution = false;
+ long mostRecentExecutionPermitted =
+ mClock.getElapsedSinceBootMillis() - BACKGROUND_PROCESS_EXEC_GAP_MS;
+ if (allUidsInBackground) {
+ for (int i = 0; i < ws.size(); ++i) {
+ RttRequesterInfo info = mRttRequesterInfo.get(ws.get(i));
+ if (info == null || info.lastRangingExecuted < mostRecentExecutionPermitted) {
+ allowExecution = true;
+ break;
+ }
+ }
+ } else {
+ allowExecution = true;
+ }
+
+ // update exec time
+ if (allowExecution) {
+ for (int i = 0; i < ws.size(); ++i) {
+ RttRequesterInfo info = mRttRequesterInfo.get(ws.get(i));
+ if (info == null) {
+ info = new RttRequesterInfo();
+ mRttRequesterInfo.put(ws.get(i), info);
+ }
+ info.lastRangingExecuted = mClock.getElapsedSinceBootMillis();
+ }
+ }
+
+ return allowExecution;
+ }
+
+ /**
* Check request for any PeerHandle Aware requests. If there are any: issue requests to
* translate the peer ID to a MAC address and abort current execution of the range request.
* The request will be re-attempted when response is received.
@@ -754,6 +836,7 @@
// dump call (asynchronous most likely)
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println(" mNextCommandId: " + mNextCommandId);
+ pw.println(" mRttRequesterInfo: " + mRttRequesterInfo);
pw.println(" mRttRequestQueue: " + mRttRequestQueue);
pw.println(" mRangingTimeoutMessage: " + mRangingTimeoutMessage);
mRttNative.dump(fd, pw, args);
@@ -783,4 +866,14 @@
", peerHandlesTranslated=").append(peerHandlesTranslated).toString();
}
}
+
+ private static class RttRequesterInfo {
+ public long lastRangingExecuted;
+
+ @Override
+ public String toString() {
+ return new StringBuilder("RttRequesterInfo: lastRangingExecuted=").append(
+ lastRangingExecuted).toString();
+ }
+ }
}
diff --git a/com/android/server/wm/AccessibilityController.java b/com/android/server/wm/AccessibilityController.java
index de4fd7c..95b139a 100644
--- a/com/android/server/wm/AccessibilityController.java
+++ b/com/android/server/wm/AccessibilityController.java
@@ -55,14 +55,14 @@
import android.view.ViewConfiguration;
import android.view.WindowInfo;
import android.view.WindowManager;
-import android.view.WindowManagerInternal.MagnificationCallbacks;
-import android.view.WindowManagerInternal.WindowsForAccessibilityCallback;
-import android.view.WindowManagerPolicy;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import com.android.internal.R;
import com.android.internal.os.SomeArgs;
+import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.wm.WindowManagerInternal.MagnificationCallbacks;
+import com.android.server.wm.WindowManagerInternal.WindowsForAccessibilityCallback;
import java.util.ArrayList;
import java.util.HashSet;
@@ -292,6 +292,8 @@
public void setMagnificationSpecLocked(MagnificationSpec spec) {
mMagnifedViewport.updateMagnificationSpecLocked(spec);
mMagnifedViewport.recomputeBoundsLocked();
+
+ mService.applyMagnificationSpec(spec);
mService.scheduleAnimationLocked();
}
@@ -421,7 +423,7 @@
public MagnificationSpec getMagnificationSpecForWindowLocked(WindowState windowState) {
MagnificationSpec spec = mMagnifedViewport.getMagnificationSpecLocked();
if (spec != null && !spec.isNop()) {
- if (!mService.mPolicy.canMagnifyWindow(windowState.mAttrs.type)) {
+ if (!windowState.shouldMagnify()) {
return null;
}
}
@@ -476,6 +478,7 @@
private final ViewportWindow mWindow;
private boolean mFullRedrawNeeded;
+ private int mTempLayer = 0;
public MagnifiedViewport() {
mWindowManager = (WindowManager) mContext.getSystemService(Service.WINDOW_SERVICE);
@@ -565,7 +568,7 @@
portionOfWindowAlreadyAccountedFor.op(nonMagnifiedBounds, Region.Op.UNION);
windowBounds.op(portionOfWindowAlreadyAccountedFor, Region.Op.DIFFERENCE);
- if (mService.mPolicy.canMagnifyWindow(windowState.mAttrs.type)) {
+ if (windowState.shouldMagnify()) {
mMagnificationRegion.op(windowBounds, Region.Op.UNION);
mMagnificationRegion.op(availableBounds, Region.Op.INTERSECT);
} else {
@@ -676,10 +679,12 @@
private void populateWindowsOnScreenLocked(SparseArray<WindowState> outWindows) {
final DisplayContent dc = mService.getDefaultDisplayContentLocked();
+ mTempLayer = 0;
dc.forAllWindows((w) -> {
if (w.isOnScreen() && w.isVisibleLw()
&& !w.mWinAnimator.mEnterAnimationPending) {
- outWindows.put(w.mLayer, w);
+ mTempLayer++;
+ outWindows.put(mTempLayer, w);
}
}, false /* traverseTopToBottom */ );
}
@@ -705,7 +710,7 @@
SurfaceControl surfaceControl = null;
try {
mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
- surfaceControl = new SurfaceControl.Builder(mService.mFxSession)
+ surfaceControl = mService.getDefaultDisplayContentLocked().makeOverlay()
.setName(SURFACE_TITLE)
.setSize(mTempPoint.x, mTempPoint.y) // not a typo
.setFormat(PixelFormat.TRANSLUCENT)
@@ -714,8 +719,6 @@
/* ignore */
}
mSurfaceControl = surfaceControl;
- mSurfaceControl.setLayerStack(mWindowManager.getDefaultDisplay()
- .getLayerStack());
mSurfaceControl.setLayer(mService.mPolicy.getWindowLayerFromTypeLw(
TYPE_MAGNIFICATION_OVERLAY)
* WindowManagerService.TYPE_LAYER_MULTIPLIER);
@@ -1005,6 +1008,8 @@
private final long mRecurringAccessibilityEventsIntervalMillis;
+ private int mTempLayer = 0;
+
public WindowsForAccessibilityObserver(WindowManagerService windowManagerService,
WindowsForAccessibilityCallback callback) {
mContext = windowManagerService.mContext;
@@ -1090,6 +1095,7 @@
if (isReportedWindowType(windowState.mAttrs.type)) {
// Add the window to the ones to be reported.
WindowInfo window = obtainPopulatedWindowInfo(windowState, boundsInScreen);
+ window.layer = addedWindows.size();
addedWindows.add(window.token);
windows.add(window);
if (windowState.isFocused()) {
@@ -1323,9 +1329,10 @@
private void populateVisibleWindowsOnScreenLocked(SparseArray<WindowState> outWindows) {
final DisplayContent dc = mService.getDefaultDisplayContentLocked();
+ mTempLayer = 0;
dc.forAllWindows((w) -> {
if (w.isVisibleLw()) {
- outWindows.put(w.mLayer, w);
+ outWindows.put(mTempLayer++, w);
}
}, false /* traverseTopToBottom */ );
}
diff --git a/com/android/server/wm/AppTransition.java b/com/android/server/wm/AppTransition.java
index c19ede0..c2cbced 100644
--- a/com/android/server/wm/AppTransition.java
+++ b/com/android/server/wm/AppTransition.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-import static android.view.WindowManagerInternal.AppTransitionListener;
import static com.android.internal.R.styleable.WindowAnimation_activityCloseEnterAnimation;
import static com.android.internal.R.styleable.WindowAnimation_activityCloseExitAnimation;
import static com.android.internal.R.styleable.WindowAnimation_activityOpenEnterAnimation;
@@ -44,6 +43,7 @@
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowManagerInternal.AppTransitionListener;
import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM;
import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_NONE;
import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM;
diff --git a/com/android/server/wm/AppWindowAnimator.java b/com/android/server/wm/AppWindowAnimator.java
index 2ef7f25..5c1d5b2 100644
--- a/com/android/server/wm/AppWindowAnimator.java
+++ b/com/android/server/wm/AppWindowAnimator.java
@@ -16,7 +16,7 @@
package com.android.server.wm;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
import static com.android.server.wm.AppTransition.TRANSIT_UNSET;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
@@ -253,7 +253,6 @@
private void updateLayers() {
mAppToken.getDisplayContent().assignWindowLayers(false /* relayoutNeeded */);
- updateThumbnailLayer();
}
private void stepThumbnailAnimation(long currentTime) {
@@ -283,27 +282,12 @@
+ "][" + tmpFloats[Matrix.MSKEW_X]
+ "," + tmpFloats[Matrix.MSCALE_Y] + "]");
thumbnail.setAlpha(thumbnailTransformation.getAlpha());
- updateThumbnailLayer();
thumbnail.setMatrix(tmpFloats[Matrix.MSCALE_X], tmpFloats[Matrix.MSKEW_Y],
tmpFloats[Matrix.MSKEW_X], tmpFloats[Matrix.MSCALE_Y]);
thumbnail.setWindowCrop(thumbnailTransformation.getClipRect());
}
/**
- * Updates the thumbnail layer z order to just above the highest animation layer if changed
- */
- void updateThumbnailLayer() {
- if (thumbnail != null) {
- final int layer = mAppToken.getHighestAnimLayer();
- if (DEBUG_LAYERS) Slog.v(TAG,
- "Setting thumbnail layer " + mAppToken + ": layer=" + layer);
- thumbnail.setLayer(layer + WindowManagerService.WINDOW_LAYER_MULTIPLIER
- - WindowManagerService.LAYER_OFFSET_THUMBNAIL);
- mThumbnailLayer = layer;
- }
- }
-
- /**
* Sometimes we need to synchronize the first frame of animation with some external event, e.g.
* Recents hiding some of its content. To achieve this, we prolong the start of the animaiton
* and keep producing the first frame of the animation.
diff --git a/com/android/server/wm/AppWindowContainerController.java b/com/android/server/wm/AppWindowContainerController.java
index 5841840..00a0d3d 100644
--- a/com/android/server/wm/AppWindowContainerController.java
+++ b/com/android/server/wm/AppWindowContainerController.java
@@ -45,10 +45,11 @@
import android.util.Slog;
import android.view.DisplayInfo;
import android.view.IApplicationToken;
-import android.view.WindowManagerPolicy.StartingSurface;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.AttributeCache;
+import com.android.server.policy.WindowManagerPolicy.StartingSurface;
+
/**
* Controller for the app window token container. This is created by activity manager to link
* activity records to the app window token container they use in window manager.
@@ -180,13 +181,12 @@
IApplicationToken token, AppWindowContainerListener listener, int index,
int requestedOrientation, boolean fullscreen, boolean showForAllUsers, int configChanges,
boolean voiceInteraction, boolean launchTaskBehind, boolean alwaysFocusable,
- int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos,
- Rect bounds) {
+ int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos) {
this(taskController, token, listener, index, requestedOrientation, fullscreen,
showForAllUsers,
configChanges, voiceInteraction, launchTaskBehind, alwaysFocusable,
targetSdkVersion, rotationAnimationHint, inputDispatchingTimeoutNanos,
- WindowManagerService.getInstance(), bounds);
+ WindowManagerService.getInstance());
}
public AppWindowContainerController(TaskWindowContainerController taskController,
@@ -194,7 +194,7 @@
int requestedOrientation, boolean fullscreen, boolean showForAllUsers, int configChanges,
boolean voiceInteraction, boolean launchTaskBehind, boolean alwaysFocusable,
int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos,
- WindowManagerService service, Rect bounds) {
+ WindowManagerService service) {
super(listener, service);
mHandler = new H(service.mH.getLooper());
mToken = token;
@@ -215,7 +215,7 @@
atoken = createAppWindow(mService, token, voiceInteraction, task.getDisplayContent(),
inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdkVersion,
requestedOrientation, rotationAnimationHint, configChanges, launchTaskBehind,
- alwaysFocusable, this, bounds);
+ alwaysFocusable, this);
if (DEBUG_TOKEN_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "addAppToken: " + atoken
+ " controller=" + taskController + " at " + index);
task.addChild(atoken, index);
@@ -227,11 +227,11 @@
boolean voiceInteraction, DisplayContent dc, long inputDispatchingTimeoutNanos,
boolean fullscreen, boolean showForAllUsers, int targetSdk, int orientation,
int rotationAnimationHint, int configChanges, boolean launchTaskBehind,
- boolean alwaysFocusable, AppWindowContainerController controller, Rect bounds) {
+ boolean alwaysFocusable, AppWindowContainerController controller) {
return new AppWindowToken(service, token, voiceInteraction, dc,
inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdk, orientation,
rotationAnimationHint, configChanges, launchTaskBehind, alwaysFocusable,
- controller, bounds);
+ controller);
}
public void removeContainer(int displayId) {
@@ -298,17 +298,6 @@
}
}
- // TODO(b/36505427): Maybe move to WindowContainerController so other sub-classes can use it as
- // a generic way to set override config. Need to untangle current ways the override config is
- // currently set for tasks and displays before we are doing that though.
- public void onOverrideConfigurationChanged(Configuration overrideConfiguration, Rect bounds) {
- synchronized(mWindowMap) {
- if (mContainer != null) {
- mContainer.onOverrideConfigurationChanged(overrideConfiguration, bounds);
- }
- }
- }
-
public void setDisablePreviewScreenshots(boolean disable) {
synchronized (mWindowMap) {
if (mContainer == null) {
diff --git a/com/android/server/wm/AppWindowToken.java b/com/android/server/wm/AppWindowToken.java
index 98db80e..c39ce98 100644
--- a/com/android/server/wm/AppWindowToken.java
+++ b/com/android/server/wm/AppWindowToken.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
@@ -28,8 +27,8 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.AppTransition.TRANSIT_UNSET;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
@@ -67,10 +66,10 @@
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
-import android.view.WindowManagerPolicy.StartingSurface;
import com.android.internal.util.ToBooleanFunction;
import com.android.server.input.InputApplicationHandle;
+import com.android.server.policy.WindowManagerPolicy.StartingSurface;
import com.android.server.wm.WindowManagerService.H;
import java.io.PrintWriter;
@@ -176,11 +175,6 @@
private boolean mLastContainsShowWhenLockedWindow;
private boolean mLastContainsDismissKeyguardWindow;
- // The bounds of this activity. Mainly used for aspect-ratio compatibility.
- // TODO(b/36505427): Every level on WindowContainer now has bounds information, which directly
- // affects the configuration. We should probably move this into that class.
- private final Rect mBounds = new Rect();
-
ArrayDeque<Rect> mFrozenBounds = new ArrayDeque<>();
ArrayDeque<Configuration> mFrozenMergedConfig = new ArrayDeque<>();
@@ -197,8 +191,8 @@
DisplayContent dc, long inputDispatchingTimeoutNanos, boolean fullscreen,
boolean showForAllUsers, int targetSdk, int orientation, int rotationAnimationHint,
int configChanges, boolean launchTaskBehind, boolean alwaysFocusable,
- AppWindowContainerController controller, Rect bounds) {
- this(service, token, voiceInteraction, dc, fullscreen, bounds);
+ AppWindowContainerController controller) {
+ this(service, token, voiceInteraction, dc, fullscreen);
setController(controller);
mInputDispatchingTimeoutNanos = inputDispatchingTimeoutNanos;
mShowForAllUsers = showForAllUsers;
@@ -215,7 +209,7 @@
}
AppWindowToken(WindowManagerService service, IApplicationToken token, boolean voiceInteraction,
- DisplayContent dc, boolean fillsParent, Rect bounds) {
+ DisplayContent dc, boolean fillsParent) {
super(service, token != null ? token.asBinder() : null, TYPE_APPLICATION, true, dc,
false /* ownerCanManageAppTokens */);
appToken = token;
@@ -223,27 +217,6 @@
mFillsParent = fillsParent;
mInputApplicationHandle = new InputApplicationHandle(this);
mAppAnimator = new AppWindowAnimator(this, service);
- if (bounds != null) {
- mBounds.set(bounds);
- }
- }
-
- void onOverrideConfigurationChanged(Configuration overrideConfiguration, Rect bounds) {
- onOverrideConfigurationChanged(overrideConfiguration);
- if (mBounds.equals(bounds)) {
- return;
- }
- // TODO(b/36505427): If bounds is in WC, then we can automatically call onResize() when set.
- mBounds.set(bounds);
- onResize();
- }
-
- void getBounds(Rect outBounds) {
- outBounds.set(mBounds);
- }
-
- boolean hasBounds() {
- return !mBounds.isEmpty();
}
void onFirstWindowDrawn(WindowState win, WindowStateAnimator winAnimator) {
diff --git a/com/android/server/wm/BlackFrame.java b/com/android/server/wm/BlackFrame.java
index 9729e50..f19cd0f 100644
--- a/com/android/server/wm/BlackFrame.java
+++ b/com/android/server/wm/BlackFrame.java
@@ -30,7 +30,6 @@
import android.util.Slog;
import android.view.Surface.OutOfResourcesException;
import android.view.SurfaceControl;
-import android.view.SurfaceSession;
/**
* Four black surfaces put together to make a black frame.
@@ -42,22 +41,22 @@
final int layer;
final SurfaceControl surface;
- BlackSurface(SurfaceSession session, int layer, int l, int t, int r, int b, int layerStack)
- throws OutOfResourcesException {
+ BlackSurface(int layer,
+ int l, int t, int r, int b, DisplayContent dc) throws OutOfResourcesException {
left = l;
top = t;
this.layer = layer;
int w = r-l;
int h = b-t;
- surface = new SurfaceControl.Builder(session)
+ surface = dc.makeOverlay()
.setName("BlackSurface")
.setSize(w, h)
.setColorLayer(true)
+ .setParent(null) // TODO: Work-around for b/69259549
.build();
surface.setAlpha(1);
- surface.setLayerStack(layerStack);
surface.setLayer(layer);
surface.show();
if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) Slog.i(TAG_WM,
@@ -114,30 +113,32 @@
}
}
- public BlackFrame(SurfaceSession session, Rect outer, Rect inner, int layer, int layerStack,
+ public BlackFrame(Rect outer, Rect inner, int layer, DisplayContent dc,
boolean forceDefaultOrientation) throws OutOfResourcesException {
boolean success = false;
mForceDefaultOrientation = forceDefaultOrientation;
+ // TODO: Why do we use 4 surfaces instead of just one big one behind the screenshot?
+ // b/68253229
mOuterRect = new Rect(outer);
mInnerRect = new Rect(inner);
try {
if (outer.top < inner.top) {
- mBlackSurfaces[0] = new BlackSurface(session, layer,
- outer.left, outer.top, inner.right, inner.top, layerStack);
+ mBlackSurfaces[0] = new BlackSurface(layer,
+ outer.left, outer.top, inner.right, inner.top, dc);
}
if (outer.left < inner.left) {
- mBlackSurfaces[1] = new BlackSurface(session, layer,
- outer.left, inner.top, inner.left, outer.bottom, layerStack);
+ mBlackSurfaces[1] = new BlackSurface(layer,
+ outer.left, inner.top, inner.left, outer.bottom, dc);
}
if (outer.bottom > inner.bottom) {
- mBlackSurfaces[2] = new BlackSurface(session, layer,
- inner.left, inner.bottom, outer.right, outer.bottom, layerStack);
+ mBlackSurfaces[2] = new BlackSurface(layer,
+ inner.left, inner.bottom, outer.right, outer.bottom, dc);
}
if (outer.right > inner.right) {
- mBlackSurfaces[3] = new BlackSurface(session, layer,
- inner.right, outer.top, outer.right, inner.bottom, layerStack);
+ mBlackSurfaces[3] = new BlackSurface(layer,
+ inner.right, outer.top, outer.right, inner.bottom, dc);
}
success = true;
} finally {
diff --git a/com/android/server/wm/BoundsAnimationController.java b/com/android/server/wm/BoundsAnimationController.java
index 7953ee4..ba67ff6 100644
--- a/com/android/server/wm/BoundsAnimationController.java
+++ b/com/android/server/wm/BoundsAnimationController.java
@@ -33,7 +33,6 @@
import android.util.Slog;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
-import android.view.WindowManagerInternal;
import com.android.internal.annotations.VisibleForTesting;
diff --git a/com/android/server/wm/CircularDisplayMask.java b/com/android/server/wm/CircularDisplayMask.java
index 2d5d1b2..2a216ab 100644
--- a/com/android/server/wm/CircularDisplayMask.java
+++ b/com/android/server/wm/CircularDisplayMask.java
@@ -33,7 +33,6 @@
import android.view.Surface;
import android.view.Surface.OutOfResourcesException;
import android.view.SurfaceControl;
-import android.view.SurfaceSession;
class CircularDisplayMask {
private static final String TAG = TAG_WITH_CLASS_NAME ? "CircularDisplayMask" : TAG_WM;
@@ -54,8 +53,10 @@
private boolean mDimensionsUnequal = false;
private int mMaskThickness;
- public CircularDisplayMask(Display display, SurfaceSession session, int zOrder,
+ public CircularDisplayMask(DisplayContent dc, int zOrder,
int screenOffset, int maskThickness) {
+ final Display display = dc.getDisplay();
+
mScreenSize = new Point();
display.getSize(mScreenSize);
if (mScreenSize.x != mScreenSize.y + screenOffset) {
@@ -66,7 +67,7 @@
SurfaceControl ctrl = null;
try {
- ctrl = new SurfaceControl.Builder(session)
+ ctrl = dc.makeOverlay()
.setName("CircularDisplayMask")
.setSize(mScreenSize.x, mScreenSize.y) // not a typo
.setFormat(PixelFormat.TRANSLUCENT)
diff --git a/com/android/server/wm/ConfigurationContainer.java b/com/android/server/wm/ConfigurationContainer.java
index cc94807..d340923 100644
--- a/com/android/server/wm/ConfigurationContainer.java
+++ b/com/android/server/wm/ConfigurationContainer.java
@@ -36,6 +36,7 @@
import android.annotation.CallSuper;
import android.app.WindowConfiguration;
import android.content.res.Configuration;
+import android.graphics.Rect;
import android.util.proto.ProtoOutputStream;
import java.io.PrintWriter;
@@ -46,6 +47,11 @@
* hierarchy.
*/
public abstract class ConfigurationContainer<E extends ConfigurationContainer> {
+ /**
+ * {@link #Rect} returned from {@link #getOverrideBounds()} to prevent original value from being
+ * set directly.
+ */
+ private Rect mReturnBounds = new Rect();
/** Contains override configuration settings applied to this configuration container. */
private Configuration mOverrideConfiguration = new Configuration();
@@ -71,6 +77,16 @@
// TODO: Can't have ag/2592611 soon enough!
private final Configuration mTmpConfig = new Configuration();
+ // Used for setting bounds
+ private final Rect mTmpRect = new Rect();
+
+ static final int BOUNDS_CHANGE_NONE = 0;
+ // Return value from {@link setBounds} indicating the position of the override bounds changed.
+ static final int BOUNDS_CHANGE_POSITION = 1;
+ // Return value from {@link setBounds} indicating the size of the override bounds changed.
+ static final int BOUNDS_CHANGE_SIZE = 1 << 1;
+
+
/**
* Returns full configuration applied to this configuration container.
* This method should be used for getting settings applied in each particular level of the
@@ -148,6 +164,118 @@
}
}
+ /**
+ * Indicates whether this container has not specified any bounds different from its parent. In
+ * this case, it will inherit the bounds of the first ancestor which specifies a bounds.
+ * @return {@code true} if no explicit bounds have been set at this container level.
+ * {@code false} otherwise.
+ */
+ public boolean matchParentBounds() {
+ return getOverrideBounds().isEmpty();
+ }
+
+ /**
+ * Returns whether the bounds specified is considered the same as the existing override bounds.
+ * This is either when the two bounds are equal or the override bounds is empty and the
+ * specified bounds is null.
+ *
+ * @return {@code true} if the bounds are equivalent, {@code false} otherwise
+ */
+ public boolean equivalentOverrideBounds(Rect bounds) {
+ return equivalentBounds(getOverrideBounds(), bounds);
+ }
+
+ /**
+ * Returns whether the two bounds are equal to each other or are a combination of null or empty.
+ */
+ public static boolean equivalentBounds(Rect bounds, Rect other) {
+ return bounds == other
+ || (bounds != null && (bounds.equals(other) || (bounds.isEmpty() && other == null)))
+ || (other != null && other.isEmpty() && bounds == null);
+ }
+
+ /**
+ * Returns the effective bounds of this container, inheriting the first non-empty bounds set in
+ * its ancestral hierarchy, including itself.
+ * @return
+ */
+ public Rect getBounds() {
+ mReturnBounds.set(getConfiguration().windowConfiguration.getBounds());
+ return mReturnBounds;
+ }
+
+ public void getBounds(Rect outBounds) {
+ outBounds.set(getBounds());
+ }
+
+ /**
+ * Returns the current bounds explicitly set on this container. The {@link Rect} handed back is
+ * shared for all calls to this method and should not be modified.
+ */
+ public Rect getOverrideBounds() {
+ mReturnBounds.set(getOverrideConfiguration().windowConfiguration.getBounds());
+
+ return mReturnBounds;
+ }
+
+ /**
+ * Sets the passed in {@link Rect} to the current bounds.
+ * @see {@link #getOverrideBounds()}.
+ */
+ public void getOverrideBounds(Rect outBounds) {
+ outBounds.set(getOverrideBounds());
+ }
+
+ /**
+ * Sets the bounds at the current hierarchy level, overriding any bounds set on an ancestor.
+ * This value will be reported when {@link #getBounds()} and {@link #getOverrideBounds()}. If
+ * an empty {@link Rect} or null is specified, this container will be considered to match its
+ * parent bounds {@see #matchParentBounds} and will inherit bounds from its parent.
+ * @param bounds The bounds defining the container size.
+ * @return a bitmask representing the types of changes made to the bounds.
+ */
+ public int setBounds(Rect bounds) {
+ int boundsChange = diffOverrideBounds(bounds);
+
+ if (boundsChange == BOUNDS_CHANGE_NONE) {
+ return boundsChange;
+ }
+
+
+ mTmpConfig.setTo(getOverrideConfiguration());
+ mTmpConfig.windowConfiguration.setBounds(bounds);
+ onOverrideConfigurationChanged(mTmpConfig);
+
+ return boundsChange;
+ }
+
+ public int setBounds(int left, int top, int right, int bottom) {
+ mTmpRect.set(left, top, right, bottom);
+ return setBounds(mTmpRect);
+ }
+
+ int diffOverrideBounds(Rect bounds) {
+ if (equivalentOverrideBounds(bounds)) {
+ return BOUNDS_CHANGE_NONE;
+ }
+
+ int boundsChange = BOUNDS_CHANGE_NONE;
+
+ final Rect existingBounds = getOverrideBounds();
+
+ if (bounds == null || existingBounds.left != bounds.left
+ || existingBounds.top != bounds.top) {
+ boundsChange |= BOUNDS_CHANGE_POSITION;
+ }
+
+ if (bounds == null || existingBounds.width() != bounds.width()
+ || existingBounds.height() != bounds.height()) {
+ boundsChange |= BOUNDS_CHANGE_SIZE;
+ }
+
+ return boundsChange;
+ }
+
public WindowConfiguration getWindowConfiguration() {
return mFullConfiguration.windowConfiguration;
}
@@ -375,6 +503,10 @@
return toString();
}
+ boolean isAlwaysOnTop() {
+ return mFullConfiguration.windowConfiguration.isAlwaysOnTop();
+ }
+
abstract protected int getChildCount();
abstract protected E getChildAt(int index);
diff --git a/com/android/server/wm/DimLayer.java b/com/android/server/wm/DimLayer.java
deleted file mode 100644
index 8fb2be8..0000000
--- a/com/android/server/wm/DimLayer.java
+++ /dev/null
@@ -1,380 +0,0 @@
-/*
- * Copyright (C) 2014 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.server.wm;
-
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DIM_LAYER;
-import static com.android.server.wm.WindowManagerDebugConfig.SHOW_SURFACE_ALLOC;
-import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.os.SystemClock;
-import android.util.Slog;
-import android.view.DisplayInfo;
-import android.view.SurfaceControl;
-
-import java.io.PrintWriter;
-
-public class DimLayer {
- private static final String TAG = TAG_WITH_CLASS_NAME ? "DimLayer" : TAG_WM;
- private final WindowManagerService mService;
-
- /** Actual surface that dims */
- private SurfaceControl mDimSurface;
-
- /** Last value passed to mDimSurface.setAlpha() */
- private float mAlpha = 0;
-
- /** Last value passed to mDimSurface.setLayer() */
- private int mLayer = -1;
-
- /** Next values to pass to mDimSurface.setPosition() and mDimSurface.setSize() */
- private final Rect mBounds = new Rect();
-
- /** Last values passed to mDimSurface.setPosition() and mDimSurface.setSize() */
- private final Rect mLastBounds = new Rect();
-
- /** True after mDimSurface.show() has been called, false after mDimSurface.hide(). */
- private boolean mShowing = false;
-
- /** Value of mAlpha when beginning transition to mTargetAlpha */
- private float mStartAlpha = 0;
-
- /** Final value of mAlpha following transition */
- private float mTargetAlpha = 0;
-
- /** Time in units of SystemClock.uptimeMillis() at which the current transition started */
- private long mStartTime;
-
- /** Time in milliseconds to take to transition from mStartAlpha to mTargetAlpha */
- private long mDuration;
-
- private boolean mDestroyed = false;
-
- private final int mDisplayId;
-
-
- /** Interface implemented by users of the dim layer */
- interface DimLayerUser {
- /** Returns true if the dim should be fullscreen. */
- boolean dimFullscreen();
- /** Returns the display info. of the dim layer user. */
- DisplayInfo getDisplayInfo();
- /** Returns true if the dim layer user is currently attached to a display */
- boolean isAttachedToDisplay();
- /** Gets the bounds of the dim layer user. */
- void getDimBounds(Rect outBounds);
- /** Returns the layer to place a dim layer. */
- default int getLayerForDim(WindowStateAnimator animator, int layerOffset,
- int defaultLayer) {
- return defaultLayer;
- }
-
- String toShortString();
- }
- /** The user of this dim layer. */
- private final DimLayerUser mUser;
-
- private final String mName;
-
- DimLayer(WindowManagerService service, DimLayerUser user, int displayId, String name) {
- mUser = user;
- mDisplayId = displayId;
- mService = service;
- mName = name;
- if (DEBUG_DIM_LAYER) Slog.v(TAG, "Ctor: displayId=" + displayId);
- }
-
- private void constructSurface(WindowManagerService service) {
- service.openSurfaceTransaction();
- try {
- mDimSurface = new SurfaceControl.Builder(service.mFxSession)
- .setName(mName)
- .setSize(16, 16)
- .setColorLayer(true)
- .build();
-
- if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) Slog.i(TAG,
- " DIM " + mDimSurface + ": CREATE");
- mDimSurface.setLayerStack(mDisplayId);
- adjustBounds();
- adjustAlpha(mAlpha);
- adjustLayer(mLayer);
- } catch (Exception e) {
- Slog.e(TAG_WM, "Exception creating Dim surface", e);
- } finally {
- service.closeSurfaceTransaction("DimLayer.constructSurface");
- }
- }
-
- /** Return true if dim layer is showing */
- boolean isDimming() {
- return mTargetAlpha != 0;
- }
-
- /** Return true if in a transition period */
- boolean isAnimating() {
- return mTargetAlpha != mAlpha;
- }
-
- float getTargetAlpha() {
- return mTargetAlpha;
- }
-
- void setLayer(int layer) {
- if (mLayer == layer) {
- return;
- }
- mLayer = layer;
- adjustLayer(layer);
- }
-
- private void adjustLayer(int layer) {
- if (mDimSurface != null) {
- mDimSurface.setLayer(layer);
- }
- }
-
- int getLayer() {
- return mLayer;
- }
-
- private void setAlpha(float alpha) {
- if (mAlpha == alpha) {
- return;
- }
- mAlpha = alpha;
- adjustAlpha(alpha);
- }
-
- private void adjustAlpha(float alpha) {
- if (DEBUG_DIM_LAYER) Slog.v(TAG, "setAlpha alpha=" + alpha);
- try {
- if (mDimSurface != null) {
- mDimSurface.setAlpha(alpha);
- }
- if (alpha == 0 && mShowing) {
- if (DEBUG_DIM_LAYER) Slog.v(TAG, "setAlpha hiding");
- if (mDimSurface != null) {
- mDimSurface.hide();
- mShowing = false;
- }
- } else if (alpha > 0 && !mShowing) {
- if (DEBUG_DIM_LAYER) Slog.v(TAG, "setAlpha showing");
- if (mDimSurface != null) {
- mDimSurface.show();
- mShowing = true;
- }
- }
- } catch (RuntimeException e) {
- Slog.w(TAG, "Failure setting alpha immediately", e);
- }
- }
-
- /**
- * NOTE: Must be called with Surface transaction open.
- */
- private void adjustBounds() {
- if (mUser.dimFullscreen()) {
- getBoundsForFullscreen(mBounds);
- }
-
- if (mDimSurface != null) {
- mDimSurface.setPosition(mBounds.left, mBounds.top);
- mDimSurface.setSize(mBounds.width(), mBounds.height());
- if (DEBUG_DIM_LAYER) Slog.v(TAG,
- "adjustBounds user=" + mUser.toShortString() + " mBounds=" + mBounds);
- }
-
- mLastBounds.set(mBounds);
- }
-
- private void getBoundsForFullscreen(Rect outBounds) {
- final int dw, dh;
- final float xPos, yPos;
- // Set surface size to screen size.
- final DisplayInfo info = mUser.getDisplayInfo();
- // Multiply by 1.5 so that rotating a frozen surface that includes this does not expose
- // a corner.
- dw = (int) (info.logicalWidth * 1.5);
- dh = (int) (info.logicalHeight * 1.5);
- // back off position so 1/4 of Surface is before and 1/4 is after.
- xPos = -1 * dw / 6;
- yPos = -1 * dh / 6;
- outBounds.set((int) xPos, (int) yPos, (int) xPos + dw, (int) yPos + dh);
- }
-
- void setBoundsForFullscreen() {
- getBoundsForFullscreen(mBounds);
- setBounds(mBounds);
- }
-
- /** @param bounds The new bounds to set */
- void setBounds(Rect bounds) {
- mBounds.set(bounds);
- if (isDimming() && !mLastBounds.equals(bounds)) {
- try {
- mService.openSurfaceTransaction();
- adjustBounds();
- } catch (RuntimeException e) {
- Slog.w(TAG, "Failure setting size", e);
- } finally {
- mService.closeSurfaceTransaction("DimLayer.setBounds");
- }
- }
- }
-
- /**
- * @param duration The time to test.
- * @return True if the duration would lead to an earlier end to the current animation.
- */
- private boolean durationEndsEarlier(long duration) {
- return SystemClock.uptimeMillis() + duration < mStartTime + mDuration;
- }
-
- /** Jump to the end of the animation.
- * NOTE: Must be called with Surface transaction open. */
- void show() {
- if (isAnimating()) {
- if (DEBUG_DIM_LAYER) Slog.v(TAG, "show: immediate");
- show(mLayer, mTargetAlpha, 0);
- }
- }
-
- /**
- * Begin an animation to a new dim value.
- * NOTE: Must be called with Surface transaction open.
- *
- * @param layer The layer to set the surface to.
- * @param alpha The dim value to end at.
- * @param duration How long to take to get there in milliseconds.
- */
- void show(int layer, float alpha, long duration) {
- if (DEBUG_DIM_LAYER) Slog.v(TAG, "show: layer=" + layer + " alpha=" + alpha
- + " duration=" + duration + ", mDestroyed=" + mDestroyed);
- if (mDestroyed) {
- Slog.e(TAG, "show: no Surface");
- // Make sure isAnimating() returns false.
- mTargetAlpha = mAlpha = 0;
- return;
- }
-
- if (mDimSurface == null) {
- constructSurface(mService);
- }
-
- if (!mLastBounds.equals(mBounds)) {
- adjustBounds();
- }
- setLayer(layer);
-
- long curTime = SystemClock.uptimeMillis();
- final boolean animating = isAnimating();
- if ((animating && (mTargetAlpha != alpha || durationEndsEarlier(duration)))
- || (!animating && mAlpha != alpha)) {
- if (duration <= 0) {
- // No animation required, just set values.
- setAlpha(alpha);
- } else {
- // Start or continue animation with new parameters.
- mStartAlpha = mAlpha;
- mStartTime = curTime;
- mDuration = duration;
- }
- }
- mTargetAlpha = alpha;
- if (DEBUG_DIM_LAYER) Slog.v(TAG, "show: mStartAlpha=" + mStartAlpha + " mStartTime="
- + mStartTime + " mTargetAlpha=" + mTargetAlpha);
- }
-
- /** Immediate hide.
- * NOTE: Must be called with Surface transaction open. */
- void hide() {
- if (mShowing) {
- if (DEBUG_DIM_LAYER) Slog.v(TAG, "hide: immediate");
- hide(0);
- }
- }
-
- /**
- * Gradually fade to transparent.
- * NOTE: Must be called with Surface transaction open.
- *
- * @param duration Time to fade in milliseconds.
- */
- void hide(long duration) {
- if (mShowing && (mTargetAlpha != 0 || durationEndsEarlier(duration))) {
- if (DEBUG_DIM_LAYER) Slog.v(TAG, "hide: duration=" + duration);
- show(mLayer, 0, duration);
- }
- }
-
- /**
- * Advance the dimming per the last #show(int, float, long) call.
- * NOTE: Must be called with Surface transaction open.
- *
- * @return True if animation is still required after this step.
- */
- boolean stepAnimation() {
- if (mDestroyed) {
- Slog.e(TAG, "stepAnimation: surface destroyed");
- // Ensure that isAnimating() returns false;
- mTargetAlpha = mAlpha = 0;
- return false;
- }
- if (isAnimating()) {
- final long curTime = SystemClock.uptimeMillis();
- final float alphaDelta = mTargetAlpha - mStartAlpha;
- float alpha = mStartAlpha + alphaDelta * (curTime - mStartTime) / mDuration;
- if (alphaDelta > 0 && alpha > mTargetAlpha ||
- alphaDelta < 0 && alpha < mTargetAlpha) {
- // Don't exceed limits.
- alpha = mTargetAlpha;
- }
- if (DEBUG_DIM_LAYER) Slog.v(TAG, "stepAnimation: curTime=" + curTime + " alpha=" + alpha);
- setAlpha(alpha);
- }
-
- return isAnimating();
- }
-
- /** Cleanup */
- void destroySurface() {
- if (DEBUG_DIM_LAYER) Slog.v(TAG, "destroySurface.");
- if (mDimSurface != null) {
- mDimSurface.destroy();
- mDimSurface = null;
- }
- mDestroyed = true;
- }
-
- public void printTo(String prefix, PrintWriter pw) {
- pw.print(prefix); pw.print("mDimSurface="); pw.print(mDimSurface);
- pw.print(" mLayer="); pw.print(mLayer);
- pw.print(" mAlpha="); pw.println(mAlpha);
- pw.print(prefix); pw.print("mLastBounds="); pw.print(mLastBounds.toShortString());
- pw.print(" mBounds="); pw.println(mBounds.toShortString());
- pw.print(prefix); pw.print("Last animation: ");
- pw.print(" mDuration="); pw.print(mDuration);
- pw.print(" mStartTime="); pw.print(mStartTime);
- pw.print(" curTime="); pw.println(SystemClock.uptimeMillis());
- pw.print(prefix); pw.print(" mStartAlpha="); pw.print(mStartAlpha);
- pw.print(" mTargetAlpha="); pw.println(mTargetAlpha);
- }
-}
diff --git a/com/android/server/wm/DimLayerController.java b/com/android/server/wm/DimLayerController.java
deleted file mode 100644
index 6f9e45a..0000000
--- a/com/android/server/wm/DimLayerController.java
+++ /dev/null
@@ -1,403 +0,0 @@
-package com.android.server.wm;
-
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DIM_LAYER;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.LAYER_OFFSET_DIM;
-
-import android.graphics.Rect;
-import android.util.ArrayMap;
-import android.util.Slog;
-import android.util.TypedValue;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.wm.DimLayer.DimLayerUser;
-
-import java.io.PrintWriter;
-
-/**
- * Centralizes the control of dim layers used for
- * {@link android.view.WindowManager.LayoutParams#FLAG_DIM_BEHIND}
- * as well as other use cases (such as dimming above a dead window).
- */
-class DimLayerController {
- private static final String TAG_LOCAL = "DimLayerController";
- private static final String TAG = TAG_WITH_CLASS_NAME ? TAG_LOCAL : TAG_WM;
-
- /** Amount of time in milliseconds to animate the dim surface from one value to another,
- * when no window animation is driving it. */
- private static final int DEFAULT_DIM_DURATION = 200;
-
- /**
- * The default amount of dim applied over a dead window
- */
- private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f;
-
- // Shared dim layer for fullscreen users. {@link DimLayerState#dimLayer} will point to this
- // instead of creating a new object per fullscreen task on a display.
- private DimLayer mSharedFullScreenDimLayer;
-
- private ArrayMap<DimLayer.DimLayerUser, DimLayerState> mState = new ArrayMap<>();
-
- private DisplayContent mDisplayContent;
-
- private Rect mTmpBounds = new Rect();
-
- DimLayerController(DisplayContent displayContent) {
- mDisplayContent = displayContent;
- }
-
- /** Updates the dim layer bounds, recreating it if needed. */
- void updateDimLayer(DimLayer.DimLayerUser dimLayerUser) {
- final DimLayerState state = getOrCreateDimLayerState(dimLayerUser);
- final boolean previousFullscreen = state.dimLayer != null
- && state.dimLayer == mSharedFullScreenDimLayer;
- DimLayer newDimLayer;
- final int displayId = mDisplayContent.getDisplayId();
- if (dimLayerUser.dimFullscreen()) {
- if (previousFullscreen && mSharedFullScreenDimLayer != null) {
- // Update the bounds for fullscreen in case of rotation.
- mSharedFullScreenDimLayer.setBoundsForFullscreen();
- return;
- }
- // Use shared fullscreen dim layer
- newDimLayer = mSharedFullScreenDimLayer;
- if (newDimLayer == null) {
- if (state.dimLayer != null) {
- // Re-purpose the previous dim layer.
- newDimLayer = state.dimLayer;
- } else {
- // Create new full screen dim layer.
- newDimLayer = new DimLayer(mDisplayContent.mService, dimLayerUser, displayId,
- getDimLayerTag(dimLayerUser));
- }
- dimLayerUser.getDimBounds(mTmpBounds);
- newDimLayer.setBounds(mTmpBounds);
- mSharedFullScreenDimLayer = newDimLayer;
- } else if (state.dimLayer != null) {
- state.dimLayer.destroySurface();
- }
- } else {
- newDimLayer = (state.dimLayer == null || previousFullscreen)
- ? new DimLayer(mDisplayContent.mService, dimLayerUser, displayId,
- getDimLayerTag(dimLayerUser))
- : state.dimLayer;
- dimLayerUser.getDimBounds(mTmpBounds);
- newDimLayer.setBounds(mTmpBounds);
- }
- state.dimLayer = newDimLayer;
- }
-
- private static String getDimLayerTag(DimLayerUser dimLayerUser) {
- return TAG_LOCAL + "/" + dimLayerUser.toShortString();
- }
-
- private DimLayerState getOrCreateDimLayerState(DimLayer.DimLayerUser dimLayerUser) {
- if (DEBUG_DIM_LAYER) Slog.v(TAG, "getOrCreateDimLayerState, dimLayerUser="
- + dimLayerUser.toShortString());
- DimLayerState state = mState.get(dimLayerUser);
- if (state == null) {
- state = new DimLayerState();
- mState.put(dimLayerUser, state);
- }
- return state;
- }
-
- private void setContinueDimming(DimLayer.DimLayerUser dimLayerUser) {
- DimLayerState state = mState.get(dimLayerUser);
- if (state == null) {
- if (DEBUG_DIM_LAYER) Slog.w(TAG, "setContinueDimming, no state for: "
- + dimLayerUser.toShortString());
- return;
- }
- state.continueDimming = true;
- }
-
- boolean isDimming() {
- for (int i = mState.size() - 1; i >= 0; i--) {
- DimLayerState state = mState.valueAt(i);
- if (state.dimLayer != null && state.dimLayer.isDimming()) {
- return true;
- }
- }
- return false;
- }
-
- void resetDimming() {
- for (int i = mState.size() - 1; i >= 0; i--) {
- mState.valueAt(i).continueDimming = false;
- }
- }
-
- private boolean getContinueDimming(DimLayer.DimLayerUser dimLayerUser) {
- DimLayerState state = mState.get(dimLayerUser);
- return state != null && state.continueDimming;
- }
-
- void startDimmingIfNeeded(DimLayer.DimLayerUser dimLayerUser,
- WindowStateAnimator newWinAnimator, boolean aboveApp) {
- // Only set dim params on the highest dimmed layer.
- // Don't turn on for an unshown surface, or for any layer but the highest dimmed layer.
- DimLayerState state = getOrCreateDimLayerState(dimLayerUser);
- state.dimAbove = aboveApp;
- if (DEBUG_DIM_LAYER) Slog.v(TAG, "startDimmingIfNeeded,"
- + " dimLayerUser=" + dimLayerUser.toShortString()
- + " newWinAnimator=" + newWinAnimator
- + " state.animator=" + state.animator);
- if (newWinAnimator.getShown() && (state.animator == null
- || !state.animator.getShown()
- || state.animator.mAnimLayer <= newWinAnimator.mAnimLayer)) {
- state.animator = newWinAnimator;
- if (state.animator.mWin.mAppToken == null && !dimLayerUser.dimFullscreen()) {
- // Dim should cover the entire screen for system windows.
- mDisplayContent.getLogicalDisplayRect(mTmpBounds);
- } else {
- dimLayerUser.getDimBounds(mTmpBounds);
- }
- state.dimLayer.setBounds(mTmpBounds);
- }
- }
-
- void stopDimmingIfNeeded() {
- if (DEBUG_DIM_LAYER) Slog.v(TAG, "stopDimmingIfNeeded, mState.size()=" + mState.size());
- for (int i = mState.size() - 1; i >= 0; i--) {
- DimLayer.DimLayerUser dimLayerUser = mState.keyAt(i);
- stopDimmingIfNeeded(dimLayerUser);
- }
- }
-
- private void stopDimmingIfNeeded(DimLayer.DimLayerUser dimLayerUser) {
- // No need to check if state is null, we know the key has a value.
- DimLayerState state = mState.get(dimLayerUser);
- if (DEBUG_DIM_LAYER) Slog.v(TAG, "stopDimmingIfNeeded,"
- + " dimLayerUser=" + dimLayerUser.toShortString()
- + " state.continueDimming=" + state.continueDimming
- + " state.dimLayer.isDimming=" + state.dimLayer.isDimming());
- if (state.animator != null && state.animator.mWin.mWillReplaceWindow) {
- return;
- }
-
- if (!state.continueDimming && state.dimLayer.isDimming()) {
- state.animator = null;
- dimLayerUser.getDimBounds(mTmpBounds);
- state.dimLayer.setBounds(mTmpBounds);
- }
- }
-
- boolean animateDimLayers() {
- int fullScreen = -1;
- int fullScreenAndDimming = -1;
- int topFullScreenUserLayer = 0;
- boolean result = false;
-
- for (int i = mState.size() - 1; i >= 0; i--) {
- final DimLayer.DimLayerUser user = mState.keyAt(i);
- final DimLayerState state = mState.valueAt(i);
-
- if (!user.isAttachedToDisplay()) {
- // Leaked dim user that is no longer attached to the display. Go ahead and clean it
- // clean-up and log what happened.
- // TODO: This is a work around for b/34395537 as the dim user should have cleaned-up
- // it self when it was detached from the display. Need to investigate how the dim
- // user is leaking...
- //Slog.wtfStack(TAG_WM, "Leaked dim user=" + user.toShortString()
- // + " state=" + state);
- Slog.w(TAG_WM, "Leaked dim user=" + user.toShortString() + " state=" + state);
- removeDimLayerUser(user);
- continue;
- }
-
- // We have to check that we are actually the shared fullscreen layer
- // for this path. If we began as non fullscreen and became fullscreen
- // (e.g. Docked stack closing), then we may not be the shared layer
- // and we have to make sure we always animate the layer.
- if (user.dimFullscreen() && state.dimLayer == mSharedFullScreenDimLayer) {
- fullScreen = i;
- if (!state.continueDimming) {
- continue;
- }
-
- // When choosing which user to assign the shared fullscreen layer to
- // we need to look at Z-order.
- if (topFullScreenUserLayer == 0 ||
- (state.animator != null && state.animator.mAnimLayer > topFullScreenUserLayer)) {
- fullScreenAndDimming = i;
- if (state.animator != null) {
- topFullScreenUserLayer = state.animator.mAnimLayer;
- }
- }
- } else {
- // We always want to animate the non fullscreen windows, they don't share their
- // dim layers.
- result |= animateDimLayers(user);
- }
- }
- // For the shared, full screen dim layer, we prefer the animation that is causing it to
- // appear.
- if (fullScreenAndDimming != -1) {
- result |= animateDimLayers(mState.keyAt(fullScreenAndDimming));
- } else if (fullScreen != -1) {
- // If there is no animation for the full screen dim layer to appear, we can use any of
- // the animators that will cause it to disappear.
- result |= animateDimLayers(mState.keyAt(fullScreen));
- }
- return result;
- }
-
- private boolean animateDimLayers(DimLayer.DimLayerUser dimLayerUser) {
- DimLayerState state = mState.get(dimLayerUser);
- if (DEBUG_DIM_LAYER) Slog.v(TAG, "animateDimLayers,"
- + " dimLayerUser=" + dimLayerUser.toShortString()
- + " state.animator=" + state.animator
- + " state.continueDimming=" + state.continueDimming);
- final int dimLayer;
- final float dimAmount;
- if (state.animator == null) {
- dimLayer = state.dimLayer.getLayer();
- dimAmount = 0;
- } else {
- if (state.dimAbove) {
- dimLayer = state.animator.mAnimLayer + LAYER_OFFSET_DIM;
- dimAmount = DEFAULT_DIM_AMOUNT_DEAD_WINDOW;
- } else {
- dimLayer = dimLayerUser.getLayerForDim(state.animator, LAYER_OFFSET_DIM,
- state.animator.mAnimLayer - LAYER_OFFSET_DIM);
- dimAmount = state.animator.mWin.mAttrs.dimAmount;
- }
- }
- final float targetAlpha = state.dimLayer.getTargetAlpha();
- if (targetAlpha != dimAmount) {
- if (state.animator == null) {
- state.dimLayer.hide(DEFAULT_DIM_DURATION);
- } else {
- long duration = (state.animator.mAnimating && state.animator.mAnimation != null)
- ? state.animator.mAnimation.computeDurationHint()
- : DEFAULT_DIM_DURATION;
- if (targetAlpha > dimAmount) {
- duration = getDimLayerFadeDuration(duration);
- }
- state.dimLayer.show(dimLayer, dimAmount, duration);
-
- // If we showed a dim layer, make sure to redo the layout because some things depend
- // on whether a dim layer is showing or not.
- if (targetAlpha == 0) {
- mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_LAYOUT;
- mDisplayContent.setLayoutNeeded();
- }
- }
- } else if (state.dimLayer.getLayer() != dimLayer) {
- state.dimLayer.setLayer(dimLayer);
- }
- if (state.dimLayer.isAnimating()) {
- if (!mDisplayContent.okToAnimate()) {
- // Jump to the end of the animation.
- state.dimLayer.show();
- } else {
- return state.dimLayer.stepAnimation();
- }
- }
- return false;
- }
-
- boolean isDimming(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator winAnimator) {
- DimLayerState state = mState.get(dimLayerUser);
- return state != null && state.animator == winAnimator && state.dimLayer.isDimming();
- }
-
- private long getDimLayerFadeDuration(long duration) {
- TypedValue tv = new TypedValue();
- mDisplayContent.mService.mContext.getResources().getValue(
- com.android.internal.R.fraction.config_dimBehindFadeDuration, tv, true);
- if (tv.type == TypedValue.TYPE_FRACTION) {
- duration = (long) tv.getFraction(duration, duration);
- } else if (tv.type >= TypedValue.TYPE_FIRST_INT && tv.type <= TypedValue.TYPE_LAST_INT) {
- duration = tv.data;
- }
- return duration;
- }
-
- void close() {
- for (int i = mState.size() - 1; i >= 0; i--) {
- DimLayerState state = mState.valueAt(i);
- state.dimLayer.destroySurface();
- }
- mState.clear();
- mSharedFullScreenDimLayer = null;
- }
-
- void removeDimLayerUser(DimLayer.DimLayerUser dimLayerUser) {
- DimLayerState state = mState.get(dimLayerUser);
- if (state != null) {
- // Destroy the surface, unless it's the shared fullscreen dim.
- if (state.dimLayer != mSharedFullScreenDimLayer) {
- state.dimLayer.destroySurface();
- }
- mState.remove(dimLayerUser);
- }
- if (mState.isEmpty()) {
- mSharedFullScreenDimLayer = null;
- }
- }
-
- @VisibleForTesting
- boolean hasDimLayerUser(DimLayer.DimLayerUser dimLayerUser) {
- return mState.containsKey(dimLayerUser);
- }
-
- @VisibleForTesting
- boolean hasSharedFullScreenDimLayer() {
- return mSharedFullScreenDimLayer != null;
- }
-
- void applyDimBehind(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator) {
- applyDim(dimLayerUser, animator, false /* aboveApp */);
- }
-
- void applyDimAbove(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator) {
- applyDim(dimLayerUser, animator, true /* aboveApp */);
- }
-
- void applyDim(
- DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator, boolean aboveApp) {
- if (dimLayerUser == null) {
- Slog.e(TAG, "Trying to apply dim layer for: " + this
- + ", but no dim layer user found.");
- return;
- }
- if (!getContinueDimming(dimLayerUser)) {
- setContinueDimming(dimLayerUser);
- if (!isDimming(dimLayerUser, animator)) {
- if (DEBUG_DIM_LAYER) Slog.v(TAG, "Win " + this + " start dimming.");
- startDimmingIfNeeded(dimLayerUser, animator, aboveApp);
- }
- }
- }
-
- private static class DimLayerState {
- // The particular window requesting a dim layer. If null, hide dimLayer.
- WindowStateAnimator animator;
- // Set to false at the start of performLayoutAndPlaceSurfaces. If it is still false by the
- // end then stop any dimming.
- boolean continueDimming;
- DimLayer dimLayer;
- boolean dimAbove;
- }
-
- void dump(String prefix, PrintWriter pw) {
- pw.println(prefix + "DimLayerController");
- final String doubleSpace = " ";
- final String prefixPlusDoubleSpace = prefix + doubleSpace;
-
- for (int i = 0, n = mState.size(); i < n; i++) {
- pw.println(prefixPlusDoubleSpace + mState.keyAt(i).toShortString());
- DimLayerState state = mState.valueAt(i);
- pw.println(prefixPlusDoubleSpace + doubleSpace + "dimLayer="
- + (state.dimLayer == mSharedFullScreenDimLayer ? "shared" : state.dimLayer)
- + ", animator=" + state.animator + ", continueDimming=" + state.continueDimming);
- if (state.dimLayer != null) {
- state.dimLayer.printTo(prefixPlusDoubleSpace + doubleSpace, pw);
- }
- }
- }
-}
diff --git a/com/android/server/wm/Dimmer.java b/com/android/server/wm/Dimmer.java
new file mode 100644
index 0000000..9fe16ae
--- /dev/null
+++ b/com/android/server/wm/Dimmer.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2017 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.server.wm;
+
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.view.SurfaceControl;
+import android.graphics.Rect;
+
+/**
+ * Utility class for use by a WindowContainer implementation to add "DimLayer" support, that is
+ * black layers of varying opacity at various Z-levels which create the effect of a Dim.
+ */
+class Dimmer {
+ private static final String TAG = "WindowManager";
+
+ private class DimState {
+ SurfaceControl mSurfaceControl;
+ boolean mDimming;
+
+ /**
+ * Used for Dims not assosciated with a WindowContainer. See {@link Dimmer#dimAbove} for
+ * details on Dim lifecycle.
+ */
+ boolean mDontReset;
+
+ DimState(SurfaceControl ctl) {
+ mSurfaceControl = ctl;
+ mDimming = true;
+ }
+ };
+
+ private ArrayMap<WindowContainer, DimState> mDimLayerUsers = new ArrayMap<>();
+
+ /**
+ * The {@link WindowContainer} that our Dim's are bounded to. We may be dimming on behalf of the
+ * host, some controller of it, or one of the hosts children.
+ */
+ private WindowContainer mHost;
+
+ Dimmer(WindowContainer host) {
+ mHost = host;
+ }
+
+ SurfaceControl makeDimLayer() {
+ final SurfaceControl control = mHost.makeChildSurface(null)
+ .setParent(mHost.getSurfaceControl())
+ .setColorLayer(true)
+ .setName("Dim Layer for - " + mHost.getName())
+ .build();
+ return control;
+ }
+
+ /**
+ * Retreive the DimState for a given child of the host.
+ */
+ DimState getDimState(WindowContainer container) {
+ DimState state = mDimLayerUsers.get(container);
+ if (state == null) {
+ final SurfaceControl ctl = makeDimLayer();
+ state = new DimState(ctl);
+ /**
+ * See documentation on {@link #dimAbove} to understand lifecycle management of Dim's
+ * via state resetting for Dim's with containers.
+ */
+ if (container == null) {
+ state.mDontReset = true;
+ }
+ mDimLayerUsers.put(container, state);
+ }
+ return state;
+ }
+
+ private void dim(SurfaceControl.Transaction t, WindowContainer container, int relativeLayer,
+ float alpha) {
+ final DimState d = getDimState(container);
+ t.show(d.mSurfaceControl);
+ if (container != null) {
+ t.setRelativeLayer(d.mSurfaceControl,
+ container.getSurfaceControl(), relativeLayer);
+ } else {
+ t.setLayer(d.mSurfaceControl, Integer.MAX_VALUE);
+ }
+ t.setAlpha(d.mSurfaceControl, alpha);
+
+ d.mDimming = true;
+ }
+
+ /**
+ * Finish a dim started by dimAbove in the case there was no call to dimAbove.
+ *
+ * @param t A Transaction in which to finish the dim.
+ */
+ void stopDim(SurfaceControl.Transaction t) {
+ DimState d = getDimState(null);
+ t.hide(d.mSurfaceControl);
+ d.mDontReset = false;
+ }
+ /**
+ * Place a Dim above the entire host container. The caller is responsible for calling stopDim to
+ * remove this effect. If the Dim can be assosciated with a particular child of the host
+ * consider using the other variant of dimAbove which ties the Dim lifetime to the child
+ * lifetime more explicitly.
+ *
+ * @param t A transaction in which to apply the Dim.
+ * @param alpha The alpha at which to Dim.
+ */
+ void dimAbove(SurfaceControl.Transaction t, float alpha) {
+ dim(t, null, 1, alpha);
+ }
+
+ /**
+ * Place a dim above the given container, which should be a child of the host container.
+ * for each call to {@link WindowContainer#prepareSurfaces} the Dim state will be reset
+ * and the child should call dimAbove again to request the Dim to continue.
+ *
+ * @param t A transaction in which to apply the Dim.
+ * @param container The container which to dim above. Should be a child of our host.
+ * @param alpha The alpha at which to Dim.
+ */
+ void dimAbove(SurfaceControl.Transaction t, WindowContainer container, float alpha) {
+ dim(t, container, 1, alpha);
+ }
+
+ /**
+ * Like {@link #dimAbove} but places the dim below the given container.
+ *
+ * @param t A transaction in which to apply the Dim.
+ * @param container The container which to dim below. Should be a child of our host.
+ * @param alpha The alpha at which to Dim.
+ */
+
+ void dimBelow(SurfaceControl.Transaction t, WindowContainer container, float alpha) {
+ dim(t, container, -1, alpha);
+ }
+
+ /**
+ * Mark all dims as pending completion on the next call to {@link #updateDims}
+ *
+ * This is intended for us by the host container, to be called at the beginning of
+ * {@link WindowContainer#prepareSurfaces}. After calling this, the container should
+ * chain {@link WindowContainer#prepareSurfaces} down to it's children to give them
+ * a chance to request dims to continue.
+ */
+ void resetDimStates() {
+ for (int i = mDimLayerUsers.size() - 1; i >= 0; i--) {
+ final DimState state = mDimLayerUsers.valueAt(i);
+ if (state.mDontReset == false) {
+ state.mDimming = false;
+ }
+ }
+ }
+
+ /**
+ * Call after invoking {@link WindowContainer#prepareSurfaces} on children as
+ * described in {@link #resetDimStates}.
+ *
+ * @param t A transaction in which to update the dims.
+ * @param bounds The bounds at which to dim.
+ * @return true if any Dims were updated.
+ */
+ boolean updateDims(SurfaceControl.Transaction t, Rect bounds) {
+ boolean didSomething = false;
+ for (int i = mDimLayerUsers.size() - 1; i >= 0; i--) {
+ DimState state = mDimLayerUsers.valueAt(i);
+ // TODO: We want to animate the addition and removal of Dim's instead of immediately
+ // acting. When we do this we need to take care to account for the "Replacing Windows"
+ // case (and seamless dim transfer).
+ if (state.mDimming == false) {
+ mDimLayerUsers.removeAt(i);
+ state.mSurfaceControl.destroy();
+ } else {
+ didSomething = true;
+ // TODO: Once we use geometry from hierarchy this falls away.
+ t.setSize(state.mSurfaceControl, bounds.width(), bounds.height());
+ t.setPosition(state.mSurfaceControl, bounds.left, bounds.top);
+ }
+ }
+ return didSomething;
+ }
+}
diff --git a/com/android/server/wm/DisplayContent.java b/com/android/server/wm/DisplayContent.java
index 4d839d0..41348ba 100644
--- a/com/android/server/wm/DisplayContent.java
+++ b/com/android/server/wm/DisplayContent.java
@@ -58,10 +58,10 @@
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.AppTransition.TRANSIT_KEYGUARD_UNOCCLUDE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_BOOT;
@@ -142,14 +142,15 @@
import android.view.Display;
import android.view.DisplayInfo;
import android.view.InputDevice;
+import android.view.MagnificationSpec;
import android.view.Surface;
import android.view.SurfaceControl;
-import android.view.WindowManagerPolicy;
+import android.view.SurfaceSession;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ToBooleanFunction;
import com.android.internal.view.IInputMethodClient;
-import android.view.DisplayFrames;
+import com.android.server.policy.WindowManagerPolicy;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -180,8 +181,8 @@
private final TaskStackContainers mTaskStackContainers = new TaskStackContainers();
// Contains all non-app window containers that should be displayed above the app containers
// (e.g. Status bar)
- private final NonAppWindowContainers mAboveAppWindowsContainers =
- new NonAppWindowContainers("mAboveAppWindowsContainers");
+ private final AboveAppWindowContainers mAboveAppWindowsContainers =
+ new AboveAppWindowContainers("mAboveAppWindowsContainers");
// Contains all non-app window containers that should be displayed below the app containers
// (e.g. Wallpaper).
private final NonAppWindowContainers mBelowAppWindowsContainers =
@@ -313,6 +314,9 @@
private final Matrix mTmpMatrix = new Matrix();
private final Region mTmpRegion = new Region();
+ /** Used for handing back size of display */
+ private final Rect mTmpBounds = new Rect();
+
WindowManagerService mService;
/** Remove this display when animation on it has completed. */
@@ -321,8 +325,6 @@
final DockedStackDividerController mDividerControllerLocked;
final PinnedStackController mPinnedStackControllerLocked;
- DimLayerController mDimLayerController;
-
final ArrayList<WindowState> mTapExcludedWindows = new ArrayList<>();
private boolean mHaveBootMsg = false;
@@ -346,10 +348,38 @@
// {@code false} if this display is in the processing of being created.
private boolean mDisplayReady = false;
- private final WindowLayersController mLayersController;
WallpaperController mWallpaperController;
int mInputMethodAnimLayerAdjustment;
+ private final SurfaceSession mSession = new SurfaceSession();
+
+ /**
+ * We organize all top-level Surfaces in to the following layers.
+ * mOverlayLayer contains a few Surfaces which are always on top of others
+ * and omitted from Screen-Magnification, for example the strict mode flash or
+ * the magnification overlay itself.
+ * {@link #mWindowingLayer} contains everything else.
+ */
+ private SurfaceControl mOverlayLayer;
+
+ /**
+ * See {@link #mOverlayLayer}
+ */
+ private SurfaceControl mWindowingLayer;
+
+ /**
+ * Specifies the size of the surfaces in {@link #mOverlayLayer} and {@link #mWindowingLayer}.
+ * <p>
+ * For these surfaces currently we use a surface based on the larger of width or height so we
+ * don't have to resize when rotating the display.
+ */
+ private int mSurfaceSize;
+
+ /**
+ * A list of surfaces to be destroyed after {@link #mPendingTransaction} is applied.
+ */
+ private final ArrayList<SurfaceControl> mPendingDestroyingSurfaces = new ArrayList<>();
+
private final Consumer<WindowState> mUpdateWindowsForAnimator = w -> {
WindowStateAnimator winAnimator = w.mWinAnimator;
if (winAnimator.hasSurface()) {
@@ -503,9 +533,6 @@
return true;
};
- private final Consumer<WindowState> mPrepareWindowSurfaces =
- w -> w.mWinAnimator.prepareSurfaceLocked(true);
-
private final Consumer<WindowState> mPerformLayout = w -> {
// Don't do layout of a window if it is not visible, or soon won't be visible, to avoid
// wasting time and funky changes while a window is animating away.
@@ -558,12 +585,6 @@
w.updateLastInsetValues();
}
- // Window frames may have changed. Update dim layer with the new bounds.
- final Task task = w.getTask();
- if (task != null) {
- mDimLayerController.updateDimLayer(task);
- }
-
if (DEBUG_LAYOUT) Slog.v(TAG, " LAYOUT: mFrame=" + w.mFrame
+ " mContainingFrame=" + w.mContainingFrame
+ " mDisplayFrame=" + w.mDisplayFrame);
@@ -657,8 +678,6 @@
}
}
- w.applyDimLayerIfNeeded();
-
if (isDefaultDisplay && obscuredChanged && w.isVisibleLw()
&& mWallpaperController.isWallpaperTarget(w)) {
// This is the wallpaper target and its obscured state changed... make sure the
@@ -741,13 +760,11 @@
* initialize direct children.
* @param display May not be null.
* @param service You know.
- * @param layersController window layer controller used to assign layer to the windows on this
- * display.
* @param wallpaperController wallpaper windows controller used to adjust the positioning of the
* wallpaper windows in the window list.
*/
DisplayContent(Display display, WindowManagerService service,
- WindowLayersController layersController, WallpaperController wallpaperController) {
+ WallpaperController wallpaperController) {
if (service.mRoot.getDisplayContent(display.getDisplayId()) != null) {
throw new IllegalArgumentException("Display with ID=" + display.getDisplayId()
+ " already exists=" + service.mRoot.getDisplayContent(display.getDisplayId())
@@ -756,7 +773,6 @@
mDisplay = display;
mDisplayId = display.getDisplayId();
- mLayersController = layersController;
mWallpaperController = wallpaperController;
display.getDisplayInfo(mDisplayInfo);
display.getMetrics(mDisplayMetrics);
@@ -766,7 +782,22 @@
initializeDisplayBaseInfo();
mDividerControllerLocked = new DockedStackDividerController(service, this);
mPinnedStackControllerLocked = new PinnedStackController(service, this);
- mDimLayerController = new DimLayerController(this);
+
+ mSurfaceSize = Math.max(mBaseDisplayHeight, mBaseDisplayWidth);
+
+ final SurfaceControl.Builder b = mService.makeSurfaceBuilder(mSession)
+ .setSize(mSurfaceSize, mSurfaceSize)
+ .setOpaque(true);
+ mWindowingLayer = b.setName("Display Root").build();
+ mOverlayLayer = b.setName("Display Overlays").build();
+
+ getPendingTransaction().setLayer(mWindowingLayer, 0)
+ .setLayerStack(mWindowingLayer, mDisplayId)
+ .show(mWindowingLayer)
+ .setLayer(mOverlayLayer, 1)
+ .setLayerStack(mOverlayLayer, mDisplayId)
+ .show(mOverlayLayer);
+ getPendingTransaction().apply();
// These are the only direct children we should ever have and they are permanent.
super.addChild(mBelowAppWindowsContainers, null);
@@ -1030,11 +1061,7 @@
setLayoutNeeded();
final int[] anim = new int[2];
- if (isDimming()) {
- anim[0] = anim[1] = 0;
- } else {
- mService.mPolicy.selectRotationAnimationLw(anim);
- }
+ mService.mPolicy.selectRotationAnimationLw(anim);
if (!rotateSeamlessly) {
mService.startFreezingDisplayLocked(inTransaction, anim[0], anim[1], this);
@@ -1071,8 +1098,7 @@
// it doesn't support hardware OpenGL emulation yet.
if (CUSTOM_SCREEN_ROTATION && screenRotationAnimation != null
&& screenRotationAnimation.hasScreenshot()) {
- if (screenRotationAnimation.setRotationInTransaction(
- rotation, mService.mFxSession,
+ if (screenRotationAnimation.setRotationInTransaction(rotation,
MAX_ANIMATION_DURATION, mService.getTransitionAnimationScaleLocked(),
mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight)) {
mService.scheduleAnimationLocked();
@@ -1201,6 +1227,8 @@
mCompatibleScreenScale = CompatibilityInfo.computeCompatibleScaling(mDisplayMetrics,
mCompatDisplayMetrics);
}
+
+ updateBounds();
return mDisplayInfo;
}
@@ -1519,8 +1547,17 @@
// See {@link PhoneWindowManager#setInitialDisplaySize}...sigh...
mService.reconfigureDisplayLocked(this);
- getDockedDividerController().onConfigurationChanged();
- getPinnedStackController().onConfigurationChanged();
+ final DockedStackDividerController dividerController = getDockedDividerController();
+
+ if (dividerController != null) {
+ getDockedDividerController().onConfigurationChanged();
+ }
+
+ final PinnedStackController pinnedStackController = getPinnedStackController();
+
+ if (pinnedStackController != null) {
+ getPinnedStackController().onConfigurationChanged();
+ }
}
/**
@@ -1659,33 +1696,6 @@
mInitialDisplayDensity = mDisplayInfo.logicalDensityDpi;
}
- void getLogicalDisplayRect(Rect out) {
- // Uses same calculation as in LogicalDisplay#configureDisplayInTransactionLocked.
- final int orientation = mDisplayInfo.rotation;
- boolean rotated = (orientation == ROTATION_90 || orientation == ROTATION_270);
- final int physWidth = rotated ? mBaseDisplayHeight : mBaseDisplayWidth;
- final int physHeight = rotated ? mBaseDisplayWidth : mBaseDisplayHeight;
- int width = mDisplayInfo.logicalWidth;
- int left = (physWidth - width) / 2;
- int height = mDisplayInfo.logicalHeight;
- int top = (physHeight - height) / 2;
- out.set(left, top, left + width, top + height);
- }
-
- private void getLogicalDisplayRect(Rect out, int orientation) {
- getLogicalDisplayRect(out);
-
- // Rotate the Rect if needed.
- final int currentRotation = mDisplayInfo.rotation;
- final int rotationDelta = deltaRotation(currentRotation, orientation);
- if (rotationDelta == ROTATION_90 || rotationDelta == ROTATION_270) {
- createRotationMatrix(rotationDelta, mBaseDisplayWidth, mBaseDisplayHeight, mTmpMatrix);
- mTmpRectF.set(out);
- mTmpMatrix.mapRect(mTmpRectF);
- mTmpRectF.round(out);
- }
- }
-
/**
* If display metrics changed, overrides are not set and it's not just a rotation - update base
* values.
@@ -1753,6 +1763,8 @@
}
mBaseDisplayRect.set(0, 0, mBaseDisplayWidth, mBaseDisplayHeight);
+
+ updateBounds();
}
void getContentRect(Rect out) {
@@ -1907,22 +1919,6 @@
}
}
- boolean animateDimLayers() {
- return mDimLayerController.animateDimLayers();
- }
-
- private void resetDimming() {
- mDimLayerController.resetDimming();
- }
-
- boolean isDimming() {
- return mDimLayerController.isDimming();
- }
-
- private void stopDimmingIfNeeded() {
- mDimLayerController.stopDimmingIfNeeded();
- }
-
@Override
void removeIfPossible() {
if (isAnimating()) {
@@ -1938,7 +1934,6 @@
try {
super.removeImmediately();
if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Removing display=" + this);
- mDimLayerController.close();
if (mService.canDispatchPointerEvents()) {
if (mTapDetector != null) {
mService.unregisterPointerEventListener(mTapDetector);
@@ -1947,6 +1942,9 @@
mService.unregisterPointerEventListener(mService.mMousePositionTracker);
}
}
+ // The pending transaction won't be applied so we should
+ // just clean up any surfaces pending destruction.
+ onPendingTransactionApplied();
} finally {
mRemovingDisplay = false;
}
@@ -2096,7 +2094,7 @@
}
void rotateBounds(int oldRotation, int newRotation, Rect bounds) {
- getLogicalDisplayRect(mTmpRect, newRotation);
+ getBounds(mTmpRect, newRotation);
// Compute a transform matrix to undo the coordinate space transformation,
// and present the window at the same physical position it previously occupied.
@@ -2228,8 +2226,7 @@
token.dump(pw, " ");
}
}
- pw.println();
- mDimLayerController.dump(prefix, pw);
+
pw.println();
// Dump stack references
@@ -2342,10 +2339,16 @@
/** Updates the layer assignment of windows on this display. */
void assignWindowLayers(boolean setLayoutNeeded) {
- mLayersController.assignWindowLayers(this);
+ assignChildLayers(getPendingTransaction());
if (setLayoutNeeded) {
setLayoutNeeded();
}
+
+ // We accumlate the layer changes in-to "getPendingTransaction()" but we defer
+ // the application of this transaction until the animation pass triggers
+ // prepareSurfaces. This allows us to synchronize Z-ordering changes with
+ // the hiding and showing of surfaces.
+ scheduleAnimation();
}
// TODO: This should probably be called any time a visual change is made to the hierarchy like
@@ -2701,10 +2704,6 @@
}
}
- void prepareWindowSurfaces() {
- forAllWindows(mPrepareWindowSurfaces, false /* traverseTopToBottom */);
- }
-
boolean inputMethodClientHasFocus(IInputMethodClient client) {
final WindowState imFocus = computeImeTarget(false /* updateImeTarget */);
if (imFocus == null) {
@@ -2846,7 +2845,6 @@
} while (pendingLayoutChanges != 0);
mTmpApplySurfaceChangesTransactionState.reset();
- resetDimming();
mTmpRecoveringMemory = recoveringMemory;
forAllWindows(mApplySurfaceChangesTransaction, true /* traverseTopToBottom */);
@@ -2857,8 +2855,6 @@
mTmpApplySurfaceChangesTransactionState.preferredModeId,
true /* inTraversal, must call performTraversalInTrans... below */);
- stopDimmingIfNeeded();
-
final boolean wallpaperVisible = mWallpaperController.isWallpaperVisible();
if (wallpaperVisible != mLastWallpaperVisible) {
mLastWallpaperVisible = wallpaperVisible;
@@ -2875,6 +2871,44 @@
return mTmpApplySurfaceChangesTransactionState.focusDisplayed;
}
+ private void updateBounds() {
+ calculateBounds(mTmpBounds);
+ setBounds(mTmpBounds);
+ }
+
+ // Determines the current display bounds based on the current state
+ private void calculateBounds(Rect out) {
+ // Uses same calculation as in LogicalDisplay#configureDisplayInTransactionLocked.
+ final int orientation = mDisplayInfo.rotation;
+ boolean rotated = (orientation == ROTATION_90 || orientation == ROTATION_270);
+ final int physWidth = rotated ? mBaseDisplayHeight : mBaseDisplayWidth;
+ final int physHeight = rotated ? mBaseDisplayWidth : mBaseDisplayHeight;
+ int width = mDisplayInfo.logicalWidth;
+ int left = (physWidth - width) / 2;
+ int height = mDisplayInfo.logicalHeight;
+ int top = (physHeight - height) / 2;
+ out.set(left, top, left + width, top + height);
+ }
+
+ @Override
+ public void getBounds(Rect out) {
+ calculateBounds(out);
+ }
+
+ private void getBounds(Rect out, int orientation) {
+ getBounds(out);
+
+ // Rotate the Rect if needed.
+ final int currentRotation = mDisplayInfo.rotation;
+ final int rotationDelta = deltaRotation(currentRotation, orientation);
+ if (rotationDelta == ROTATION_90 || rotationDelta == ROTATION_270) {
+ createRotationMatrix(rotationDelta, mBaseDisplayWidth, mBaseDisplayHeight, mTmpMatrix);
+ mTmpRectF.set(out);
+ mTmpMatrix.mapRect(mTmpRectF);
+ mTmpRectF.round(out);
+ }
+ }
+
void performLayout(boolean initial, boolean updateInputWindows) {
if (!isLayoutNeeded()) {
return;
@@ -2935,241 +2969,30 @@
* Takes a snapshot of the display. In landscape mode this grabs the whole screen.
* In portrait mode, it grabs the full screenshot.
*
- * @param width the width of the target bitmap
- * @param height the height of the target bitmap
- * @param includeFullDisplay true if the screen should not be cropped before capture
- * @param frameScale the scale to apply to the frame, only used when width = -1 and height = -1
* @param config of the output bitmap
* @param wallpaperOnly true if only the wallpaper layer should be included in the screenshot
- * @param includeDecor whether to include window decors, like the status or navigation bar
- * background of the window
*/
- Bitmap screenshotApplications(IBinder appToken, int width, int height,
- boolean includeFullDisplay, float frameScale, Bitmap.Config config,
- boolean wallpaperOnly, boolean includeDecor) {
- Bitmap bitmap = screenshotApplications(appToken, width, height, includeFullDisplay,
- frameScale, wallpaperOnly, includeDecor, SurfaceControl::screenshot);
- if (bitmap == null) {
- return null;
- }
-
- if (DEBUG_SCREENSHOT) {
- // TEST IF IT's ALL BLACK
- int[] buffer = new int[bitmap.getWidth() * bitmap.getHeight()];
- bitmap.getPixels(buffer, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(),
- bitmap.getHeight());
- boolean allBlack = true;
- final int firstColor = buffer[0];
- for (int i = 0; i < buffer.length; i++) {
- if (buffer[i] != firstColor) {
- allBlack = false;
- break;
- }
- }
- if (allBlack) {
- final WindowState appWin = mScreenshotApplicationState.appWin;
- final int maxLayer = mScreenshotApplicationState.maxLayer;
- final int minLayer = mScreenshotApplicationState.minLayer;
- Slog.i(TAG_WM, "Screenshot " + appWin + " was monochrome(" +
- Integer.toHexString(firstColor) + ")! mSurfaceLayer=" +
- (appWin != null ?
- appWin.mWinAnimator.mSurfaceController.getLayer() : "null") +
- " minLayer=" + minLayer + " maxLayer=" + maxLayer);
- }
- }
-
- // Create a copy of the screenshot that is immutable and backed in ashmem.
- // This greatly reduces the overhead of passing the bitmap between processes.
- Bitmap ret = bitmap.createAshmemBitmap(config);
- bitmap.recycle();
- return ret;
- }
-
- GraphicBuffer screenshotApplicationsToBuffer(IBinder appToken, int width, int height,
- boolean includeFullDisplay, float frameScale, boolean wallpaperOnly,
- boolean includeDecor) {
- return screenshotApplications(appToken, width, height, includeFullDisplay, frameScale,
- wallpaperOnly, includeDecor, SurfaceControl::screenshotToBuffer);
- }
-
- private <E> E screenshotApplications(IBinder appToken, int width, int height,
- boolean includeFullDisplay, float frameScale, boolean wallpaperOnly,
- boolean includeDecor, Screenshoter<E> screenshoter) {
- int dw = mDisplayInfo.logicalWidth;
- int dh = mDisplayInfo.logicalHeight;
- if (dw == 0 || dh == 0) {
- if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, "Screenshot of " + appToken
- + ": returning null. logical widthxheight=" + dw + "x" + dh);
- return null;
- }
-
- E bitmap;
-
- mScreenshotApplicationState.reset(appToken == null && !wallpaperOnly);
- final Rect frame = new Rect();
- final Rect stackBounds = new Rect();
-
- final int aboveAppLayer = (mService.mPolicy.getWindowLayerFromTypeLw(TYPE_APPLICATION) + 1)
- * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
- final MutableBoolean mutableIncludeFullDisplay = new MutableBoolean(includeFullDisplay);
- synchronized(mService.mWindowMap) {
+ Bitmap screenshotDisplay(Bitmap.Config config, boolean wallpaperOnly) {
+ synchronized (mService.mWindowMap) {
if (!mService.mPolicy.isScreenOn()) {
- if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, "Attempted to take screenshot while display"
- + " was off.");
- return null;
- }
- // Figure out the part of the screen that is actually the app.
- mScreenshotApplicationState.appWin = null;
- forAllWindows(w -> {
- if (!w.mHasSurface) {
- return false;
+ if (DEBUG_SCREENSHOT) {
+ Slog.i(TAG_WM, "Attempted to take screenshot while display was off.");
}
- if (w.mLayer >= aboveAppLayer) {
- return false;
- }
- if (wallpaperOnly && !w.mIsWallpaper) {
- return false;
- }
- if (w.mIsImWindow) {
- return false;
- } else if (w.mIsWallpaper) {
- // If this is the wallpaper layer and we're only looking for the wallpaper layer
- // then the target window state is this one.
- if (wallpaperOnly) {
- mScreenshotApplicationState.appWin = w;
- }
-
- if (mScreenshotApplicationState.appWin == null) {
- // We have not ran across the target window yet, so it is probably behind
- // the wallpaper. This can happen when the keyguard is up and all windows
- // are moved behind the wallpaper. We don't want to include the wallpaper
- // layer in the screenshot as it will cover-up the layer of the target
- // window.
- return false;
- }
- // Fall through. The target window is in front of the wallpaper. For this
- // case we want to include the wallpaper layer in the screenshot because
- // the target window might have some transparent areas.
- } else if (appToken != null) {
- if (w.mAppToken == null || w.mAppToken.token != appToken) {
- // This app window is of no interest if it is not associated with the
- // screenshot app.
- return false;
- }
- mScreenshotApplicationState.appWin = w;
- }
-
- // Include this window.
-
- final WindowStateAnimator winAnim = w.mWinAnimator;
- int layer = winAnim.mSurfaceController.getLayer();
- if (mScreenshotApplicationState.maxLayer < layer) {
- mScreenshotApplicationState.maxLayer = layer;
- }
- if (mScreenshotApplicationState.minLayer > layer) {
- mScreenshotApplicationState.minLayer = layer;
- }
-
- // Don't include wallpaper in bounds calculation
- if (!w.mIsWallpaper && !mutableIncludeFullDisplay.value) {
- if (includeDecor) {
- final Task task = w.getTask();
- if (task != null) {
- task.getBounds(frame);
- } else {
-
- // No task bounds? Too bad! Ain't no screenshot then.
- return true;
- }
- } else {
- final Rect wf = w.mFrame;
- final Rect cr = w.mContentInsets;
- int left = wf.left + cr.left;
- int top = wf.top + cr.top;
- int right = wf.right - cr.right;
- int bottom = wf.bottom - cr.bottom;
- frame.union(left, top, right, bottom);
- w.getVisibleBounds(stackBounds);
- if (!Rect.intersects(frame, stackBounds)) {
- // Set frame empty if there's no intersection.
- frame.setEmpty();
- }
- }
- }
-
- final boolean foundTargetWs =
- (w.mAppToken != null && w.mAppToken.token == appToken)
- || (mScreenshotApplicationState.appWin != null && wallpaperOnly);
- if (foundTargetWs && winAnim.getShown() && winAnim.mLastAlpha > 0f) {
- mScreenshotApplicationState.screenshotReady = true;
- }
-
- if (w.isObscuringDisplay()){
- return true;
- }
- return false;
- }, true /* traverseTopToBottom */);
-
- final WindowState appWin = mScreenshotApplicationState.appWin;
- final boolean screenshotReady = mScreenshotApplicationState.screenshotReady;
- final int maxLayer = mScreenshotApplicationState.maxLayer;
- final int minLayer = mScreenshotApplicationState.minLayer;
-
- if (appToken != null && appWin == null) {
- // Can't find a window to snapshot.
- if (DEBUG_SCREENSHOT) Slog.i(TAG_WM,
- "Screenshot: Couldn't find a surface matching " + appToken);
return null;
}
- if (!screenshotReady) {
- Slog.i(TAG_WM, "Failed to capture screenshot of " + appToken +
- " appWin=" + (appWin == null ? "null" : (appWin + " drawState=" +
- appWin.mWinAnimator.mDrawState)));
+ if (wallpaperOnly && !shouldScreenshotWallpaper()) {
return null;
}
- // Screenshot is ready to be taken. Everything from here below will continue
- // through the bottom of the loop and return a value. We only stay in the loop
- // because we don't want to release the mWindowMap lock until the screenshot is
- // taken.
+ int dw = mDisplayInfo.logicalWidth;
+ int dh = mDisplayInfo.logicalHeight;
- if (maxLayer == 0) {
- if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, "Screenshot of " + appToken
- + ": returning null maxLayer=" + maxLayer);
+ if (dw <= 0 || dh <= 0) {
return null;
}
- if (!mutableIncludeFullDisplay.value) {
- // Constrain frame to the screen size.
- if (!frame.intersect(0, 0, dw, dh)) {
- frame.setEmpty();
- }
- } else {
- // Caller just wants entire display.
- frame.set(0, 0, dw, dh);
- }
- if (frame.isEmpty()) {
- return null;
- }
-
- if (width < 0) {
- width = (int) (frame.width() * frameScale);
- }
- if (height < 0) {
- height = (int) (frame.height() * frameScale);
- }
-
- // Tell surface flinger what part of the image to crop. Take the top
- // right part of the application, and crop the larger dimension to fit.
- Rect crop = new Rect(frame);
- if (width / (float) frame.width() < height / (float) frame.height()) {
- int cropWidth = (int)((float)width / (float)height * frame.height());
- crop.right = crop.left + cropWidth;
- } else {
- int cropHeight = (int)((float)height / (float)width * frame.width());
- crop.bottom = crop.top + cropHeight;
- }
+ final Rect frame = new Rect(0, 0, dw, dh);
// The screenshot API does not apply the current screen rotation.
int rot = mDisplay.getRotation();
@@ -3178,43 +3001,52 @@
rot = (rot == ROTATION_90) ? ROTATION_270 : ROTATION_90;
}
- // Surfaceflinger is not aware of orientation, so convert our logical
- // crop to surfaceflinger's portrait orientation.
- convertCropForSurfaceFlinger(crop, rot, dw, dh);
-
- if (DEBUG_SCREENSHOT) {
- Slog.i(TAG_WM, "Screenshot: " + dw + "x" + dh + " from " + minLayer + " to "
- + maxLayer + " appToken=" + appToken);
- forAllWindows(w -> {
- final WindowSurfaceController controller = w.mWinAnimator.mSurfaceController;
- Slog.i(TAG_WM, w + ": " + w.mLayer
- + " animLayer=" + w.mWinAnimator.mAnimLayer
- + " surfaceLayer=" + ((controller == null)
- ? "null" : controller.getLayer()));
- }, false /* traverseTopToBottom */);
- }
+ // SurfaceFlinger is not aware of orientation, so convert our logical
+ // crop to SurfaceFlinger's portrait orientation.
+ convertCropForSurfaceFlinger(frame, rot, dw, dh);
final ScreenRotationAnimation screenRotationAnimation =
mService.mAnimator.getScreenRotationAnimationLocked(DEFAULT_DISPLAY);
final boolean inRotation = screenRotationAnimation != null &&
screenRotationAnimation.isAnimating();
- if (DEBUG_SCREENSHOT && inRotation) Slog.v(TAG_WM,
- "Taking screenshot while rotating");
+ if (DEBUG_SCREENSHOT && inRotation) Slog.v(TAG_WM, "Taking screenshot while rotating");
- // We force pending transactions to flush before taking
- // the screenshot by pushing an empty synchronous transaction.
- SurfaceControl.openTransaction();
- SurfaceControl.closeTransactionSync();
-
- bitmap = screenshoter.screenshot(crop, width, height, minLayer, maxLayer,
- inRotation, rot);
+ // TODO(b/68392460): We should screenshot Task controls directly
+ // but it's difficult at the moment as the Task doesn't have the
+ // correct size set.
+ final Bitmap bitmap = SurfaceControl.screenshot(frame, dw, dh, 0, 1, inRotation, rot);
if (bitmap == null) {
- Slog.w(TAG_WM, "Screenshot failure taking screenshot for (" + dw + "x" + dh
- + ") to layer " + maxLayer);
+ Slog.w(TAG_WM, "Failed to take screenshot");
return null;
}
+
+ // Create a copy of the screenshot that is immutable and backed in ashmem.
+ // This greatly reduces the overhead of passing the bitmap between processes.
+ final Bitmap ret = bitmap.createAshmemBitmap(config);
+ bitmap.recycle();
+ return ret;
}
- return bitmap;
+ }
+
+ private boolean shouldScreenshotWallpaper() {
+ MutableBoolean screenshotReady = new MutableBoolean(false);
+
+ forAllWindows(w -> {
+ if (!w.mIsWallpaper) {
+ return false;
+ }
+
+ // Found the wallpaper window
+ final WindowStateAnimator winAnim = w.mWinAnimator;
+
+ if (winAnim.getShown() && winAnim.mLastAlpha > 0f) {
+ screenshotReady.value = true;
+ }
+
+ return true;
+ }, true /* traverseTopToBottom */);
+
+ return screenshotReady.value;
}
// TODO: Can this use createRotationMatrix()?
@@ -3366,6 +3198,10 @@
* I.e Activities.
*/
private final class TaskStackContainers extends DisplayChildWindowContainer<TaskStack> {
+ /**
+ * A control placed at the appropriate level for transitions to occur.
+ */
+ SurfaceControl mAnimationLayer = null;
// Cached reference to some special stacks we tend to get a lot so we don't need to loop
// through the list to find them.
@@ -3677,13 +3513,85 @@
// to prevent freezing/unfreezing the display too early.
return mLastOrientation;
}
+
+ @Override
+ void assignChildLayers(SurfaceControl.Transaction t) {
+ final int NORMAL_STACK_STATE = 0;
+ final int BOOSTED_STATE = 1;
+ final int ALWAYS_ON_TOP_STATE = 2;
+
+ // We allow stacks to change visual order from the AM specified order due to
+ // Z-boosting during animations. However we must take care to ensure TaskStacks
+ // which are marked as alwaysOnTop remain that way.
+ int layer = 0;
+ for (int state = 0; state <= ALWAYS_ON_TOP_STATE; state++) {
+ for (int i = 0; i < mChildren.size(); i++) {
+ final TaskStack s = mChildren.get(i);
+ layer++;
+ if (state == NORMAL_STACK_STATE) {
+ s.assignLayer(t, layer);
+ } else if (state == BOOSTED_STATE && s.needsZBoost()) {
+ s.assignLayer(t, layer);
+ } else if (state == ALWAYS_ON_TOP_STATE &&
+ s.isAlwaysOnTop()) {
+ s.assignLayer(t, layer);
+ }
+ s.assignChildLayers(t);
+ }
+ // The appropriate place for App-Transitions to occur is right
+ // above all other animations but still below things in the Picture-and-Picture
+ // windowing mode.
+ if (state == BOOSTED_STATE && mAnimationLayer != null) {
+ t.setLayer(mAnimationLayer, layer + 1);
+ }
+ }
+ }
+
+ @Override
+ void onParentSet() {
+ super.onParentSet();
+ if (getParent() != null) {
+ mAnimationLayer = makeSurface().build();
+ } else {
+ mAnimationLayer.destroy();
+ mAnimationLayer = null;
+ }
+ }
+ }
+
+ private final class AboveAppWindowContainers extends NonAppWindowContainers {
+ AboveAppWindowContainers(String name) {
+ super(name);
+ }
+
+ void assignChildLayers(SurfaceControl.Transaction t, WindowContainer imeContainer) {
+ boolean needAssignIme = imeContainer != null
+ && imeContainer.getSurfaceControl() != null;
+ for (int j = 0; j < mChildren.size(); ++j) {
+ final WindowToken wt = mChildren.get(j);
+ wt.assignLayer(t, j);
+ wt.assignChildLayers(t);
+
+ int layer = mService.mPolicy.getWindowLayerFromTypeLw(
+ wt.windowType, wt.mOwnerCanManageAppTokens);
+ if (needAssignIme && layer >= TYPE_INPUT_METHOD_DIALOG) {
+ t.setRelativeLayer(imeContainer.getSurfaceControl(),
+ wt.getSurfaceControl(), -1);
+ needAssignIme = false;
+ }
+ }
+ if (needAssignIme) {
+ t.setRelativeLayer(imeContainer.getSurfaceControl(),
+ getSurfaceControl(), Integer.MIN_VALUE);
+ }
+ }
}
/**
* Window container class that contains all containers on this display that are not related to
* Apps. E.g. status bar.
*/
- private final class NonAppWindowContainers extends DisplayChildWindowContainer<WindowToken> {
+ private class NonAppWindowContainers extends DisplayChildWindowContainer<WindowToken> {
/**
* Compares two child window tokens returns -1 if the first is lesser than the second in
* terms of z-order and 1 otherwise.
@@ -3752,12 +3660,124 @@
}
}
+ SurfaceControl.Builder makeSurface(SurfaceSession s) {
+ return mService.makeSurfaceBuilder(s)
+ .setParent(mWindowingLayer);
+ }
+
+ @Override
+ SurfaceSession getSession() {
+ return mSession;
+ }
+
+ @Override
+ SurfaceControl.Builder makeChildSurface(WindowContainer child) {
+ SurfaceSession s = child != null ? child.getSession() : getSession();
+ final SurfaceControl.Builder b = mService.makeSurfaceBuilder(s);
+ b.setSize(mSurfaceSize, mSurfaceSize);
+
+ if (child == null) {
+ return b;
+ }
+
+ return b.setName(child.getName())
+ .setParent(mWindowingLayer);
+ }
+
/**
- * Interface to screenshot into various types, i.e. {@link Bitmap} and {@link GraphicBuffer}.
+ * The makeSurface variants are for use by the window-container
+ * hierarchy. makeOverlay here is a function for various non windowing
+ * overlays like the ScreenRotation screenshot, the Strict Mode Flash
+ * and other potpourii.
*/
- @FunctionalInterface
- private interface Screenshoter<E> {
- E screenshot(Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
- boolean useIdentityTransform, int rotation);
+ SurfaceControl.Builder makeOverlay() {
+ return mService.makeSurfaceBuilder(mSession)
+ .setParent(mOverlayLayer);
+ }
+
+ void applyMagnificationSpec(MagnificationSpec spec) {
+ applyMagnificationSpec(getPendingTransaction(), spec);
+ getPendingTransaction().apply();
+ }
+
+ @Override
+ void onParentSet() {
+ // Since we are the top of the SurfaceControl hierarchy here
+ // we create the root surfaces explicitly rather than chaining
+ // up as the default implementation in onParentSet does. So we
+ // explicitly do NOT call super here.
+ }
+
+ @Override
+ void assignChildLayers(SurfaceControl.Transaction t) {
+ t.setLayer(mOverlayLayer, 1)
+ .setLayer(mWindowingLayer, 0);
+
+ // These are layers as children of "mWindowingLayer"
+ mBelowAppWindowsContainers.assignLayer(t, 0);
+ mTaskStackContainers.assignLayer(t, 1);
+ mAboveAppWindowsContainers.assignLayer(t, 2);
+
+ WindowState imeTarget = mService.mInputMethodTarget;
+ boolean needAssignIme = true;
+
+ // In the case where we have an IME target that is not in split-screen
+ // mode IME assignment is easy. We just need the IME to go directly above
+ // the target. This way children of the target will naturally go above the IME
+ // and everyone is happy.
+ //
+ // In the case of split-screen windowing mode, we need to elevate the IME above the
+ // docked divider while keeping the app itself below the docked divider, so instead
+ // we use relative layering of the IME targets child windows, and place the
+ // IME in the non-app layer (see {@link AboveAppWindowContainers#assignChildLayers}).
+ //
+ // In the case where we have no IME target we assign it where it's base layer would
+ // place it in the AboveAppWindowContainers.
+ if (imeTarget != null && !imeTarget.inSplitScreenWindowingMode()
+ && (imeTarget.getSurfaceControl() != null)) {
+ t.setRelativeLayer(mImeWindowsContainers.getSurfaceControl(),
+ imeTarget.getSurfaceControl(),
+ // TODO: We need to use an extra level on the app surface to ensure
+ // this is always above SurfaceView but always below attached window.
+ 1);
+ needAssignIme = false;
+ }
+
+ // Above we have assigned layers to our children, now we ask them to assign
+ // layers to their children.
+ mBelowAppWindowsContainers.assignChildLayers(t);
+ mTaskStackContainers.assignChildLayers(t);
+ mAboveAppWindowsContainers.assignChildLayers(t,
+ needAssignIme == true ? mImeWindowsContainers : null);
+ mImeWindowsContainers.assignChildLayers(t);
+ }
+
+ /**
+ * Here we satisfy an unfortunate special case of the IME in split-screen mode. Imagine
+ * that the IME target is one of the docked applications. We'd like the docked divider to be
+ * above both of the applications, and we'd like the IME to be above the docked divider.
+ * However we need child windows of the applications to be above the IME (Text drag handles).
+ * This is a non-strictly hierarcical layering and we need to break out of the Z ordering
+ * somehow. We do this by relatively ordering children of the target to the IME in cooperation
+ * with {@link #WindowState#assignLayer}
+ */
+ void assignRelativeLayerForImeTargetChild(SurfaceControl.Transaction t, WindowContainer child) {
+ t.setRelativeLayer(child.getSurfaceControl(), mImeWindowsContainers.getSurfaceControl(), 1);
+ }
+
+ @Override
+ void destroyAfterPendingTransaction(SurfaceControl surface) {
+ mPendingDestroyingSurfaces.add(surface);
+ }
+
+ /**
+ * Destroys any surfaces that have been put into the pending list with
+ * {@link #destroyAfterTransaction}.
+ */
+ void onPendingTransactionApplied() {
+ for (int i = mPendingDestroyingSurfaces.size() - 1; i >= 0; i--) {
+ mPendingDestroyingSurfaces.get(i).destroy();
+ }
+ mPendingDestroyingSurfaces.clear();
}
}
diff --git a/com/android/server/wm/DisplayFrames.java b/com/android/server/wm/DisplayFrames.java
new file mode 100644
index 0000000..0249713
--- /dev/null
+++ b/com/android/server/wm/DisplayFrames.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2017 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.server.wm;
+
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+import static com.android.server.wm.proto.DisplayFramesProto.STABLE_BOUNDS;
+
+import android.graphics.Rect;
+import android.util.proto.ProtoOutputStream;
+import android.view.DisplayInfo;
+
+import java.io.PrintWriter;
+
+/**
+ * Container class for all the display frames that affect how we do window layout on a display.
+ * @hide
+ */
+public class DisplayFrames {
+ public final int mDisplayId;
+
+ /**
+ * The current size of the screen; really; extends into the overscan area of the screen and
+ * doesn't account for any system elements like the status bar.
+ */
+ public final Rect mOverscan = new Rect();
+
+ /**
+ * The current visible size of the screen; really; (ir)regardless of whether the status bar can
+ * be hidden but not extending into the overscan area.
+ */
+ public final Rect mUnrestricted = new Rect();
+
+ /** Like mOverscan*, but allowed to move into the overscan region where appropriate. */
+ public final Rect mRestrictedOverscan = new Rect();
+
+ /**
+ * The current size of the screen; these may be different than (0,0)-(dw,dh) if the status bar
+ * can't be hidden; in that case it effectively carves out that area of the display from all
+ * other windows.
+ */
+ public final Rect mRestricted = new Rect();
+
+ /**
+ * During layout, the current screen borders accounting for any currently visible system UI
+ * elements.
+ */
+ public final Rect mSystem = new Rect();
+
+ /** For applications requesting stable content insets, these are them. */
+ public final Rect mStable = new Rect();
+
+ /**
+ * For applications requesting stable content insets but have also set the fullscreen window
+ * flag, these are the stable dimensions without the status bar.
+ */
+ public final Rect mStableFullscreen = new Rect();
+
+ /**
+ * During layout, the current screen borders with all outer decoration (status bar, input method
+ * dock) accounted for.
+ */
+ public final Rect mCurrent = new Rect();
+
+ /**
+ * During layout, the frame in which content should be displayed to the user, accounting for all
+ * screen decoration except for any space they deem as available for other content. This is
+ * usually the same as mCurrent*, but may be larger if the screen decor has supplied content
+ * insets.
+ */
+ public final Rect mContent = new Rect();
+
+ /**
+ * During layout, the frame in which voice content should be displayed to the user, accounting
+ * for all screen decoration except for any space they deem as available for other content.
+ */
+ public final Rect mVoiceContent = new Rect();
+
+ /** During layout, the current screen borders along which input method windows are placed. */
+ public final Rect mDock = new Rect();
+
+ private final Rect mDisplayInfoOverscan = new Rect();
+ private final Rect mRotatedDisplayInfoOverscan = new Rect();
+ public int mDisplayWidth;
+ public int mDisplayHeight;
+
+ public int mRotation;
+
+ public DisplayFrames(int displayId, DisplayInfo info) {
+ mDisplayId = displayId;
+ onDisplayInfoUpdated(info);
+ }
+
+ public void onDisplayInfoUpdated(DisplayInfo info) {
+ mDisplayWidth = info.logicalWidth;
+ mDisplayHeight = info.logicalHeight;
+ mRotation = info.rotation;
+ mDisplayInfoOverscan.set(
+ info.overscanLeft, info.overscanTop, info.overscanRight, info.overscanBottom);
+ }
+
+ public void onBeginLayout() {
+ switch (mRotation) {
+ case ROTATION_90:
+ mRotatedDisplayInfoOverscan.left = mDisplayInfoOverscan.top;
+ mRotatedDisplayInfoOverscan.top = mDisplayInfoOverscan.right;
+ mRotatedDisplayInfoOverscan.right = mDisplayInfoOverscan.bottom;
+ mRotatedDisplayInfoOverscan.bottom = mDisplayInfoOverscan.left;
+ break;
+ case ROTATION_180:
+ mRotatedDisplayInfoOverscan.left = mDisplayInfoOverscan.right;
+ mRotatedDisplayInfoOverscan.top = mDisplayInfoOverscan.bottom;
+ mRotatedDisplayInfoOverscan.right = mDisplayInfoOverscan.left;
+ mRotatedDisplayInfoOverscan.bottom = mDisplayInfoOverscan.top;
+ break;
+ case ROTATION_270:
+ mRotatedDisplayInfoOverscan.left = mDisplayInfoOverscan.bottom;
+ mRotatedDisplayInfoOverscan.top = mDisplayInfoOverscan.left;
+ mRotatedDisplayInfoOverscan.right = mDisplayInfoOverscan.top;
+ mRotatedDisplayInfoOverscan.bottom = mDisplayInfoOverscan.right;
+ break;
+ default:
+ mRotatedDisplayInfoOverscan.set(mDisplayInfoOverscan);
+ break;
+ }
+
+ mRestrictedOverscan.set(0, 0, mDisplayWidth, mDisplayHeight);
+ mOverscan.set(mRestrictedOverscan);
+ mSystem.set(mRestrictedOverscan);
+ mUnrestricted.set(mRotatedDisplayInfoOverscan);
+ mUnrestricted.right = mDisplayWidth - mUnrestricted.right;
+ mUnrestricted.bottom = mDisplayHeight - mUnrestricted.bottom;
+ mRestricted.set(mUnrestricted);
+ mDock.set(mUnrestricted);
+ mContent.set(mUnrestricted);
+ mVoiceContent.set(mUnrestricted);
+ mStable.set(mUnrestricted);
+ mStableFullscreen.set(mUnrestricted);
+ mCurrent.set(mUnrestricted);
+
+ }
+
+ public int getInputMethodWindowVisibleHeight() {
+ return mDock.bottom - mCurrent.bottom;
+ }
+
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ mStable.writeToProto(proto, STABLE_BOUNDS);
+ proto.end(token);
+ }
+
+ public void dump(String prefix, PrintWriter pw) {
+ pw.println(prefix + "DisplayFrames w=" + mDisplayWidth + " h=" + mDisplayHeight
+ + " r=" + mRotation);
+ final String myPrefix = prefix + " ";
+ dumpFrame(mStable, "mStable", myPrefix, pw);
+ dumpFrame(mStableFullscreen, "mStableFullscreen", myPrefix, pw);
+ dumpFrame(mDock, "mDock", myPrefix, pw);
+ dumpFrame(mCurrent, "mCurrent", myPrefix, pw);
+ dumpFrame(mSystem, "mSystem", myPrefix, pw);
+ dumpFrame(mContent, "mContent", myPrefix, pw);
+ dumpFrame(mVoiceContent, "mVoiceContent", myPrefix, pw);
+ dumpFrame(mOverscan, "mOverscan", myPrefix, pw);
+ dumpFrame(mRestrictedOverscan, "mRestrictedOverscan", myPrefix, pw);
+ dumpFrame(mRestricted, "mRestricted", myPrefix, pw);
+ dumpFrame(mUnrestricted, "mUnrestricted", myPrefix, pw);
+ dumpFrame(mDisplayInfoOverscan, "mDisplayInfoOverscan", myPrefix, pw);
+ dumpFrame(mRotatedDisplayInfoOverscan, "mRotatedDisplayInfoOverscan", myPrefix, pw);
+ }
+
+ private void dumpFrame(Rect frame, String name, String prefix, PrintWriter pw) {
+ pw.print(prefix + name + "="); frame.printShortString(pw); pw.println();
+ }
+}
diff --git a/com/android/server/wm/DockedStackDividerController.java b/com/android/server/wm/DockedStackDividerController.java
index d79ba89..a37598e 100644
--- a/com/android/server/wm/DockedStackDividerController.java
+++ b/com/android/server/wm/DockedStackDividerController.java
@@ -54,7 +54,6 @@
import com.android.internal.policy.DividerSnapAlgorithm;
import com.android.internal.policy.DockedDividerUtils;
import com.android.server.LocalServices;
-import com.android.server.wm.DimLayer.DimLayerUser;
import com.android.server.wm.WindowManagerService.H;
import java.io.PrintWriter;
@@ -62,7 +61,7 @@
/**
* Keeps information about the docked stack divider.
*/
-public class DockedStackDividerController implements DimLayerUser {
+public class DockedStackDividerController {
private static final String TAG = TAG_WITH_CLASS_NAME ? "DockedStackDividerController" : TAG_WM;
@@ -114,7 +113,6 @@
private boolean mLastVisibility = false;
private final RemoteCallbackList<IDockedStackListener> mDockedStackListeners
= new RemoteCallbackList<>();
- private final DimLayer mDimLayer;
private boolean mMinimizedDock;
private int mOriginalDockedSide = DOCKED_INVALID;
@@ -141,13 +139,12 @@
private boolean mImeHideRequested;
private final Rect mLastDimLayerRect = new Rect();
private float mLastDimLayerAlpha;
+ private TaskStack mDimmedStack;
DockedStackDividerController(WindowManagerService service, DisplayContent displayContent) {
mService = service;
mDisplayContent = displayContent;
final Context context = service.mContext;
- mDimLayer = new DimLayer(displayContent.mService, this, displayContent.getDisplayId(),
- "DockedStackDim");
mMinimizedDockInterpolator = AnimationUtils.loadInterpolator(
context, android.R.interpolator.fast_out_slow_in);
loadDimens();
@@ -463,6 +460,11 @@
}
mOriginalDockedSide = DOCKED_INVALID;
setMinimizedDockedStack(false /* minimizedDock */, false /* animate */);
+
+ if (mDimmedStack != null) {
+ mDimmedStack.stopDimming();
+ mDimmedStack = null;
+ }
}
/**
@@ -564,34 +566,12 @@
final TaskStack dockedStack = mDisplayContent.getSplitScreenPrimaryStack();
boolean visibleAndValid = visible && stack != null && dockedStack != null;
if (visibleAndValid) {
- stack.getDimBounds(mTmpRect);
- if (mTmpRect.height() > 0 && mTmpRect.width() > 0) {
- if (!mLastDimLayerRect.equals(mTmpRect) || mLastDimLayerAlpha != alpha) {
- try {
- // TODO: This should use the regular animation transaction - here and below
- mService.openSurfaceTransaction();
- mDimLayer.setBounds(mTmpRect);
- mDimLayer.show(getResizeDimLayer(), alpha, 0 /* duration */);
- } finally {
- mService.closeSurfaceTransaction("setResizeDimLayer");
- }
- }
- mLastDimLayerRect.set(mTmpRect);
- mLastDimLayerAlpha = alpha;
- } else {
- visibleAndValid = false;
- }
+ mDimmedStack = stack;
+ stack.dim(alpha);
}
- if (!visibleAndValid) {
- if (mLastDimLayerAlpha != 0f) {
- try {
- mService.openSurfaceTransaction();
- mDimLayer.hide();
- } finally {
- mService.closeSurfaceTransaction("setResizeDimLayer");
- }
- }
- mLastDimLayerAlpha = 0f;
+ if (!visibleAndValid && stack != null) {
+ mDimmedStack = null;
+ stack.stopDimming();
}
}
@@ -675,8 +655,8 @@
}
private boolean isWithinDisplay(Task task) {
- task.mStack.getBounds(mTmpRect);
- mDisplayContent.getLogicalDisplayRect(mTmpRect2);
+ task.getBounds(mTmpRect);
+ mDisplayContent.getBounds(mTmpRect2);
return mTmpRect.intersect(mTmpRect2);
}
@@ -829,12 +809,8 @@
return animateForMinimizedDockedStack(now);
} else if (mAnimatingForIme) {
return animateForIme(now);
- } else {
- if (mDimLayer != null && mDimLayer.isDimming()) {
- mDimLayer.setLayer(getResizeDimLayer());
- }
- return false;
}
+ return false;
}
private boolean animateForIme(long now) {
@@ -942,27 +918,6 @@
+ (1 - t) * (CLIP_REVEAL_MEET_LAST - CLIP_REVEAL_MEET_EARLIEST);
}
- @Override
- public boolean dimFullscreen() {
- return false;
- }
-
- @Override
- public DisplayInfo getDisplayInfo() {
- return mDisplayContent.getDisplayInfo();
- }
-
- @Override
- public boolean isAttachedToDisplay() {
- return mDisplayContent != null;
- }
-
- @Override
- public void getDimBounds(Rect outBounds) {
- // This dim layer user doesn't need this.
- }
-
- @Override
public String toShortString() {
return TAG;
}
@@ -977,10 +932,6 @@
pw.println(prefix + " mMinimizedDock=" + mMinimizedDock);
pw.println(prefix + " mAdjustedForIme=" + mAdjustedForIme);
pw.println(prefix + " mAdjustedForDivider=" + mAdjustedForDivider);
- if (mDimLayer.isDimming()) {
- pw.println(prefix + " Dim layer is dimming: ");
- mDimLayer.printTo(prefix + " ", pw);
- }
}
void writeToProto(ProtoOutputStream proto, long fieldId) {
diff --git a/com/android/server/wm/DragDropController.java b/com/android/server/wm/DragDropController.java
index 4567e10..65951dc 100644
--- a/com/android/server/wm/DragDropController.java
+++ b/com/android/server/wm/DragDropController.java
@@ -36,9 +36,10 @@
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.View;
-import android.view.WindowManagerInternal.IDragDropCallback;
+
import com.android.internal.util.Preconditions;
import com.android.server.input.InputWindowHandle;
+import com.android.server.wm.WindowManagerInternal.IDragDropCallback;
/**
* Managing drag and drop operations initiated by View#startDragAndDrop.
diff --git a/com/android/server/wm/DragState.java b/com/android/server/wm/DragState.java
index e81d366..112e62f 100644
--- a/com/android/server/wm/DragState.java
+++ b/com/android/server/wm/DragState.java
@@ -645,15 +645,15 @@
try (final SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) {
transaction.setPosition(
mSurfaceControl,
- (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_X),
- (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_Y));
+ (float) animation.getAnimatedValue(ANIMATED_PROPERTY_X),
+ (float) animation.getAnimatedValue(ANIMATED_PROPERTY_Y));
transaction.setAlpha(
mSurfaceControl,
- (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_ALPHA));
+ (float) animation.getAnimatedValue(ANIMATED_PROPERTY_ALPHA));
transaction.setMatrix(
mSurfaceControl,
- (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_SCALE), 0,
- 0, (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_SCALE));
+ (float) animation.getAnimatedValue(ANIMATED_PROPERTY_SCALE), 0,
+ 0, (float) animation.getAnimatedValue(ANIMATED_PROPERTY_SCALE));
transaction.apply();
}
}
diff --git a/com/android/server/wm/EmulatorDisplayOverlay.java b/com/android/server/wm/EmulatorDisplayOverlay.java
index 8bec8d7..fddf6ca 100644
--- a/com/android/server/wm/EmulatorDisplayOverlay.java
+++ b/com/android/server/wm/EmulatorDisplayOverlay.java
@@ -49,19 +49,19 @@
private int mRotation;
private boolean mVisible;
- public EmulatorDisplayOverlay(Context context, Display display, SurfaceSession session,
+ public EmulatorDisplayOverlay(Context context, DisplayContent dc,
int zOrder) {
+ final Display display = dc.getDisplay();
mScreenSize = new Point();
display.getSize(mScreenSize);
SurfaceControl ctrl = null;
try {
- ctrl = new SurfaceControl.Builder(session)
+ ctrl = dc.makeOverlay()
.setName("EmulatorDisplayOverlay")
.setSize(mScreenSize.x, mScreenSize.y)
.setFormat(PixelFormat.TRANSLUCENT)
.build();
- ctrl.setLayerStack(display.getLayerStack());
ctrl.setLayer(zOrder);
ctrl.setPosition(0, 0);
ctrl.show();
diff --git a/com/android/server/wm/InputMonitor.java b/com/android/server/wm/InputMonitor.java
index a766097..7e29a3a 100644
--- a/com/android/server/wm/InputMonitor.java
+++ b/com/android/server/wm/InputMonitor.java
@@ -47,11 +47,11 @@
import android.view.InputEventReceiver;
import android.view.KeyEvent;
import android.view.WindowManager;
-import android.view.WindowManagerPolicy;
import com.android.server.input.InputApplicationHandle;
import com.android.server.input.InputManagerService;
import com.android.server.input.InputWindowHandle;
+import com.android.server.policy.WindowManagerPolicy;
import java.io.PrintWriter;
import java.util.Arrays;
@@ -661,8 +661,8 @@
if (w.inPinnedWindowingMode()) {
if (mAddPipInputConsumerHandle
&& (inputWindowHandle.layer <= pipInputConsumer.mWindowHandle.layer)) {
- // Update the bounds of the Pip input consumer to match the Pinned stack
- w.getStack().getBounds(pipTouchableBounds);
+ // Update the bounds of the Pip input consumer to match the window bounds.
+ w.getBounds(pipTouchableBounds);
pipInputConsumer.mWindowHandle.touchableRegion.set(pipTouchableBounds);
addInputWindowHandle(pipInputConsumer.mWindowHandle);
mAddPipInputConsumerHandle = false;
diff --git a/com/android/server/wm/KeyguardDisableHandler.java b/com/android/server/wm/KeyguardDisableHandler.java
index 2eb186b..4a20f1a 100644
--- a/com/android/server/wm/KeyguardDisableHandler.java
+++ b/com/android/server/wm/KeyguardDisableHandler.java
@@ -29,7 +29,8 @@
import android.os.TokenWatcher;
import android.util.Log;
import android.util.Pair;
-import android.view.WindowManagerPolicy;
+
+import com.android.server.policy.WindowManagerPolicy;
public class KeyguardDisableHandler extends Handler {
private static final String TAG = TAG_WITH_CLASS_NAME ? "KeyguardDisableHandler" : TAG_WM;
diff --git a/com/android/server/wm/PinnedStackWindowController.java b/com/android/server/wm/PinnedStackWindowController.java
index 41f076d..b021a72 100644
--- a/com/android/server/wm/PinnedStackWindowController.java
+++ b/com/android/server/wm/PinnedStackWindowController.java
@@ -106,7 +106,7 @@
} else {
// Otherwise, use the display bounds
toBounds = new Rect();
- mContainer.getDisplayContent().getLogicalDisplayRect(toBounds);
+ mContainer.getDisplayContent().getBounds(toBounds);
}
} else if (fromFullscreen) {
schedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_END;
diff --git a/com/android/server/wm/PointerEventDispatcher.java b/com/android/server/wm/PointerEventDispatcher.java
index 484987e..ab8b8d4 100644
--- a/com/android/server/wm/PointerEventDispatcher.java
+++ b/com/android/server/wm/PointerEventDispatcher.java
@@ -21,7 +21,7 @@
import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.MotionEvent;
-import android.view.WindowManagerPolicy.PointerEventListener;
+import android.view.WindowManagerPolicyConstants.PointerEventListener;
import com.android.server.UiThread;
diff --git a/com/android/server/wm/RootWindowContainer.java b/com/android/server/wm/RootWindowContainer.java
index f541926..4008811 100644
--- a/com/android/server/wm/RootWindowContainer.java
+++ b/com/android/server/wm/RootWindowContainer.java
@@ -59,10 +59,10 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE;
import static android.view.WindowManager.LayoutParams.TYPE_DREAM;
import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_KEEP_SCREEN_ON;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
@@ -138,7 +138,6 @@
ParcelFileDescriptor mSurfaceTraceFd;
RemoteEventTrace mRemoteEventTrace;
- private final WindowLayersController mLayersController;
final WallpaperController mWallpaperController;
private final Handler mHandler;
@@ -163,7 +162,6 @@
RootWindowContainer(WindowManagerService service) {
mService = service;
mHandler = new MyHandler(service.mH.getLooper());
- mLayersController = new WindowLayersController(mService);
mWallpaperController = new WallpaperController(mService);
}
@@ -231,7 +229,7 @@
}
private DisplayContent createDisplayContent(final Display display) {
- final DisplayContent dc = new DisplayContent(display, mService, mLayersController,
+ final DisplayContent dc = new DisplayContent(display, mService,
mWallpaperController);
final int displayId = display.getDisplayId();
@@ -1103,4 +1101,9 @@
String getName() {
return "ROOT";
}
+
+ @Override
+ void scheduleAnimation() {
+ mService.scheduleAnimationLocked();
+ }
}
diff --git a/com/android/server/wm/ScreenRotationAnimation.java b/com/android/server/wm/ScreenRotationAnimation.java
index 3350fea..5a39de5 100644
--- a/com/android/server/wm/ScreenRotationAnimation.java
+++ b/com/android/server/wm/ScreenRotationAnimation.java
@@ -28,7 +28,6 @@
import android.content.Context;
import android.graphics.Matrix;
-import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
@@ -225,12 +224,12 @@
}
public ScreenRotationAnimation(Context context, DisplayContent displayContent,
- SurfaceSession session, boolean inTransaction, boolean forceDefaultOrientation,
+ boolean inTransaction, boolean forceDefaultOrientation,
boolean isSecure, WindowManagerService service) {
mService = service;
mContext = context;
mDisplayContent = displayContent;
- displayContent.getLogicalDisplayRect(mOriginalDisplayRect);
+ displayContent.getBounds(mOriginalDisplayRect);
// Screenshot does NOT include rotation!
final Display display = displayContent.getDisplay();
@@ -269,7 +268,7 @@
try {
try {
- mSurfaceControl = new SurfaceControl.Builder(session)
+ mSurfaceControl = displayContent.makeOverlay()
.setName("ScreenshotSurface")
.setSize(mWidth, mHeight)
.setSecure(isSecure)
@@ -281,7 +280,6 @@
// TODO(multidisplay): we should use the proper display
SurfaceControl.screenshot(SurfaceControl.getBuiltInDisplay(
SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN), sur);
- mSurfaceControl.setLayerStack(display.getLayerStack());
mSurfaceControl.setLayer(SCREEN_FREEZE_LAYER_SCREENSHOT);
mSurfaceControl.setAlpha(0);
mSurfaceControl.show();
@@ -313,7 +311,7 @@
float x = mTmpFloats[Matrix.MTRANS_X];
float y = mTmpFloats[Matrix.MTRANS_Y];
if (mForceDefaultOrientation) {
- mDisplayContent.getLogicalDisplayRect(mCurrentDisplayRect);
+ mDisplayContent.getBounds(mCurrentDisplayRect);
x -= mCurrentDisplayRect.left;
y -= mCurrentDisplayRect.top;
}
@@ -370,11 +368,11 @@
}
// Must be called while in a transaction.
- public boolean setRotationInTransaction(int rotation, SurfaceSession session,
+ public boolean setRotationInTransaction(int rotation,
long maxAnimationDuration, float animationScale, int finalWidth, int finalHeight) {
setRotationInTransaction(rotation);
if (TWO_PHASE_ANIMATION) {
- return startAnimation(session, maxAnimationDuration, animationScale,
+ return startAnimation(maxAnimationDuration, animationScale,
finalWidth, finalHeight, false, 0, 0);
}
@@ -385,7 +383,7 @@
/**
* Returns true if animating.
*/
- private boolean startAnimation(SurfaceSession session, long maxAnimationDuration,
+ private boolean startAnimation(long maxAnimationDuration,
float animationScale, int finalWidth, int finalHeight, boolean dismissing,
int exitAnim, int enterAnim) {
if (mSurfaceControl == null) {
@@ -561,8 +559,8 @@
Rect outer = new Rect(-mOriginalWidth*1, -mOriginalHeight*1,
mOriginalWidth*2, mOriginalHeight*2);
Rect inner = new Rect(0, 0, mOriginalWidth, mOriginalHeight);
- mCustomBlackFrame = new BlackFrame(session, outer, inner,
- SCREEN_FREEZE_LAYER_CUSTOM, layerStack, false);
+ mCustomBlackFrame = new BlackFrame(outer, inner,
+ SCREEN_FREEZE_LAYER_CUSTOM, mDisplayContent, false);
mCustomBlackFrame.setMatrix(mFrameInitialMatrix);
} catch (OutOfResourcesException e) {
Slog.w(TAG, "Unable to allocate black surface", e);
@@ -601,8 +599,8 @@
mOriginalWidth*2, mOriginalHeight*2);
inner = new Rect(0, 0, mOriginalWidth, mOriginalHeight);
}
- mExitingBlackFrame = new BlackFrame(session, outer, inner,
- SCREEN_FREEZE_LAYER_EXIT, layerStack, mForceDefaultOrientation);
+ mExitingBlackFrame = new BlackFrame(outer, inner,
+ SCREEN_FREEZE_LAYER_EXIT, mDisplayContent, mForceDefaultOrientation);
mExitingBlackFrame.setMatrix(mFrameInitialMatrix);
} catch (OutOfResourcesException e) {
Slog.w(TAG, "Unable to allocate black surface", e);
@@ -624,8 +622,8 @@
Rect outer = new Rect(-finalWidth*1, -finalHeight*1,
finalWidth*2, finalHeight*2);
Rect inner = new Rect(0, 0, finalWidth, finalHeight);
- mEnteringBlackFrame = new BlackFrame(session, outer, inner,
- SCREEN_FREEZE_LAYER_ENTER, layerStack, false);
+ mEnteringBlackFrame = new BlackFrame(outer, inner,
+ SCREEN_FREEZE_LAYER_ENTER, mDisplayContent, false);
} catch (OutOfResourcesException e) {
Slog.w(TAG, "Unable to allocate black surface", e);
} finally {
@@ -642,7 +640,7 @@
/**
* Returns true if animating.
*/
- public boolean dismiss(SurfaceSession session, long maxAnimationDuration,
+ public boolean dismiss(long maxAnimationDuration,
float animationScale, int finalWidth, int finalHeight, int exitAnim, int enterAnim) {
if (DEBUG_STATE) Slog.v(TAG, "Dismiss!");
if (mSurfaceControl == null) {
@@ -650,7 +648,7 @@
return false;
}
if (!mStarted) {
- startAnimation(session, maxAnimationDuration, animationScale, finalWidth, finalHeight,
+ startAnimation(maxAnimationDuration, animationScale, finalWidth, finalHeight,
true, exitAnim, enterAnim);
}
if (!mStarted) {
diff --git a/com/android/server/wm/SnapshotStartingData.java b/com/android/server/wm/SnapshotStartingData.java
index 35f35db..c9e43c5 100644
--- a/com/android/server/wm/SnapshotStartingData.java
+++ b/com/android/server/wm/SnapshotStartingData.java
@@ -17,8 +17,8 @@
package com.android.server.wm;
import android.app.ActivityManager.TaskSnapshot;
-import android.graphics.GraphicBuffer;
-import android.view.WindowManagerPolicy.StartingSurface;
+
+import com.android.server.policy.WindowManagerPolicy.StartingSurface;
/**
* Represents starting data for snapshot starting windows.
diff --git a/com/android/server/wm/SplashScreenStartingData.java b/com/android/server/wm/SplashScreenStartingData.java
index 4b14f86..f52ce38 100644
--- a/com/android/server/wm/SplashScreenStartingData.java
+++ b/com/android/server/wm/SplashScreenStartingData.java
@@ -18,7 +18,8 @@
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
-import android.view.WindowManagerPolicy.StartingSurface;
+
+import com.android.server.policy.WindowManagerPolicy.StartingSurface;
/**
* Represents starting data for splash screens, i.e. "traditional" starting windows.
diff --git a/com/android/server/wm/StackWindowController.java b/com/android/server/wm/StackWindowController.java
index 95c1d53..e7547bf 100644
--- a/com/android/server/wm/StackWindowController.java
+++ b/com/android/server/wm/StackWindowController.java
@@ -87,12 +87,6 @@
}
}
- public boolean isVisible() {
- synchronized (mWindowMap) {
- return mContainer != null && mContainer.isVisible();
- }
- }
-
public void reparent(int displayId, Rect outStackBounds, boolean onTop) {
synchronized (mWindowMap) {
if (mContainer == null) {
@@ -111,8 +105,7 @@
}
}
- public void positionChildAt(TaskWindowContainerController child, int position, Rect bounds,
- Configuration overrideConfig) {
+ public void positionChildAt(TaskWindowContainerController child, int position) {
synchronized (mWindowMap) {
if (DEBUG_STACK) Slog.i(TAG_WM, "positionChildAt: positioning task=" + child
+ " at " + position);
@@ -126,7 +119,7 @@
"positionChildAt: could not find stack for task=" + mContainer);
return;
}
- child.mContainer.positionAt(position, bounds, overrideConfig);
+ child.mContainer.positionAt(position);
mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
}
}
@@ -178,24 +171,22 @@
* Re-sizes a stack and its containing tasks.
*
* @param bounds New stack bounds. Passing in null sets the bounds to fullscreen.
- * @param configs Configurations for tasks in the resized stack, keyed by task id.
* @param taskBounds Bounds for tasks in the resized stack, keyed by task id.
- * @return True if the stack is now fullscreen.
+ * @param taskTempInsetBounds Inset bounds for individual tasks, keyed by task id.
*/
- public boolean resize(Rect bounds, SparseArray<Configuration> configs,
- SparseArray<Rect> taskBounds, SparseArray<Rect> taskTempInsetBounds) {
+ public void resize(Rect bounds, SparseArray<Rect> taskBounds,
+ SparseArray<Rect> taskTempInsetBounds) {
synchronized (mWindowMap) {
if (mContainer == null) {
throw new IllegalArgumentException("resizeStack: stack " + this + " not found.");
}
// We might trigger a configuration change. Save the current task bounds for freezing.
mContainer.prepareFreezingTaskBounds();
- if (mContainer.setBounds(bounds, configs, taskBounds, taskTempInsetBounds)
+ if (mContainer.setBounds(bounds, taskBounds, taskTempInsetBounds)
&& mContainer.isVisible()) {
mContainer.getDisplayContent().setLayoutNeeded();
mService.mWindowPlacerLocked.performSurfacePlacement();
}
- return mContainer.getRawFullscreen();
}
}
@@ -227,7 +218,7 @@
public void getRawBounds(Rect outBounds) {
synchronized (mWindowMap) {
- if (mContainer.getRawFullscreen()) {
+ if (mContainer.matchParentBounds()) {
outBounds.setEmpty();
} else {
mContainer.getRawBounds(outBounds);
@@ -275,6 +266,7 @@
final Rect parentAppBounds = parentConfig.windowConfiguration.getAppBounds();
+ config.windowConfiguration.setBounds(bounds);
config.windowConfiguration.setAppBounds(!bounds.isEmpty() ? bounds : null);
boolean intersectParentBounds = false;
diff --git a/com/android/server/wm/StartingData.java b/com/android/server/wm/StartingData.java
index 8c564bb..eb5011f 100644
--- a/com/android/server/wm/StartingData.java
+++ b/com/android/server/wm/StartingData.java
@@ -16,7 +16,7 @@
package com.android.server.wm;
-import android.view.WindowManagerPolicy.StartingSurface;
+import com.android.server.policy.WindowManagerPolicy.StartingSurface;
/**
* Represents the model about how a starting window should be constructed.
diff --git a/com/android/server/wm/StrictModeFlash.java b/com/android/server/wm/StrictModeFlash.java
index eb8ee69..f51a6a9 100644
--- a/com/android/server/wm/StrictModeFlash.java
+++ b/com/android/server/wm/StrictModeFlash.java
@@ -41,15 +41,14 @@
private boolean mDrawNeeded;
private final int mThickness = 20;
- public StrictModeFlash(Display display, SurfaceSession session) {
+ public StrictModeFlash(DisplayContent dc) {
SurfaceControl ctrl = null;
try {
- ctrl = new SurfaceControl.Builder(session)
+ ctrl = dc.makeOverlay()
.setName("StrictModeFlash")
.setSize(1, 1)
.setFormat(PixelFormat.TRANSLUCENT)
.build();
- ctrl.setLayerStack(display.getLayerStack());
ctrl.setLayer(WindowManagerService.TYPE_LAYER_MULTIPLIER * 101); // one more than Watermark? arbitrary.
ctrl.setPosition(0, 0);
ctrl.show();
diff --git a/com/android/server/wm/SurfaceBuilderFactory.java b/com/android/server/wm/SurfaceBuilderFactory.java
new file mode 100644
index 0000000..5390e5a
--- /dev/null
+++ b/com/android/server/wm/SurfaceBuilderFactory.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 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.server.wm;
+
+import android.view.SurfaceSession;
+import android.view.SurfaceControl;
+
+interface SurfaceBuilderFactory {
+ SurfaceControl.Builder make(SurfaceSession s);
+};
+
diff --git a/com/android/server/wm/SurfaceControlWithBackground.java b/com/android/server/wm/SurfaceControlWithBackground.java
index a5080d5..7c5bd43 100644
--- a/com/android/server/wm/SurfaceControlWithBackground.java
+++ b/com/android/server/wm/SurfaceControlWithBackground.java
@@ -16,7 +16,13 @@
package com.android.server.wm;
-import android.graphics.PixelFormat;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+
+import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_BOTTOM;
+import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_LEFT;
+import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_RIGHT;
+
import android.graphics.Rect;
import android.graphics.Region;
import android.os.IBinder;
@@ -24,13 +30,6 @@
import android.view.Surface;
import android.view.Surface.OutOfResourcesException;
import android.view.SurfaceControl;
-import android.view.SurfaceSession;
-
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
-import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManagerPolicy.NAV_BAR_BOTTOM;
-import static android.view.WindowManagerPolicy.NAV_BAR_LEFT;
-import static android.view.WindowManagerPolicy.NAV_BAR_RIGHT;
/**
* SurfaceControl extension that has black background behind navigation bar area for fullscreen
diff --git a/com/android/server/wm/Task.java b/com/android/server/wm/Task.java
index 13435d7..8aa129a 100644
--- a/com/android/server/wm/Task.java
+++ b/com/android/server/wm/Task.java
@@ -51,14 +51,8 @@
import java.io.PrintWriter;
import java.util.function.Consumer;
-class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerUser {
+class Task extends WindowContainer<AppWindowToken> {
static final String TAG = TAG_WITH_CLASS_NAME ? "Task" : TAG_WM;
- // Return value from {@link setBounds} indicating no change was made to the Task bounds.
- private static final int BOUNDS_CHANGE_NONE = 0;
- // Return value from {@link setBounds} indicating the position of the Task bounds changed.
- private static final int BOUNDS_CHANGE_POSITION = 1;
- // Return value from {@link setBounds} indicating the size of the Task bounds changed.
- private static final int BOUNDS_CHANGE_SIZE = 1 << 1;
// TODO: Track parent marks like this in WindowContainer.
TaskStack mStack;
@@ -67,8 +61,6 @@
private boolean mDeferRemoval = false;
final WindowManagerService mService;
- // Content limits relative to the DisplayContent this sits in.
- private Rect mBounds = new Rect();
final Rect mPreparedFrozenBounds = new Rect();
final Configuration mPreparedFrozenMergedConfig = new Configuration();
@@ -78,13 +70,12 @@
// Device rotation as of the last time {@link #mBounds} was set.
private int mRotation;
- // Whether mBounds is fullscreen
- private boolean mFillsParent = true;
-
// For comparison with DisplayContent bounds.
private Rect mTmpRect = new Rect();
// For handling display rotations.
private Rect mTmpRect2 = new Rect();
+ // For retrieving dim bounds
+ private Rect mTmpRect3 = new Rect();
// Resize mode of the task. See {@link ActivityInfo#resizeMode}
private int mResizeMode;
@@ -105,8 +96,11 @@
// stack moves and we in fact do so when moving from full screen to pinned.
private boolean mPreserveNonFloatingState = false;
- Task(int taskId, TaskStack stack, int userId, WindowManagerService service, Rect bounds,
- int resizeMode, boolean supportsPictureInPicture, TaskDescription taskDescription,
+ private Dimmer mDimmer = new Dimmer(this);
+ private final Rect mTmpDimBoundsRect = new Rect();
+
+ Task(int taskId, TaskStack stack, int userId, WindowManagerService service, int resizeMode,
+ boolean supportsPictureInPicture, TaskDescription taskDescription,
TaskWindowContainerController controller) {
mTaskId = taskId;
mStack = stack;
@@ -115,7 +109,7 @@
mResizeMode = resizeMode;
mSupportsPictureInPicture = supportsPictureInPicture;
setController(controller);
- setBounds(bounds, getOverrideConfiguration());
+ setBounds(getOverrideBounds());
mTaskDescription = taskDescription;
// Tasks have no set orientation value (including SCREEN_ORIENTATION_UNSPECIFIED).
@@ -188,12 +182,6 @@
EventLog.writeEvent(WM_TASK_REMOVED, mTaskId, "removeTask");
mDeferRemoval = false;
- // Make sure to remove dim layer user first before removing task its from parent.
- DisplayContent content = getDisplayContent();
- if (content != null) {
- content.mDimLayerController.removeDimLayerUser(this);
- }
-
super.removeImmediately();
}
@@ -230,13 +218,14 @@
}
/** @see com.android.server.am.ActivityManagerService#positionTaskInStack(int, int, int). */
- void positionAt(int position, Rect bounds, Configuration overrideConfig) {
+ void positionAt(int position) {
mStack.positionChildAt(position, this, false /* includingParents */);
- resizeLocked(bounds, overrideConfig, false /* force */);
}
@Override
void onParentSet() {
+ super.onParentSet();
+
// Update task bounds if needed.
updateDisplayInfo(getDisplayContent());
@@ -272,50 +261,37 @@
}
}
- /** Set the task bounds. Passing in null sets the bounds to fullscreen. */
- // TODO: There is probably not a need to pass in overrideConfig anymore since any change to it
- // will be automatically propagated from the AM. Also, mBound is going to be in
- // WindowConfiguration long term.
- private int setBounds(Rect bounds, Configuration overrideConfig) {
- if (overrideConfig == null) {
- overrideConfig = EMPTY;
+ public int setBounds(Rect bounds, boolean forceResize) {
+ final int boundsChanged = setBounds(bounds);
+
+ if (forceResize && (boundsChanged & BOUNDS_CHANGE_SIZE) != BOUNDS_CHANGE_SIZE) {
+ onResize();
+ return BOUNDS_CHANGE_SIZE | boundsChanged;
}
- boolean oldFullscreen = mFillsParent;
+ return boundsChanged;
+ }
+
+ /** Set the task bounds. Passing in null sets the bounds to fullscreen. */
+ @Override
+ public int setBounds(Rect bounds) {
int rotation = Surface.ROTATION_0;
final DisplayContent displayContent = mStack.getDisplayContent();
if (displayContent != null) {
- displayContent.getLogicalDisplayRect(mTmpRect);
rotation = displayContent.getDisplayInfo().rotation;
- mFillsParent = bounds == null;
- if (mFillsParent) {
- bounds = mTmpRect;
- }
- }
-
- if (bounds == null) {
+ } else if (bounds == null) {
// Can't set to fullscreen if we don't have a display to get bounds from...
return BOUNDS_CHANGE_NONE;
}
- if (mBounds.equals(bounds) && oldFullscreen == mFillsParent && mRotation == rotation) {
+
+ if (equivalentOverrideBounds(bounds)) {
return BOUNDS_CHANGE_NONE;
}
- int boundsChange = BOUNDS_CHANGE_NONE;
- if (mBounds.left != bounds.left || mBounds.top != bounds.top) {
- boundsChange |= BOUNDS_CHANGE_POSITION;
- }
- if (mBounds.width() != bounds.width() || mBounds.height() != bounds.height()) {
- boundsChange |= BOUNDS_CHANGE_SIZE;
- }
-
- mBounds.set(bounds);
+ final int boundsChange = super.setBounds(bounds);
mRotation = rotation;
- if (displayContent != null) {
- displayContent.mDimLayerController.updateDimLayer(this);
- }
- onOverrideConfigurationChanged(overrideConfig);
+
return boundsChange;
}
@@ -363,28 +339,12 @@
return isResizeable();
}
- boolean resizeLocked(Rect bounds, Configuration overrideConfig, boolean forced) {
- int boundsChanged = setBounds(bounds, overrideConfig);
- if (forced) {
- boundsChanged |= BOUNDS_CHANGE_SIZE;
- }
- if (boundsChanged == BOUNDS_CHANGE_NONE) {
- return false;
- }
- if ((boundsChanged & BOUNDS_CHANGE_SIZE) == BOUNDS_CHANGE_SIZE) {
- onResize();
- } else {
- onMovedByResize();
- }
- return true;
- }
-
/**
* Prepares the task bounds to be frozen with the current size. See
* {@link AppWindowToken#freezeBounds}.
*/
void prepareFreezingBounds() {
- mPreparedFrozenBounds.set(mBounds);
+ mPreparedFrozenBounds.set(getBounds());
mPreparedFrozenMergedConfig.setTo(getConfiguration());
}
@@ -410,30 +370,30 @@
mTmpRect2.offsetTo(adjustedBounds.left, adjustedBounds.top);
}
setTempInsetBounds(tempInsetBounds);
- resizeLocked(mTmpRect2, getOverrideConfiguration(), false /* forced */);
+ setBounds(mTmpRect2, false /* forced */);
}
/** Return true if the current bound can get outputted to the rest of the system as-is. */
private boolean useCurrentBounds() {
final DisplayContent displayContent = getDisplayContent();
- return mFillsParent
+ return matchParentBounds()
|| !inSplitScreenSecondaryWindowingMode()
|| displayContent == null
|| displayContent.getSplitScreenPrimaryStackIgnoringVisibility() != null;
}
- /** Original bounds of the task if applicable, otherwise fullscreen rect. */
- void getBounds(Rect out) {
+ @Override
+ public void getBounds(Rect out) {
if (useCurrentBounds()) {
// No need to adjust the output bounds if fullscreen or the docked stack is visible
// since it is already what we want to represent to the rest of the system.
- out.set(mBounds);
+ super.getBounds(out);
return;
}
// The bounds has been adjusted to accommodate for a docked stack, but the docked stack is
// not currently visible. Go ahead a represent it as fullscreen to the rest of the system.
- mStack.getDisplayContent().getLogicalDisplayRect(out);
+ mStack.getDisplayContent().getBounds(out);
}
/**
@@ -482,7 +442,6 @@
}
/** Bounds of the task to be used for dimming, as well as touch related tests. */
- @Override
public void getDimBounds(Rect out) {
final DisplayContent displayContent = mStack.getDisplayContent();
// It doesn't matter if we in particular are part of the resize, since we couldn't have
@@ -494,7 +453,7 @@
return;
}
- if (!mFillsParent) {
+ if (!matchParentBounds()) {
// When minimizing the docked stack when going home, we don't adjust the task bounds
// so we need to intersect the task bounds with the stack bounds here.
//
@@ -505,11 +464,11 @@
mStack.getBounds(out);
} else {
mStack.getBounds(mTmpRect);
- mTmpRect.intersect(mBounds);
+ mTmpRect.intersect(getBounds());
}
out.set(mTmpRect);
} else {
- out.set(mBounds);
+ out.set(getBounds());
}
return;
}
@@ -517,7 +476,7 @@
// The bounds has been adjusted to accommodate for a docked stack, but the docked stack is
// not currently visible. Go ahead a represent it as fullscreen to the rest of the system.
if (displayContent != null) {
- displayContent.getLogicalDisplayRect(out);
+ displayContent.getBounds(out);
}
}
@@ -545,10 +504,10 @@
if (displayContent == null) {
return;
}
- if (mFillsParent) {
+ if (matchParentBounds()) {
// TODO: Yeah...not sure if this works with WindowConfiguration, but shouldn't be a
// problem once we move mBounds into WindowConfiguration.
- setBounds(null, getOverrideConfiguration());
+ setBounds(null);
return;
}
final int newRotation = displayContent.getDisplayInfo().rotation;
@@ -561,18 +520,18 @@
// task bounds so it stays in the same place.
// - Rotate the bounds and notify activity manager if the task can be resized independently
// from its stack. The stack will take care of task rotation for the other case.
- mTmpRect2.set(mBounds);
+ mTmpRect2.set(getBounds());
if (!getWindowConfiguration().canResizeTask()) {
- setBounds(mTmpRect2, getOverrideConfiguration());
+ setBounds(mTmpRect2);
return;
}
displayContent.rotateBounds(mRotation, newRotation, mTmpRect2);
- if (setBounds(mTmpRect2, getOverrideConfiguration()) != BOUNDS_CHANGE_NONE) {
+ if (setBounds(mTmpRect2) != BOUNDS_CHANGE_NONE) {
final TaskWindowContainerController controller = getController();
if (controller != null) {
- controller.requestResize(mBounds, RESIZE_MODE_SYSTEM_SCREEN_ROTATION);
+ controller.requestResize(getBounds(), RESIZE_MODE_SYSTEM_SCREEN_ROTATION);
}
}
}
@@ -634,26 +593,9 @@
return null;
}
- @Override
- public boolean dimFullscreen() {
- return isFullscreen();
- }
-
- @Override
- public int getLayerForDim(WindowStateAnimator animator, int layerOffset, int defaultLayer) {
- // If the dim layer is for a starting window, move the dim layer back in the z-order behind
- // the lowest activity window to ensure it does not occlude the main window if it is
- // translucent
- final AppWindowToken appToken = animator.mWin.mAppToken;
- if (animator.mAttrType == TYPE_APPLICATION_STARTING && hasChild(appToken) ) {
- return Math.min(defaultLayer, appToken.getLowestAnimLayer() - layerOffset);
- }
- return defaultLayer;
- }
-
boolean isFullscreen() {
if (useCurrentBounds()) {
- return mFillsParent;
+ return matchParentBounds();
}
// The bounds has been adjusted to accommodate for a docked stack, but the docked stack
// is not currently visible. Go ahead a represent it as fullscreen to the rest of the
@@ -661,16 +603,6 @@
return true;
}
- @Override
- public DisplayInfo getDisplayInfo() {
- return getDisplayContent().getDisplayInfo();
- }
-
- @Override
- public boolean isAttachedToDisplay() {
- return getDisplayContent() != null;
- }
-
void forceWindowsScaleable(boolean force) {
mService.openSurfaceTransaction();
try {
@@ -692,7 +624,7 @@
@Override
boolean fillsParent() {
- return mFillsParent || !getWindowConfiguration().canResizeTask();
+ return matchParentBounds() || !getWindowConfiguration().canResizeTask();
}
@Override
@@ -718,9 +650,18 @@
mPreserveNonFloatingState = false;
}
+ Dimmer getDimmer() {
+ return mDimmer;
+ }
+
@Override
- public String toShortString() {
- return "Task=" + mTaskId;
+ void prepareSurfaces() {
+ mDimmer.resetDimStates();
+ super.prepareSurfaces();
+ getDimBounds(mTmpDimBoundsRect);
+ if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) {
+ scheduleAnimation();
+ }
}
@CallSuper
@@ -733,8 +674,8 @@
final AppWindowToken appWindowToken = mChildren.get(i);
appWindowToken.writeToProto(proto, APP_WINDOW_TOKENS, trim);
}
- proto.write(FILLS_PARENT, mFillsParent);
- mBounds.writeToProto(proto, BOUNDS);
+ proto.write(FILLS_PARENT, matchParentBounds());
+ getBounds().writeToProto(proto, BOUNDS);
mTempInsetBounds.writeToProto(proto, TEMP_INSET_BOUNDS);
proto.end(token);
}
@@ -743,8 +684,7 @@
final String doublePrefix = prefix + " ";
pw.println(prefix + "taskId=" + mTaskId);
- pw.println(doublePrefix + "mFillsParent=" + mFillsParent);
- pw.println(doublePrefix + "mBounds=" + mBounds.toShortString());
+ pw.println(doublePrefix + "mBounds=" + getBounds().toShortString());
pw.println(doublePrefix + "mdr=" + mDeferRemoval);
pw.println(doublePrefix + "appTokens=" + mChildren);
pw.println(doublePrefix + "mTempInsetBounds=" + mTempInsetBounds.toShortString());
@@ -757,4 +697,8 @@
wtoken.dump(pw, triplePrefix);
}
}
+
+ String toShortString() {
+ return "Task=" + mTaskId;
+ }
}
diff --git a/com/android/server/wm/TaskPositioner.java b/com/android/server/wm/TaskPositioner.java
index 12f6b5a..87d0a40 100644
--- a/com/android/server/wm/TaskPositioner.java
+++ b/com/android/server/wm/TaskPositioner.java
@@ -59,7 +59,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-class TaskPositioner implements DimLayer.DimLayerUser {
+class TaskPositioner {
private static final boolean DEBUG_ORIENTATION_VIOLATIONS = false;
private static final String TAG_LOCAL = "TaskPositioner";
private static final String TAG = TAG_WITH_CLASS_NAME ? TAG_LOCAL : TAG_WM;
@@ -99,9 +99,6 @@
private WindowPositionerEventReceiver mInputEventReceiver;
private Display mDisplay;
private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
- private DimLayer mDimLayer;
- @CtrlType
- private int mCurrentDimSide;
private Rect mTmpRect = new Rect();
private int mSideMargin;
private int mMinVisibleWidth;
@@ -207,15 +204,6 @@
mService.mActivityManager.resizeTask(
mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER_FORCED);
}
-
- if (mCurrentDimSide != CTRL_NONE) {
- final int createMode = mCurrentDimSide == CTRL_LEFT
- ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT
- : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
- mService.mActivityManager.setTaskWindowingModeSplitScreenPrimary(
- mTask.mTaskId, createMode, true /*toTop*/, true /* animate */,
- null /* initialBounds */);
- }
} catch(RemoteException e) {}
// Post back to WM to handle clean-ups. We still need the input
@@ -243,7 +231,9 @@
/**
* @param display The Display that the window being dragged is on.
*/
- void register(Display display) {
+ void register(DisplayContent displayContent) {
+ final Display display = displayContent.getDisplay();
+
if (DEBUG_TASK_POSITIONING) {
Slog.d(TAG, "Registering task positioner");
}
@@ -305,7 +295,6 @@
}
mService.pauseRotationLocked();
- mDimLayer = new DimLayer(mService, this, mDisplay.getDisplayId(), TAG_LOCAL);
mSideMargin = dipToPixel(SIDE_MARGIN_DIP, mDisplayMetrics);
mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, mDisplayMetrics);
mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, mDisplayMetrics);
@@ -336,12 +325,6 @@
mDragWindowHandle = null;
mDragApplicationHandle = null;
mDisplay = null;
-
- if (mDimLayer != null) {
- mDimLayer.destroySurface();
- mDimLayer = null;
- }
- mCurrentDimSide = CTRL_NONE;
mDragEnded = true;
// Resume rotations after a drag.
@@ -399,6 +382,27 @@
mStartOrientationWasLandscape = startBounds.width() >= startBounds.height();
mWindowOriginalBounds.set(startBounds);
+ // Notify the app that resizing has started, even though we haven't received any new
+ // bounds yet. This will guarantee that the app starts the backdrop renderer before
+ // configuration changes which could cause an activity restart.
+ if (mResizing) {
+ synchronized (mService.mWindowMap) {
+ notifyMoveLocked(startX, startY);
+ }
+
+ // Perform the resize on the WMS handler thread when we don't have the WMS lock held
+ // to ensure that we don't deadlock WMS and AMS. Note that WindowPositionerEventReceiver
+ // callbacks are delivered on the same handler so this initial resize is always
+ // guaranteed to happen before subsequent drag resizes.
+ mService.mH.post(() -> {
+ try {
+ mService.mActivityManager.resizeTask(
+ mTask.mTaskId, startBounds, RESIZE_MODE_USER_FORCED);
+ } catch (RemoteException e) {
+ }
+ });
+ }
+
// Make sure we always have valid drag bounds even if the drag ends before any move events
// have been handled.
mWindowDragBounds.set(startBounds);
@@ -434,7 +438,6 @@
}
updateWindowDragBounds(nX, nY, mTmpRect);
- updateDimLayerVisibility(nX);
return false;
}
@@ -621,88 +624,6 @@
"updateWindowDragBounds: " + mWindowDragBounds);
}
- private void updateDimLayerVisibility(int x) {
- @CtrlType
- int dimSide = getDimSide(x);
- if (dimSide == mCurrentDimSide) {
- return;
- }
-
- mCurrentDimSide = dimSide;
-
- if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION updateDimLayerVisibility");
- mService.openSurfaceTransaction();
- if (mCurrentDimSide == CTRL_NONE) {
- mDimLayer.hide();
- } else {
- showDimLayer();
- }
- mService.closeSurfaceTransaction("updateDimLayerVisibility");
- }
-
- /**
- * Returns the side of the screen the dim layer should be shown.
- * @param x horizontal coordinate used to determine if the dim layer should be shown
- * @return Returns {@link #CTRL_LEFT} if the dim layer should be shown on the left half of the
- * screen, {@link #CTRL_RIGHT} if on the right side, or {@link #CTRL_NONE} if the dim layer
- * shouldn't be shown.
- */
- private int getDimSide(int x) {
- if (!mTask.mStack.inFreeformWindowingMode()
- || !mTask.mStack.fillsParent()
- || mTask.mStack.getConfiguration().orientation != ORIENTATION_LANDSCAPE) {
- return CTRL_NONE;
- }
-
- mTask.mStack.getDimBounds(mTmpRect);
- if (x - mSideMargin <= mTmpRect.left) {
- return CTRL_LEFT;
- }
- if (x + mSideMargin >= mTmpRect.right) {
- return CTRL_RIGHT;
- }
-
- return CTRL_NONE;
- }
-
- private void showDimLayer() {
- mTask.mStack.getDimBounds(mTmpRect);
- if (mCurrentDimSide == CTRL_LEFT) {
- mTmpRect.right = mTmpRect.centerX();
- } else if (mCurrentDimSide == CTRL_RIGHT) {
- mTmpRect.left = mTmpRect.centerX();
- }
-
- mDimLayer.setBounds(mTmpRect);
- mDimLayer.show(mService.getDragLayerLocked(), RESIZING_HINT_ALPHA,
- RESIZING_HINT_DURATION_MS);
- }
-
- @Override /** {@link DimLayer.DimLayerUser} */
- public boolean dimFullscreen() {
- return isFullscreen();
- }
-
- boolean isFullscreen() {
- return false;
- }
-
- @Override /** {@link DimLayer.DimLayerUser} */
- public DisplayInfo getDisplayInfo() {
- return mTask.mStack.getDisplayInfo();
- }
-
- @Override
- public boolean isAttachedToDisplay() {
- return mTask != null && mTask.getDisplayContent() != null;
- }
-
- @Override
- public void getDimBounds(Rect out) {
- // This dim layer user doesn't need this.
- }
-
- @Override
public String toShortString() {
return TAG;
}
diff --git a/com/android/server/wm/TaskSnapshotController.java b/com/android/server/wm/TaskSnapshotController.java
index 54ef065..84e475a 100644
--- a/com/android/server/wm/TaskSnapshotController.java
+++ b/com/android/server/wm/TaskSnapshotController.java
@@ -18,12 +18,12 @@
import static com.android.server.wm.TaskSnapshotPersister.DISABLE_FULL_SIZED_BITMAPS;
import static com.android.server.wm.TaskSnapshotPersister.REDUCED_SCALE;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.annotation.Nullable;
import android.app.ActivityManager;
-import android.app.ActivityManager.StackId;
import android.app.ActivityManager.TaskSnapshot;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
@@ -35,16 +35,17 @@
import android.util.Slog;
import android.view.DisplayListCanvas;
import android.view.RenderNode;
+import android.view.SurfaceControl;
import android.view.ThreadedRenderer;
import android.view.WindowManager.LayoutParams;
-import android.view.WindowManagerPolicy.ScreenOffListener;
-import android.view.WindowManagerPolicy.StartingSurface;
-
-import com.google.android.collect.Sets;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.policy.WindowManagerPolicy.ScreenOffListener;
+import com.android.server.policy.WindowManagerPolicy.StartingSurface;
import com.android.server.wm.TaskSnapshotSurface.SystemBarBackgroundPainter;
+import com.google.android.collect.Sets;
+
import java.io.PrintWriter;
/**
@@ -210,11 +211,28 @@
if (mainWindow == null) {
return null;
}
+ if (!mService.mPolicy.isScreenOn()) {
+ if (DEBUG_SCREENSHOT) {
+ Slog.i(TAG_WM, "Attempted to take screenshot while display was off.");
+ }
+ return null;
+ }
+ if (task.getSurfaceControl() == null) {
+ return null;
+ }
+
final boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic();
final float scaleFraction = isLowRamDevice ? REDUCED_SCALE : 1f;
- final GraphicBuffer buffer = top.mDisplayContent.screenshotApplicationsToBuffer(top.token,
- -1, -1, false, scaleFraction, false, true);
+ final Rect taskFrame = new Rect();
+ task.getBounds(taskFrame);
+
+ final GraphicBuffer buffer = SurfaceControl.captureLayersToBuffer(
+ task.getSurfaceControl().getHandle(), taskFrame, scaleFraction);
+
if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) {
+ if (DEBUG_SCREENSHOT) {
+ Slog.w(TAG_WM, "Failed to take screenshot");
+ }
return null;
}
return new TaskSnapshot(buffer, top.getConfiguration().orientation,
diff --git a/com/android/server/wm/TaskSnapshotSurface.java b/com/android/server/wm/TaskSnapshotSurface.java
index 3ce090a..41915a3 100644
--- a/com/android/server/wm/TaskSnapshotSurface.java
+++ b/com/android/server/wm/TaskSnapshotSurface.java
@@ -18,9 +18,7 @@
import static android.graphics.Color.WHITE;
import static android.graphics.Color.alpha;
-import static android.view.SurfaceControl.HIDDEN;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
-import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
import static android.view.WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
import static android.view.WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE;
@@ -67,12 +65,12 @@
import android.view.ViewGroup.LayoutParams;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
-import android.view.WindowManagerPolicy.StartingSurface;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.DecorView;
import com.android.internal.view.BaseIWindow;
+import com.android.server.policy.WindowManagerPolicy.StartingSurface;
/**
* This class represents a starting window that shows a snapshot.
diff --git a/com/android/server/wm/TaskStack.java b/com/android/server/wm/TaskStack.java
index 053fb47..4a3a3fc 100644
--- a/com/android/server/wm/TaskStack.java
+++ b/com/android/server/wm/TaskStack.java
@@ -31,12 +31,11 @@
import static android.view.WindowManager.DOCKED_LEFT;
import static android.view.WindowManager.DOCKED_RIGHT;
import static android.view.WindowManager.DOCKED_TOP;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.LAYER_OFFSET_DIM;
import static com.android.server.wm.proto.StackProto.ANIMATION_BACKGROUND_SURFACE_IS_DIMMING;
import static com.android.server.wm.proto.StackProto.BOUNDS;
import static com.android.server.wm.proto.StackProto.FILLS_PARENT;
@@ -55,6 +54,7 @@
import android.util.proto.ProtoOutputStream;
import android.view.DisplayInfo;
import android.view.Surface;
+import android.view.SurfaceControl;
import com.android.internal.policy.DividerSnapAlgorithm;
import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
@@ -63,7 +63,7 @@
import java.io.PrintWriter;
-public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLayerUser,
+public class TaskStack extends WindowContainer<Task> implements
BoundsAnimationTarget {
/** Minimum size of an adjusted stack bounds relative to original stack bounds. Used to
* restrict IME adjustment so that a min portion of top stack remains visible.*/
@@ -87,9 +87,6 @@
private Rect mTmpRect2 = new Rect();
private Rect mTmpRect3 = new Rect();
- /** Content limits relative to the DisplayContent this sits in. */
- private Rect mBounds = new Rect();
-
/** Stack bounds adjusted to screen content area (taking into account IM windows, etc.) */
private final Rect mAdjustedBounds = new Rect();
@@ -99,17 +96,14 @@
*/
private final Rect mFullyAdjustedImeBounds = new Rect();
- /** Whether mBounds is fullscreen */
- private boolean mFillsParent = true;
-
// Device rotation as of the last time {@link #mBounds} was set.
private int mRotation;
/** Density as of last time {@link #mBounds} was set. */
private int mDensity;
- /** Support for non-zero {@link android.view.animation.Animation#getBackgroundColor()} */
- private DimLayer mAnimationBackgroundSurface;
+ private SurfaceControl mAnimationBackgroundSurface;
+ private boolean mAnimationBackgroundSurfaceIsShown = false;
/** The particular window with an Animation with non-zero background color. */
private WindowStateAnimator mAnimationBackgroundAnimator;
@@ -149,6 +143,13 @@
Rect mPreAnimationBounds = new Rect();
+ private Dimmer mDimmer = new Dimmer(this);
+
+ /**
+ * For {@link #prepareSurfaces}.
+ */
+ final Rect mTmpDimBoundsRect = new Rect();
+
TaskStack(WindowManagerService service, int stackId, StackWindowController controller) {
mService = service;
mStackId = stackId;
@@ -172,27 +173,20 @@
/**
* Set the bounds of the stack and its containing tasks.
* @param stackBounds New stack bounds. Passing in null sets the bounds to fullscreen.
- * @param configs Configuration for individual tasks, keyed by task id.
* @param taskBounds Bounds for individual tasks, keyed by task id.
+ * @param taskTempInsetBounds Inset bounds for individual tasks, keyed by task id.
* @return True if the stack bounds was changed.
* */
boolean setBounds(
- Rect stackBounds, SparseArray<Configuration> configs, SparseArray<Rect> taskBounds,
- SparseArray<Rect> taskTempInsetBounds) {
+ Rect stackBounds, SparseArray<Rect> taskBounds, SparseArray<Rect> taskTempInsetBounds) {
setBounds(stackBounds);
// Update bounds of containing tasks.
for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; --taskNdx) {
final Task task = mChildren.get(taskNdx);
- Configuration config = configs.get(task.mTaskId);
- if (config != null) {
- Rect bounds = taskBounds.get(task.mTaskId);
- task.resizeLocked(bounds, config, false /* forced */);
- task.setTempInsetBounds(taskTempInsetBounds != null ?
- taskTempInsetBounds.get(task.mTaskId) : null);
- } else {
- Slog.wtf(TAG_WM, "No config for task: " + task + ", is there a mismatch with AM?");
- }
+ task.setBounds(taskBounds.get(task.mTaskId), false /* forced */);
+ task.setTempInsetBounds(taskTempInsetBounds != null ?
+ taskTempInsetBounds.get(task.mTaskId) : null);
}
return true;
}
@@ -219,20 +213,20 @@
final boolean adjusted = !mAdjustedBounds.isEmpty();
Rect insetBounds = null;
if (adjusted && isAdjustedForMinimizedDockedStack()) {
- insetBounds = mBounds;
+ insetBounds = getRawBounds();
} else if (adjusted && mAdjustedForIme) {
if (mImeGoingAway) {
- insetBounds = mBounds;
+ insetBounds = getRawBounds();
} else {
insetBounds = mFullyAdjustedImeBounds;
}
}
- alignTasksToAdjustedBounds(adjusted ? mAdjustedBounds : mBounds, insetBounds);
+ alignTasksToAdjustedBounds(adjusted ? mAdjustedBounds : getRawBounds(), insetBounds);
mDisplayContent.setLayoutNeeded();
}
private void alignTasksToAdjustedBounds(Rect adjustedBounds, Rect tempInsetBounds) {
- if (mFillsParent) {
+ if (matchParentBounds()) {
return;
}
@@ -245,53 +239,85 @@
}
}
- private boolean setBounds(Rect bounds) {
- boolean oldFullscreen = mFillsParent;
+ private void updateAnimationBackgroundBounds() {
+ if (mAnimationBackgroundSurface == null) {
+ return;
+ }
+ getRawBounds(mTmpRect);
+ // TODO: Should be in relative coordinates.
+ getPendingTransaction().setSize(mAnimationBackgroundSurface, mTmpRect.width(),
+ mTmpRect.height()).setPosition(mAnimationBackgroundSurface, mTmpRect.left,
+ mTmpRect.top);
+ scheduleAnimation();
+ }
+
+ private void hideAnimationSurface() {
+ if (mAnimationBackgroundSurface == null) {
+ return;
+ }
+ getPendingTransaction().hide(mAnimationBackgroundSurface);
+ mAnimationBackgroundSurfaceIsShown = false;
+ scheduleAnimation();
+ }
+
+ private void showAnimationSurface(float alpha) {
+ if (mAnimationBackgroundSurface == null) {
+ return;
+ }
+ getPendingTransaction().setLayer(mAnimationBackgroundSurface, Integer.MIN_VALUE)
+ .setAlpha(mAnimationBackgroundSurface, alpha)
+ .show(mAnimationBackgroundSurface);
+ mAnimationBackgroundSurfaceIsShown = true;
+ scheduleAnimation();
+ }
+
+ @Override
+ public int setBounds(Rect bounds) {
+ return setBounds(getOverrideBounds(), bounds);
+ }
+
+ private int setBounds(Rect existing, Rect bounds) {
int rotation = Surface.ROTATION_0;
int density = DENSITY_DPI_UNDEFINED;
if (mDisplayContent != null) {
- mDisplayContent.getLogicalDisplayRect(mTmpRect);
+ mDisplayContent.getBounds(mTmpRect);
rotation = mDisplayContent.getDisplayInfo().rotation;
density = mDisplayContent.getDisplayInfo().logicalDensityDpi;
- mFillsParent = bounds == null;
- if (mFillsParent) {
- bounds = mTmpRect;
- }
}
- if (bounds == null) {
- // Can't set to fullscreen if we don't have a display to get bounds from...
- return false;
+ if (equivalentBounds(existing, bounds) && mRotation == rotation) {
+ return BOUNDS_CHANGE_NONE;
}
- if (mBounds.equals(bounds) && oldFullscreen == mFillsParent && mRotation == rotation) {
- return false;
- }
+
+ final int result = super.setBounds(bounds);
if (mDisplayContent != null) {
- mDisplayContent.mDimLayerController.updateDimLayer(this);
- mAnimationBackgroundSurface.setBounds(bounds);
+ updateAnimationBackgroundBounds();
}
- mBounds.set(bounds);
mRotation = rotation;
mDensity = density;
updateAdjustedBounds();
- return true;
+ return result;
}
/** Bounds of the stack without adjusting for other factors in the system like visibility
* of docked stack.
- * Most callers should be using {@link #getBounds} as it take into consideration other system
- * factors. */
+ * Most callers should be using {@link ConfigurationContainer#getOverrideBounds} as it take into
+ * consideration other system factors. */
void getRawBounds(Rect out) {
- out.set(mBounds);
+ out.set(getRawBounds());
+ }
+
+ Rect getRawBounds() {
+ return super.getBounds();
}
/** Return true if the current bound can get outputted to the rest of the system as-is. */
private boolean useCurrentBounds() {
- if (mFillsParent
+ if (matchParentBounds()
|| !inSplitScreenSecondaryWindowingMode()
|| mDisplayContent == null
|| mDisplayContent.getSplitScreenPrimaryStack() != null) {
@@ -300,24 +326,29 @@
return false;
}
- public void getBounds(Rect out) {
+ @Override
+ public void getBounds(Rect bounds) {
+ bounds.set(getBounds());
+ }
+
+ @Override
+ public Rect getBounds() {
if (useCurrentBounds()) {
// If we're currently adjusting for IME or minimized docked stack, we use the adjusted
// bounds; otherwise, no need to adjust the output bounds if fullscreen or the docked
// stack is visible since it is already what we want to represent to the rest of the
// system.
if (!mAdjustedBounds.isEmpty()) {
- out.set(mAdjustedBounds);
+ return mAdjustedBounds;
} else {
- out.set(mBounds);
+ return super.getBounds();
}
- return;
}
// The bounds has been adjusted to accommodate for a docked stack, but the docked stack
// is not currently visible. Go ahead a represent it as fullscreen to the rest of the
// system.
- mDisplayContent.getLogicalDisplayRect(out);
+ return mDisplayContent.getBounds();
}
/**
@@ -338,7 +369,7 @@
mBoundsAnimationSourceHintBounds.setEmpty();
}
- mPreAnimationBounds.set(mBounds);
+ mPreAnimationBounds.set(getRawBounds());
}
/**
@@ -368,7 +399,6 @@
}
/** Bounds of the stack with other system factors taken into consideration. */
- @Override
public void getDimBounds(Rect out) {
getBounds(out);
}
@@ -384,12 +414,12 @@
if (bounds != null) {
setBounds(bounds);
return;
- } else if (mFillsParent) {
+ } else if (matchParentBounds()) {
setBounds(null);
return;
}
- mTmpRect2.set(mBounds);
+ mTmpRect2.set(getRawBounds());
final int newRotation = mDisplayContent.getDisplayInfo().rotation;
final int newDensity = mDisplayContent.getDisplayInfo().logicalDensityDpi;
if (mRotation == newRotation && mDensity == newDensity) {
@@ -432,14 +462,14 @@
return false;
}
- if (mFillsParent) {
+ if (matchParentBounds()) {
// Update stack bounds again since rotation changed since updateDisplayInfo().
setBounds(null);
// Return false since we don't need the client to resize.
return false;
}
- mTmpRect2.set(mBounds);
+ mTmpRect2.set(getRawBounds());
mDisplayContent.rotateBounds(mRotation, newRotation, mTmpRect2);
if (inSplitScreenPrimaryWindowingMode()) {
repositionPrimarySplitScreenStackAfterRotation(mTmpRect2);
@@ -476,7 +506,7 @@
if (mDisplayContent.getDockedDividerController().canPrimaryStackDockTo(dockSide)) {
return;
}
- mDisplayContent.getLogicalDisplayRect(mTmpRect);
+ mDisplayContent.getBounds(mTmpRect);
dockSide = DockedDividerUtils.invertDockSide(dockSide);
switch (dockSide) {
case DOCKED_LEFT:
@@ -700,9 +730,12 @@
}
mDisplayContent = dc;
- mAnimationBackgroundSurface = new DimLayer(mService, this, mDisplayContent.getDisplayId(),
- "animation background stackId=" + mStackId);
+
updateBoundsForWindowModeChange();
+ mAnimationBackgroundSurface = makeChildSurface(null).setColorLayer(true)
+ .setName("animation background stackId=" + mStackId)
+ .build();
+
super.onDisplayChanged(dc);
}
@@ -718,7 +751,7 @@
// not fullscreen. If it's fullscreen, it means that we are in the transition of
// dismissing it, so we must not resize this stack.
bounds = new Rect();
- mDisplayContent.getLogicalDisplayRect(mTmpRect);
+ mDisplayContent.getBounds(mTmpRect);
mTmpRect2.setEmpty();
if (splitScreenStack != null) {
splitScreenStack.getRawBounds(mTmpRect2);
@@ -781,7 +814,7 @@
}
if (!inSplitScreenWindowingMode() || mDisplayContent == null) {
- outStackBounds.set(mBounds);
+ outStackBounds.set(getRawBounds());
return;
}
@@ -796,7 +829,7 @@
// The docked stack is being dismissed, but we caught before it finished being
// dismissed. In that case we want to treat it as if it is not occupying any space and
// let others occupy the whole display.
- mDisplayContent.getLogicalDisplayRect(outStackBounds);
+ mDisplayContent.getBounds(outStackBounds);
return;
}
@@ -804,11 +837,11 @@
if (dockedSide == DOCKED_INVALID) {
// Not sure how you got here...Only thing we can do is return current bounds.
Slog.e(TAG_WM, "Failed to get valid docked side for docked stack=" + dockedStack);
- outStackBounds.set(mBounds);
+ outStackBounds.set(getRawBounds());
return;
}
- mDisplayContent.getLogicalDisplayRect(mTmpRect);
+ mDisplayContent.getBounds(mTmpRect);
dockedStack.getRawBounds(mTmpRect2);
final boolean dockedOnTopOrLeft = dockedSide == DOCKED_TOP || dockedSide == DOCKED_LEFT;
getStackDockedModeBounds(mTmpRect, outStackBounds, mTmpRect2,
@@ -914,16 +947,16 @@
@Override
void onParentSet() {
+ super.onParentSet();
+
if (getParent() != null || mDisplayContent == null) {
return;
}
- // Looks like the stack was removed from the display. Go ahead and clean things up.
- mDisplayContent.mDimLayerController.removeDimLayerUser(this);
EventLog.writeEvent(EventLogTags.WM_STACK_REMOVED, mStackId);
if (mAnimationBackgroundSurface != null) {
- mAnimationBackgroundSurface.destroySurface();
+ mAnimationBackgroundSurface.destroy();
mAnimationBackgroundSurface = null;
}
@@ -933,9 +966,7 @@
void resetAnimationBackgroundAnimator() {
mAnimationBackgroundAnimator = null;
- if (mAnimationBackgroundSurface != null) {
- mAnimationBackgroundSurface.hide();
- }
+ hideAnimationSurface();
}
void setAnimationBackground(WindowStateAnimator winAnimator, int color) {
@@ -944,8 +975,7 @@
|| animLayer < mAnimationBackgroundAnimator.mAnimLayer) {
mAnimationBackgroundAnimator = winAnimator;
animLayer = mDisplayContent.getLayerForAnimationBackground(winAnimator);
- mAnimationBackgroundSurface.show(animLayer - LAYER_OFFSET_DIM,
- ((color >> 24) & 0xff) / 255f, 0);
+ showAnimationSurface(((color >> 24) & 0xff) / 255f);
}
}
@@ -1112,14 +1142,14 @@
// occluded by IME. We shift its bottom up by the height of the IME, but
// leaves at least 30% of the top stack visible.
final int minTopStackBottom =
- getMinTopStackBottom(displayContentRect, mBounds.bottom);
+ getMinTopStackBottom(displayContentRect, getRawBounds().bottom);
final int bottom = Math.max(
- mBounds.bottom - yOffset + dividerWidth - dividerWidthInactive,
+ getRawBounds().bottom - yOffset + dividerWidth - dividerWidthInactive,
minTopStackBottom);
- mTmpAdjustedBounds.set(mBounds);
- mTmpAdjustedBounds.bottom =
- (int) (mAdjustImeAmount * bottom + (1 - mAdjustImeAmount) * mBounds.bottom);
- mFullyAdjustedImeBounds.set(mBounds);
+ mTmpAdjustedBounds.set(getRawBounds());
+ mTmpAdjustedBounds.bottom = (int) (mAdjustImeAmount * bottom + (1 - mAdjustImeAmount)
+ * getRawBounds().bottom);
+ mFullyAdjustedImeBounds.set(getRawBounds());
} else {
// When the stack is on bottom and has no focus, it's only adjusted for divider width.
final int dividerWidthDelta = dividerWidthInactive - dividerWidth;
@@ -1129,22 +1159,24 @@
// We try to move it up by the height of the IME window, but only to the extent
// that leaves at least 30% of the top stack visible.
// 'top' is where the top of bottom stack will move to in this case.
- final int topBeforeImeAdjust = mBounds.top - dividerWidth + dividerWidthInactive;
+ final int topBeforeImeAdjust =
+ getRawBounds().top - dividerWidth + dividerWidthInactive;
final int minTopStackBottom =
- getMinTopStackBottom(displayContentRect, mBounds.top - dividerWidth);
+ getMinTopStackBottom(displayContentRect,
+ getRawBounds().top - dividerWidth);
final int top = Math.max(
- mBounds.top - yOffset, minTopStackBottom + dividerWidthInactive);
+ getRawBounds().top - yOffset, minTopStackBottom + dividerWidthInactive);
- mTmpAdjustedBounds.set(mBounds);
+ mTmpAdjustedBounds.set(getRawBounds());
// Account for the adjustment for IME and divider width separately.
// (top - topBeforeImeAdjust) is the amount of movement due to IME only,
// and dividerWidthDelta is due to divider width change only.
- mTmpAdjustedBounds.top = mBounds.top +
+ mTmpAdjustedBounds.top = getRawBounds().top +
(int) (mAdjustImeAmount * (top - topBeforeImeAdjust) +
mAdjustDividerAmount * dividerWidthDelta);
- mFullyAdjustedImeBounds.set(mBounds);
+ mFullyAdjustedImeBounds.set(getRawBounds());
mFullyAdjustedImeBounds.top = top;
- mFullyAdjustedImeBounds.bottom = top + mBounds.height();
+ mFullyAdjustedImeBounds.bottom = top + getRawBounds().height();
}
return true;
}
@@ -1158,21 +1190,21 @@
if (dockSide == DOCKED_TOP) {
mService.getStableInsetsLocked(DEFAULT_DISPLAY, mTmpRect);
int topInset = mTmpRect.top;
- mTmpAdjustedBounds.set(mBounds);
- mTmpAdjustedBounds.bottom =
- (int) (minimizeAmount * topInset + (1 - minimizeAmount) * mBounds.bottom);
+ mTmpAdjustedBounds.set(getRawBounds());
+ mTmpAdjustedBounds.bottom = (int) (minimizeAmount * topInset + (1 - minimizeAmount)
+ * getRawBounds().bottom);
} else if (dockSide == DOCKED_LEFT) {
- mTmpAdjustedBounds.set(mBounds);
- final int width = mBounds.width();
+ mTmpAdjustedBounds.set(getRawBounds());
+ final int width = getRawBounds().width();
mTmpAdjustedBounds.right =
(int) (minimizeAmount * mDockedStackMinimizeThickness
- + (1 - minimizeAmount) * mBounds.right);
+ + (1 - minimizeAmount) * getRawBounds().right);
mTmpAdjustedBounds.left = mTmpAdjustedBounds.right - width;
} else if (dockSide == DOCKED_RIGHT) {
- mTmpAdjustedBounds.set(mBounds);
- mTmpAdjustedBounds.left =
- (int) (minimizeAmount * (mBounds.right - mDockedStackMinimizeThickness)
- + (1 - minimizeAmount) * mBounds.left);
+ mTmpAdjustedBounds.set(getRawBounds());
+ mTmpAdjustedBounds.left = (int) (minimizeAmount *
+ (getRawBounds().right - mDockedStackMinimizeThickness)
+ + (1 - minimizeAmount) * getRawBounds().left);
}
return true;
}
@@ -1194,9 +1226,9 @@
if (dockSide == DOCKED_TOP) {
mService.getStableInsetsLocked(DEFAULT_DISPLAY, mTmpRect);
int topInset = mTmpRect.top;
- return mBounds.bottom - topInset;
+ return getRawBounds().bottom - topInset;
} else if (dockSide == DOCKED_LEFT || dockSide == DOCKED_RIGHT) {
- return mBounds.width() - mDockedStackMinimizeThickness;
+ return getRawBounds().width() - mDockedStackMinimizeThickness;
} else {
return 0;
}
@@ -1230,11 +1262,12 @@
return;
}
- final Rect insetBounds = mImeGoingAway ? mBounds : mFullyAdjustedImeBounds;
+ final Rect insetBounds = mImeGoingAway ? getRawBounds() : mFullyAdjustedImeBounds;
task.alignToAdjustedBounds(mAdjustedBounds, insetBounds, getDockSide() == DOCKED_TOP);
mDisplayContent.setLayoutNeeded();
}
+
boolean isAdjustedForMinimizedDockedStack() {
return mMinimizeAmount != 0f;
}
@@ -1248,17 +1281,16 @@
for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; taskNdx--) {
mChildren.get(taskNdx).writeToProto(proto, TASKS, trim);
}
- proto.write(FILLS_PARENT, mFillsParent);
- mBounds.writeToProto(proto, BOUNDS);
- proto.write(ANIMATION_BACKGROUND_SURFACE_IS_DIMMING, mAnimationBackgroundSurface.isDimming());
+ proto.write(FILLS_PARENT, matchParentBounds());
+ getRawBounds().writeToProto(proto, BOUNDS);
+ proto.write(ANIMATION_BACKGROUND_SURFACE_IS_DIMMING, mAnimationBackgroundSurfaceIsShown);
proto.end(token);
}
public void dump(String prefix, PrintWriter pw) {
pw.println(prefix + "mStackId=" + mStackId);
pw.println(prefix + "mDeferRemoval=" + mDeferRemoval);
- pw.println(prefix + "mFillsParent=" + mFillsParent);
- pw.println(prefix + "mBounds=" + mBounds.toShortString());
+ pw.println(prefix + "mBounds=" + getRawBounds().toShortString());
if (mMinimizeAmount != 0f) {
pw.println(prefix + "mMinimizeAmount=" + mMinimizeAmount);
}
@@ -1273,9 +1305,8 @@
for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; taskNdx--) {
mChildren.get(taskNdx).dump(prefix + " ", pw);
}
- if (mAnimationBackgroundSurface.isDimming()) {
- pw.println(prefix + "mWindowAnimationBackgroundSurface:");
- mAnimationBackgroundSurface.printTo(prefix + " ", pw);
+ if (mAnimationBackgroundSurfaceIsShown) {
+ pw.println(prefix + "mWindowAnimationBackgroundSurface is shown");
}
if (!mExitingAppTokens.isEmpty()) {
pw.println();
@@ -1290,23 +1321,10 @@
}
}
- /** Fullscreen status of the stack without adjusting for other factors in the system like
- * visibility of docked stack.
- * Most callers should be using {@link #fillsParent} as it take into consideration other
- * system factors. */
- boolean getRawFullscreen() {
- return mFillsParent;
- }
-
- @Override
- public boolean dimFullscreen() {
- return !isActivityTypeStandard() || fillsParent();
- }
-
@Override
boolean fillsParent() {
if (useCurrentBounds()) {
- return mFillsParent;
+ return matchParentBounds();
}
// The bounds has been adjusted to accommodate for a docked stack, but the docked stack
// is not currently visible. Go ahead a represent it as fullscreen to the rest of the
@@ -1315,16 +1333,6 @@
}
@Override
- public DisplayInfo getDisplayInfo() {
- return mDisplayContent.getDisplayInfo();
- }
-
- @Override
- public boolean isAttachedToDisplay() {
- return mDisplayContent != null;
- }
-
- @Override
public String toString() {
return "{stackId=" + mStackId + " tasks=" + mChildren + "}";
}
@@ -1333,7 +1341,6 @@
return toShortString();
}
- @Override
public String toShortString() {
return "Stack=" + mStackId;
}
@@ -1343,7 +1350,7 @@
* information which side of the screen was the dock anchored.
*/
int getDockSide() {
- return getDockSide(mBounds);
+ return getDockSide(getRawBounds());
}
private int getDockSide(Rect bounds) {
@@ -1353,7 +1360,7 @@
if (mDisplayContent == null) {
return DOCKED_INVALID;
}
- mDisplayContent.getLogicalDisplayRect(mTmpRect);
+ mDisplayContent.getBounds(mTmpRect);
final int orientation = mDisplayContent.getConfiguration().orientation;
return getDockSideUnchecked(bounds, mTmpRect, orientation);
}
@@ -1473,7 +1480,7 @@
*/
if (task.isActivityTypeHome() && isMinimizedDockAndHomeStackResizable()) {
- mDisplayContent.getLogicalDisplayRect(mTmpRect);
+ mDisplayContent.getBounds(mTmpRect);
} else {
task.getDimBounds(mTmpRect);
}
@@ -1691,4 +1698,32 @@
|| activityType == ACTIVITY_TYPE_RECENTS
|| activityType == ACTIVITY_TYPE_ASSISTANT;
}
+
+ Dimmer getDimmer() {
+ return mDimmer;
+ }
+
+ @Override
+ void prepareSurfaces() {
+ mDimmer.resetDimStates();
+ super.prepareSurfaces();
+ getDimBounds(mTmpDimBoundsRect);
+ if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) {
+ scheduleAnimation();
+ }
+ }
+
+ public DisplayInfo getDisplayInfo() {
+ return mDisplayContent.getDisplayInfo();
+ }
+
+ void dim(float alpha) {
+ mDimmer.dimAbove(getPendingTransaction(), alpha);
+ scheduleAnimation();
+ }
+
+ void stopDimming() {
+ mDimmer.stopDim(getPendingTransaction());
+ scheduleAnimation();
+ }
}
diff --git a/com/android/server/wm/TaskTapPointerEventListener.java b/com/android/server/wm/TaskTapPointerEventListener.java
index 42a2d9d..84ad576 100644
--- a/com/android/server/wm/TaskTapPointerEventListener.java
+++ b/com/android/server/wm/TaskTapPointerEventListener.java
@@ -20,7 +20,7 @@
import android.graphics.Region;
import android.hardware.input.InputManager;
import android.view.MotionEvent;
-import android.view.WindowManagerPolicy.PointerEventListener;
+import android.view.WindowManagerPolicyConstants.PointerEventListener;
import com.android.server.wm.WindowManagerService.H;
diff --git a/com/android/server/wm/TaskWindowContainerController.java b/com/android/server/wm/TaskWindowContainerController.java
index b3bb0b7..5caae32 100644
--- a/com/android/server/wm/TaskWindowContainerController.java
+++ b/com/android/server/wm/TaskWindowContainerController.java
@@ -18,7 +18,6 @@
import android.app.ActivityManager.TaskDescription;
import android.app.ActivityManager.TaskSnapshot;
-import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
@@ -30,6 +29,7 @@
import java.lang.ref.WeakReference;
import static com.android.server.EventLogTags.WM_TASK_CREATED;
+import static com.android.server.wm.ConfigurationContainer.BOUNDS_CHANGE_NONE;
import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
@@ -75,7 +75,7 @@
+ stackController);
}
EventLog.writeEvent(WM_TASK_CREATED, taskId, stack.mStackId);
- final Task task = createTask(taskId, stack, userId, bounds, resizeMode,
+ final Task task = createTask(taskId, stack, userId, resizeMode,
supportsPictureInPicture, taskDescription);
final int position = toTop ? POSITION_TOP : POSITION_BOTTOM;
// We only want to move the parents to the parents if we are creating this task at the
@@ -85,10 +85,10 @@
}
@VisibleForTesting
- Task createTask(int taskId, TaskStack stack, int userId, Rect bounds, int resizeMode,
+ Task createTask(int taskId, TaskStack stack, int userId, int resizeMode,
boolean supportsPictureInPicture, TaskDescription taskDescription) {
- return new Task(taskId, stack, userId, mService, bounds, resizeMode,
- supportsPictureInPicture, taskDescription, this);
+ return new Task(taskId, stack, userId, mService, resizeMode, supportsPictureInPicture,
+ taskDescription, this);
}
@Override
@@ -151,14 +151,14 @@
}
}
- public void resize(Rect bounds, Configuration overrideConfig, boolean relayout,
- boolean forced) {
+ public void resize(boolean relayout, boolean forced) {
synchronized (mWindowMap) {
if (mContainer == null) {
throw new IllegalArgumentException("resizeTask: taskId " + mTaskId + " not found.");
}
- if (mContainer.resizeLocked(bounds, overrideConfig, forced) && relayout) {
+ if (mContainer.setBounds(mContainer.getOverrideBounds(), forced) != BOUNDS_CHANGE_NONE
+ && relayout) {
mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
}
}
diff --git a/com/android/server/wm/WallpaperController.java b/com/android/server/wm/WallpaperController.java
index 629cc86..3ae4549 100644
--- a/com/android/server/wm/WallpaperController.java
+++ b/com/android/server/wm/WallpaperController.java
@@ -23,8 +23,8 @@
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.AppTransition.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER;
diff --git a/com/android/server/wm/Watermark.java b/com/android/server/wm/Watermark.java
index d97aaac..9216b66 100644
--- a/com/android/server/wm/Watermark.java
+++ b/com/android/server/wm/Watermark.java
@@ -53,7 +53,7 @@
private int mLastDH;
private boolean mDrawNeeded;
- Watermark(Display display, DisplayMetrics dm, SurfaceSession session, String[] tokens) {
+ Watermark(DisplayContent dc, DisplayMetrics dm, String[] tokens) {
if (false) {
Log.i(TAG_WM, "*********************** WATERMARK");
for (int i=0; i<tokens.length; i++) {
@@ -61,7 +61,7 @@
}
}
- mDisplay = display;
+ mDisplay = dc.getDisplay();
mTokens = tokens;
StringBuilder builder = new StringBuilder(32);
@@ -114,7 +114,7 @@
SurfaceControl ctrl = null;
try {
- ctrl = new SurfaceControl.Builder(session)
+ ctrl = dc.makeOverlay()
.setName("WatermarkSurface")
.setSize(1, 1)
.setFormat(PixelFormat.TRANSLUCENT)
diff --git a/com/android/server/wm/WindowAnimator.java b/com/android/server/wm/WindowAnimator.java
index 1912095..7c56f00 100644
--- a/com/android/server/wm/WindowAnimator.java
+++ b/com/android/server/wm/WindowAnimator.java
@@ -30,10 +30,9 @@
import android.util.SparseArray;
import android.util.TimeUtils;
import android.view.Choreographer;
-import android.view.SurfaceControl;
-import android.view.WindowManagerPolicy;
import com.android.server.AnimationThread;
+import com.android.server.policy.WindowManagerPolicy;
import java.io.PrintWriter;
@@ -200,7 +199,7 @@
++mAnimTransactionSequence;
dc.updateWindowsForAnimator(this);
dc.updateWallpaperForAnimator(this);
- dc.prepareWindowSurfaces();
+ dc.prepareSurfaces();
}
for (int i = 0; i < numDisplays; i++) {
@@ -214,8 +213,6 @@
if (screenRotationAnimation != null) {
screenRotationAnimation.updateSurfacesInTransaction();
}
-
- orAnimating(dc.animateDimLayers());
orAnimating(dc.getDockedDividerController().animate(mCurrentTime));
//TODO (multidisplay): Magnification is supported only for the default display.
if (accessibilityController != null && dc.isDefaultDisplay) {
@@ -237,6 +234,13 @@
if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION animate");
}
+ final int numDisplays = mDisplayContentsAnimators.size();
+ for (int i = 0; i < numDisplays; i++) {
+ final int displayId = mDisplayContentsAnimators.keyAt(i);
+ final DisplayContent dc = mService.mRoot.getDisplayContentOrCreate(displayId);
+ dc.onPendingTransactionApplied();
+ }
+
boolean hasPendingLayoutChanges = mService.mRoot.hasPendingLayoutChanges(this);
boolean doRequest = false;
if (mBulkUpdateParams != 0) {
@@ -271,6 +275,7 @@
mService.destroyPreservedSurfaceLocked();
mService.mWindowPlacerLocked.destroyPendingSurfaces();
+
if (DEBUG_WINDOW_TRACE) {
Slog.i(TAG, "!!! animate: exit mAnimating=" + mAnimating
+ " mBulkUpdateParams=" + Integer.toHexString(mBulkUpdateParams)
diff --git a/com/android/server/wm/WindowContainer.java b/com/android/server/wm/WindowContainer.java
index 8f4b897..6467582 100644
--- a/com/android/server/wm/WindowContainer.java
+++ b/com/android/server/wm/WindowContainer.java
@@ -21,9 +21,13 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static com.android.server.wm.proto.WindowContainerProto.CONFIGURATION_CONTAINER;
import static com.android.server.wm.proto.WindowContainerProto.ORIENTATION;
+import static android.view.SurfaceControl.Transaction;
import android.annotation.CallSuper;
import android.content.res.Configuration;
+import android.view.MagnificationSpec;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
import android.util.Pools;
import android.util.proto.ProtoOutputStream;
@@ -64,7 +68,14 @@
new Pools.SynchronizedPool<>(3);
// The owner/creator for this container. No controller if null.
- private WindowContainerController mController;
+ WindowContainerController mController;
+
+ protected SurfaceControl mSurfaceControl;
+
+ /**
+ * Applied as part of the animation pass in "prepareSurfaces".
+ */
+ private Transaction mPendingTransaction = new Transaction();
@Override
final protected WindowContainer getParent() {
@@ -101,7 +112,22 @@
* Supposed to be overridden and contain actions that should be executed after parent was set.
*/
void onParentSet() {
- // Do nothing by default.
+ if (mParent == null) {
+ return;
+ }
+ if (mSurfaceControl == null) {
+ // If we don't yet have a surface, but we now have a parent, we should
+ // build a surface.
+ mSurfaceControl = makeSurface().build();
+ getPendingTransaction().show(mSurfaceControl);
+ } else {
+ // If we have a surface but a new parent, we just need to perform a reparent.
+ getPendingTransaction().reparent(mSurfaceControl, mParent.mSurfaceControl.getHandle());
+ }
+
+ // Either way we need to ask the parent to assign us a Z-order.
+ mParent.assignChildLayers();
+ scheduleAnimation();
}
// Temp. holders for a chain of containers we are currently processing.
@@ -188,6 +214,11 @@
mChildren.remove(child);
}
+ if (mSurfaceControl != null) {
+ destroyAfterPendingTransaction(mSurfaceControl);
+ mSurfaceControl = null;
+ }
+
if (mParent != null) {
mParent.removeChild(this);
}
@@ -195,6 +226,7 @@
if (mController != null) {
setController(null);
}
+
}
/**
@@ -286,11 +318,24 @@
* @see #mFullConfiguration
*/
@Override
- final public void onOverrideConfigurationChanged(Configuration overrideConfiguration) {
+ public void onOverrideConfigurationChanged(Configuration overrideConfiguration) {
+ // We must diff before the configuration is applied so that we can capture the change
+ // against the existing bounds.
+ final int diff = diffOverrideBounds(overrideConfiguration.windowConfiguration.getBounds());
super.onOverrideConfigurationChanged(overrideConfiguration);
if (mParent != null) {
mParent.onDescendantOverrideConfigurationChanged();
}
+
+ if (diff == BOUNDS_CHANGE_NONE) {
+ return;
+ }
+
+ if ((diff & BOUNDS_CHANGE_SIZE) == BOUNDS_CHANGE_SIZE) {
+ onResize();
+ } else {
+ onMovedByResize();
+ }
}
/**
@@ -407,7 +452,7 @@
}
/**
-a * Returns whether this child is on top of the window hierarchy.
+ * @return Whether this child is on top of the window hierarchy.
*/
boolean isOnTop() {
return getParent().getTopChild() == this && getParent().isOnTop();
@@ -673,6 +718,82 @@
mController = controller;
}
+ SurfaceControl.Builder makeSurface() {
+ final WindowContainer p = getParent();
+ return p.makeChildSurface(this);
+ }
+
+ /**
+ * @param child The WindowContainer this child surface is for, or null if the Surface
+ * is not assosciated with a WindowContainer (e.g. a surface used for Dimming).
+ */
+ SurfaceControl.Builder makeChildSurface(WindowContainer child) {
+ final WindowContainer p = getParent();
+ // Give the parent a chance to set properties. In hierarchy v1 we rely
+ // on this to set full-screen dimensions on all our Surface-less Layers.
+ return p.makeChildSurface(child)
+ .setParent(mSurfaceControl);
+ }
+
+ /**
+ * @return Whether this WindowContainer should be magnified by the accessibility magnifier.
+ */
+ boolean shouldMagnify() {
+ for (int i = 0; i < mChildren.size(); i++) {
+ if (!mChildren.get(i).shouldMagnify()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ SurfaceSession getSession() {
+ if (getParent() != null) {
+ return getParent().getSession();
+ }
+ return null;
+ }
+
+ void assignLayer(Transaction t, int layer) {
+ if (mSurfaceControl != null) {
+ t.setLayer(mSurfaceControl, layer);
+ }
+ }
+
+ void assignChildLayers(Transaction t) {
+ int layer = 0;
+ boolean boosting = false;
+
+ // We use two passes as a way to promote children which
+ // need Z-boosting to the end of the list.
+ for (int i = 0; i < 2; i++ ) {
+ for (int j = 0; j < mChildren.size(); ++j) {
+ final WindowContainer wc = mChildren.get(j);
+ if (wc.needsZBoost() && !boosting) {
+ continue;
+ }
+ wc.assignLayer(t, layer);
+ wc.assignChildLayers(t);
+
+ layer++;
+ }
+ boosting = true;
+ }
+ }
+
+ void assignChildLayers() {
+ assignChildLayers(getPendingTransaction());
+ }
+
+ boolean needsZBoost() {
+ for (int i = 0; i < mChildren.size(); i++) {
+ if (mChildren.get(i).needsZBoost()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Write to a protocol buffer output stream. Protocol buffer message definition is at
* {@link com.android.server.wm.proto.WindowContainerProto}.
@@ -719,4 +840,59 @@
mConsumerWrapperPool.release(this);
}
}
+
+ // TODO(b/68336570): Should this really be on WindowContainer since it
+ // can only be used on the top-level nodes that aren't animated?
+ // (otherwise we would be fighting other callers of setMatrix).
+ void applyMagnificationSpec(Transaction t, MagnificationSpec spec) {
+ if (shouldMagnify()) {
+ t.setMatrix(mSurfaceControl, spec.scale, 0, 0, spec.scale)
+ .setPosition(mSurfaceControl, spec.offsetX, spec.offsetY);
+ } else {
+ for (int i = 0; i < mChildren.size(); i++) {
+ mChildren.get(i).applyMagnificationSpec(t, spec);
+ }
+ }
+ }
+
+ /**
+ * TODO: Once we totally eliminate global transaction we will pass transaction in here
+ * rather than merging to global.
+ */
+ void prepareSurfaces() {
+ SurfaceControl.mergeToGlobalTransaction(getPendingTransaction());
+ for (int i = 0; i < mChildren.size(); i++) {
+ mChildren.get(i).prepareSurfaces();
+ }
+ }
+
+ /**
+ * Trigger a call to prepareSurfaces from the animation thread, such that
+ * mPendingTransaction will be applied.
+ */
+ void scheduleAnimation() {
+ if (mParent != null) {
+ mParent.scheduleAnimation();
+ }
+ }
+
+ SurfaceControl getSurfaceControl() {
+ return mSurfaceControl;
+ }
+
+ /**
+ * Destroy a given surface after executing mPendingTransaction. This is
+ * largely a workaround for destroy not being part of transactions
+ * rather than an intentional design, so please take care when
+ * expanding use.
+ */
+ void destroyAfterPendingTransaction(SurfaceControl surface) {
+ if (mParent != null) {
+ mParent.destroyAfterPendingTransaction(surface);
+ }
+ }
+
+ Transaction getPendingTransaction() {
+ return mPendingTransaction;
+ }
}
diff --git a/com/android/server/wm/WindowLayersController.java b/com/android/server/wm/WindowLayersController.java
deleted file mode 100644
index 7caf2fe..0000000
--- a/com/android/server/wm/WindowLayersController.java
+++ /dev/null
@@ -1,273 +0,0 @@
-/*
- * Copyright (C) 2015 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.server.wm;
-
-import android.util.Slog;
-
-import java.util.ArrayDeque;
-import java.util.function.Consumer;
-
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.TYPE_LAYER_OFFSET;
-import static com.android.server.wm.WindowManagerService.WINDOW_LAYER_MULTIPLIER;
-
-/**
- * Controller for assigning layers to windows on the display.
- *
- * This class encapsulates general algorithm for assigning layers and special rules that we need to
- * apply on top. The general algorithm goes through windows from bottom to the top and the higher
- * the window is, the higher layer is assigned. The final layer is equal to base layer +
- * adjustment from the order. This means that the window list is assumed to be ordered roughly by
- * the base layer (there are exceptions, e.g. due to keyguard and wallpaper and they need to be
- * handled with care, because they break the algorithm).
- *
- * On top of the general algorithm we add special rules, that govern such amazing things as:
- * <li>IME (which has higher base layer, but will be positioned above application windows)</li>
- * <li>docked/pinned windows (that need to be lifted above other application windows, including
- * animations)
- * <li>dock divider (which needs to live above applications, but below IME)</li>
- * <li>replaced windows, which need to live above their normal level, because they anticipate
- * an animation</li>.
- */
-class WindowLayersController {
- private final WindowManagerService mService;
-
- WindowLayersController(WindowManagerService service) {
- mService = service;
- }
-
- private ArrayDeque<WindowState> mPinnedWindows = new ArrayDeque<>();
- private ArrayDeque<WindowState> mDockedWindows = new ArrayDeque<>();
- private ArrayDeque<WindowState> mAssistantWindows = new ArrayDeque<>();
- private ArrayDeque<WindowState> mInputMethodWindows = new ArrayDeque<>();
- private WindowState mDockDivider = null;
- private ArrayDeque<WindowState> mReplacingWindows = new ArrayDeque<>();
- private int mCurBaseLayer;
- private int mCurLayer;
- private boolean mAnyLayerChanged;
- private int mHighestApplicationLayer;
- private int mHighestDockedAffectedLayer;
- private int mHighestLayerInImeTargetBaseLayer;
- private WindowState mImeTarget;
- private boolean mAboveImeTarget;
- private ArrayDeque<WindowState> mAboveImeTargetAppWindows = new ArrayDeque();
-
- private final Consumer<WindowState> mAssignWindowLayersConsumer = w -> {
- boolean layerChanged = false;
-
- int oldLayer = w.mLayer;
- if (w.mBaseLayer == mCurBaseLayer) {
- mCurLayer += WINDOW_LAYER_MULTIPLIER;
- } else {
- mCurBaseLayer = mCurLayer = w.mBaseLayer;
- }
- assignAnimLayer(w, mCurLayer);
-
- // TODO: Preserved old behavior of code here but not sure comparing oldLayer to
- // mAnimLayer and mLayer makes sense...though the worst case would be unintentional
- // layer reassignment.
- if (w.mLayer != oldLayer || w.mWinAnimator.mAnimLayer != oldLayer) {
- layerChanged = true;
- mAnyLayerChanged = true;
- }
-
- if (w.mAppToken != null) {
- mHighestApplicationLayer = Math.max(mHighestApplicationLayer,
- w.mWinAnimator.mAnimLayer);
- }
- if (mImeTarget != null && w.mBaseLayer == mImeTarget.mBaseLayer) {
- mHighestLayerInImeTargetBaseLayer = Math.max(mHighestLayerInImeTargetBaseLayer,
- w.mWinAnimator.mAnimLayer);
- }
- if (w.getAppToken() != null && w.inSplitScreenSecondaryWindowingMode()) {
- mHighestDockedAffectedLayer = Math.max(mHighestDockedAffectedLayer,
- w.mWinAnimator.mAnimLayer);
- }
-
- collectSpecialWindows(w);
-
- if (layerChanged) {
- w.scheduleAnimationIfDimming();
- }
- };
-
- final void assignWindowLayers(DisplayContent dc) {
- if (DEBUG_LAYERS) Slog.v(TAG_WM, "Assigning layers based",
- new RuntimeException("here").fillInStackTrace());
-
- reset();
- dc.forAllWindows(mAssignWindowLayersConsumer, false /* traverseTopToBottom */);
-
- adjustSpecialWindows();
-
- //TODO (multidisplay): Magnification is supported only for the default display.
- if (mService.mAccessibilityController != null && mAnyLayerChanged
- && dc.getDisplayId() == DEFAULT_DISPLAY) {
- mService.mAccessibilityController.onWindowLayersChangedLocked();
- }
-
- if (DEBUG_LAYERS) logDebugLayers(dc);
- }
-
- private void logDebugLayers(DisplayContent dc) {
- dc.forAllWindows((w) -> {
- final WindowStateAnimator winAnimator = w.mWinAnimator;
- Slog.v(TAG_WM, "Assign layer " + w + ": " + "mBase=" + w.mBaseLayer
- + " mLayer=" + w.mLayer + (w.mAppToken == null
- ? "" : " mAppLayer=" + w.mAppToken.getAnimLayerAdjustment())
- + " =mAnimLayer=" + winAnimator.mAnimLayer);
- }, false /* traverseTopToBottom */);
- }
-
- private void reset() {
- mPinnedWindows.clear();
- mInputMethodWindows.clear();
- mDockedWindows.clear();
- mAssistantWindows.clear();
- mReplacingWindows.clear();
- mDockDivider = null;
-
- mCurBaseLayer = 0;
- mCurLayer = 0;
- mAnyLayerChanged = false;
-
- mHighestApplicationLayer = 0;
- mHighestDockedAffectedLayer = 0;
- mHighestLayerInImeTargetBaseLayer = (mImeTarget != null) ? mImeTarget.mBaseLayer : 0;
- mImeTarget = mService.mInputMethodTarget;
- mAboveImeTarget = false;
- mAboveImeTargetAppWindows.clear();
- }
-
- private void collectSpecialWindows(WindowState w) {
- if (w.mAttrs.type == TYPE_DOCK_DIVIDER) {
- mDockDivider = w;
- return;
- }
- if (w.mWillReplaceWindow) {
- mReplacingWindows.add(w);
- }
- if (w.mIsImWindow) {
- mInputMethodWindows.add(w);
- return;
- }
- if (mImeTarget != null) {
- if (w.getParentWindow() == mImeTarget && w.mSubLayer > 0) {
- // Child windows of the ime target with a positive sub-layer should be placed above
- // the IME.
- mAboveImeTargetAppWindows.add(w);
- } else if (mAboveImeTarget && w.mAppToken != null) {
- // windows of apps above the IME target should be placed above the IME.
- mAboveImeTargetAppWindows.add(w);
- }
- if (w == mImeTarget) {
- mAboveImeTarget = true;
- }
- }
-
- final int windowingMode = w.getWindowingMode();
- if (windowingMode == WINDOWING_MODE_PINNED) {
- mPinnedWindows.add(w);
- } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
- mDockedWindows.add(w);
- }
- if (w.isActivityTypeAssistant()) {
- mAssistantWindows.add(w);
- }
- }
-
- private void adjustSpecialWindows() {
- // The following adjustments are beyond the highest docked-affected layer
- int layer = mHighestDockedAffectedLayer + TYPE_LAYER_OFFSET;
-
- // Adjust the docked stack windows and dock divider above only the windows that are affected
- // by the docked stack. When this happens, also boost the assistant window layers, otherwise
- // the docked stack windows & divider would be promoted above the assistant.
- if (!mDockedWindows.isEmpty() && mHighestDockedAffectedLayer > 0) {
- while (!mDockedWindows.isEmpty()) {
- final WindowState window = mDockedWindows.remove();
- layer = assignAndIncreaseLayerIfNeeded(window, layer);
- }
-
- layer = assignAndIncreaseLayerIfNeeded(mDockDivider, layer);
-
- while (!mAssistantWindows.isEmpty()) {
- final WindowState window = mAssistantWindows.remove();
- if (window.mLayer > mHighestDockedAffectedLayer) {
- layer = assignAndIncreaseLayerIfNeeded(window, layer);
- }
- }
- }
-
- // The following adjustments are beyond the highest app layer or boosted layer
- layer = Math.max(layer, mHighestApplicationLayer + WINDOW_LAYER_MULTIPLIER);
-
- // We know that we will be animating a relaunching window in the near future, which will
- // receive a z-order increase. We want the replaced window to immediately receive the same
- // treatment, e.g. to be above the dock divider.
- while (!mReplacingWindows.isEmpty()) {
- layer = assignAndIncreaseLayerIfNeeded(mReplacingWindows.remove(), layer);
- }
-
- while (!mPinnedWindows.isEmpty()) {
- layer = assignAndIncreaseLayerIfNeeded(mPinnedWindows.remove(), layer);
- }
-
- // Make sure IME is the highest window in the base layer of it's target.
- if (mImeTarget != null) {
- if (mImeTarget.mAppToken == null) {
- // For non-app ime targets adjust the layer we start from to match what we found
- // when assigning layers. Otherwise, just use the highest app layer we have some far.
- layer = mHighestLayerInImeTargetBaseLayer + WINDOW_LAYER_MULTIPLIER;
- }
-
- while (!mInputMethodWindows.isEmpty()) {
- layer = assignAndIncreaseLayerIfNeeded(mInputMethodWindows.remove(), layer);
- }
-
- // Adjust app windows the should be displayed above the IME since they are above the IME
- // target.
- while (!mAboveImeTargetAppWindows.isEmpty()) {
- layer = assignAndIncreaseLayerIfNeeded(mAboveImeTargetAppWindows.remove(), layer);
- }
- }
-
- }
-
- private int assignAndIncreaseLayerIfNeeded(WindowState win, int layer) {
- if (win != null) {
- assignAnimLayer(win, layer);
- // Make sure we leave space in-between normal windows for dims and such.
- layer += WINDOW_LAYER_MULTIPLIER;
- }
- return layer;
- }
-
- private void assignAnimLayer(WindowState w, int layer) {
- w.mLayer = layer;
- w.mWinAnimator.mAnimLayer = w.getAnimLayerAdjustment()
- + w.getSpecialWindowAnimLayerAdjustment();
- if (w.mAppToken != null) {
- w.mAppToken.mAppAnimator.updateThumbnailLayer();
- }
- }
-}
diff --git a/com/android/server/wm/WindowManagerInternal.java b/com/android/server/wm/WindowManagerInternal.java
new file mode 100644
index 0000000..036f7b0
--- /dev/null
+++ b/com/android/server/wm/WindowManagerInternal.java
@@ -0,0 +1,395 @@
+/*
+ * Copyright (C) 2017 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.server.wm;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ClipData;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.IBinder;
+import android.view.IInputFilter;
+import android.view.IWindow;
+import android.view.MagnificationSpec;
+import android.view.WindowInfo;
+import android.view.animation.Animation;
+
+import com.android.server.policy.WindowManagerPolicy;
+
+import java.util.List;
+
+/**
+ * Window manager local system service interface.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class WindowManagerInternal {
+
+ /**
+ * Interface to receive a callback when the windows reported for
+ * accessibility changed.
+ */
+ public interface WindowsForAccessibilityCallback {
+
+ /**
+ * Called when the windows for accessibility changed.
+ *
+ * @param windows The windows for accessibility.
+ */
+ public void onWindowsForAccessibilityChanged(List<WindowInfo> windows);
+ }
+
+ /**
+ * Callbacks for contextual changes that affect the screen magnification
+ * feature.
+ */
+ public interface MagnificationCallbacks {
+
+ /**
+ * Called when the region where magnification operates changes. Note that this isn't the
+ * entire screen. For example, IMEs are not magnified.
+ *
+ * @param magnificationRegion the current magnification region
+ */
+ public void onMagnificationRegionChanged(Region magnificationRegion);
+
+ /**
+ * Called when an application requests a rectangle on the screen to allow
+ * the client to apply the appropriate pan and scale.
+ *
+ * @param left The rectangle left.
+ * @param top The rectangle top.
+ * @param right The rectangle right.
+ * @param bottom The rectangle bottom.
+ */
+ public void onRectangleOnScreenRequested(int left, int top, int right, int bottom);
+
+ /**
+ * Notifies that the rotation changed.
+ *
+ * @param rotation The current rotation.
+ */
+ public void onRotationChanged(int rotation);
+
+ /**
+ * Notifies that the context of the user changed. For example, an application
+ * was started.
+ */
+ public void onUserContextChanged();
+ }
+
+ /**
+ * Abstract class to be notified about {@link com.android.server.wm.AppTransition} events. Held
+ * as an abstract class so a listener only needs to implement the methods of its interest.
+ */
+ public static abstract class AppTransitionListener {
+
+ /**
+ * Called when an app transition is being setup and about to be executed.
+ */
+ public void onAppTransitionPendingLocked() {}
+
+ /**
+ * Called when a pending app transition gets cancelled.
+ *
+ * @param transit transition type indicating what kind of transition got cancelled
+ */
+ public void onAppTransitionCancelledLocked(int transit) {}
+
+ /**
+ * Called when an app transition gets started
+ *
+ * @param transit transition type indicating what kind of transition gets run, must be one
+ * of AppTransition.TRANSIT_* values
+ * @param openToken the token for the opening app
+ * @param closeToken the token for the closing app
+ * @param openAnimation the animation for the opening app
+ * @param closeAnimation the animation for the closing app
+ *
+ * @return Return any bit set of {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_LAYOUT},
+ * {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_CONFIG},
+ * {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_WALLPAPER},
+ * or {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_ANIM}.
+ */
+ public int onAppTransitionStartingLocked(int transit, IBinder openToken, IBinder closeToken,
+ Animation openAnimation, Animation closeAnimation) {
+ return 0;
+ }
+
+ /**
+ * Called when an app transition is finished running.
+ *
+ * @param token the token for app whose transition has finished
+ */
+ public void onAppTransitionFinishedLocked(IBinder token) {}
+ }
+
+ /**
+ * An interface to be notified about hardware keyboard status.
+ */
+ public interface OnHardKeyboardStatusChangeListener {
+ public void onHardKeyboardStatusChange(boolean available);
+ }
+
+ /**
+ * An interface to customize drag and drop behaviors.
+ */
+ public interface IDragDropCallback {
+ /**
+ * Called when drag operation is started.
+ */
+ default boolean performDrag(IWindow window, IBinder dragToken,
+ int touchSource, float touchX, float touchY, float thumbCenterX, float thumbCenterY,
+ ClipData data) {
+ return true;
+ }
+
+ /**
+ * Called when drop result is reported.
+ */
+ default void reportDropResult(IWindow window, boolean consumed) {}
+
+ /**
+ * Called when drag operation is cancelled.
+ */
+ default void cancelDragAndDrop(IBinder dragToken) {}
+ }
+
+ /**
+ * Request that the window manager call
+ * {@link DisplayManagerInternal#performTraversalInTransactionFromWindowManager}
+ * within a surface transaction at a later time.
+ */
+ public abstract void requestTraversalFromDisplayManager();
+
+ /**
+ * Set by the accessibility layer to observe changes in the magnified region,
+ * rotation, and other window transformations related to display magnification
+ * as the window manager is responsible for doing the actual magnification
+ * and has access to the raw window data while the accessibility layer serves
+ * as a controller.
+ *
+ * @param callbacks The callbacks to invoke.
+ */
+ public abstract void setMagnificationCallbacks(@Nullable MagnificationCallbacks callbacks);
+
+ /**
+ * Set by the accessibility layer to specify the magnification and panning to
+ * be applied to all windows that should be magnified.
+ *
+ * @param spec The MagnficationSpec to set.
+ *
+ * @see #setMagnificationCallbacks(MagnificationCallbacks)
+ */
+ public abstract void setMagnificationSpec(MagnificationSpec spec);
+
+ /**
+ * Set by the accessibility framework to indicate whether the magnifiable regions of the display
+ * should be shown.
+ *
+ * @param show {@code true} to show magnifiable region bounds, {@code false} to hide
+ */
+ public abstract void setForceShowMagnifiableBounds(boolean show);
+
+ /**
+ * Obtains the magnification regions.
+ *
+ * @param magnificationRegion the current magnification region
+ */
+ public abstract void getMagnificationRegion(@NonNull Region magnificationRegion);
+
+ /**
+ * Gets the magnification and translation applied to a window given its token.
+ * Not all windows are magnified and the window manager policy determines which
+ * windows are magnified. The returned result also takes into account the compat
+ * scale if necessary.
+ *
+ * @param windowToken The window's token.
+ *
+ * @return The magnification spec for the window.
+ *
+ * @see #setMagnificationCallbacks(MagnificationCallbacks)
+ */
+ public abstract MagnificationSpec getCompatibleMagnificationSpecForWindow(
+ IBinder windowToken);
+
+ /**
+ * Sets a callback for observing which windows are touchable for the purposes
+ * of accessibility.
+ *
+ * @param callback The callback.
+ */
+ public abstract void setWindowsForAccessibilityCallback(
+ WindowsForAccessibilityCallback callback);
+
+ /**
+ * Sets a filter for manipulating the input event stream.
+ *
+ * @param filter The filter implementation.
+ */
+ public abstract void setInputFilter(IInputFilter filter);
+
+ /**
+ * Gets the token of the window that has input focus.
+ *
+ * @return The token.
+ */
+ public abstract IBinder getFocusedWindowToken();
+
+ /**
+ * @return Whether the keyguard is engaged.
+ */
+ public abstract boolean isKeyguardLocked();
+
+ /**
+ * @return Whether the keyguard is showing and not occluded.
+ */
+ public abstract boolean isKeyguardShowingAndNotOccluded();
+
+ /**
+ * Gets the frame of a window given its token.
+ *
+ * @param token The token.
+ * @param outBounds The frame to populate.
+ */
+ public abstract void getWindowFrame(IBinder token, Rect outBounds);
+
+ /**
+ * Opens the global actions dialog.
+ */
+ public abstract void showGlobalActions();
+
+ /**
+ * Invalidate all visible windows. Then report back on the callback once all windows have
+ * redrawn.
+ */
+ public abstract void waitForAllWindowsDrawn(Runnable callback, long timeout);
+
+ /**
+ * Adds a window token for a given window type.
+ *
+ * @param token The token to add.
+ * @param type The window type.
+ * @param displayId The display to add the token to.
+ */
+ public abstract void addWindowToken(android.os.IBinder token, int type, int displayId);
+
+ /**
+ * Removes a window token.
+ *
+ * @param token The toke to remove.
+ * @param removeWindows Whether to also remove the windows associated with the token.
+ * @param displayId The display to remove the token from.
+ */
+ public abstract void removeWindowToken(android.os.IBinder token, boolean removeWindows,
+ int displayId);
+
+ /**
+ * Registers a listener to be notified about app transition events.
+ *
+ * @param listener The listener to register.
+ */
+ public abstract void registerAppTransitionListener(AppTransitionListener listener);
+
+ /**
+ * Retrieves a height of input method window.
+ */
+ public abstract int getInputMethodWindowVisibleHeight();
+
+ /**
+ * Saves last input method window for transition.
+ *
+ * Note that it is assumed that this method is called only by InputMethodManagerService.
+ */
+ public abstract void saveLastInputMethodWindowForTransition();
+
+ /**
+ * Clears last input method window for transition.
+ *
+ * Note that it is assumed that this method is called only by InputMethodManagerService.
+ */
+ public abstract void clearLastInputMethodWindowForTransition();
+
+ /**
+ * Notifies WindowManagerService that the current IME window status is being changed.
+ *
+ * <p>Only {@link com.android.server.InputMethodManagerService} is the expected and tested
+ * caller of this method.</p>
+ *
+ * @param imeToken token to track the active input method. Corresponding IME windows can be
+ * identified by checking {@link android.view.WindowManager.LayoutParams#token}.
+ * Note that there is no guarantee that the corresponding window is already
+ * created
+ * @param imeWindowVisible whether the active IME thinks that its window should be visible or
+ * hidden, no matter how WindowManagerService will react / has reacted
+ * to corresponding API calls. Note that this state is not guaranteed
+ * to be synchronized with state in WindowManagerService.
+ * @param dismissImeOnBackKeyPressed {@code true} if the software keyboard is shown and the back
+ * key is expected to dismiss the software keyboard.
+ * @param targetWindowToken token to identify the target window that the IME is associated with.
+ * {@code null} when application, system, or the IME itself decided to
+ * change its window visibility before being associated with any target
+ * window.
+ */
+ public abstract void updateInputMethodWindowStatus(@NonNull IBinder imeToken,
+ boolean imeWindowVisible, boolean dismissImeOnBackKeyPressed,
+ @Nullable IBinder targetWindowToken);
+
+ /**
+ * Returns true when the hardware keyboard is available.
+ */
+ public abstract boolean isHardKeyboardAvailable();
+
+ /**
+ * Sets the callback listener for hardware keyboard status changes.
+ *
+ * @param listener The listener to set.
+ */
+ public abstract void setOnHardKeyboardStatusChangeListener(
+ OnHardKeyboardStatusChangeListener listener);
+
+ /** Returns true if a stack in the windowing mode is currently visible. */
+ public abstract boolean isStackVisible(int windowingMode);
+
+ /**
+ * @return True if and only if the docked divider is currently in resize mode.
+ */
+ public abstract boolean isDockedDividerResizing();
+
+ /**
+ * Requests the window manager to recompute the windows for accessibility.
+ */
+ public abstract void computeWindowsForAccessibility();
+
+ /**
+ * Called after virtual display Id is updated by
+ * {@link com.android.server.vr.Vr2dDisplay} with a specific
+ * {@param vr2dDisplayId}.
+ */
+ public abstract void setVr2dDisplayId(int vr2dDisplayId);
+
+ /**
+ * Sets callback to DragDropController.
+ */
+ public abstract void registerDragDropControllerCallback(IDragDropCallback callback);
+
+ /**
+ * @see android.view.IWindowManager#lockNow
+ */
+ public abstract void lockNow();
+}
diff --git a/com/android/server/wm/WindowManagerService.java b/com/android/server/wm/WindowManagerService.java
index ce34306..82f842c 100644
--- a/com/android/server/wm/WindowManagerService.java
+++ b/com/android/server/wm/WindowManagerService.java
@@ -65,10 +65,10 @@
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.LockGuard.INDEX_WINDOW;
import static com.android.server.LockGuard.installLock;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.AppTransition.TRANSIT_UNSET;
import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_END;
import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_START;
@@ -126,7 +126,6 @@
import android.app.IActivityManager;
import android.app.IAssistDataReceiver;
import android.content.BroadcastReceiver;
-import android.content.ClipData;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -219,10 +218,7 @@
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.view.WindowManagerGlobal;
-import android.view.WindowManagerInternal;
-import android.view.WindowManagerPolicy;
-import android.view.WindowManagerPolicy.PointerEventListener;
-import android.view.WindowManagerPolicy.ScreenOffListener;
+import android.view.WindowManagerPolicyConstants.PointerEventListener;
import android.view.animation.Animation;
import android.view.inputmethod.InputMethodManagerInternal;
@@ -245,7 +241,8 @@
import com.android.server.UiThread;
import com.android.server.Watchdog;
import com.android.server.input.InputManagerService;
-import android.view.DisplayFrames;
+import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.policy.WindowManagerPolicy.ScreenOffListener;
import com.android.server.power.ShutdownThread;
import com.android.server.utils.PriorityDump;
@@ -529,7 +526,6 @@
AccessibilityController mAccessibilityController;
- final SurfaceSession mFxSession;
Watermark mWatermark;
StrictModeFlash mStrictModeFlash;
CircularDisplayMask mCircularDisplayMask;
@@ -804,6 +800,13 @@
static WindowManagerThreadPriorityBooster sThreadPriorityBooster =
new WindowManagerThreadPriorityBooster();
+ class DefaultSurfaceBuilderFactory implements SurfaceBuilderFactory {
+ public SurfaceControl.Builder make(SurfaceSession s) {
+ return new SurfaceControl.Builder(s);
+ }
+ };
+ SurfaceBuilderFactory mSurfaceBuilderFactory = new DefaultSurfaceBuilderFactory();
+
static void boostPriorityForLockedSection() {
sThreadPriorityBooster.boost();
}
@@ -992,7 +995,6 @@
mPointerEventDispatcher = null;
}
- mFxSession = new SurfaceSession();
mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
mDisplays = mDisplayManager.getDisplays();
for (Display display : mDisplays) {
@@ -2478,11 +2480,8 @@
mWaitingForConfig = true;
displayContent.setLayoutNeeded();
int anim[] = new int[2];
- if (displayContent.isDimming()) {
- anim[0] = anim[1] = 0;
- } else {
- mPolicy.selectRotationAnimationLw(anim);
- }
+ mPolicy.selectRotationAnimationLw(anim);
+
startFreezingDisplayLocked(false, anim[0], anim[1], displayContent);
config = new Configuration(mTempConfiguration);
}
@@ -3592,8 +3591,7 @@
com.android.internal.R.dimen.circular_display_mask_thickness);
mCircularDisplayMask = new CircularDisplayMask(
- getDefaultDisplayContentLocked().getDisplay(),
- mFxSession,
+ getDefaultDisplayContentLocked(),
mPolicy.getWindowLayerFromTypeLw(
WindowManager.LayoutParams.TYPE_POINTER)
* TYPE_LAYER_MULTIPLIER + 10, screenOffset, maskThickness);
@@ -3621,8 +3619,7 @@
if (mEmulatorDisplayOverlay == null) {
mEmulatorDisplayOverlay = new EmulatorDisplayOverlay(
mContext,
- getDefaultDisplayContentLocked().getDisplay(),
- mFxSession,
+ getDefaultDisplayContentLocked(),
mPolicy.getWindowLayerFromTypeLw(
WindowManager.LayoutParams.TYPE_POINTER)
* TYPE_LAYER_MULTIPLIER + 10);
@@ -3670,7 +3667,7 @@
// TODO(multi-display): support multiple displays
if (mStrictModeFlash == null) {
mStrictModeFlash = new StrictModeFlash(
- getDefaultDisplayContentLocked().getDisplay(), mFxSession);
+ getDefaultDisplayContentLocked());
}
mStrictModeFlash.setVisibility(on);
} finally {
@@ -3694,9 +3691,8 @@
}
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "screenshotWallpaper");
- return screenshotApplications(null /* appToken */, DEFAULT_DISPLAY, -1 /* width */,
- -1 /* height */, true /* includeFullDisplay */, 1f /* frameScale */,
- Bitmap.Config.ARGB_8888, true /* wallpaperOnly */, false /* includeDecor */);
+ return screenshotApplications(DEFAULT_DISPLAY, Bitmap.Config.ARGB_8888,
+ true /* wallpaperOnly */);
} finally {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
@@ -3715,10 +3711,8 @@
}
FgThread.getHandler().post(() -> {
- Bitmap bm = screenshotApplications(null /* appToken */, DEFAULT_DISPLAY,
- -1 /* width */, -1 /* height */, true /* includeFullDisplay */,
- 1f /* frameScale */, Bitmap.Config.ARGB_8888, false /* wallpaperOnly */,
- false /* includeDecor */);
+ Bitmap bm = screenshotApplications(DEFAULT_DISPLAY, Bitmap.Config.ARGB_8888,
+ false /* wallpaperOnly */);
try {
receiver.onHandleAssistScreenshot(bm);
} catch (RemoteException e) {
@@ -3752,29 +3746,21 @@
* In portrait mode, it grabs the full screenshot.
*
* @param displayId the Display to take a screenshot of.
- * @param width the width of the target bitmap
- * @param height the height of the target bitmap
- * @param includeFullDisplay true if the screen should not be cropped before capture
- * @param frameScale the scale to apply to the frame, only used when width = -1 and height = -1
* @param config of the output bitmap
* @param wallpaperOnly true if only the wallpaper layer should be included in the screenshot
- * @param includeDecor whether to include window decors, like the status or navigation bar
- * background of the window
*/
- private Bitmap screenshotApplications(IBinder appToken, int displayId, int width,
- int height, boolean includeFullDisplay, float frameScale, Bitmap.Config config,
- boolean wallpaperOnly, boolean includeDecor) {
+ private Bitmap screenshotApplications(int displayId, Bitmap.Config config,
+ boolean wallpaperOnly) {
final DisplayContent displayContent;
synchronized(mWindowMap) {
displayContent = mRoot.getDisplayContentOrCreate(displayId);
if (displayContent == null) {
- if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, "Screenshot of " + appToken
- + ": returning null. No Display for displayId=" + displayId);
+ if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, "Screenshot returning null. No Display for "
+ + "displayId=" + displayId);
return null;
}
}
- return displayContent.screenshotApplications(appToken, width, height,
- includeFullDisplay, frameScale, config, wallpaperOnly, includeDecor);
+ return displayContent.screenshotDisplay(config, wallpaperOnly);
}
/**
@@ -4522,7 +4508,7 @@
Display display = displayContent.getDisplay();
mTaskPositioner = new TaskPositioner(this);
- mTaskPositioner.register(display);
+ mTaskPositioner.register(displayContent);
mInputMonitor.updateInputWindowsLw(true /*force*/);
// We need to grab the touch focus so that the touch events during the
@@ -5519,7 +5505,7 @@
}
void reconfigureDisplayLocked(@NonNull DisplayContent displayContent) {
- if (!mDisplayReady) {
+ if (!displayContent.isReady()) {
return;
}
displayContent.configureDisplayPolicy();
@@ -5892,7 +5878,7 @@
displayContent.updateDisplayInfo();
screenRotationAnimation = new ScreenRotationAnimation(mContext, displayContent,
- mFxSession, inTransaction, mPolicy.isDefaultOrientationForced(), isSecure,
+ inTransaction, mPolicy.isDefaultOrientationForced(), isSecure,
this);
mAnimator.setScreenRotationAnimationLocked(mFrozenDisplayId,
screenRotationAnimation);
@@ -5952,11 +5938,10 @@
// TODO(multidisplay): rotation on main screen only.
DisplayInfo displayInfo = displayContent.getDisplayInfo();
// Get rotation animation again, with new top window
- boolean isDimming = displayContent.isDimming();
- if (!mPolicy.validateRotationAnimationLw(mExitAnimId, mEnterAnimId, isDimming)) {
+ if (!mPolicy.validateRotationAnimationLw(mExitAnimId, mEnterAnimId, false)) {
mExitAnimId = mEnterAnimId = 0;
}
- if (screenRotationAnimation.dismiss(mFxSession, MAX_ANIMATION_DURATION,
+ if (screenRotationAnimation.dismiss(MAX_ANIMATION_DURATION,
getTransitionAnimationScaleLocked(), displayInfo.logicalWidth,
displayInfo.logicalHeight, mExitAnimId, mEnterAnimId)) {
scheduleAnimationLocked();
@@ -6040,8 +6025,8 @@
if (toks != null && toks.length > 0) {
// TODO(multi-display): Show watermarks on secondary displays.
final DisplayContent displayContent = getDefaultDisplayContentLocked();
- mWatermark = new Watermark(displayContent.getDisplay(),
- displayContent.mRealDisplayMetrics, mFxSession, toks);
+ mWatermark = new Watermark(displayContent, displayContent.mRealDisplayMetrics,
+ toks);
}
}
} catch (FileNotFoundException e) {
@@ -7456,6 +7441,11 @@
public void registerDragDropControllerCallback(IDragDropCallback callback) {
mDragDropController.registerCallback(callback);
}
+
+ @Override
+ public void lockNow() {
+ WindowManagerService.this.lockNow(null);
+ }
}
void registerAppFreezeListener(AppFreezeListener listener) {
@@ -7557,4 +7547,13 @@
w.setForceHideNonSystemOverlayWindowIfNeeded(hideSystemAlertWindows);
}, false /* traverseTopToBottom */);
}
+
+ public void applyMagnificationSpec(MagnificationSpec spec) {
+ getDefaultDisplayContentLocked().applyMagnificationSpec(spec);
+ }
+
+ SurfaceControl.Builder makeSurfaceBuilder(SurfaceSession s) {
+ return mSurfaceBuilderFactory.make(s);
+ }
}
+
diff --git a/com/android/server/wm/WindowManagerShellCommand.java b/com/android/server/wm/WindowManagerShellCommand.java
index 4b98d9d..2358695 100644
--- a/com/android/server/wm/WindowManagerShellCommand.java
+++ b/com/android/server/wm/WindowManagerShellCommand.java
@@ -16,6 +16,8 @@
package com.android.server.wm;
+import static android.os.Build.IS_USER;
+
import android.os.ShellCommand;
import java.io.PrintWriter;
@@ -50,8 +52,10 @@
pw.println(" help");
pw.println(" Print this help text.");
pw.println();
- pw.println(" tracing (start | stop)");
- pw.println(" start or stop window tracing");
- pw.println();
+ if (!IS_USER){
+ pw.println(" tracing (start | stop)");
+ pw.println(" start or stop window tracing");
+ pw.println();
+ }
}
}
diff --git a/com/android/server/wm/WindowState.java b/com/android/server/wm/WindowState.java
index 6b1932d..c2ac905 100644
--- a/com/android/server/wm/WindowState.java
+++ b/com/android/server/wm/WindowState.java
@@ -23,6 +23,7 @@
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE;
+import static android.view.SurfaceControl.Transaction;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON;
@@ -41,6 +42,7 @@
import static android.view.WindowManager.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH;
@@ -54,6 +56,10 @@
import static android.view.WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType;
@@ -61,10 +67,10 @@
import static android.view.WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_FREEFORM;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
-import static android.view.WindowManagerPolicy.TRANSIT_ENTER;
-import static android.view.WindowManagerPolicy.TRANSIT_EXIT;
-import static android.view.WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+import static com.android.server.policy.WindowManagerPolicy.TRANSIT_ENTER;
+import static com.android.server.policy.WindowManagerPolicy.TRANSIT_EXIT;
+import static com.android.server.policy.WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
@@ -147,14 +153,16 @@
import android.view.InputChannel;
import android.view.InputEvent;
import android.view.InputEventReceiver;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.WindowInfo;
import android.view.WindowManager;
-import android.view.WindowManagerPolicy;
import com.android.internal.util.ToBooleanFunction;
import com.android.server.input.InputWindowHandle;
+import com.android.server.policy.WindowManagerPolicy;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
@@ -602,6 +610,14 @@
};
};
+ /**
+ * Indicates whether we have requested a Dim (in the sense of {@link Dimmer}) from our host
+ * container.
+ */
+ private boolean mIsDimming = false;
+
+ private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f;
+
WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
WindowState parentWindow, int appOp, int seq, WindowManager.LayoutParams a,
int viewVisibility, int ownerId, boolean ownerCanAddInternalSystemWindow) {
@@ -799,7 +815,7 @@
layoutXDiff = 0;
layoutYDiff = 0;
} else {
- getContainerBounds(mContainingFrame);
+ getBounds(mContainingFrame);
if (mAppToken != null && !mAppToken.mFrozenBounds.isEmpty()) {
// If the bounds are frozen, we still want to translate the window freely and only
@@ -957,7 +973,7 @@
mContentInsets.setEmpty();
mVisibleInsets.setEmpty();
} else {
- getDisplayContent().getLogicalDisplayRect(mTmpRect);
+ getDisplayContent().getBounds(mTmpRect);
// Override right and/or bottom insets in case if the frame doesn't fit the screen in
// non-fullscreen mode.
boolean overrideRightInset = !windowsAreFloating && !inFullscreenContainer
@@ -1029,6 +1045,18 @@
+ " of=" + mOutsets.toShortString());
}
+ // TODO: Look into whether this override is still necessary.
+ @Override
+ public Rect getBounds() {
+ if (isInMultiWindowMode()) {
+ return getTask().getBounds();
+ } else if (mAppToken != null){
+ return mAppToken.getBounds();
+ } else {
+ return super.getBounds();
+ }
+ }
+
@Override
public Rect getFrameLw() {
return mFrame;
@@ -2034,23 +2062,6 @@
return isVisibleOrAdding();
}
- void scheduleAnimationIfDimming() {
- final DisplayContent dc = getDisplayContent();
- if (dc == null) {
- return;
- }
-
- // If layout is currently deferred, we want to hold of with updating the layers.
- if (mService.mWindowPlacerLocked.isLayoutDeferred()) {
- return;
- }
- final DimLayer.DimLayerUser dimLayerUser = getDimLayerUser();
- if (dimLayerUser != null && dc.mDimLayerController.isDimming(dimLayerUser, mWinAnimator)) {
- // Force an animation pass just to update the mDimLayer layer.
- mService.scheduleAnimationLocked();
- }
- }
-
private final class DeadWindowEventReceiver extends InputEventReceiver {
DeadWindowEventReceiver(InputChannel inputChannel) {
super(inputChannel, mService.mH.getLooper());
@@ -2106,31 +2117,16 @@
mInputWindowHandle.inputChannel = null;
}
- void applyDimLayerIfNeeded() {
- // When the app is terminated (eg. from Recents), the task might have already been
- // removed with the window pending removal. Don't apply dim in such cases, as there
- // will be no more updateDimLayer() calls, which leaves the dimlayer invalid.
- final AppWindowToken token = mAppToken;
- if (token != null && token.removed) {
- return;
- }
-
- final DisplayContent dc = getDisplayContent();
- if (!mAnimatingExit && mAppDied) {
- // If app died visible, apply a dim over the window to indicate that it's inactive
- dc.mDimLayerController.applyDimAbove(getDimLayerUser(), mWinAnimator);
- } else if ((mAttrs.flags & FLAG_DIM_BEHIND) != 0
- && dc != null && !mAnimatingExit && isVisible()) {
- dc.mDimLayerController.applyDimBehind(getDimLayerUser(), mWinAnimator);
- }
- }
-
- private DimLayer.DimLayerUser getDimLayerUser() {
+ private Dimmer getDimmer() {
Task task = getTask();
if (task != null) {
- return task;
+ return task.getDimmer();
}
- return getStack();
+ TaskStack taskStack = getStack();
+ if (taskStack != null) {
+ return taskStack.getDimmer();
+ }
+ return null;
}
/** Returns true if the replacement window was removed. */
@@ -2152,9 +2148,6 @@
private void removeReplacedWindow() {
if (DEBUG_ADD_REMOVE) Slog.d(TAG, "Removing replaced window: " + this);
- if (isDimming()) {
- transferDimToReplacement();
- }
mWillReplaceWindow = false;
mAnimateReplacingWindow = false;
mReplacingRemoveRequested = false;
@@ -2217,11 +2210,11 @@
// need to intercept touches outside of that window. The dim layer user
// associated with the window (task or stack) will give us the good bounds, as
// they would be used to display the dim layer.
- final DimLayer.DimLayerUser dimLayerUser = getDimLayerUser();
- if (dimLayerUser != null) {
- dimLayerUser.getDimBounds(mTmpRect);
+ final Task task = getTask();
+ if (task != null) {
+ task.getDimBounds(mTmpRect);
} else {
- getVisibleBounds(mTmpRect);
+ getStack().getDimBounds(mTmpRect);
}
if (inFreeformWindowingMode()) {
// For freeform windows we the touch region to include the whole surface for the
@@ -2465,7 +2458,7 @@
mPolicyVisibility = true;
mPolicyVisibilityAfterAnim = true;
if (doAnimation) {
- mWinAnimator.applyAnimationLocked(WindowManagerPolicy.TRANSIT_ENTER, true);
+ mWinAnimator.applyAnimationLocked(TRANSIT_ENTER, true);
}
if (requestAnim) {
mService.scheduleAnimationLocked();
@@ -2493,7 +2486,7 @@
return false;
}
if (doAnimation) {
- mWinAnimator.applyAnimationLocked(WindowManagerPolicy.TRANSIT_EXIT, false);
+ mWinAnimator.applyAnimationLocked(TRANSIT_EXIT, false);
if (mWinAnimator.mAnimation == null) {
doAnimation = false;
}
@@ -2729,14 +2722,6 @@
return displayContent.isDefaultDisplay;
}
- @Override
- public boolean isDimming() {
- final DimLayer.DimLayerUser dimLayerUser = getDimLayerUser();
- final DisplayContent dc = getDisplayContent();
- return dimLayerUser != null && dc != null
- && dc.mDimLayerController.isDimming(dimLayerUser, mWinAnimator);
- }
-
void setShowToOwnerOnlyLocked(boolean showToOwnerOnly) {
mShowToOwnerOnly = showToOwnerOnly;
}
@@ -2979,33 +2964,12 @@
/** Is this window in a container that takes up the entire screen space? */
private boolean inFullscreenContainer() {
- if (mAppToken == null) {
- return true;
- }
- if (mAppToken.hasBounds()) {
- return false;
- }
- return !isInMultiWindowMode();
+ return mAppToken == null || (mAppToken.matchParentBounds() && !isInMultiWindowMode());
}
/** @return true when the window is in fullscreen task, but has non-fullscreen bounds set. */
boolean isLetterboxedAppWindow() {
- final Task task = getTask();
- final boolean taskIsFullscreen = task != null && task.isFullscreen();
- final boolean appWindowIsFullscreen = mAppToken != null && !mAppToken.hasBounds();
-
- return taskIsFullscreen && !appWindowIsFullscreen;
- }
-
- /** Returns the appropriate bounds to use for computing frames. */
- private void getContainerBounds(Rect outBounds) {
- if (isInMultiWindowMode()) {
- getTask().getBounds(outBounds);
- } else if (mAppToken != null){
- mAppToken.getBounds(outBounds);
- } else {
- outBounds.setEmpty();
- }
+ return !isInMultiWindowMode() && mAppToken != null && !mAppToken.matchParentBounds();
}
boolean isDragResizeChanged() {
@@ -3071,7 +3035,7 @@
if (task == null) {
return false;
}
- if (!inSplitScreenWindowingMode()) {
+ if (!inSplitScreenWindowingMode() && !inFreeformWindowingMode()) {
return false;
}
if (mAttrs.width != MATCH_PARENT || mAttrs.height != MATCH_PARENT) {
@@ -3591,15 +3555,6 @@
return winY;
}
- private void transferDimToReplacement() {
- final DimLayer.DimLayerUser dimLayerUser = getDimLayerUser();
- final DisplayContent dc = getDisplayContent();
- if (dimLayerUser != null && dc != null) {
- dc.mDimLayerController.applyDim(dimLayerUser,
- mReplacementWindow.mWinAnimator, (mAttrs.flags & FLAG_DIM_BEHIND) != 0);
- }
- }
-
// During activity relaunch due to resize, we sometimes use window replacement
// for only child windows (as the main window is handled by window preservation)
// and the big surface.
@@ -4156,9 +4111,6 @@
policyCrop.intersect(-mCompatFrame.left, -mCompatFrame.top,
displayInfo.logicalWidth - mCompatFrame.left,
displayInfo.logicalHeight - mCompatFrame.top);
- } else if (mLayer >= mService.mSystemDecorLayer) {
- // Above the decor layer is easy, just use the entire window
- policyCrop.set(0, 0, mCompatFrame.width(), mCompatFrame.height());
} else if (mDecorFrame.isEmpty()) {
// Windows without policy decor aren't cropped.
policyCrop.set(0, 0, mCompatFrame.width(), mCompatFrame.height());
@@ -4388,4 +4340,78 @@
return false;
}
}
+
+ @Override
+ boolean shouldMagnify() {
+ if (mAttrs.type == TYPE_INPUT_METHOD ||
+ mAttrs.type == TYPE_INPUT_METHOD_DIALOG ||
+ mAttrs.type == TYPE_MAGNIFICATION_OVERLAY ||
+ mAttrs.type == TYPE_NAVIGATION_BAR ||
+ // It's tempting to wonder: Have we forgotten the rounded corners overlay?
+ // worry not: it's a fake TYPE_NAVIGATION_BAR_PANEL
+ mAttrs.type == TYPE_NAVIGATION_BAR_PANEL ||
+ mAttrs.type == TYPE_STATUS_BAR) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ SurfaceSession getSession() {
+ if (mSession.mSurfaceSession != null) {
+ return mSession.mSurfaceSession;
+ } else {
+ return getParent().getSession();
+ }
+ }
+
+ @Override
+ boolean needsZBoost() {
+ return getAnimLayerAdjustment() > 0 || mWillReplaceWindow;
+ }
+
+ @Override
+ SurfaceControl.Builder makeSurface() {
+ return getParent().makeChildSurface(this);
+ }
+
+ private void applyDims(Dimmer dimmer) {
+ if (!mAnimatingExit && mAppDied) {
+ mIsDimming = true;
+ getDimmer().dimAbove(getPendingTransaction(), this, DEFAULT_DIM_AMOUNT_DEAD_WINDOW);
+ } else if ((mAttrs.flags & FLAG_DIM_BEHIND) != 0
+ && !mAnimatingExit && isVisible()) {
+ mIsDimming = true;
+ getDimmer().dimBelow(getPendingTransaction(), this, mAttrs.dimAmount);
+ }
+ }
+
+ @Override
+ void prepareSurfaces() {
+ final Dimmer dimmer = getDimmer();
+ mIsDimming = false;
+ if (dimmer != null) {
+ applyDims(dimmer);
+ }
+
+ mWinAnimator.prepareSurfaceLocked(true);
+ super.prepareSurfaces();
+ }
+
+ @Override
+ void assignLayer(Transaction t, int layer) {
+ // See comment in assignRelativeLayerForImeTargetChild
+ if (!isChildWindow()
+ || (mService.mInputMethodTarget != getParentWindow())
+ || !inSplitScreenWindowingMode()) {
+ super.assignLayer(t, layer);
+ return;
+ }
+ getDisplayContent().assignRelativeLayerForImeTargetChild(t, this);
+ }
+
+ @Override
+ public boolean isDimming() {
+ return mIsDimming;
+ }
}
diff --git a/com/android/server/wm/WindowStateAnimator.java b/com/android/server/wm/WindowStateAnimator.java
index 86397ae..4b5661f 100644
--- a/com/android/server/wm/WindowStateAnimator.java
+++ b/com/android/server/wm/WindowStateAnimator.java
@@ -60,17 +60,17 @@
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.view.DisplayInfo;
-import android.view.MagnificationSpec;
import android.view.Surface.OutOfResourcesException;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
-import android.view.WindowManagerPolicy;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.AnimationUtils;
import android.view.animation.Transformation;
+import com.android.server.policy.WindowManagerPolicy;
+
import java.io.PrintWriter;
import java.io.FileDescriptor;
@@ -559,7 +559,10 @@
}
if (SHOW_TRANSACTIONS) WindowManagerService.logSurface(mWin, "SET FREEZE LAYER", false);
if (mSurfaceController != null) {
- mSurfaceController.setLayer(mAnimLayer + 1);
+ // Our SurfaceControl is always at layer 0 within the parent Surface managed by
+ // window-state. We want this old Surface to stay on top of the new one
+ // until we do the swap, so we place it at layer 1.
+ mSurfaceController.mSurfaceControl.setLayer(1);
}
mDestroyPreservedSurfaceUponRedraw = true;
mSurfaceDestroyDeferred = true;
@@ -730,7 +733,6 @@
try {
mSurfaceController.setPositionInTransaction(mTmpSize.left, mTmpSize.top, false);
mSurfaceController.setLayerStackInTransaction(getLayerStack());
- mSurfaceController.setLayer(mAnimLayer);
} finally {
mService.closeSurfaceTransaction("createSurfaceLocked");
}
@@ -867,29 +869,11 @@
mPendingDestroySurface = null;
}
- void applyMagnificationSpec(MagnificationSpec spec, Matrix transform) {
- final int surfaceInsetLeft = mWin.mAttrs.surfaceInsets.left;
- final int surfaceInsetTop = mWin.mAttrs.surfaceInsets.top;
-
- if (spec != null && !spec.isNop()) {
- float scale = spec.scale;
- transform.postScale(scale, scale);
- transform.postTranslate(spec.offsetX, spec.offsetY);
-
- // As we are scaling the whole surface, to keep the content
- // in the same position we will also have to scale the surfaceInsets.
- transform.postTranslate(-(surfaceInsetLeft*scale - surfaceInsetLeft),
- -(surfaceInsetTop*scale - surfaceInsetTop));
- }
- }
-
void computeShownFrameLocked() {
final boolean selfTransformation = mHasLocalTransformation;
- Transformation attachedTransformation =
- (mParentWinAnimator != null && mParentWinAnimator.mHasLocalTransformation)
- ? mParentWinAnimator.mTransformation : null;
Transformation appTransformation = (mAppAnimator != null && mAppAnimator.hasTransformation)
? mAppAnimator.transformation : null;
+ Transformation wallpaperTargetTransformation = null;
// Wallpapers are animated based on the "real" window they
// are currently targeting.
@@ -899,9 +883,9 @@
if (wallpaperAnimator.mHasLocalTransformation &&
wallpaperAnimator.mAnimation != null &&
!wallpaperAnimator.mAnimation.getDetachWallpaper()) {
- attachedTransformation = wallpaperAnimator.mTransformation;
- if (DEBUG_WALLPAPER && attachedTransformation != null) {
- Slog.v(TAG, "WP target attached xform: " + attachedTransformation);
+ wallpaperTargetTransformation = wallpaperAnimator.mTransformation;
+ if (DEBUG_WALLPAPER && wallpaperTargetTransformation != null) {
+ Slog.v(TAG, "WP target attached xform: " + wallpaperTargetTransformation);
}
}
final AppWindowAnimator wpAppAnimator = wallpaperTarget.mAppToken == null ?
@@ -923,7 +907,7 @@
screenRotationAnimation != null && screenRotationAnimation.isAnimating();
mHasClipRect = false;
- if (selfTransformation || attachedTransformation != null
+ if (selfTransformation || wallpaperTargetTransformation != null
|| appTransformation != null || screenAnimation) {
// cache often used attributes locally
final Rect frame = mWin.mFrame;
@@ -953,27 +937,31 @@
if (selfTransformation) {
tmpMatrix.postConcat(mTransformation.getMatrix());
}
- if (attachedTransformation != null) {
- tmpMatrix.postConcat(attachedTransformation.getMatrix());
+
+ if (wallpaperTargetTransformation != null) {
+ tmpMatrix.postConcat(wallpaperTargetTransformation.getMatrix());
}
if (appTransformation != null) {
tmpMatrix.postConcat(appTransformation.getMatrix());
}
+ int left = frame.left;
+ int top = frame.top;
+ if (mWin.isChildWindow()) {
+ WindowState parent = mWin.getParentWindow();
+ left -= parent.mFrame.left;
+ top -= parent.mFrame.top;
+ }
+
// The translation that applies the position of the window needs to be applied at the
// end in case that other translations include scaling. Otherwise the scaling will
// affect this translation. But it needs to be set before the screen rotation animation
// so the pivot point is at the center of the screen for all windows.
- tmpMatrix.postTranslate(frame.left + mWin.mXOffset, frame.top + mWin.mYOffset);
+ tmpMatrix.postTranslate(left + mWin.mXOffset, top + mWin.mYOffset);
if (screenAnimation) {
tmpMatrix.postConcat(screenRotationAnimation.getEnterTransformation().getMatrix());
}
- MagnificationSpec spec = getMagnificationSpec();
- if (spec != null) {
- applyMagnificationSpec(spec, tmpMatrix);
- }
-
// "convert" it into SurfaceFlinger's format
// (a 2x2 matrix + an offset)
// Here we must not transform the position of the surface
@@ -1004,8 +992,8 @@
if (selfTransformation) {
mShownAlpha *= mTransformation.getAlpha();
}
- if (attachedTransformation != null) {
- mShownAlpha *= attachedTransformation.getAlpha();
+ if (wallpaperTargetTransformation != null) {
+ mShownAlpha *= wallpaperTargetTransformation.getAlpha();
}
if (appTransformation != null) {
mShownAlpha *= appTransformation.getAlpha();
@@ -1036,8 +1024,8 @@
&& (mShownAlpha == 1.0 || mShownAlpha == 0.0)) Slog.v(
TAG, "computeShownFrameLocked: Animating " + this + " mAlpha=" + mAlpha
+ " self=" + (selfTransformation ? mTransformation.getAlpha() : "null")
- + " attached=" + (attachedTransformation == null ?
- "null" : attachedTransformation.getAlpha())
+ + " attached=" + (wallpaperTargetTransformation == null ?
+ "null" : wallpaperTargetTransformation.getAlpha())
+ " app=" + (appTransformation == null ? "null" : appTransformation.getAlpha())
+ " screen=" + (screenAnimation ?
screenRotationAnimation.getEnterTransformation().getAlpha() : "null"));
@@ -1057,49 +1045,16 @@
TAG, "computeShownFrameLocked: " + this +
" not attached, mAlpha=" + mAlpha);
- MagnificationSpec spec = getMagnificationSpec();
- if (spec != null) {
- final Rect frame = mWin.mFrame;
- final float tmpFloats[] = mService.mTmpFloats;
- final Matrix tmpMatrix = mWin.mTmpMatrix;
-
- tmpMatrix.setScale(mWin.mGlobalScale, mWin.mGlobalScale);
- tmpMatrix.postTranslate(frame.left + mWin.mXOffset, frame.top + mWin.mYOffset);
-
- applyMagnificationSpec(spec, tmpMatrix);
-
- tmpMatrix.getValues(tmpFloats);
-
- mHaveMatrix = true;
- mDsDx = tmpFloats[Matrix.MSCALE_X];
- mDtDx = tmpFloats[Matrix.MSKEW_Y];
- mDtDy = tmpFloats[Matrix.MSKEW_X];
- mDsDy = tmpFloats[Matrix.MSCALE_Y];
- float x = tmpFloats[Matrix.MTRANS_X];
- float y = tmpFloats[Matrix.MTRANS_Y];
- mWin.mShownPosition.set(Math.round(x), Math.round(y));
-
- mShownAlpha = mAlpha;
- } else {
- mWin.mShownPosition.set(mWin.mFrame.left, mWin.mFrame.top);
- if (mWin.mXOffset != 0 || mWin.mYOffset != 0) {
- mWin.mShownPosition.offset(mWin.mXOffset, mWin.mYOffset);
- }
- mShownAlpha = mAlpha;
- mHaveMatrix = false;
- mDsDx = mWin.mGlobalScale;
- mDtDx = 0;
- mDtDy = 0;
- mDsDy = mWin.mGlobalScale;
+ mWin.mShownPosition.set(mWin.mFrame.left, mWin.mFrame.top);
+ if (mWin.mXOffset != 0 || mWin.mYOffset != 0) {
+ mWin.mShownPosition.offset(mWin.mXOffset, mWin.mYOffset);
}
- }
-
- private MagnificationSpec getMagnificationSpec() {
- //TODO (multidisplay): Magnification is supported only for the default display.
- if (mService.mAccessibilityController != null && mWin.getDisplayId() == DEFAULT_DISPLAY) {
- return mService.mAccessibilityController.getMagnificationSpecForWindowLocked(mWin);
- }
- return null;
+ mShownAlpha = mAlpha;
+ mHaveMatrix = false;
+ mDsDx = mWin.mGlobalScale;
+ mDtDx = 0;
+ mDtDy = 0;
+ mDsDy = mWin.mGlobalScale;
}
/**
@@ -1140,26 +1095,6 @@
w.expandForSurfaceInsets(finalClipRect);
}
- // We may be applying a magnification spec to all windows,
- // simulating a transformation in screen space, in which case
- // we need to transform all other screen space values...including
- // the final crop. This is kind of messed up and we should look
- // in to actually transforming screen-space via a parent-layer.
- // b/38322835
- MagnificationSpec spec = getMagnificationSpec();
- if (spec != null && !spec.isNop()) {
- Matrix transform = mWin.mTmpMatrix;
- RectF finalCrop = mService.mTmpRectF;
- transform.reset();
- transform.postScale(spec.scale, spec.scale);
- transform.postTranslate(-spec.offsetX, -spec.offsetY);
- transform.mapRect(finalCrop);
- finalClipRect.top = (int) finalCrop.top;
- finalClipRect.left = (int) finalCrop.left;
- finalClipRect.right = (int) finalCrop.right;
- finalClipRect.bottom = (int) finalCrop.bottom;
- }
-
return true;
}
@@ -1517,7 +1452,6 @@
mReportSurfaceResized = true;
mAnimator.setPendingLayoutChanges(w.getDisplayId(),
WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER);
- w.applyDimLayerIfNeeded();
}
}
@@ -1615,7 +1549,6 @@
mDtDy * w.mHScale * mExtraHScale,
mDsDy * w.mVScale * mExtraVScale,
recoveringMemory);
- mSurfaceController.setLayer(mAnimLayer);
if (prepared && mDrawState == HAS_DRAWN) {
if (mLastHidden) {
@@ -2045,7 +1978,7 @@
final float width = w.mFrame.width();
final float height = w.mFrame.height();
- mService.getDefaultDisplayContentLocked().getLogicalDisplayRect(displayRect);
+ mService.getDefaultDisplayContentLocked().getBounds(displayRect);
final float displayWidth = displayRect.width();
final float displayHeight = displayRect.height();
diff --git a/com/android/server/wm/WindowSurfaceController.java b/com/android/server/wm/WindowSurfaceController.java
index a214523..6746754 100644
--- a/com/android/server/wm/WindowSurfaceController.java
+++ b/com/android/server/wm/WindowSurfaceController.java
@@ -53,7 +53,7 @@
final WindowStateAnimator mAnimator;
- private SurfaceControlWithBackground mSurfaceControl;
+ SurfaceControlWithBackground mSurfaceControl;
// Should only be set from within setShown().
private boolean mSurfaceShown = false;
@@ -101,7 +101,8 @@
mWindowSession = win.mSession;
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "new SurfaceControl");
- final SurfaceControl.Builder b = new SurfaceControl.Builder(s)
+ final SurfaceControl.Builder b = win.makeSurface()
+ .setParent(win.getSurfaceControl())
.setName(name)
.setSize(w, h)
.setFormat(format)
@@ -245,25 +246,6 @@
}
}
- void setLayer(int layer) {
- if (mSurfaceControl != null) {
- mService.openSurfaceTransaction();
- try {
- if (mAnimator.mWin.usesRelativeZOrdering()) {
- mSurfaceControl.setRelativeLayer(
- mAnimator.mWin.getParentWindow()
- .mWinAnimator.mSurfaceController.mSurfaceControl,
- -1);
- } else {
- mSurfaceLayer = layer;
- mSurfaceControl.setLayer(layer);
- }
- } finally {
- mService.closeSurfaceTransaction("setLayer");
- }
- }
- }
-
void setLayerStackInTransaction(int layerStack) {
if (mSurfaceControl != null) {
mSurfaceControl.setLayerStack(layerStack);
diff --git a/com/android/server/wm/WindowSurfacePlacer.java b/com/android/server/wm/WindowSurfacePlacer.java
index cd5e475..169d0a3 100644
--- a/com/android/server/wm/WindowSurfacePlacer.java
+++ b/com/android/server/wm/WindowSurfacePlacer.java
@@ -20,8 +20,8 @@
import static android.app.ActivityManagerInternal.APP_TRANSITION_SNAPSHOT;
import static android.app.ActivityManagerInternal.APP_TRANSITION_SPLASH_SCREEN;
import static android.app.ActivityManagerInternal.APP_TRANSITION_WINDOWS_DRAWN;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_CLOSE;
import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_OPEN;
import static com.android.server.wm.AppTransition.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
@@ -704,7 +704,7 @@
// Create a new surface for the thumbnail
WindowState window = appToken.findMainWindow();
- final SurfaceControl surfaceControl = new SurfaceControl.Builder(mService.mFxSession)
+ final SurfaceControl surfaceControl = appToken.makeSurface()
.setName("thumbnail anim")
.setSize(dirty.width(), dirty.height())
.setFormat(PixelFormat.TRANSLUCENT)
@@ -712,7 +712,6 @@
window != null ? window.mOwnerUid : Binder.getCallingUid())
.build();
- surfaceControl.setLayerStack(display.getLayerStack());
if (SHOW_TRANSACTIONS) {
Slog.i(TAG, " THUMBNAIL " + surfaceControl + ": CREATE");
}
@@ -750,10 +749,13 @@
anim.restrictDuration(MAX_ANIMATION_DURATION);
anim.scaleCurrentDuration(mService.getTransitionAnimationScaleLocked());
- openingAppAnimator.updateThumbnailLayer();
openingAppAnimator.thumbnail = surfaceControl;
openingAppAnimator.thumbnailAnimation = anim;
mService.mAppTransition.getNextAppTransitionStartRect(taskId, mTmpStartRect);
+
+ // We parent the thumbnail to the app token, and just place it
+ // on top of anything else in the app token.
+ surfaceControl.setLayer(Integer.MAX_VALUE);
} catch (Surface.OutOfResourcesException e) {
Slog.e(TAG, "Can't allocate thumbnail/Canvas surface w="
+ dirty.width() + " h=" + dirty.height(), e);
diff --git a/com/android/server/wm/WindowTracing.java b/com/android/server/wm/WindowTracing.java
index 5657f6c..c858b19 100644
--- a/com/android/server/wm/WindowTracing.java
+++ b/com/android/server/wm/WindowTracing.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.os.Build.IS_USER;
import static com.android.server.wm.proto.WindowManagerTraceFileProto.ENTRY;
import static com.android.server.wm.proto.WindowManagerTraceFileProto.MAGIC_NUMBER;
import static com.android.server.wm.proto.WindowManagerTraceFileProto.MAGIC_NUMBER_H;
@@ -62,11 +63,16 @@
}
void startTrace(PrintWriter pw) throws IOException {
+ if (IS_USER){
+ logAndPrintln(pw, "Error: Tracing is not supported on user builds.");
+ return;
+ }
synchronized (mLock) {
logAndPrintln(pw, "Start tracing to " + mTraceFile + ".");
mWriteQueue.clear();
mTraceFile.delete();
try (OutputStream os = new FileOutputStream(mTraceFile)) {
+ mTraceFile.setReadable(true, false);
ProtoOutputStream proto = new ProtoOutputStream(os);
proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
proto.flush();
@@ -82,6 +88,10 @@
}
void stopTrace(PrintWriter pw) {
+ if (IS_USER){
+ logAndPrintln(pw, "Error: Tracing is not supported on user builds.");
+ return;
+ }
synchronized (mLock) {
logAndPrintln(pw, "Stop tracing to " + mTraceFile + ". Waiting for traces to flush.");
mEnabled = mEnabledLockFree = false;
@@ -147,9 +157,11 @@
}
static WindowTracing createDefaultAndStartLooper(Context context) {
- File file = new File("/data/system/window_trace.proto");
+ File file = new File("/data/misc/wmtrace/wm_trace.pb");
WindowTracing windowTracing = new WindowTracing(file);
- new Thread(windowTracing::loop, "window_tracing").start();
+ if (!IS_USER){
+ new Thread(windowTracing::loop, "window_tracing").start();
+ }
return windowTracing;
}
diff --git a/com/android/settingslib/Utils.java b/com/android/settingslib/Utils.java
index 2186169..eb33842 100644
--- a/com/android/settingslib/Utils.java
+++ b/com/android/settingslib/Utils.java
@@ -105,7 +105,8 @@
}
}
return new UserIconDrawable(iconSize).setIconDrawable(
- UserIcons.getDefaultUserIcon(user.id, /* light= */ false)).bake();
+ UserIcons.getDefaultUserIcon(context.getResources(), user.id, /* light= */ false))
+ .bake();
}
/** Formats a double from 0.0..100.0 with an option to round **/
diff --git a/com/android/settingslib/drawer/CategoryManager.java b/com/android/settingslib/drawer/CategoryManager.java
index ee7885d..0703330 100644
--- a/com/android/settingslib/drawer/CategoryManager.java
+++ b/com/android/settingslib/drawer/CategoryManager.java
@@ -18,7 +18,6 @@
import android.content.ComponentName;
import android.content.Context;
import android.support.annotation.VisibleForTesting;
-import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
@@ -27,7 +26,6 @@
import com.android.settingslib.applications.InterestingConfigChanges;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -104,10 +102,10 @@
}
for (int i = 0; i < mCategories.size(); i++) {
DashboardCategory category = mCategories.get(i);
- for (int j = 0; j < category.tiles.size(); j++) {
- Tile tile = category.tiles.get(j);
+ for (int j = 0; j < category.getTilesCount(); j++) {
+ Tile tile = category.getTile(j);
if (tileBlacklist.contains(tile.intent.getComponent())) {
- category.tiles.remove(j--);
+ category.removeTile(j--);
}
}
}
@@ -181,7 +179,7 @@
newCategory = new DashboardCategory();
categoryByKeyMap.put(newCategoryKey, newCategory);
}
- newCategory.tiles.add(tile);
+ newCategory.addTile(tile);
}
}
}
@@ -198,7 +196,7 @@
synchronized void sortCategories(Context context,
Map<String, DashboardCategory> categoryByKeyMap) {
for (Entry<String, DashboardCategory> categoryEntry : categoryByKeyMap.entrySet()) {
- sortCategoriesForExternalTiles(context, categoryEntry.getValue());
+ categoryEntry.getValue().sortTiles(context.getPackageName());
}
}
@@ -210,16 +208,16 @@
synchronized void filterDuplicateTiles(Map<String, DashboardCategory> categoryByKeyMap) {
for (Entry<String, DashboardCategory> categoryEntry : categoryByKeyMap.entrySet()) {
final DashboardCategory category = categoryEntry.getValue();
- final int count = category.tiles.size();
+ final int count = category.getTilesCount();
final Set<ComponentName> components = new ArraySet<>();
for (int i = count - 1; i >= 0; i--) {
- final Tile tile = category.tiles.get(i);
+ final Tile tile = category.getTile(i);
if (tile.intent == null) {
continue;
}
final ComponentName tileComponent = tile.intent.getComponent();
if (components.contains(tileComponent)) {
- category.tiles.remove(i);
+ category.removeTile(i);
} else {
components.add(tileComponent);
}
@@ -234,28 +232,7 @@
*/
private synchronized void sortCategoriesForExternalTiles(Context context,
DashboardCategory dashboardCategory) {
- final String skipPackageName = context.getPackageName();
+ dashboardCategory.sortTiles(context.getPackageName());
- // Sort tiles based on [priority, package within priority]
- Collections.sort(dashboardCategory.tiles, (tile1, tile2) -> {
- final String package1 = tile1.intent.getComponent().getPackageName();
- final String package2 = tile2.intent.getComponent().getPackageName();
- final int packageCompare = CASE_INSENSITIVE_ORDER.compare(package1, package2);
- // First sort by priority
- final int priorityCompare = tile2.priority - tile1.priority;
- if (priorityCompare != 0) {
- return priorityCompare;
- }
- // Then sort by package name, skip package take precedence
- if (packageCompare != 0) {
- if (TextUtils.equals(package1, skipPackageName)) {
- return -1;
- }
- if (TextUtils.equals(package2, skipPackageName)) {
- return 1;
- }
- }
- return packageCompare;
- });
}
}
diff --git a/com/android/settingslib/drawer/DashboardCategory.java b/com/android/settingslib/drawer/DashboardCategory.java
index f6f8168..a966e82 100644
--- a/com/android/settingslib/drawer/DashboardCategory.java
+++ b/com/android/settingslib/drawer/DashboardCategory.java
@@ -16,6 +16,8 @@
package com.android.settingslib.drawer;
+import static java.lang.String.CASE_INSENSITIVE_ORDER;
+
import android.content.ComponentName;
import android.os.Parcel;
import android.os.Parcelable;
@@ -23,6 +25,8 @@
import android.util.Log;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
public class DashboardCategory implements Parcelable {
@@ -48,39 +52,59 @@
/**
* List of the category's children
*/
- public List<Tile> tiles = new ArrayList<>();
+ private List<Tile> mTiles = new ArrayList<>();
+ DashboardCategory(DashboardCategory in) {
+ if (in != null) {
+ title = in.title;
+ key = in.key;
+ priority = in.priority;
+ for (Tile tile : in.mTiles) {
+ mTiles.add(tile);
+ }
+ }
+ }
public DashboardCategory() {
// Empty
}
+ /**
+ * Get a copy of the list of the category's children.
+ *
+ * Note: the returned list serves as a read-only list. If tiles needs to be added or removed
+ * from the actual tiles list, it should be done through {@link #addTile}, {@link #removeTile}.
+ */
+ public List<Tile> getTiles() {
+ return Collections.unmodifiableList(mTiles);
+ }
+
public void addTile(Tile tile) {
- tiles.add(tile);
+ mTiles.add(tile);
}
public void addTile(int n, Tile tile) {
- tiles.add(n, tile);
+ mTiles.add(n, tile);
}
public void removeTile(Tile tile) {
- tiles.remove(tile);
+ mTiles.remove(tile);
}
public void removeTile(int n) {
- tiles.remove(n);
+ mTiles.remove(n);
}
public int getTilesCount() {
- return tiles.size();
+ return mTiles.size();
}
public Tile getTile(int n) {
- return tiles.get(n);
+ return mTiles.get(n);
}
public boolean containsComponent(ComponentName component) {
- for (Tile tile : tiles) {
+ for (Tile tile : mTiles) {
if (TextUtils.equals(tile.intent.getComponent().getClassName(),
component.getClassName())) {
if (DEBUG) {
@@ -95,6 +119,40 @@
return false;
}
+ /**
+ * Sort priority value for tiles in this category.
+ */
+ public void sortTiles() {
+ Collections.sort(mTiles, TILE_COMPARATOR);
+ }
+
+ /**
+ * Sort priority value and package name for tiles in this category.
+ */
+ public void sortTiles(String skipPackageName) {
+ // Sort mTiles based on [priority, package within priority]
+ Collections.sort(mTiles, (tile1, tile2) -> {
+ final String package1 = tile1.intent.getComponent().getPackageName();
+ final String package2 = tile2.intent.getComponent().getPackageName();
+ final int packageCompare = CASE_INSENSITIVE_ORDER.compare(package1, package2);
+ // First sort by priority
+ final int priorityCompare = tile2.priority - tile1.priority;
+ if (priorityCompare != 0) {
+ return priorityCompare;
+ }
+ // Then sort by package name, skip package take precedence
+ if (packageCompare != 0) {
+ if (TextUtils.equals(package1, skipPackageName)) {
+ return -1;
+ }
+ if (TextUtils.equals(package2, skipPackageName)) {
+ return 1;
+ }
+ }
+ return packageCompare;
+ });
+ }
+
@Override
public int describeContents() {
return 0;
@@ -106,11 +164,11 @@
dest.writeString(key);
dest.writeInt(priority);
- final int count = tiles.size();
+ final int count = mTiles.size();
dest.writeInt(count);
for (int n = 0; n < count; n++) {
- Tile tile = tiles.get(n);
+ Tile tile = mTiles.get(n);
tile.writeToParcel(dest, flags);
}
}
@@ -124,7 +182,7 @@
for (int n = 0; n < count; n++) {
Tile tile = Tile.CREATOR.createFromParcel(in);
- tiles.add(tile);
+ mTiles.add(tile);
}
}
@@ -141,4 +199,13 @@
return new DashboardCategory[size];
}
};
+
+ public static final Comparator<Tile> TILE_COMPARATOR =
+ new Comparator<Tile>() {
+ @Override
+ public int compare(Tile lhs, Tile rhs) {
+ return rhs.priority - lhs.priority;
+ }
+ };
+
}
diff --git a/com/android/settingslib/drawer/TileUtils.java b/com/android/settingslib/drawer/TileUtils.java
index 038dcf8..e986e0f 100644
--- a/com/android/settingslib/drawer/TileUtils.java
+++ b/com/android/settingslib/drawer/TileUtils.java
@@ -253,7 +253,7 @@
}
ArrayList<DashboardCategory> categories = new ArrayList<>(categoryMap.values());
for (DashboardCategory category : categories) {
- Collections.sort(category.tiles, TILE_COMPARATOR);
+ category.sortTiles();
}
Collections.sort(categories, CATEGORY_COMPARATOR);
if (DEBUG_TIMING) Log.d(LOG_TAG, "getCategories took "
@@ -595,14 +595,6 @@
return pathSegments.get(0);
}
- public static final Comparator<Tile> TILE_COMPARATOR =
- new Comparator<Tile>() {
- @Override
- public int compare(Tile lhs, Tile rhs) {
- return rhs.priority - lhs.priority;
- }
- };
-
private static final Comparator<DashboardCategory> CATEGORY_COMPARATOR =
new Comparator<DashboardCategory>() {
@Override
diff --git a/com/android/settingslib/drawer/UserAdapter.java b/com/android/settingslib/drawer/UserAdapter.java
index b27d823..8a09df2 100644
--- a/com/android/settingslib/drawer/UserAdapter.java
+++ b/com/android/settingslib/drawer/UserAdapter.java
@@ -64,7 +64,8 @@
if (um.getUserIcon(userId) != null) {
icon = new BitmapDrawable(context.getResources(), um.getUserIcon(userId));
} else {
- icon = UserIcons.getDefaultUserIcon(userId, /* light= */ false);
+ icon = UserIcons.getDefaultUserIcon(
+ context.getResources(), userId, /* light= */ false);
}
}
this.mIcon = encircle(context, icon);
diff --git a/com/android/setupwizardlib/GlifPatternDrawable.java b/com/android/setupwizardlib/GlifPatternDrawable.java
index 51c1a49..c1d968a 100644
--- a/com/android/setupwizardlib/GlifPatternDrawable.java
+++ b/com/android/setupwizardlib/GlifPatternDrawable.java
@@ -23,7 +23,6 @@
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
-import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
@@ -96,7 +95,6 @@
private int mColor;
private Paint mTempPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- private ColorFilter mColorFilter;
public GlifPatternDrawable(int color) {
setColor(color);
@@ -140,17 +138,10 @@
canvas.clipRect(bounds);
scaleCanvasToBounds(canvas, bitmap, bounds);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB
- && canvas.isHardwareAccelerated()) {
- mTempPaint.setColorFilter(mColorFilter);
- canvas.drawBitmap(bitmap, 0, 0, mTempPaint);
- } else {
- // Software renderer doesn't work properly with ColorMatrix filter on ALPHA_8 bitmaps.
- canvas.drawColor(Color.BLACK);
- mTempPaint.setColor(Color.WHITE);
- canvas.drawBitmap(bitmap, 0, 0, mTempPaint);
- canvas.drawColor(mColor);
- }
+ canvas.drawColor(Color.BLACK);
+ mTempPaint.setColor(Color.WHITE);
+ canvas.drawBitmap(bitmap, 0, 0, mTempPaint);
+ canvas.drawColor(mColor);
canvas.restore();
}
@@ -299,12 +290,6 @@
final int g = Color.green(color);
final int b = Color.blue(color);
mColor = Color.argb(COLOR_ALPHA_INT, r, g, b);
- mColorFilter = new ColorMatrixColorFilter(new float[] {
- 0, 0, 0, 1 - COLOR_ALPHA, r * COLOR_ALPHA,
- 0, 0, 0, 1 - COLOR_ALPHA, g * COLOR_ALPHA,
- 0, 0, 0, 1 - COLOR_ALPHA, b * COLOR_ALPHA,
- 0, 0, 0, 0, 255
- });
invalidateSelf();
}
diff --git a/com/android/systemui/OverviewProxyService.java b/com/android/systemui/OverviewProxyService.java
index 2e4a5a4..22922e7 100644
--- a/com/android/systemui/OverviewProxyService.java
+++ b/com/android/systemui/OverviewProxyService.java
@@ -22,11 +22,8 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
-import android.graphics.Bitmap;
import android.graphics.Rect;
-import android.net.Uri;
import android.os.Binder;
-import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -36,12 +33,14 @@
import android.util.Log;
import android.view.SurfaceControl;
+import com.android.systemui.OverviewProxyService.OverviewProxyListener;
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.recents.ISystemUiProxy;
-import com.android.systemui.OverviewProxyService.OverviewProxyListener;
+import com.android.systemui.shared.system.GraphicBufferCompat;
import com.android.systemui.statusbar.policy.CallbackController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -67,12 +66,12 @@
private int mConnectionBackoffAttempts;
private ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() {
- public Bitmap screenshot(Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
- boolean useIdentityTransform, int rotation) {
+ public GraphicBufferCompat screenshot(Rect sourceCrop, int width, int height, int minLayer,
+ int maxLayer, boolean useIdentityTransform, int rotation) {
long token = Binder.clearCallingIdentity();
try {
- return SurfaceControl.screenshot(sourceCrop, width, height, minLayer, maxLayer,
- useIdentityTransform, rotation);
+ return new GraphicBufferCompat(SurfaceControl.screenshotToBuffer(sourceCrop, width,
+ height, minLayer, maxLayer, useIdentityTransform, rotation));
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/com/android/systemui/SystemUIFactory.java b/com/android/systemui/SystemUIFactory.java
index 0c067ff..526a8f4 100644
--- a/com/android/systemui/SystemUIFactory.java
+++ b/com/android/systemui/SystemUIFactory.java
@@ -28,6 +28,7 @@
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.ScrimView;
+import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.KeyguardBouncer;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.LockIcon;
@@ -86,10 +87,10 @@
public ScrimController createScrimController(LightBarController lightBarController,
ScrimView scrimBehind, ScrimView scrimInFront, View headsUpScrim,
- LockscreenWallpaper lockscreenWallpaper,
- Consumer<Boolean> scrimVisibleListener) {
+ LockscreenWallpaper lockscreenWallpaper, Consumer<Boolean> scrimVisibleListener,
+ DozeParameters dozeParameters) {
return new ScrimController(lightBarController, scrimBehind, scrimInFront, headsUpScrim,
- scrimVisibleListener);
+ scrimVisibleListener, dozeParameters);
}
public NotificationIconAreaController createNotificationIconAreaController(Context context,
diff --git a/com/android/systemui/doze/DozeHost.java b/com/android/systemui/doze/DozeHost.java
index 7db118d..2f607ee 100644
--- a/com/android/systemui/doze/DozeHost.java
+++ b/com/android/systemui/doze/DozeHost.java
@@ -35,7 +35,6 @@
boolean isBlockingDoze();
void startPendingIntentDismissingKeyguard(PendingIntent intent);
- void abortPulsing();
void extendPulse();
void setAnimateWakeup(boolean animateWakeup);
diff --git a/com/android/systemui/keyguard/KeyguardSliceProvider.java b/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 03018f7..6ddc76b 100644
--- a/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -84,7 +84,7 @@
@Override
public Slice onBindSlice(Uri sliceUri) {
- return new Slice.Builder(sliceUri).addText(mLastText, Slice.HINT_TITLE).build();
+ return new Slice.Builder(sliceUri).addText(mLastText, null, Slice.HINT_TITLE).build();
}
@Override
diff --git a/com/android/systemui/keyguard/KeyguardViewMediator.java b/com/android/systemui/keyguard/KeyguardViewMediator.java
index a35ba9f..c92acd0 100644
--- a/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -60,7 +60,7 @@
import android.util.Log;
import android.util.Slog;
import android.view.ViewGroup;
-import android.view.WindowManagerPolicy;
+import android.view.WindowManagerPolicyConstants;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
@@ -129,7 +129,7 @@
* false, this will override all other conditions for turning on the keyguard.
*
* Threading and synchronization:
- * This class is created by the initialization routine of the {@link android.view.WindowManagerPolicy},
+ * This class is created by the initialization routine of the {@link WindowManagerPolicyConstants},
* and runs on its thread. The keyguard UI is created from that thread in the
* constructor of this class. The apis may be called from other threads, including the
* {@link com.android.server.input.InputManagerService}'s and {@link android.view.WindowManager}'s.
@@ -766,8 +766,8 @@
/**
* Called to let us know the screen was turned off.
- * @param why either {@link android.view.WindowManagerPolicy#OFF_BECAUSE_OF_USER} or
- * {@link android.view.WindowManagerPolicy#OFF_BECAUSE_OF_TIMEOUT}.
+ * @param why either {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_USER} or
+ * {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_TIMEOUT}.
*/
public void onStartedGoingToSleep(int why) {
if (DEBUG) Log.d(TAG, "onStartedGoingToSleep(" + why + ")");
@@ -797,8 +797,8 @@
}
} else if (mShowing) {
mPendingReset = true;
- } else if ((why == WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT && timeout > 0)
- || (why == WindowManagerPolicy.OFF_BECAUSE_OF_USER && !lockImmediately)) {
+ } else if ((why == WindowManagerPolicyConstants.OFF_BECAUSE_OF_TIMEOUT && timeout > 0)
+ || (why == WindowManagerPolicyConstants.OFF_BECAUSE_OF_USER && !lockImmediately)) {
doKeyguardLaterLocked(timeout);
mLockLater = true;
} else if (!mLockPatternUtils.isLockScreenDisabled(currentUser)) {
@@ -1031,7 +1031,7 @@
}
/**
- * Same semantics as {@link android.view.WindowManagerPolicy#enableKeyguard}; provide
+ * Same semantics as {@link WindowManagerPolicyConstants#enableKeyguard}; provide
* a way for external stuff to override normal keyguard behavior. For instance
* the phone app disables the keyguard when it receives incoming calls.
*/
@@ -1780,13 +1780,13 @@
int flags = 0;
if (mStatusBarKeyguardViewManager.shouldDisableWindowAnimationsForUnlock()
|| mWakeAndUnlocking) {
- flags |= WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS;
+ flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS;
}
if (mStatusBarKeyguardViewManager.isGoingToNotificationShade()) {
- flags |= WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
+ flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
}
if (mStatusBarKeyguardViewManager.isUnlockWithWallpaper()) {
- flags |= WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
+ flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
}
mUpdateMonitor.setKeyguardGoingAway(true /* goingAway */);
@@ -2028,12 +2028,9 @@
}
public StatusBarKeyguardViewManager registerStatusBar(StatusBar statusBar,
- ViewGroup container,
- ScrimController scrimController,
- FingerprintUnlockController fingerprintUnlockController) {
+ ViewGroup container, FingerprintUnlockController fingerprintUnlockController) {
mStatusBarKeyguardViewManager.registerStatusBar(statusBar, container,
- scrimController, fingerprintUnlockController,
- mDismissCallbackRegistry);
+ fingerprintUnlockController, mDismissCallbackRegistry);
return mStatusBarKeyguardViewManager;
}
diff --git a/com/android/systemui/pip/phone/PipTouchHandler.java b/com/android/systemui/pip/phone/PipTouchHandler.java
index 2b48e0f..51175d1 100644
--- a/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -387,7 +387,9 @@
}
case MotionEvent.ACTION_HOVER_ENTER:
case MotionEvent.ACTION_HOVER_MOVE: {
- if (mAccessibilityManager.isEnabled() && !mSendingHoverAccessibilityEvents) {
+ if (mAccessibilityManager.isObservedEventType(
+ AccessibilityEvent.TYPE_VIEW_HOVER_ENTER)
+ && !mSendingHoverAccessibilityEvents) {
AccessibilityEvent event = AccessibilityEvent.obtain(
AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
event.setImportantForAccessibility(true);
@@ -400,7 +402,9 @@
break;
}
case MotionEvent.ACTION_HOVER_EXIT: {
- if (mAccessibilityManager.isEnabled() && mSendingHoverAccessibilityEvents) {
+ if (mAccessibilityManager.isObservedEventType(
+ AccessibilityEvent.TYPE_VIEW_HOVER_EXIT)
+ && mSendingHoverAccessibilityEvents) {
AccessibilityEvent event = AccessibilityEvent.obtain(
AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
event.setImportantForAccessibility(true);
diff --git a/com/android/systemui/pip/tv/PipManager.java b/com/android/systemui/pip/tv/PipManager.java
index eef43d2..a984680 100644
--- a/com/android/systemui/pip/tv/PipManager.java
+++ b/com/android/systemui/pip/tv/PipManager.java
@@ -625,9 +625,7 @@
@Override
public void onTaskStackChanged() {
if (DEBUG) Log.d(TAG, "onTaskStackChanged()");
- if (!checkCurrentUserId(mContext, DEBUG)) {
- return;
- }
+
if (getState() != STATE_NO_PIP) {
boolean hasPip = false;
@@ -662,9 +660,7 @@
@Override
public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
if (DEBUG) Log.d(TAG, "onActivityPinned()");
- if (!checkCurrentUserId(mContext, DEBUG)) {
- return;
- }
+
StackInfo stackInfo = getPinnedStackInfo();
if (stackInfo == null) {
Log.w(TAG, "Cannot find pinned stack");
@@ -690,9 +686,7 @@
@Override
public void onPinnedActivityRestartAttempt(boolean clearedTask) {
if (DEBUG) Log.d(TAG, "onPinnedActivityRestartAttempt()");
- if (!checkCurrentUserId(mContext, DEBUG)) {
- return;
- }
+
// If PIPed activity is launched again by Launcher or intent, make it fullscreen.
movePipToFullscreen();
}
@@ -700,9 +694,7 @@
@Override
public void onPinnedStackAnimationEnded() {
if (DEBUG) Log.d(TAG, "onPinnedStackAnimationEnded()");
- if (!checkCurrentUserId(mContext, DEBUG)) {
- return;
- }
+
switch (getState()) {
case STATE_PIP_MENU:
showPipMenu();
diff --git a/com/android/systemui/recents/RecentsImpl.java b/com/android/systemui/recents/RecentsImpl.java
index 3b1b2f9..663f206 100644
--- a/com/android/systemui/recents/RecentsImpl.java
+++ b/com/android/systemui/recents/RecentsImpl.java
@@ -23,14 +23,12 @@
import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS;
import android.app.ActivityManager;
-import android.app.ActivityManager.TaskSnapshot;
import android.app.ActivityOptions;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
-import android.graphics.GraphicBuffer;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
@@ -89,7 +87,6 @@
import com.android.systemui.shared.recents.view.RecentsTransition;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.stackdivider.DividerView;
-import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
import com.android.systemui.statusbar.phone.StatusBar;
import java.util.ArrayList;
@@ -156,7 +153,8 @@
// Launched from app is always the worst case (in terms of how many
// thumbnails/tasks visible)
launchState.launchedFromApp = true;
- mBackgroundLayoutAlgorithm.update(plan.getTaskStack(), EMPTY_SET, launchState);
+ mBackgroundLayoutAlgorithm.update(plan.getTaskStack(), EMPTY_SET, launchState,
+ -1 /* lastScrollPPresent */);
VisibilityReport visibilityReport =
mBackgroundLayoutAlgorithm.computeStackVisibilityReport(
stack.getTasks());
@@ -656,13 +654,6 @@
// the resize mode already.
if (ssp.setTaskWindowingModeSplitScreenPrimary(taskId, stackCreateMode, initialBounds)) {
EventBus.getDefault().send(new DockedTopTaskEvent(dragMode, initialBounds));
- showRecents(
- false /* triggeredFromAltTab */,
- dragMode == NavigationBarGestureHelper.DRAG_MODE_RECENTS,
- false /* animate */,
- true /* launchedWhileDockingTask*/,
- false /* fromHome */,
- DividerView.INVALID_RECENTS_GROW_TARGET);
}
}
diff --git a/com/android/systemui/recents/misc/SystemServicesProxy.java b/com/android/systemui/recents/misc/SystemServicesProxy.java
index d89bab7..2d3080b 100644
--- a/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -21,12 +21,10 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManager.StackInfo;
import android.app.ActivityOptions;
@@ -49,9 +47,6 @@
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.IRemoteCallback;
-import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
@@ -63,7 +58,6 @@
import android.util.Log;
import android.util.MutableBoolean;
import android.view.Display;
-import android.view.IAppTransitionAnimationSpecsFuture;
import android.view.IDockedStackListener;
import android.view.IWindowManager;
import android.view.WindowManager;
@@ -74,16 +68,12 @@
import com.android.internal.app.AssistUtils;
import com.android.internal.os.BackgroundThread;
import com.android.systemui.Dependency;
-import com.android.systemui.R;
import com.android.systemui.UiOffloadThread;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsImpl;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.statusbar.policy.UserInfoController;
import java.util.List;
-import java.util.function.Consumer;
/**
* Acts as a shim around the real system services that we need to access data from, and provides
@@ -268,22 +258,6 @@
return mIsSafeMode;
}
- /** Docks a task to the side of the screen and starts it. */
- public boolean startTaskInDockedMode(int taskId, int createMode) {
- if (mIam == null) return false;
-
- try {
- final ActivityOptions options = ActivityOptions.makeBasic();
- options.setSplitScreenCreateMode(createMode);
- options.setLaunchWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
- mIam.startActivityFromRecents(taskId, options.toBundle());
- return true;
- } catch (Exception e) {
- Log.e(TAG, "Failed to dock task: " + taskId + " with createMode: " + createMode, e);
- }
- return false;
- }
-
/** Moves an already resumed task to the side of the screen to initiate split screen. */
public boolean setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode,
Rect initialBounds) {
@@ -397,7 +371,7 @@
if (mIam == null) return false;
try {
- return mIam.isInLockTaskMode();
+ return mIam.getLockTaskModeState() == ActivityManager.LOCK_TASK_MODE_PINNED;
} catch (RemoteException e) {
return false;
}
@@ -540,16 +514,6 @@
}
}
- public void overridePendingAppTransitionMultiThumbFuture(
- IAppTransitionAnimationSpecsFuture future, IRemoteCallback animStartedListener,
- boolean scaleUp) {
- try {
- mIwm.overridePendingAppTransitionMultiThumbFuture(future, animStartedListener, scaleUp);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to override transition: " + e);
- }
- }
-
/**
* Updates the visibility of recents.
*/
diff --git a/com/android/systemui/recents/views/RecentsView.java b/com/android/systemui/recents/views/RecentsView.java
index 1440fc1..e3ed1aa 100644
--- a/com/android/systemui/recents/views/RecentsView.java
+++ b/com/android/systemui/recents/views/RecentsView.java
@@ -16,13 +16,14 @@
package com.android.systemui.recents.views;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
+
import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.Nullable;
import android.app.ActivityOptions;
-import android.app.ActivityOptions.OnAnimationStartedListener;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Canvas;
@@ -33,11 +34,11 @@
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
+import android.os.IRemoteCallback;
import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.Log;
import android.util.MathUtils;
-import android.view.AppTransitionAnimationSpec;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@@ -86,13 +87,15 @@
import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
import com.android.systemui.recents.misc.ReferenceCountedTrigger;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.TaskStack;
+import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
import com.android.systemui.shared.recents.view.RecentsTransition;
import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.ActivityOptionsCompat;
+import com.android.systemui.shared.system.WindowManagerWrapper;
import com.android.systemui.stackdivider.WindowManagerProxy;
import com.android.systemui.statusbar.FlingAnimationUtils;
import com.android.systemui.statusbar.phone.ScrimController;
@@ -608,16 +611,17 @@
// rect to its final layout-space rect
Utilities.setViewFrameFromTranslation(event.taskView);
- // Dock the task and launch it
- SystemServicesProxy ssp = Recents.getSystemServices();
- if (ssp.startTaskInDockedMode(event.task.key.id, dockState.createMode)) {
+ final ActivityOptions options = ActivityOptionsCompat.makeSplitScreenOptions(
+ dockState.createMode == SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT);
+ if (ActivityManagerWrapper.getInstance().startActivityFromRecents(event.task.key.id,
+ options)) {
final Runnable animStartedListener = () -> {
EventBus.getDefault().send(new DockedFirstAnimationFrameEvent());
- // Remove the task and don't bother relaying out, as all the tasks will be
- // relaid out when the stack changes on the multiwindow change event
+ // Remove the task and don't bother relaying out, as all the tasks
+ // will be relaid out when the stack changes on the multiwindow
+ // change event
getStack().removeTask(event.task, null, true /* fromDockGesture */);
};
-
final Rect taskRect = getTaskRect(event.taskView);
AppTransitionAnimationSpecsFuture future = new AppTransitionAnimationSpecsFuture(
getHandler()) {
@@ -626,10 +630,8 @@
return mTransitionHelper.composeDockAnimationSpec(event.taskView, taskRect);
}
};
- ssp.overridePendingAppTransitionMultiThumbFuture(future.getFuture(),
- RecentsTransition.wrapStartedListener(getHandler(), animStartedListener),
- true /* scaleUp */);
-
+ WindowManagerWrapper.getInstance().overridePendingAppTransitionMultiThumbFuture(
+ future, animStartedListener, getHandler(), true /* scaleUp */);
MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_DRAG_DROP,
event.task.getTopComponent().flattenToShortString());
} else {
@@ -1032,11 +1034,9 @@
if (taskIndex > -1) {
taskIndexFromFront = stack.getTaskCount() - taskIndex - 1;
}
- EventBus.getDefault().send(new LaunchTaskSucceededEvent(
- taskIndexFromFront));
+ EventBus.getDefault().send(new LaunchTaskSucceededEvent(taskIndexFromFront));
} else {
- Log.e(TAG, mContext.getString(R.string.recents_launch_error_message,
- task.title));
+ Log.e(TAG, mContext.getString(R.string.recents_launch_error_message, task.title));
// Dismiss the task if we fail to launch it
if (taskView != null) {
diff --git a/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index 600da04..d9f79bb 100644
--- a/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -431,7 +431,7 @@
* in the stack.
*/
public void update(TaskStack stack, ArraySet<Task.TaskKey> ignoreTasksSet,
- RecentsActivityLaunchState launchState) {
+ RecentsActivityLaunchState launchState, float lastScrollPPercent) {
SystemServicesProxy ssp = Recents.getSystemServices();
// Clear the progress map
@@ -506,6 +506,8 @@
if (launchState.launchedWithAltTab) {
mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP);
+ } else if (0 <= lastScrollPPercent && lastScrollPPercent <= 1) {
+ mInitialScrollP = Utilities.mapRange(lastScrollPPercent, mMinScrollP, mMaxScrollP);
} else if (Recents.getConfiguration().isLowRamDevice) {
mInitialScrollP = mTaskStackLowRamLayoutAlgorithm.getInitialScrollP(mNumStackTasks,
scrollToFront);
diff --git a/com/android/systemui/recents/views/TaskStackView.java b/com/android/systemui/recents/views/TaskStackView.java
index 1197501..36c9095 100644
--- a/com/android/systemui/recents/views/TaskStackView.java
+++ b/com/android/systemui/recents/views/TaskStackView.java
@@ -209,6 +209,9 @@
private int mLastHeight;
private boolean mStackActionButtonVisible;
+ // Percentage of last ScrollP from the min to max scrollP that lives after configuration changes
+ private float mLastScrollPPercent;
+
// We keep track of the task view focused by user interaction and draw a frame around it in the
// grid layout.
private TaskViewFocusFrame mTaskViewFocusFrame;
@@ -327,6 +330,7 @@
mStackScroller.reset();
mStableLayoutAlgorithm.reset();
mLayoutAlgorithm.reset();
+ mLastScrollPPercent = -1;
}
// Since we always animate to the same place in (the initial state), always reset the stack
@@ -822,7 +826,7 @@
public void updateLayoutAlgorithm(boolean boundScrollToNewMinMax,
RecentsActivityLaunchState launchState) {
// Compute the min and max scroll values
- mLayoutAlgorithm.update(mStack, mIgnoreTasks, launchState);
+ mLayoutAlgorithm.update(mStack, mIgnoreTasks, launchState, mLastScrollPPercent);
if (boundScrollToNewMinMax) {
mStackScroller.boundScroll();
@@ -1150,6 +1154,8 @@
if (mTaskViewsClipDirty) {
clipTaskViews();
}
+ mLastScrollPPercent = Utilities.clamp(Utilities.unmapRange(mStackScroller.getStackScroll(),
+ mLayoutAlgorithm.mMinScrollP, mLayoutAlgorithm.mMaxScrollP), 0, 1);
}
/**
diff --git a/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java b/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java
index 8e2a25c..4834bb1 100644
--- a/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java
+++ b/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java
@@ -45,6 +45,11 @@
*/
public class RecentsTaskLoadPlan {
+ /** The set of conditions to preload tasks. */
+ public static class PreloadOptions {
+ public boolean loadTitles = true;
+ }
+
/** The set of conditions to load tasks. */
public static class Options {
public int runningTaskId = -1;
@@ -80,7 +85,8 @@
* Note: Do not lock, since this can be calling back to the loader, which separately also drives
* this call (callers should synchronize on the loader before making this call).
*/
- public void preloadPlan(RecentsTaskLoader loader, int runningTaskId, int currentUserId) {
+ public void preloadPlan(PreloadOptions opts, RecentsTaskLoader loader, int runningTaskId,
+ int currentUserId) {
Resources res = mContext.getResources();
ArrayList<Task> allTasks = new ArrayList<>();
if (mRawTasks == null) {
@@ -110,9 +116,12 @@
}
// Load the title, icon, and color
- String title = loader.getAndUpdateActivityTitle(taskKey, t.taskDescription);
- String titleDescription = loader.getAndUpdateContentDescription(taskKey,
- t.taskDescription);
+ String title = opts.loadTitles
+ ? loader.getAndUpdateActivityTitle(taskKey, t.taskDescription)
+ : "";
+ String titleDescription = opts.loadTitles
+ ? loader.getAndUpdateContentDescription(taskKey, t.taskDescription)
+ : "";
Drawable icon = isStackTask
? loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, res, false)
: null;
diff --git a/com/android/systemui/shared/recents/model/RecentsTaskLoader.java b/com/android/systemui/shared/recents/model/RecentsTaskLoader.java
index 9a991cf..0f68026 100644
--- a/com/android/systemui/shared/recents/model/RecentsTaskLoader.java
+++ b/com/android/systemui/shared/recents/model/RecentsTaskLoader.java
@@ -32,6 +32,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan.Options;
+import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan.PreloadOptions;
import com.android.systemui.shared.recents.model.Task.TaskKey;
import com.android.systemui.shared.recents.model.TaskKeyLruCache.EvictionCallback;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -155,7 +156,7 @@
int currentUserId) {
try {
Trace.beginSection("preloadPlan");
- plan.preloadPlan(this, runningTaskId, currentUserId);
+ plan.preloadPlan(new PreloadOptions(), this, runningTaskId, currentUserId);
} finally {
Trace.endSection();
}
diff --git a/com/android/systemui/shared/recents/utilities/AppTrace.java b/com/android/systemui/shared/recents/utilities/AppTrace.java
new file mode 100644
index 0000000..0241c59
--- /dev/null
+++ b/com/android/systemui/shared/recents/utilities/AppTrace.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2017 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.shared.recents.utilities;
+
+import static android.os.Trace.TRACE_TAG_APP;
+
+/**
+ * Helper class for internal trace functions.
+ */
+public class AppTrace {
+
+ /**
+ * Begins a new async trace section with the given {@param key} and {@param cookie}.
+ */
+ public static void start(String key, int cookie) {
+ android.os.Trace.asyncTraceBegin(TRACE_TAG_APP, key, cookie);
+ }
+
+ /**
+ * Begins a new async trace section with the given {@param key}.
+ */
+ public static void start(String key) {
+ android.os.Trace.asyncTraceBegin(TRACE_TAG_APP, key, 0);
+ }
+
+ /**
+ * Ends an existing async trace section with the given {@param key}.
+ */
+ public static void end(String key) {
+ android.os.Trace.asyncTraceEnd(TRACE_TAG_APP, key, 0);
+ }
+
+ /**
+ * Ends an existing async trace section with the given {@param key} and {@param cookie}.
+ */
+ public static void end(String key, int cookie) {
+ android.os.Trace.asyncTraceEnd(TRACE_TAG_APP, key, cookie);
+ }
+
+ /**
+ * Begins a new trace section with the given {@param key}. Can be nested.
+ */
+ public static void beginSection(String key) {
+ android.os.Trace.beginSection(key);
+ }
+
+ /**
+ * Ends an existing trace section started in the last {@link #beginSection(String)}.
+ */
+ public static void endSection() {
+ android.os.Trace.endSection();
+ }
+
+ /**
+ * Traces a counter value.
+ */
+ public static void count(String name, int count) {
+ android.os.Trace.traceCounter(TRACE_TAG_APP, name, count);
+ }
+}
diff --git a/com/android/systemui/shared/system/ActivityManagerWrapper.java b/com/android/systemui/shared/system/ActivityManagerWrapper.java
index f6fab86..eb2d12e 100644
--- a/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -319,32 +319,39 @@
mBackgroundExecutor.submit(new Runnable() {
@Override
public void run() {
+ boolean result = false;
try {
- ActivityManager.getService().startActivityFromRecents(taskKey.id,
- finalOptions == null ? null : finalOptions.toBundle());
- if (resultCallback != null) {
- resultCallbackHandler.post(new Runnable() {
- @Override
- public void run() {
- resultCallback.accept(true);
- }
- });
- }
+ result = startActivityFromRecents(taskKey.id, finalOptions);
} catch (Exception e) {
- if (resultCallback != null) {
- resultCallbackHandler.post(new Runnable() {
- @Override
- public void run() {
- resultCallback.accept(false);
- }
- });
- }
+ // Fall through
+ }
+ final boolean finalResult = result;
+ if (resultCallback != null) {
+ resultCallbackHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ resultCallback.accept(finalResult);
+ }
+ });
}
}
});
}
/**
+ * Starts a task from Recents synchronously.
+ */
+ public boolean startActivityFromRecents(int taskId, ActivityOptions options) {
+ try {
+ Bundle optsBundle = options == null ? null : options.toBundle();
+ ActivityManager.getService().startActivityFromRecents(taskId, optsBundle);
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ /**
* Registers a task stack listener with the system.
* This should be called on the main thread.
*/
diff --git a/com/android/systemui/shared/system/ActivityOptionsCompat.java b/com/android/systemui/shared/system/ActivityOptionsCompat.java
new file mode 100644
index 0000000..705a215
--- /dev/null
+++ b/com/android/systemui/shared/system/ActivityOptionsCompat.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2017 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.shared.system;
+
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+
+import android.app.ActivityOptions;
+
+/**
+ * Wrapper around internal ActivityOptions creation.
+ */
+public abstract class ActivityOptionsCompat {
+
+ /**
+ * @return ActivityOptions for starting a task in split screen.
+ */
+ public static ActivityOptions makeSplitScreenOptions(boolean dockTopLeft) {
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+ options.setSplitScreenCreateMode(dockTopLeft
+ ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT
+ : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT);
+ return options;
+ }
+}
diff --git a/com/android/systemui/shared/system/BackgroundExecutor.java b/com/android/systemui/shared/system/BackgroundExecutor.java
index cfd1f9a..0bd89a7 100644
--- a/com/android/systemui/shared/system/BackgroundExecutor.java
+++ b/com/android/systemui/shared/system/BackgroundExecutor.java
@@ -16,6 +16,7 @@
package com.android.systemui.shared.system;
+import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
@@ -37,6 +38,13 @@
}
/**
+ * Runs the given {@param callable} on one of the background executor threads.
+ */
+ public <T> Future<T> submit(Callable<T> callable) {
+ return mExecutorService.submit(callable);
+ }
+
+ /**
* Runs the given {@param runnable} on one of the background executor threads.
*/
public Future<?> submit(Runnable runnable) {
diff --git a/com/android/systemui/shared/system/ChoreographerCompat.java b/com/android/systemui/shared/system/ChoreographerCompat.java
new file mode 100644
index 0000000..4d422bb
--- /dev/null
+++ b/com/android/systemui/shared/system/ChoreographerCompat.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 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.shared.system;
+
+import static android.view.Choreographer.CALLBACK_INPUT;
+
+import android.view.Choreographer;
+
+/**
+ * Wraps the internal choreographer.
+ */
+public class ChoreographerCompat {
+
+ /**
+ * Posts an input callback to the choreographer.
+ */
+ public static void postInputFrame(Choreographer choreographer, Runnable runnable) {
+ choreographer.postCallback(CALLBACK_INPUT, runnable, null);
+ }
+}
diff --git a/com/android/systemui/shared/system/GraphicBufferCompat.java b/com/android/systemui/shared/system/GraphicBufferCompat.java
new file mode 100644
index 0000000..66b8fed
--- /dev/null
+++ b/com/android/systemui/shared/system/GraphicBufferCompat.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 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.shared.system;
+
+import android.graphics.Bitmap;
+import android.graphics.GraphicBuffer;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Wraps the internal graphic buffer.
+ */
+public class GraphicBufferCompat implements Parcelable {
+
+ private GraphicBuffer mBuffer;
+
+ public GraphicBufferCompat(GraphicBuffer buffer) {
+ mBuffer = buffer;
+ }
+
+ public GraphicBufferCompat(Parcel in) {
+ mBuffer = GraphicBuffer.CREATOR.createFromParcel(in);
+ }
+
+ public Bitmap toBitmap() {
+ return mBuffer != null
+ ? Bitmap.createHardwareBitmap(mBuffer)
+ : null;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ mBuffer.writeToParcel(dest, flags);
+ }
+
+ public static final Parcelable.Creator<GraphicBufferCompat> CREATOR
+ = new Parcelable.Creator<GraphicBufferCompat>() {
+ public GraphicBufferCompat createFromParcel(Parcel in) {
+ return new GraphicBufferCompat(in);
+ }
+
+ public GraphicBufferCompat[] newArray(int size) {
+ return new GraphicBufferCompat[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/com/android/systemui/shared/system/WindowManagerWrapper.java b/com/android/systemui/shared/system/WindowManagerWrapper.java
index 1477558..225dbb4 100644
--- a/com/android/systemui/shared/system/WindowManagerWrapper.java
+++ b/com/android/systemui/shared/system/WindowManagerWrapper.java
@@ -19,8 +19,15 @@
import static android.view.Display.DEFAULT_DISPLAY;
import android.graphics.Rect;
+import android.os.Handler;
+import android.os.IRemoteCallback;
+import android.os.RemoteException;
+import android.util.Log;
import android.view.WindowManagerGlobal;
+import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
+import com.android.systemui.shared.recents.view.RecentsTransition;
+
public class WindowManagerWrapper {
private static final String TAG = "WindowManagerWrapper";
@@ -38,8 +45,24 @@
try {
WindowManagerGlobal.getWindowManagerService().getStableInsets(DEFAULT_DISPLAY,
outStableInsets);
- } catch (Exception e) {
- e.printStackTrace();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to get stable insets", e);
+ }
+ }
+
+ /**
+ * Overrides a pending app transition.
+ */
+ public void overridePendingAppTransitionMultiThumbFuture(
+ AppTransitionAnimationSpecsFuture animationSpecFuture,
+ Runnable animStartedCallback, Handler animStartedCallbackHandler, boolean scaleUp) {
+ try {
+ WindowManagerGlobal.getWindowManagerService()
+ .overridePendingAppTransitionMultiThumbFuture(animationSpecFuture.getFuture(),
+ RecentsTransition.wrapStartedListener(animStartedCallbackHandler,
+ animStartedCallback), scaleUp);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to override pending app transition (multi-thumbnail future): ", e);
}
}
}
diff --git a/com/android/systemui/shortcut/ShortcutKeyDispatcher.java b/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
index 1cda301..da79884 100644
--- a/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
+++ b/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
@@ -20,6 +20,8 @@
import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
import static android.os.UserHandle.USER_CURRENT;
+import static com.android.systemui.statusbar.phone.NavigationBarGestureHelper.DRAG_MODE_NONE;
+
import android.app.ActivityManager;
import android.content.res.Configuration;
import android.os.RemoteException;
@@ -36,6 +38,7 @@
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.stackdivider.Divider;
import com.android.systemui.stackdivider.DividerView;
+import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
import java.util.List;
@@ -89,20 +92,11 @@
try {
int dockSide = mWindowManagerService.getDockedStackSide();
if (dockSide == WindowManager.DOCKED_INVALID) {
- // If there is no window docked, we dock the top-most window.
+ // Split the screen
Recents recents = getComponent(Recents.class);
- int dockMode = (shortcutCode == SC_DOCK_LEFT)
+ recents.splitPrimaryTask(DRAG_MODE_NONE, (shortcutCode == SC_DOCK_LEFT)
? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT
- : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
- List<ActivityManager.RecentTaskInfo> taskList =
- ActivityManagerWrapper.getInstance().getRecentTasks(1, USER_CURRENT);
- recents.showRecentApps(
- false /* triggeredFromAltTab */,
- false /* fromHome */);
- if (!taskList.isEmpty()) {
- SystemServicesProxy.getInstance(mContext).startTaskInDockedMode(
- taskList.get(0).id, dockMode);
- }
+ : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT, null, -1);
} else {
// If there is already a docked window, we respond by resizing the docking pane.
DividerView dividerView = getComponent(Divider.class).getView();
diff --git a/com/android/systemui/statusbar/ActivatableNotificationView.java b/com/android/systemui/statusbar/ActivatableNotificationView.java
index 84b7015..ff0357a 100644
--- a/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -695,6 +695,11 @@
mBackgroundNormal.getVisibility() == View.VISIBLE ? 1.0f : 0.0f);
}
+ protected void updateBackgroundClipping() {
+ mBackgroundNormal.setBottomAmountClips(!isChildInGroup());
+ mBackgroundDimmed.setBottomAmountClips(!isChildInGroup());
+ }
+
protected boolean shouldHideBackground() {
return mDark;
}
@@ -901,12 +906,45 @@
contentView.setAlpha(contentAlpha);
}
+ @Override
+ protected void applyRoundness() {
+ super.applyRoundness();
+ applyBackgroundRoundness(getCurrentBackgroundRadiusTop(),
+ getCurrentBackgroundRadiusBottom());
+ }
+
+ protected void applyBackgroundRoundness(float topRadius, float bottomRadius) {
+ mBackgroundDimmed.setRoundness(topRadius, bottomRadius);
+ mBackgroundNormal.setRoundness(topRadius, bottomRadius);
+ }
+
+ @Override
+ protected void setBackgroundTop(int backgroundTop) {
+ mBackgroundDimmed.setBackgroundTop(backgroundTop);
+ mBackgroundNormal.setBackgroundTop(backgroundTop);
+ }
+
protected abstract View getContentView();
public int calculateBgColor() {
return calculateBgColor(true /* withTint */, true /* withOverRide */);
}
+ @Override
+ public void setCurrentSidePaddings(float currentSidePaddings) {
+ super.setCurrentSidePaddings(currentSidePaddings);
+ mBackgroundNormal.setCurrentSidePaddings(currentSidePaddings);
+ mBackgroundDimmed.setCurrentSidePaddings(currentSidePaddings);
+ }
+
+ @Override
+ protected boolean childNeedsClipping(View child) {
+ if (child instanceof NotificationBackgroundView && isClippingNeeded()) {
+ return true;
+ }
+ return super.childNeedsClipping(child);
+ }
+
/**
* @param withTint should a possible tint be factored in?
* @param withOverRide should the value be interpolated with {@link #mOverrideTint}
diff --git a/com/android/systemui/statusbar/CommandQueue.java b/com/android/systemui/statusbar/CommandQueue.java
index 6349275..8e1b104 100644
--- a/com/android/systemui/statusbar/CommandQueue.java
+++ b/com/android/systemui/statusbar/CommandQueue.java
@@ -82,6 +82,7 @@
private static final int MSG_TOGGLE_PANEL = 35 << MSG_SHIFT;
private static final int MSG_SHOW_SHUTDOWN_UI = 36 << MSG_SHIFT;
private static final int MSG_SET_TOP_APP_HIDES_STATUS_BAR = 37 << MSG_SHIFT;
+ private static final int MSG_ROTATION_PROPOSAL = 38 << MSG_SHIFT;
public static final int FLAG_EXCLUDE_NONE = 0;
public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -142,6 +143,8 @@
default void handleSystemKey(int arg1) { }
default void handleShowGlobalActionsMenu() { }
default void handleShowShutdownUi(boolean isReboot, String reason) { }
+
+ default void onRotationProposal(int rotation) { }
}
@VisibleForTesting
@@ -458,6 +461,15 @@
}
}
+ @Override
+ public void onProposedRotationChanged(int rotation) {
+ synchronized (mLock) {
+ mHandler.removeMessages(MSG_ROTATION_PROPOSAL);
+ mHandler.obtainMessage(MSG_ROTATION_PROPOSAL, rotation, 0,
+ null).sendToTarget();
+ }
+ }
+
private final class H extends Handler {
private H(Looper l) {
super(l);
@@ -654,6 +666,11 @@
mCallbacks.get(i).setTopAppHidesStatusBar(msg.arg1 != 0);
}
break;
+ case MSG_ROTATION_PROPOSAL:
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).onRotationProposal(msg.arg1);
+ }
+ break;
}
}
}
diff --git a/com/android/systemui/statusbar/ExpandableNotificationRow.java b/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 8ff950e..23d9cae 100644
--- a/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -26,6 +26,7 @@
import android.content.Context;
import android.content.res.Resources;
import android.content.res.Configuration;
+import android.graphics.Path;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.ColorDrawable;
@@ -65,7 +66,6 @@
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
import com.android.systemui.statusbar.NotificationGuts.GutsContent;
import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
-import com.android.systemui.statusbar.notification.AboveShelfObserver;
import com.android.systemui.statusbar.notification.HybridNotificationView;
import com.android.systemui.statusbar.notification.NotificationInflater;
import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -102,7 +102,9 @@
private int mIconTransformContentShift;
private int mIconTransformContentShiftNoIcon;
private int mNotificationMinHeightLegacy;
+ private int mNotificationMinHeightBeforeP;
private int mMaxHeadsUpHeightLegacy;
+ private int mMaxHeadsUpHeightBeforeP;
private int mMaxHeadsUpHeight;
private int mMaxHeadsUpHeightIncreased;
private int mNotificationMinHeight;
@@ -435,9 +437,10 @@
boolean customView = layout.getContractedChild().getId()
!= com.android.internal.R.id.status_bar_latest_event_content;
boolean beforeN = mEntry.targetSdk < Build.VERSION_CODES.N;
+ boolean beforeP = mEntry.targetSdk < Build.VERSION_CODES.P;
int minHeight;
- if (customView && beforeN && !mIsSummaryWithChildren) {
- minHeight = mNotificationMinHeightLegacy;
+ if (customView && beforeP && !mIsSummaryWithChildren) {
+ minHeight = beforeN ? mNotificationMinHeightLegacy : mNotificationMinHeightBeforeP;
} else if (mUseIncreasedCollapsedHeight && layout == mPrivateLayout) {
minHeight = mNotificationMinHeightLarge;
} else {
@@ -447,8 +450,8 @@
layout.getHeadsUpChild().getId()
!= com.android.internal.R.id.status_bar_latest_event_content;
int headsUpheight;
- if (headsUpCustom && beforeN) {
- headsUpheight = mMaxHeadsUpHeightLegacy;
+ if (headsUpCustom && beforeP) {
+ headsUpheight = beforeN ? mMaxHeadsUpHeightLegacy : mMaxHeadsUpHeightBeforeP;
} else if (mUseIncreasedHeadsUpHeight && layout == mPrivateLayout) {
headsUpheight = mMaxHeadsUpHeightIncreased;
} else {
@@ -535,6 +538,7 @@
}
onChildrenCountChanged();
row.setIsChildInGroup(false, null);
+ row.setBottomRoundness(0.0f, false /* animate */);
}
@Override
@@ -563,6 +567,7 @@
mNotificationParent.updateBackgroundForGroupState();
}
updateIconVisibilities();
+ updateBackgroundClipping();
}
@Override
@@ -916,6 +921,7 @@
addView(mMenuRow.getMenuView(), menuIndex);
}
for (NotificationContentView l : mLayouts) {
+ l.initView();
l.reInflateViews();
}
mNotificationInflater.onDensityOrFontScaleChanged();
@@ -1025,6 +1031,7 @@
mKeepInParent = keepInParent;
}
+ @Override
public boolean isRemoved() {
return mRemoved;
}
@@ -1264,6 +1271,8 @@
private void initDimens() {
mNotificationMinHeightLegacy = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_min_height_legacy);
+ mNotificationMinHeightBeforeP = NotificationUtils.getFontScaledHeight(mContext,
+ R.dimen.notification_min_height_before_p);
mNotificationMinHeight = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_min_height);
mNotificationMinHeightLarge = NotificationUtils.getFontScaledHeight(mContext,
@@ -1274,6 +1283,8 @@
R.dimen.notification_ambient_height);
mMaxHeadsUpHeightLegacy = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_max_heads_up_height_legacy);
+ mMaxHeadsUpHeightBeforeP = NotificationUtils.getFontScaledHeight(mContext,
+ R.dimen.notification_max_heads_up_height_before_p);
mMaxHeadsUpHeight = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_max_heads_up_height);
mMaxHeadsUpHeightIncreased = NotificationUtils.getFontScaledHeight(mContext,
@@ -1752,6 +1763,7 @@
mPrivateLayout.updateExpandButtons(isExpandable());
updateChildrenHeaderAppearance();
updateChildrenVisibility();
+ applyChildrenRoundness();
}
public void updateChildrenHeaderAppearance() {
@@ -2332,6 +2344,56 @@
}
}
+ @Override
+ protected boolean childNeedsClipping(View child) {
+ if (child instanceof NotificationContentView) {
+ NotificationContentView contentView = (NotificationContentView) child;
+ if (isClippingNeeded()) {
+ return true;
+ } else if (!hasNoRoundingAndNoPadding() && contentView.shouldClipToSidePaddings()) {
+ return true;
+ }
+ } else if (child == mChildrenContainer) {
+ if (isClippingNeeded() || ((isGroupExpanded() || isGroupExpansionChanging())
+ && getClipBottomAmount() != 0.0f && getCurrentBottomRoundness() != 0.0f)) {
+ return true;
+ }
+ } else if (child instanceof NotificationGuts) {
+ return !hasNoRoundingAndNoPadding();
+ }
+ return super.childNeedsClipping(child);
+ }
+
+ @Override
+ protected void applyRoundness() {
+ super.applyRoundness();
+ applyChildrenRoundness();
+ }
+
+ private void applyChildrenRoundness() {
+ if (mIsSummaryWithChildren) {
+ mChildrenContainer.setCurrentBottomRoundness(getCurrentBottomRoundness());
+ }
+ }
+
+ @Override
+ public Path getCustomClipPath(View child) {
+ if (child instanceof NotificationGuts) {
+ return getClipPath(true, /* ignoreTranslation */
+ false /* clipRoundedToBottom */);
+ }
+ if (child instanceof NotificationChildrenContainer) {
+ return getClipPath(false, /* ignoreTranslation */
+ true /* clipRoundedToBottom */);
+ }
+ return super.getCustomClipPath(child);
+ }
+
+ private boolean hasNoRoundingAndNoPadding() {
+ return mCurrentSidePaddings == 0 && getCurrentBottomRoundness() == 0.0f
+ && getCurrentTopRoundness() == 0.0f;
+ }
+
public boolean isShowingAmbient() {
return mShowAmbient;
}
@@ -2344,6 +2406,20 @@
}
}
+ @Override
+ public void setCurrentSidePaddings(float currentSidePaddings) {
+ if (mIsSummaryWithChildren) {
+ List<ExpandableNotificationRow> notificationChildren =
+ mChildrenContainer.getNotificationChildren();
+ int size = notificationChildren.size();
+ for (int i = 0; i < size; i++) {
+ ExpandableNotificationRow row = notificationChildren.get(i);
+ row.setCurrentSidePaddings(currentSidePaddings);
+ }
+ }
+ super.setCurrentSidePaddings(currentSidePaddings);
+ }
+
public static class NotificationViewState extends ExpandableViewState {
private final StackScrollState mOverallState;
diff --git a/com/android/systemui/statusbar/ExpandableOutlineView.java b/com/android/systemui/statusbar/ExpandableOutlineView.java
index 2556890..b3d6e32 100644
--- a/com/android/systemui/statusbar/ExpandableOutlineView.java
+++ b/com/android/systemui/statusbar/ExpandableOutlineView.java
@@ -18,23 +18,58 @@
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Canvas;
import android.graphics.Outline;
+import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewOutlineProvider;
+
+import com.android.settingslib.Utils;
import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
+import com.android.systemui.statusbar.notification.PropertyAnimator;
+import com.android.systemui.statusbar.stack.AnimationProperties;
+import com.android.systemui.statusbar.stack.StackStateAnimator;
/**
* Like {@link ExpandableView}, but setting an outline for the height and clipping.
*/
public abstract class ExpandableOutlineView extends ExpandableView {
+ private static final AnimatableProperty TOP_ROUNDNESS = AnimatableProperty.from(
+ "topRoundness",
+ ExpandableOutlineView::setTopRoundnessInternal,
+ ExpandableOutlineView::getCurrentTopRoundness,
+ R.id.top_roundess_animator_tag,
+ R.id.top_roundess_animator_end_tag,
+ R.id.top_roundess_animator_start_tag);
+ private static final AnimatableProperty BOTTOM_ROUNDNESS = AnimatableProperty.from(
+ "bottomRoundness",
+ ExpandableOutlineView::setBottomRoundnessInternal,
+ ExpandableOutlineView::getCurrentBottomRoundness,
+ R.id.bottom_roundess_animator_tag,
+ R.id.bottom_roundess_animator_end_tag,
+ R.id.bottom_roundess_animator_start_tag);
+ private static final AnimationProperties ROUNDNESS_PROPERTIES =
+ new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+ private static final Path EMPTY_PATH = new Path();
+
private final Rect mOutlineRect = new Rect();
private boolean mCustomOutline;
private float mOutlineAlpha = -1f;
private float mOutlineRadius;
+ private boolean mAlwaysRoundBothCorners;
+ private Path mTmpPath = new Path();
+ private Path mTmpPath2 = new Path();
+ private float mCurrentBottomRoundness;
+ private float mCurrentTopRoundness;
+ private float mBottomRoundness;
+ private float mTopRoundness;
+ private int mBackgroundTop;
+ protected int mCurrentSidePaddings;
/**
* {@code true} if the children views of the {@link ExpandableOutlineView} are translated when
@@ -45,61 +80,248 @@
private final ViewOutlineProvider mProvider = new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
- int translation = mShouldTranslateContents ? (int) getTranslation() : 0;
- if (!mCustomOutline) {
- outline.setRoundRect(translation,
- mClipTopAmount,
- getWidth() + translation,
- Math.max(getActualHeight() - mClipBottomAmount, mClipTopAmount),
- mOutlineRadius);
- } else {
- outline.setRoundRect(mOutlineRect, mOutlineRadius);
+ Path clipPath = getClipPath();
+ if (clipPath != null && clipPath.isConvex()) {
+ // The path might not be convex in border cases where the view is small and clipped
+ outline.setConvexPath(clipPath);
}
outline.setAlpha(mOutlineAlpha);
}
};
+ private Path getClipPath() {
+ return getClipPath(false, /* ignoreTranslation */
+ false /* clipRoundedToBottom */);
+ }
+
+ protected Path getClipPath(boolean ignoreTranslation, boolean clipRoundedToBottom) {
+ int left;
+ int top;
+ int right;
+ int bottom;
+ int height;
+ Path intersectPath = null;
+ if (!mCustomOutline) {
+ int translation = mShouldTranslateContents && !ignoreTranslation
+ ? (int) getTranslation() : 0;
+ left = Math.max(translation + mCurrentSidePaddings, mCurrentSidePaddings);
+ top = mClipTopAmount + mBackgroundTop;
+ right = getWidth() - mCurrentSidePaddings + Math.min(translation, 0);
+ bottom = Math.max(getActualHeight(), top);
+ int intersectBottom = Math.max(getActualHeight() - mClipBottomAmount, top);
+ if (bottom != intersectBottom) {
+ if (clipRoundedToBottom) {
+ bottom = intersectBottom;
+ } else {
+ getRoundedRectPath(left, top, right,
+ intersectBottom, 0.0f,
+ 0.0f, mTmpPath2);
+ intersectPath = mTmpPath2;
+ }
+ }
+ } else {
+ left = mOutlineRect.left;
+ top = mOutlineRect.top;
+ right = mOutlineRect.right;
+ bottom = mOutlineRect.bottom;
+ left = Math.max(mCurrentSidePaddings, left);
+ right = Math.min(getWidth() - mCurrentSidePaddings, right);
+ }
+ height = bottom - top;
+ if (height == 0) {
+ return EMPTY_PATH;
+ }
+ float topRoundness = mAlwaysRoundBothCorners
+ ? mOutlineRadius : mCurrentTopRoundness * mOutlineRadius;
+ float bottomRoundness = mAlwaysRoundBothCorners
+ ? mOutlineRadius : mCurrentBottomRoundness * mOutlineRadius;
+ if (topRoundness + bottomRoundness > height) {
+ float overShoot = topRoundness + bottomRoundness - height;
+ topRoundness -= overShoot * mCurrentTopRoundness
+ / (mCurrentTopRoundness + mCurrentBottomRoundness);
+ bottomRoundness -= overShoot * mCurrentBottomRoundness
+ / (mCurrentTopRoundness + mCurrentBottomRoundness);
+ }
+ getRoundedRectPath(left, top, right, bottom, topRoundness,
+ bottomRoundness, mTmpPath);
+ Path roundedRectPath = mTmpPath;
+ if (intersectPath != null) {
+ roundedRectPath.op(intersectPath, Path.Op.INTERSECT);
+ }
+ return roundedRectPath;
+ }
+
+ protected Path getRoundedRectPath(int left, int top, int right, int bottom, float topRoundness,
+ float bottomRoundness) {
+ getRoundedRectPath(left, top, right, bottom, topRoundness, bottomRoundness,
+ mTmpPath);
+ return mTmpPath;
+ }
+
+ private void getRoundedRectPath(int left, int top, int right, int bottom, float topRoundness,
+ float bottomRoundness, Path outPath) {
+ outPath.reset();
+ int width = right - left;
+ float topRoundnessX = topRoundness;
+ float bottomRoundnessX = bottomRoundness;
+ topRoundnessX = Math.min(width / 2, topRoundnessX);
+ bottomRoundnessX = Math.min(width / 2, bottomRoundnessX);
+ if (topRoundness > 0.0f) {
+ outPath.moveTo(left, top + topRoundness);
+ outPath.quadTo(left, top, left + topRoundnessX, top);
+ outPath.lineTo(right - topRoundnessX, top);
+ outPath.quadTo(right, top, right, top + topRoundness);
+ } else {
+ outPath.moveTo(left, top);
+ outPath.lineTo(right, top);
+ }
+ if (bottomRoundness > 0.0f) {
+ outPath.lineTo(right, bottom - bottomRoundness);
+ outPath.quadTo(right, bottom, right - bottomRoundnessX, bottom);
+ outPath.lineTo(left + bottomRoundnessX, bottom);
+ outPath.quadTo(left, bottom, left, bottom - bottomRoundness);
+ } else {
+ outPath.lineTo(right, bottom);
+ outPath.lineTo(left, bottom);
+ }
+ outPath.close();
+ }
+
public ExpandableOutlineView(Context context, AttributeSet attrs) {
super(context, attrs);
setOutlineProvider(mProvider);
initDimens();
}
+ @Override
+ protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+ canvas.save();
+ if (childNeedsClipping(child)) {
+ Path clipPath = getCustomClipPath(child);
+ if (clipPath == null) {
+ clipPath = getClipPath();
+ }
+ if (clipPath != null) {
+ canvas.clipPath(clipPath);
+ }
+ }
+ boolean result = super.drawChild(canvas, child, drawingTime);
+ canvas.restore();
+ return result;
+ }
+
+ protected boolean childNeedsClipping(View child) {
+ return false;
+ }
+
+ protected boolean isClippingNeeded() {
+ return mAlwaysRoundBothCorners || mCustomOutline || getTranslation() != 0 ;
+
+ }
+
private void initDimens() {
Resources res = getResources();
mShouldTranslateContents =
res.getBoolean(R.bool.config_translateNotificationContentsOnSwipe);
mOutlineRadius = res.getDimension(R.dimen.notification_shadow_radius);
- setClipToOutline(res.getBoolean(R.bool.config_clipNotificationsToOutline));
+ mAlwaysRoundBothCorners = res.getBoolean(R.bool.config_clipNotificationsToOutline);
+ if (!mAlwaysRoundBothCorners) {
+ mOutlineRadius = res.getDimensionPixelSize(
+ Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius));
+ }
+ setClipToOutline(mAlwaysRoundBothCorners);
+ }
+
+ public void setTopRoundness(float topRoundness, boolean animate) {
+ if (mTopRoundness != topRoundness) {
+ mTopRoundness = topRoundness;
+ PropertyAnimator.setProperty(this, TOP_ROUNDNESS, topRoundness,
+ ROUNDNESS_PROPERTIES, animate);
+ }
+ }
+
+ protected void applyRoundness() {
+ invalidateOutline();
+ invalidate();
+ }
+
+ public float getCurrentBackgroundRadiusTop() {
+ return mCurrentTopRoundness * mOutlineRadius;
+ }
+
+ public float getCurrentTopRoundness() {
+ return mCurrentTopRoundness;
+ }
+
+ public float getCurrentBottomRoundness() {
+ return mCurrentBottomRoundness;
+ }
+
+ protected float getCurrentBackgroundRadiusBottom() {
+ return mCurrentBottomRoundness * mOutlineRadius;
+ }
+
+ public void setBottomRoundness(float bottomRoundness, boolean animate) {
+ if (mBottomRoundness != bottomRoundness) {
+ mBottomRoundness = bottomRoundness;
+ PropertyAnimator.setProperty(this, BOTTOM_ROUNDNESS, bottomRoundness,
+ ROUNDNESS_PROPERTIES, animate);
+ }
+ }
+
+ protected void setBackgroundTop(int backgroundTop) {
+ if (mBackgroundTop != backgroundTop) {
+ mBackgroundTop = backgroundTop;
+ invalidateOutline();
+ }
+ }
+
+ private void setTopRoundnessInternal(float topRoundness) {
+ mCurrentTopRoundness = topRoundness;
+ applyRoundness();
+ }
+
+ private void setBottomRoundnessInternal(float bottomRoundness) {
+ mCurrentBottomRoundness = bottomRoundness;
+ applyRoundness();
}
public void onDensityOrFontScaleChanged() {
initDimens();
- invalidateOutline();
+ applyRoundness();
}
@Override
public void setActualHeight(int actualHeight, boolean notifyListeners) {
+ int previousHeight = getActualHeight();
super.setActualHeight(actualHeight, notifyListeners);
- invalidateOutline();
+ if (previousHeight != actualHeight) {
+ applyRoundness();
+ }
}
@Override
public void setClipTopAmount(int clipTopAmount) {
+ int previousAmount = getClipTopAmount();
super.setClipTopAmount(clipTopAmount);
- invalidateOutline();
+ if (previousAmount != clipTopAmount) {
+ applyRoundness();
+ }
}
@Override
public void setClipBottomAmount(int clipBottomAmount) {
+ int previousAmount = getClipBottomAmount();
super.setClipBottomAmount(clipBottomAmount);
- invalidateOutline();
+ if (previousAmount != clipBottomAmount) {
+ applyRoundness();
+ }
}
protected void setOutlineAlpha(float alpha) {
if (alpha != mOutlineAlpha) {
mOutlineAlpha = alpha;
- invalidateOutline();
+ applyRoundness();
}
}
@@ -113,8 +335,7 @@
setOutlineRect(rect.left, rect.top, rect.right, rect.bottom);
} else {
mCustomOutline = false;
- setClipToOutline(false);
- invalidateOutline();
+ applyRoundness();
}
}
@@ -151,15 +372,22 @@
protected void setOutlineRect(float left, float top, float right, float bottom) {
mCustomOutline = true;
- setClipToOutline(true);
mOutlineRect.set((int) left, (int) top, (int) right, (int) bottom);
// Outlines need to be at least 1 dp
mOutlineRect.bottom = (int) Math.max(top, mOutlineRect.bottom);
mOutlineRect.right = (int) Math.max(left, mOutlineRect.right);
-
- invalidateOutline();
+ applyRoundness();
}
+ public Path getCustomClipPath(View child) {
+ return null;
+ }
+
+ public void setCurrentSidePaddings(float currentSidePaddings) {
+ mCurrentSidePaddings = (int) currentSidePaddings;
+ invalidateOutline();
+ invalidate();
+ }
}
diff --git a/com/android/systemui/statusbar/ExpandableView.java b/com/android/systemui/statusbar/ExpandableView.java
index aac9af8..18b9860 100644
--- a/com/android/systemui/statusbar/ExpandableView.java
+++ b/com/android/systemui/statusbar/ExpandableView.java
@@ -202,6 +202,10 @@
return mDark;
}
+ public boolean isRemoved() {
+ return false;
+ }
+
/**
* See {@link #setHideSensitive}. This is a variant which notifies this view in advance about
* the upcoming state of hiding sensitive notifications. It gets called at the very beginning
diff --git a/com/android/systemui/statusbar/NotificationBackgroundView.java b/com/android/systemui/statusbar/NotificationBackgroundView.java
index 81a99bc..68cf51c 100644
--- a/com/android/systemui/statusbar/NotificationBackgroundView.java
+++ b/com/android/systemui/statusbar/NotificationBackgroundView.java
@@ -19,37 +19,57 @@
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Canvas;
-import android.graphics.ColorFilter;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.RippleDrawable;
import android.util.AttributeSet;
import android.view.View;
+import com.android.systemui.R;
+
/**
* A view that can be used for both the dimmed and normal background of an notification.
*/
public class NotificationBackgroundView extends View {
+ private final boolean mDontModifyCorners;
private Drawable mBackground;
private int mClipTopAmount;
private int mActualHeight;
private int mClipBottomAmount;
private int mTintColor;
+ private float[] mCornerRadii = new float[8];
+ private int mCurrentSidePaddings;
+ private boolean mBottomIsRounded;
+ private int mBackgroundTop;
+ private boolean mBottomAmountClips = true;
public NotificationBackgroundView(Context context, AttributeSet attrs) {
super(context, attrs);
+ mDontModifyCorners = getResources().getBoolean(
+ R.bool.config_clipNotificationsToOutline);
}
@Override
protected void onDraw(Canvas canvas) {
- draw(canvas, mBackground);
+ if (mClipTopAmount + mClipBottomAmount < mActualHeight - mBackgroundTop) {
+ canvas.save();
+ canvas.clipRect(0, mClipTopAmount, getWidth(), mActualHeight - mClipBottomAmount);
+ draw(canvas, mBackground);
+ canvas.restore();
+ }
}
private void draw(Canvas canvas, Drawable drawable) {
- int bottom = mActualHeight - mClipBottomAmount;
- if (drawable != null && bottom > mClipTopAmount) {
- drawable.setBounds(0, mClipTopAmount, getWidth(), bottom);
+ if (drawable != null) {
+ int bottom = mActualHeight;
+ if (mBottomIsRounded && mBottomAmountClips) {
+ bottom -= mClipBottomAmount;
+ }
+ drawable.setBounds(mCurrentSidePaddings, mBackgroundTop,
+ getWidth() - mCurrentSidePaddings, bottom);
drawable.draw(canvas);
}
}
@@ -87,6 +107,7 @@
unscheduleDrawable(mBackground);
}
mBackground = background;
+ mBackground.mutate();
if (mBackground != null) {
mBackground.setCallback(this);
setTint(mTintColor);
@@ -94,6 +115,7 @@
if (mBackground instanceof RippleDrawable) {
((RippleDrawable) mBackground).setForceSoftware(true);
}
+ updateBackgroundRadii();
invalidate();
}
@@ -152,4 +174,45 @@
public void setDrawableAlpha(int drawableAlpha) {
mBackground.setAlpha(drawableAlpha);
}
+
+ public void setRoundness(float topRoundness, float bottomRoundNess) {
+ mBottomIsRounded = bottomRoundNess != 0.0f;
+ mCornerRadii[0] = topRoundness;
+ mCornerRadii[1] = topRoundness;
+ mCornerRadii[2] = topRoundness;
+ mCornerRadii[3] = topRoundness;
+ mCornerRadii[4] = bottomRoundNess;
+ mCornerRadii[5] = bottomRoundNess;
+ mCornerRadii[6] = bottomRoundNess;
+ mCornerRadii[7] = bottomRoundNess;
+ updateBackgroundRadii();
+ }
+
+ public void setBottomAmountClips(boolean clips) {
+ if (clips != mBottomAmountClips) {
+ mBottomAmountClips = clips;
+ invalidate();
+ }
+ }
+
+ private void updateBackgroundRadii() {
+ if (mDontModifyCorners) {
+ return;
+ }
+ if (mBackground instanceof LayerDrawable) {
+ GradientDrawable gradientDrawable =
+ (GradientDrawable) ((LayerDrawable) mBackground).getDrawable(0);
+ gradientDrawable.setCornerRadii(mCornerRadii);
+ }
+ }
+
+ public void setCurrentSidePaddings(float currentSidePaddings) {
+ mCurrentSidePaddings = (int) currentSidePaddings;
+ invalidate();
+ }
+
+ public void setBackgroundTop(int backgroundTop) {
+ mBackgroundTop = backgroundTop;
+ invalidate();
+ }
}
diff --git a/com/android/systemui/statusbar/NotificationContentView.java b/com/android/systemui/statusbar/NotificationContentView.java
index 9e059c8..39c2131 100644
--- a/com/android/systemui/statusbar/NotificationContentView.java
+++ b/com/android/systemui/statusbar/NotificationContentView.java
@@ -24,6 +24,7 @@
import android.os.Build;
import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.NotificationHeaderView;
import android.view.View;
import android.view.ViewGroup;
@@ -34,8 +35,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.NotificationColorUtil;
import com.android.systemui.R;
-import com.android.systemui.statusbar.notification.HybridNotificationView;
import com.android.systemui.statusbar.notification.HybridGroupManager;
+import com.android.systemui.statusbar.notification.HybridNotificationView;
import com.android.systemui.statusbar.notification.NotificationCustomViewWrapper;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.NotificationViewWrapper;
@@ -49,6 +50,7 @@
*/
public class NotificationContentView extends FrameLayout {
+ private static final String TAG = "NotificationContentView";
public static final int VISIBLE_TYPE_CONTRACTED = 0;
public static final int VISIBLE_TYPE_EXPANDED = 1;
public static final int VISIBLE_TYPE_HEADSUP = 2;
@@ -58,9 +60,9 @@
public static final int UNDEFINED = -1;
private final Rect mClipBounds = new Rect();
- private final int mMinContractedHeight;
- private final int mNotificationContentMarginEnd;
+ private int mMinContractedHeight;
+ private int mNotificationContentMarginEnd;
private View mContractedChild;
private View mExpandedChild;
private View mHeadsUpChild;
@@ -134,15 +136,22 @@
private int mClipBottomAmount;
private boolean mIsLowPriority;
private boolean mIsContentExpandable;
+ private int mCustomViewSidePaddings;
public NotificationContentView(Context context, AttributeSet attrs) {
super(context, attrs);
mHybridGroupManager = new HybridGroupManager(getContext(), this);
+ initView();
+ }
+
+ public void initView() {
mMinContractedHeight = getResources().getDimensionPixelSize(
R.dimen.min_notification_layout_height);
mNotificationContentMarginEnd = getResources().getDimensionPixelSize(
com.android.internal.R.dimen.notification_content_margin_end);
+ mCustomViewSidePaddings = getResources().getDimensionPixelSize(
+ R.dimen.notification_content_custom_view_side_padding);
}
public void setHeights(int smallHeight, int headsUpMaxHeight, int maxHeight,
@@ -178,7 +187,7 @@
: MeasureSpec.makeMeasureSpec(size, useExactly
? MeasureSpec.EXACTLY
: MeasureSpec.AT_MOST);
- mExpandedChild.measure(widthMeasureSpec, spec);
+ measureChildWithMargins(mExpandedChild, widthMeasureSpec, 0, spec, 0);
maxChildHeight = Math.max(maxChildHeight, mExpandedChild.getMeasuredHeight());
}
if (mContractedChild != null) {
@@ -196,22 +205,22 @@
} else {
heightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST);
}
- mContractedChild.measure(widthMeasureSpec, heightSpec);
+ measureChildWithMargins(mContractedChild, widthMeasureSpec, 0, heightSpec, 0);
int measuredHeight = mContractedChild.getMeasuredHeight();
if (measuredHeight < mMinContractedHeight) {
heightSpec = MeasureSpec.makeMeasureSpec(mMinContractedHeight, MeasureSpec.EXACTLY);
- mContractedChild.measure(widthMeasureSpec, heightSpec);
+ measureChildWithMargins(mContractedChild, widthMeasureSpec, 0, heightSpec, 0);
}
maxChildHeight = Math.max(maxChildHeight, measuredHeight);
if (updateContractedHeaderWidth()) {
- mContractedChild.measure(widthMeasureSpec, heightSpec);
+ measureChildWithMargins(mContractedChild, widthMeasureSpec, 0, heightSpec, 0);
}
if (mExpandedChild != null
&& mContractedChild.getMeasuredHeight() > mExpandedChild.getMeasuredHeight()) {
// the Expanded child is smaller then the collapsed. Let's remeasure it.
heightSpec = MeasureSpec.makeMeasureSpec(mContractedChild.getMeasuredHeight(),
MeasureSpec.EXACTLY);
- mExpandedChild.measure(widthMeasureSpec, heightSpec);
+ measureChildWithMargins(mExpandedChild, widthMeasureSpec, 0, heightSpec, 0);
}
}
if (mHeadsUpChild != null) {
@@ -223,9 +232,9 @@
size = Math.min(size, layoutParams.height);
useExactly = true;
}
- mHeadsUpChild.measure(widthMeasureSpec,
+ measureChildWithMargins(mHeadsUpChild, widthMeasureSpec, 0,
MeasureSpec.makeMeasureSpec(size, useExactly ? MeasureSpec.EXACTLY
- : MeasureSpec.AT_MOST));
+ : MeasureSpec.AT_MOST), 0);
maxChildHeight = Math.max(maxChildHeight, mHeadsUpChild.getMeasuredHeight());
}
if (mSingleLineView != null) {
@@ -382,6 +391,38 @@
mContractedWrapper = NotificationViewWrapper.wrap(getContext(), child,
mContainingNotification);
mContractedWrapper.setDark(mDark, false /* animate */, 0 /* delay */);
+ updateMargins(child);
+ }
+
+ private void updateMargins(View child) {
+ if (child == null) {
+ return;
+ }
+ NotificationViewWrapper wrapper = getWrapperForView(child);
+ boolean isCustomView = wrapper instanceof NotificationCustomViewWrapper;
+ boolean needsMargins = isCustomView &&
+ child.getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.P;
+ int padding = needsMargins ? mCustomViewSidePaddings : 0;
+ MarginLayoutParams layoutParams = (MarginLayoutParams) child.getLayoutParams();
+ layoutParams.setMarginStart(padding);
+ layoutParams.setMarginEnd(padding);
+ child.setLayoutParams(layoutParams);
+ }
+
+ private NotificationViewWrapper getWrapperForView(View child) {
+ if (child == mContractedChild) {
+ return mContractedWrapper;
+ }
+ if (child == mExpandedChild) {
+ return mExpandedWrapper;
+ }
+ if (child == mHeadsUpChild) {
+ return mHeadsUpWrapper;
+ }
+ if (child == mAmbientChild) {
+ return mAmbientWrapper;
+ }
+ return null;
}
public void setExpandedChild(View child) {
@@ -415,6 +456,7 @@
mExpandedChild = child;
mExpandedWrapper = NotificationViewWrapper.wrap(getContext(), child,
mContainingNotification);
+ updateMargins(child);
}
public void setHeadsUpChild(View child) {
@@ -448,6 +490,7 @@
mHeadsUpChild = child;
mHeadsUpWrapper = NotificationViewWrapper.wrap(getContext(), child,
mContainingNotification);
+ updateMargins(child);
}
public void setAmbientChild(View child) {
@@ -643,6 +686,13 @@
int endHeight = getViewForVisibleType(mVisibleType).getHeight();
int progress = Math.abs(mContentHeight - startHeight);
int totalDistance = Math.abs(endHeight - startHeight);
+ if (totalDistance == 0) {
+ Log.wtf(TAG, "the total transformation distance is 0"
+ + "\n StartType: " + mTransformationStartVisibleType + " height: " + startHeight
+ + "\n VisibleType: " + mVisibleType + " height: " + endHeight
+ + "\n mContentHeight: " + mContentHeight);
+ return 1.0f;
+ }
float amount = (float) progress / (float) totalDistance;
return Math.min(1.0f, amount);
}
@@ -1459,4 +1509,20 @@
}
return false;
}
+
+ public boolean shouldClipToSidePaddings() {
+ boolean needsPaddings = shouldClipToSidePaddings(getVisibleType());
+ if (mUserExpanding) {
+ needsPaddings |= shouldClipToSidePaddings(mTransformationStartVisibleType);
+ }
+ return needsPaddings;
+ }
+
+ private boolean shouldClipToSidePaddings(int visibleType) {
+ NotificationViewWrapper visibleWrapper = getVisibleWrapper(visibleType);
+ if (visibleWrapper == null) {
+ return false;
+ }
+ return visibleWrapper.shouldClipToSidePaddings();
+ }
}
diff --git a/com/android/systemui/statusbar/NotificationGutsManager.java b/com/android/systemui/statusbar/NotificationGutsManager.java
index b585bdf..f451fda 100644
--- a/com/android/systemui/statusbar/NotificationGutsManager.java
+++ b/com/android/systemui/statusbar/NotificationGutsManager.java
@@ -37,6 +37,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.Interpolators;
@@ -75,17 +76,20 @@
private NotificationGuts mNotificationGutsExposed;
private NotificationMenuRowPlugin.MenuItem mGutsMenuItem;
private final NotificationInfo.CheckSaveListener mCheckSaveListener;
+ private final OnSettingsClickListener mOnSettingsClickListener;
private String mKeyToRemoveOnGutsClosed;
public NotificationGutsManager(
NotificationPresenter presenter,
NotificationStackScrollLayout stackScroller,
NotificationInfo.CheckSaveListener checkSaveListener,
- Context context) {
+ Context context,
+ OnSettingsClickListener onSettingsClickListener) {
mPresenter = presenter;
mStackScroller = stackScroller;
mCheckSaveListener = checkSaveListener;
mContext = context;
+ mOnSettingsClickListener = onSettingsClickListener;
Resources res = context.getResources();
mNonBlockablePkgs = new HashSet<>();
@@ -189,6 +193,7 @@
onSettingsClick = (View v, NotificationChannel channel, int appUid) -> {
mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_INFO);
guts.resetFalsingCheck();
+ mOnSettingsClickListener.onClick(sbn.getKey());
startAppNotificationSettingsActivity(pkg, appUid, channel);
};
}
@@ -352,4 +357,8 @@
pw.print("mKeyToRemoveOnGutsClosed: ");
pw.println(mKeyToRemoveOnGutsClosed);
}
+
+ public interface OnSettingsClickListener {
+ void onClick(String key);
+ }
}
diff --git a/com/android/systemui/statusbar/NotificationMenuRow.java b/com/android/systemui/statusbar/NotificationMenuRow.java
index 99b4b07..b2604fe 100644
--- a/com/android/systemui/statusbar/NotificationMenuRow.java
+++ b/com/android/systemui/statusbar/NotificationMenuRow.java
@@ -88,6 +88,7 @@
private float mHorizSpaceForIcon = -1;
private int mVertSpaceForIcons = -1;
private int mIconPadding = -1;
+ private int mSidePadding;
private float mAlpha = 0f;
private float mPrevX;
@@ -175,6 +176,7 @@
final Resources res = mContext.getResources();
mHorizSpaceForIcon = res.getDimensionPixelSize(R.dimen.notification_menu_icon_size);
mVertSpaceForIcons = res.getDimensionPixelSize(R.dimen.notification_min_height);
+ mSidePadding = res.getDimensionPixelSize(R.dimen.notification_lockscreen_side_paddings);
mIconPadding = res.getDimensionPixelSize(R.dimen.notification_menu_icon_padding);
mMenuItems.clear();
// Construct the menu items based on the notification
@@ -496,8 +498,8 @@
final int count = mMenuContainer.getChildCount();
for (int i = 0; i < count; i++) {
final View v = mMenuContainer.getChildAt(i);
- final float left = i * mHorizSpaceForIcon;
- final float right = mParent.getWidth() - (mHorizSpaceForIcon * (i + 1));
+ final float left = mSidePadding + i * mHorizSpaceForIcon;
+ final float right = mParent.getWidth() - (mHorizSpaceForIcon * (i + 1)) - mSidePadding;
v.setX(showOnLeft ? left : right);
}
mOnLeft = showOnLeft;
diff --git a/com/android/systemui/statusbar/NotificationShelf.java b/com/android/systemui/statusbar/NotificationShelf.java
index 5557dde..b7a00eb 100644
--- a/com/android/systemui/statusbar/NotificationShelf.java
+++ b/com/android/systemui/statusbar/NotificationShelf.java
@@ -85,6 +85,7 @@
private boolean mVibrationOnAnimation;
private boolean mUserTouchingScreen;
private boolean mTouchActive;
+ private float mFirstElementRoundness;
public NotificationShelf(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -107,6 +108,7 @@
mViewInvertHelper = new ViewInvertHelper(mShelfIcons,
NotificationPanelView.DOZE_ANIMATION_DURATION);
mShelfState = new ShelfState();
+ setBottomRoundness(1.0f, false /* animate */);
initDimens();
}
@@ -252,6 +254,8 @@
boolean expandingAnimated = mAmbientState.isExpansionChanging()
&& !mAmbientState.isPanelTracking();
int baseZHeight = mAmbientState.getBaseZHeight();
+ int backgroundTop = 0;
+ float firstElementRoundness = 0.0f;
while (notificationIndex < mHostLayout.getChildCount()) {
ExpandableView child = (ExpandableView) mHostLayout.getChildAt(notificationIndex);
notificationIndex++;
@@ -302,9 +306,20 @@
if (notGoneIndex != 0 || !aboveShelf) {
row.setAboveShelf(false);
}
+ if (notGoneIndex == 0) {
+ StatusBarIconView icon = row.getEntry().expandedIcon;
+ NotificationIconContainer.IconState iconState = getIconState(icon);
+ if (iconState.clampedAppearAmount == 1.0f) {
+ // only if the first icon is fully in the shelf we want to clip to it!
+ backgroundTop = (int) (row.getTranslationY() - getTranslationY());
+ firstElementRoundness = row.getCurrentTopRoundness();
+ }
+ }
notGoneIndex++;
previousColor = ownColorUntinted;
}
+ setBackgroundTop(backgroundTop);
+ setFirstElementRoundness(firstElementRoundness);
mShelfIcons.setSpeedBumpIndex(mAmbientState.getSpeedBumpIndex());
mShelfIcons.calculateIconTranslations();
mShelfIcons.applyIconStates();
@@ -325,6 +340,13 @@
}
}
+ private void setFirstElementRoundness(float firstElementRoundness) {
+ if (mFirstElementRoundness != firstElementRoundness) {
+ mFirstElementRoundness = firstElementRoundness;
+ setTopRoundness(firstElementRoundness, false /* animate */);
+ }
+ }
+
private void updateIconClipAmount(ExpandableNotificationRow row) {
float maxTop = row.getTranslationY();
StatusBarIconView icon = row.getEntry().expandedIcon;
diff --git a/com/android/systemui/statusbar/RemoteInputController.java b/com/android/systemui/statusbar/RemoteInputController.java
index 7f28c4c..ff6c775 100644
--- a/com/android/systemui/statusbar/RemoteInputController.java
+++ b/com/android/systemui/statusbar/RemoteInputController.java
@@ -19,7 +19,6 @@
import com.android.internal.util.Preconditions;
import com.android.systemui.Dependency;
import com.android.systemui.statusbar.phone.StatusBarWindowManager;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.RemoteInputView;
import android.util.ArrayMap;
@@ -38,11 +37,11 @@
= new ArrayList<>();
private final ArrayMap<String, Object> mSpinning = new ArrayMap<>();
private final ArrayList<Callback> mCallbacks = new ArrayList<>(3);
- private final HeadsUpManager mHeadsUpManager;
+ private final Delegate mDelegate;
- public RemoteInputController(HeadsUpManager headsUpManager) {
+ public RemoteInputController(Delegate delegate) {
addCallback(Dependency.get(StatusBarWindowManager.class));
- mHeadsUpManager = headsUpManager;
+ mDelegate = delegate;
}
/**
@@ -114,7 +113,7 @@
}
private void apply(NotificationData.Entry entry) {
- mHeadsUpManager.setRemoteInputActive(entry, isRemoteInputActive(entry));
+ mDelegate.setRemoteInputActive(entry, isRemoteInputActive(entry));
boolean remoteInputActive = isRemoteInputActive();
int N = mCallbacks.size();
for (int i = 0; i < N; i++) {
@@ -204,9 +203,35 @@
}
}
+ public void requestDisallowLongPressAndDismiss() {
+ mDelegate.requestDisallowLongPressAndDismiss();
+ }
+
+ public void lockScrollTo(NotificationData.Entry entry) {
+ mDelegate.lockScrollTo(entry);
+ }
+
public interface Callback {
default void onRemoteInputActive(boolean active) {}
default void onRemoteInputSent(NotificationData.Entry entry) {}
}
+
+ public interface Delegate {
+ /**
+ * Activate remote input if necessary.
+ */
+ void setRemoteInputActive(NotificationData.Entry entry, boolean remoteInputActive);
+
+ /**
+ * Request that the view does not dismiss nor perform long press for the current touch.
+ */
+ void requestDisallowLongPressAndDismiss();
+
+ /**
+ * Request that the view is made visible by scrolling to it, and keep the scroll locked until
+ * the user scrolls, or {@param v} loses focus or is detached.
+ */
+ void lockScrollTo(NotificationData.Entry entry);
+ }
}
diff --git a/com/android/systemui/statusbar/ScrimView.java b/com/android/systemui/statusbar/ScrimView.java
index a53e348..8830352 100644
--- a/com/android/systemui/statusbar/ScrimView.java
+++ b/com/android/systemui/statusbar/ScrimView.java
@@ -41,6 +41,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.internal.colorextraction.drawable.GradientDrawable;
+import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -50,6 +51,7 @@
public class ScrimView extends View implements ConfigurationController.ConfigurationListener {
private static final String TAG = "ScrimView";
private final ColorExtractor.GradientColors mColors;
+ private int mDensity;
private boolean mDrawAsSrc;
private float mViewAlpha = 1.0f;
private ValueAnimator mAlphaAnimator;
@@ -72,6 +74,7 @@
}
};
private Runnable mChangeRunnable;
+ private int mCornerRadius;
public ScrimView(Context context) {
this(context, null);
@@ -93,6 +96,24 @@
mColors = new ColorExtractor.GradientColors();
updateScreenSize();
updateColorWithTint(false);
+ initView();
+ final Configuration currentConfig = mContext.getResources().getConfiguration();
+ mDensity = currentConfig.densityDpi;
+ }
+
+ private void initView() {
+ mCornerRadius = getResources().getDimensionPixelSize(
+ Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius));
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ int densityDpi = newConfig.densityDpi;
+ if (mDensity != densityDpi) {
+ mDensity = densityDpi;
+ initView();
+ }
}
@Override
@@ -145,6 +166,28 @@
mDrawable.draw(canvas);
canvas.restore();
}
+ // We also need to draw the rounded corners of the background
+ canvas.save();
+ canvas.clipRect(mExcludedRect.left, mExcludedRect.top,
+ mExcludedRect.left + mCornerRadius, mExcludedRect.top + mCornerRadius);
+ mDrawable.draw(canvas);
+ canvas.restore();
+ canvas.save();
+ canvas.clipRect(mExcludedRect.right - mCornerRadius, mExcludedRect.top,
+ mExcludedRect.right, mExcludedRect.top + mCornerRadius);
+ mDrawable.draw(canvas);
+ canvas.restore();
+ canvas.save();
+ canvas.clipRect(mExcludedRect.left, mExcludedRect.bottom - mCornerRadius,
+ mExcludedRect.left + mCornerRadius, mExcludedRect.bottom);
+ mDrawable.draw(canvas);
+ canvas.restore();
+ canvas.save();
+ canvas.clipRect(mExcludedRect.right - mCornerRadius,
+ mExcludedRect.bottom - mCornerRadius,
+ mExcludedRect.right, mExcludedRect.bottom);
+ mDrawable.draw(canvas);
+ canvas.restore();
}
}
}
@@ -252,6 +295,13 @@
return false;
}
+ /**
+ * It might look counterintuitive to have another method to set the alpha instead of
+ * only using {@link #setAlpha(float)}. In this case we're in a hardware layer
+ * optimizing blend modes, so it makes sense.
+ *
+ * @param alpha Gradient alpha from 0 to 1.
+ */
public void setViewAlpha(float alpha) {
if (alpha != mViewAlpha) {
mViewAlpha = alpha;
diff --git a/com/android/systemui/statusbar/car/CarNavigationBarController.java b/com/android/systemui/statusbar/car/CarNavigationBarController.java
index f5c77f2..64c52ed 100644
--- a/com/android/systemui/statusbar/car/CarNavigationBarController.java
+++ b/com/android/systemui/statusbar/car/CarNavigationBarController.java
@@ -369,7 +369,7 @@
private void onFacetClicked(Intent intent, int index) {
String packageName = intent.getPackage();
- if (packageName == null) {
+ if (packageName == null && !intent.getCategories().contains(Intent.CATEGORY_HOME)) {
return;
}
diff --git a/com/android/systemui/statusbar/notification/AnimatableProperty.java b/com/android/systemui/statusbar/notification/AnimatableProperty.java
new file mode 100644
index 0000000..d7b211f
--- /dev/null
+++ b/com/android/systemui/statusbar/notification/AnimatableProperty.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017 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.notification;
+
+import android.util.FloatProperty;
+import android.util.Property;
+import android.view.View;
+
+import com.android.systemui.statusbar.stack.AnimationProperties;
+
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * An animatable property of a view. Used with {@link PropertyAnimator}
+ */
+public interface AnimatableProperty {
+ int getAnimationStartTag();
+
+ int getAnimationEndTag();
+
+ int getAnimatorTag();
+
+ Property getProperty();
+
+ static <T extends View> AnimatableProperty from(String name, BiConsumer<T, Float> setter,
+ Function<T, Float> getter, int animatorTag, int startValueTag, int endValueTag) {
+ Property<T, Float> property = new FloatProperty<T>(name) {
+
+ @Override
+ public Float get(T object) {
+ return getter.apply(object);
+ }
+
+ @Override
+ public void setValue(T object, float value) {
+ setter.accept(object, value);
+ }
+ };
+ return new AnimatableProperty() {
+ @Override
+ public int getAnimationStartTag() {
+ return startValueTag;
+ }
+
+ @Override
+ public int getAnimationEndTag() {
+ return endValueTag;
+ }
+
+ @Override
+ public int getAnimatorTag() {
+ return animatorTag;
+ }
+
+ @Override
+ public Property getProperty() {
+ return property;
+ }
+ };
+ }
+}
diff --git a/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java b/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
index fc420eb..27defca 100644
--- a/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
+++ b/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
@@ -89,6 +89,7 @@
private void transformViewInternal(MessagingLayoutTransformState mlt,
float transformationAmount, boolean to) {
+ ensureVisible();
ArrayList<MessagingGroup> ownGroups = filterHiddenGroups(
mMessagingLayout.getMessagingGroups());
ArrayList<MessagingGroup> otherGroups = filterHiddenGroups(
@@ -332,6 +333,7 @@
@Override
public void setVisible(boolean visible, boolean force) {
+ super.setVisible(visible, force);
resetTransformedView();
ArrayList<MessagingGroup> ownGroups = mMessagingLayout.getMessagingGroups();
for (int i = 0; i < ownGroups.size(); i++) {
diff --git a/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java b/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
index bca4b43..66682e4 100644
--- a/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
+++ b/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
+import android.os.Build;
import android.view.View;
import com.android.systemui.R;
@@ -37,6 +38,7 @@
private final Paint mGreyPaint = new Paint();
private boolean mIsLegacy;
private int mLegacyColor;
+ private boolean mBeforeP;
protected NotificationCustomViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
super(ctx, view, row);
@@ -115,4 +117,17 @@
super.setLegacy(legacy);
mIsLegacy = legacy;
}
+
+ @Override
+ public boolean shouldClipToSidePaddings() {
+ // Before P we ensure that they are now drawing inside out content bounds since we inset
+ // the view. If they target P, then we don't have that guarantee and we need to be safe.
+ return !mBeforeP;
+ }
+
+ @Override
+ public void onContentUpdated(ExpandableNotificationRow row) {
+ super.onContentUpdated(row);
+ mBeforeP = row.getEntry().targetSdk < Build.VERSION_CODES.P;
+ }
}
diff --git a/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java b/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java
index eb211a1..060e6d6 100644
--- a/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java
+++ b/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java
@@ -58,6 +58,11 @@
@Override
public boolean isDimmable() {
- return false;
+ return getCustomBackgroundColor() == 0;
+ }
+
+ @Override
+ public boolean shouldClipToSidePaddings() {
+ return true;
}
}
diff --git a/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
index fd085d9..e07112f 100644
--- a/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
+++ b/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
@@ -265,6 +265,11 @@
updateActionOffset();
}
+ @Override
+ public boolean shouldClipToSidePaddings() {
+ return mActionsContainer != null && mActionsContainer.getVisibility() != View.GONE;
+ }
+
private void updateActionOffset() {
if (mActionsContainer != null) {
// We should never push the actions higher than they are in the headsup view.
diff --git a/com/android/systemui/statusbar/notification/NotificationViewWrapper.java b/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
index 1cd5f15..8a767bb 100644
--- a/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
+++ b/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
@@ -194,4 +194,8 @@
public int getMinLayoutHeight() {
return 0;
}
+
+ public boolean shouldClipToSidePaddings() {
+ return false;
+ }
}
diff --git a/com/android/systemui/statusbar/notification/PropertyAnimator.java b/com/android/systemui/statusbar/notification/PropertyAnimator.java
index 80ba943..92dcc9e 100644
--- a/com/android/systemui/statusbar/notification/PropertyAnimator.java
+++ b/com/android/systemui/statusbar/notification/PropertyAnimator.java
@@ -34,6 +34,19 @@
*/
public class PropertyAnimator {
+ public static <T extends View> void setProperty(final T view,
+ AnimatableProperty animatableProperty, float newEndValue,
+ AnimationProperties properties, boolean animated) {
+ int animatorTag = animatableProperty.getAnimatorTag();
+ ValueAnimator previousAnimator = ViewState.getChildTag(view, animatorTag);
+ if (previousAnimator != null || animated) {
+ startAnimation(view, animatableProperty, newEndValue, properties);
+ } else {
+ // no new animation needed, let's just apply the value
+ animatableProperty.getProperty().set(view, newEndValue);
+ }
+ }
+
public static <T extends View> void startAnimation(final T view,
AnimatableProperty animatableProperty, float newEndValue,
AnimationProperties properties) {
@@ -102,10 +115,4 @@
view.setTag(animationEndTag, newEndValue);
}
- public interface AnimatableProperty {
- int getAnimationStartTag();
- int getAnimationEndTag();
- int getAnimatorTag();
- Property getProperty();
- }
}
diff --git a/com/android/systemui/statusbar/notification/TransformState.java b/com/android/systemui/statusbar/notification/TransformState.java
index ad07af0..dec5303 100644
--- a/com/android/systemui/statusbar/notification/TransformState.java
+++ b/com/android/systemui/statusbar/notification/TransformState.java
@@ -95,18 +95,22 @@
public void transformViewFrom(TransformState otherState, float transformationAmount) {
mTransformedView.animate().cancel();
if (sameAs(otherState)) {
- if (mTransformedView.getVisibility() == View.INVISIBLE
- || mTransformedView.getAlpha() != 1.0f) {
- // We have the same content, lets show ourselves
- mTransformedView.setAlpha(1.0f);
- mTransformedView.setVisibility(View.VISIBLE);
- }
+ ensureVisible();
} else {
CrossFadeHelper.fadeIn(mTransformedView, transformationAmount);
}
transformViewFullyFrom(otherState, transformationAmount);
}
+ protected void ensureVisible() {
+ if (mTransformedView.getVisibility() == View.INVISIBLE
+ || mTransformedView.getAlpha() != 1.0f) {
+ // We have the same content, lets show ourselves
+ mTransformedView.setAlpha(1.0f);
+ mTransformedView.setVisibility(View.VISIBLE);
+ }
+ }
+
public void transformViewFullyFrom(TransformState otherState, float transformationAmount) {
transformViewFrom(otherState, TRANSFORM_ALL, null, transformationAmount);
}
diff --git a/com/android/systemui/statusbar/phone/DozeParameters.java b/com/android/systemui/statusbar/phone/DozeParameters.java
index 6b7397b..3f57c2f 100644
--- a/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -46,10 +46,8 @@
public void dump(PrintWriter pw) {
pw.println(" DozeParameters:");
pw.print(" getDisplayStateSupported(): "); pw.println(getDisplayStateSupported());
- pw.print(" getPulseDuration(pickup=false): "); pw.println(getPulseDuration(false));
- pw.print(" getPulseDuration(pickup=true): "); pw.println(getPulseDuration(true));
- pw.print(" getPulseInDuration(pickup=false): "); pw.println(getPulseInDuration(false));
- pw.print(" getPulseInDuration(pickup=true): "); pw.println(getPulseInDuration(true));
+ pw.print(" getPulseDuration(): "); pw.println(getPulseDuration());
+ pw.print(" getPulseInDuration(): "); pw.println(getPulseInDuration());
pw.print(" getPulseInVisibleDuration(): "); pw.println(getPulseVisibleDuration());
pw.print(" getPulseOutDuration(): "); pw.println(getPulseOutDuration());
pw.print(" getPulseOnSigMotion(): "); pw.println(getPulseOnSigMotion());
@@ -81,14 +79,12 @@
return mContext.getResources().getBoolean(R.bool.doze_suspend_display_state_supported);
}
- public int getPulseDuration(boolean pickup) {
- return getPulseInDuration(pickup) + getPulseVisibleDuration() + getPulseOutDuration();
+ public int getPulseDuration() {
+ return getPulseInDuration() + getPulseVisibleDuration() + getPulseOutDuration();
}
- public int getPulseInDuration(boolean pickupOrDoubleTap) {
- return pickupOrDoubleTap
- ? getInt("doze.pulse.duration.in.pickup", R.integer.doze_pulse_duration_in_pickup)
- : getInt("doze.pulse.duration.in", R.integer.doze_pulse_duration_in);
+ public int getPulseInDuration() {
+ return getInt("doze.pulse.duration.in", R.integer.doze_pulse_duration_in);
}
public int getPulseVisibleDuration() {
diff --git a/com/android/systemui/statusbar/phone/DozeScrimController.java b/com/android/systemui/statusbar/phone/DozeScrimController.java
index 8afb849..1011383 100644
--- a/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -16,16 +16,11 @@
package com.android.systemui.statusbar.phone;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.content.Context;
import android.os.Handler;
import android.util.Log;
-import android.view.animation.Interpolator;
-import com.android.systemui.Interpolators;
import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
@@ -40,74 +35,59 @@
private final Handler mHandler = new Handler();
private final ScrimController mScrimController;
- private final Context mContext;
-
private boolean mDozing;
private DozeHost.PulseCallback mPulseCallback;
private int mPulseReason;
- private Animator mInFrontAnimator;
- private Animator mBehindAnimator;
- private float mInFrontTarget;
- private float mBehindTarget;
- private boolean mDozingAborted;
- private boolean mWakeAndUnlocking;
private boolean mFullyPulsing;
- private float mAodFrontScrimOpacity = 0;
- private Runnable mSetDozeInFrontAlphaDelayed;
+ private final ScrimController.Callback mScrimCallback = new ScrimController.Callback() {
+ @Override
+ public void onDisplayBlanked() {
+ if (DEBUG) {
+ Log.d(TAG, "Pulse in, mDozing=" + mDozing + " mPulseReason="
+ + DozeLog.pulseReasonToString(mPulseReason));
+ }
+ if (!mDozing) {
+ return;
+ }
+
+ // Signal that the pulse is ready to turn the screen on and draw.
+ pulseStarted();
+ }
+
+ @Override
+ public void onFinished() {
+ if (DEBUG) {
+ Log.d(TAG, "Pulse in finished, mDozing=" + mDozing);
+ }
+ if (!mDozing) {
+ return;
+ }
+ mHandler.postDelayed(mPulseOut, mDozeParameters.getPulseVisibleDuration());
+ mHandler.postDelayed(mPulseOutExtended,
+ mDozeParameters.getPulseVisibleDurationExtended());
+ mFullyPulsing = true;
+ }
+
+ /**
+ * Transition was aborted before it was over.
+ */
+ @Override
+ public void onCancelled() {
+ pulseFinished();
+ }
+ };
public DozeScrimController(ScrimController scrimController, Context context) {
- mContext = context;
mScrimController = scrimController;
mDozeParameters = new DozeParameters(context);
}
- public void setDozing(boolean dozing, boolean animate) {
+ public void setDozing(boolean dozing) {
if (mDozing == dozing) return;
mDozing = dozing;
- mWakeAndUnlocking = false;
- if (mDozing) {
- mDozingAborted = false;
- abortAnimations();
- mScrimController.setDozeBehindAlpha(1f);
- setDozeInFrontAlpha(mDozeParameters.getAlwaysOn() ? mAodFrontScrimOpacity : 1f);
- } else {
+ if (!mDozing) {
cancelPulsing();
- if (animate) {
- startScrimAnimation(false /* inFront */, 0f /* target */,
- NotificationPanelView.DOZE_ANIMATION_DURATION,
- Interpolators.LINEAR_OUT_SLOW_IN);
- startScrimAnimation(true /* inFront */, 0f /* target */,
- NotificationPanelView.DOZE_ANIMATION_DURATION,
- Interpolators.LINEAR_OUT_SLOW_IN);
- } else {
- abortAnimations();
- mScrimController.setDozeBehindAlpha(0f);
- setDozeInFrontAlpha(0f);
- }
- }
- }
-
- /**
- * Set the opacity of the front scrim when showing AOD1
- *
- * Used to emulate lower brightness values than the hardware supports natively.
- */
- public void setAodDimmingScrim(float scrimOpacity) {
- mAodFrontScrimOpacity = scrimOpacity;
- if (mDozing && !isPulsing() && !mDozingAborted && !mWakeAndUnlocking
- && mDozeParameters.getAlwaysOn()) {
- setDozeInFrontAlpha(mAodFrontScrimOpacity);
- }
- }
-
- public void setWakeAndUnlocking() {
- // Immediately abort the doze scrims in case of wake-and-unlock
- // for pulsing so the Keyguard fade-out animation scrim can take over.
- if (!mWakeAndUnlocking) {
- mWakeAndUnlocking = true;
- mScrimController.setDozeBehindAlpha(0f);
- setDozeInFrontAlpha(0f);
}
}
@@ -118,37 +98,21 @@
}
if (!mDozing || mPulseCallback != null) {
+ if (DEBUG) {
+ Log.d(TAG, "Pulse supressed. Dozing: " + mDozeParameters + " had callback? "
+ + (mPulseCallback != null));
+ }
// Pulse suppressed.
callback.onPulseFinished();
return;
}
- // Begin pulse. Note that it's very important that the pulse finished callback
+ // Begin pulse. Note that it's very important that the pulse finished callback
// be invoked when we're done so that the caller can drop the pulse wakelock.
mPulseCallback = callback;
mPulseReason = reason;
- setDozeInFrontAlpha(1f);
- mHandler.post(mPulseIn);
- }
- /**
- * Aborts pulsing immediately.
- */
- public void abortPulsing() {
- cancelPulsing();
- if (mDozing && !mWakeAndUnlocking) {
- mScrimController.setDozeBehindAlpha(1f);
- setDozeInFrontAlpha(mDozeParameters.getAlwaysOn() && !mDozingAborted
- ? mAodFrontScrimOpacity : 1f);
- }
- }
-
- /**
- * Aborts dozing immediately.
- */
- public void abortDoze() {
- mDozingAborted = true;
- abortPulsing();
+ mScrimController.transitionTo(ScrimState.PULSING, mScrimCallback);
}
public void pulseOutNow() {
@@ -157,17 +121,6 @@
}
}
- public void onScreenTurnedOn() {
- if (isPulsing()) {
- final boolean pickupOrDoubleTap = mPulseReason == DozeLog.PULSE_REASON_SENSOR_PICKUP
- || mPulseReason == DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP;
- startScrimAnimation(true /* inFront */, 0f,
- mDozeParameters.getPulseInDuration(pickupOrDoubleTap),
- pickupOrDoubleTap ? Interpolators.LINEAR_OUT_SLOW_IN : Interpolators.ALPHA_OUT,
- mPulseInFinished);
- }
- }
-
public boolean isPulsing() {
return mPulseCallback != null;
}
@@ -181,11 +134,9 @@
}
private void cancelPulsing() {
- if (DEBUG) Log.d(TAG, "Cancel pulsing");
-
if (mPulseCallback != null) {
+ if (DEBUG) Log.d(TAG, "Cancel pulsing");
mFullyPulsing = false;
- mHandler.removeCallbacks(mPulseIn);
mHandler.removeCallbacks(mPulseOut);
mHandler.removeCallbacks(mPulseOutExtended);
pulseFinished();
@@ -193,151 +144,20 @@
}
private void pulseStarted() {
+ DozeLog.tracePulseStart(mPulseReason);
if (mPulseCallback != null) {
mPulseCallback.onPulseStarted();
}
}
private void pulseFinished() {
+ DozeLog.tracePulseFinish();
if (mPulseCallback != null) {
mPulseCallback.onPulseFinished();
mPulseCallback = null;
}
}
- private void abortAnimations() {
- if (mInFrontAnimator != null) {
- mInFrontAnimator.cancel();
- }
- if (mBehindAnimator != null) {
- mBehindAnimator.cancel();
- }
- }
-
- private void startScrimAnimation(final boolean inFront, float target, long duration,
- Interpolator interpolator) {
- startScrimAnimation(inFront, target, duration, interpolator, null /* endRunnable */);
- }
-
- private void startScrimAnimation(final boolean inFront, float target, long duration,
- Interpolator interpolator, final Runnable endRunnable) {
- Animator current = getCurrentAnimator(inFront);
- if (current != null) {
- float currentTarget = getCurrentTarget(inFront);
- if (currentTarget == target) {
- return;
- }
- current.cancel();
- }
- ValueAnimator anim = ValueAnimator.ofFloat(getDozeAlpha(inFront), target);
- anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float value = (float) animation.getAnimatedValue();
- setDozeAlpha(inFront, value);
- }
- });
- anim.setInterpolator(interpolator);
- anim.setDuration(duration);
- anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- setCurrentAnimator(inFront, null);
- if (endRunnable != null) {
- endRunnable.run();
- }
- }
- });
- anim.start();
- setCurrentAnimator(inFront, anim);
- setCurrentTarget(inFront, target);
- }
-
- private float getCurrentTarget(boolean inFront) {
- return inFront ? mInFrontTarget : mBehindTarget;
- }
-
- private void setCurrentTarget(boolean inFront, float target) {
- if (inFront) {
- mInFrontTarget = target;
- } else {
- mBehindTarget = target;
- }
- }
-
- private Animator getCurrentAnimator(boolean inFront) {
- return inFront ? mInFrontAnimator : mBehindAnimator;
- }
-
- private void setCurrentAnimator(boolean inFront, Animator animator) {
- if (inFront) {
- mInFrontAnimator = animator;
- } else {
- mBehindAnimator = animator;
- }
- }
-
- private void setDozeAlpha(boolean inFront, float alpha) {
- if (mWakeAndUnlocking) {
- return;
- }
- if (inFront) {
- mScrimController.setDozeInFrontAlpha(alpha);
- } else {
- mScrimController.setDozeBehindAlpha(alpha);
- }
- }
-
- private float getDozeAlpha(boolean inFront) {
- return inFront
- ? mScrimController.getDozeInFrontAlpha()
- : mScrimController.getDozeBehindAlpha();
- }
-
- private void setDozeInFrontAlpha(float opacity) {
- setDozeInFrontAlphaDelayed(opacity, 0 /* delay */);
-
- }
-
- private void setDozeInFrontAlphaDelayed(float opacity, long delayMs) {
- if (mSetDozeInFrontAlphaDelayed != null) {
- mHandler.removeCallbacks(mSetDozeInFrontAlphaDelayed);
- mSetDozeInFrontAlphaDelayed = null;
- }
- if (delayMs <= 0) {
- mScrimController.setDozeInFrontAlpha(opacity);
- } else {
- mHandler.postDelayed(mSetDozeInFrontAlphaDelayed = () -> {
- setDozeInFrontAlpha(opacity);
- }, delayMs);
- }
- }
-
- private final Runnable mPulseIn = new Runnable() {
- @Override
- public void run() {
- if (DEBUG) Log.d(TAG, "Pulse in, mDozing=" + mDozing + " mPulseReason="
- + DozeLog.pulseReasonToString(mPulseReason));
- if (!mDozing) return;
- DozeLog.tracePulseStart(mPulseReason);
-
- // Signal that the pulse is ready to turn the screen on and draw.
- pulseStarted();
- }
- };
-
- private final Runnable mPulseInFinished = new Runnable() {
- @Override
- public void run() {
- if (DEBUG) Log.d(TAG, "Pulse in finished, mDozing=" + mDozing);
- if (!mDozing) return;
- mHandler.postDelayed(mPulseOut, mDozeParameters.getPulseVisibleDuration());
- mHandler.postDelayed(mPulseOutExtended,
- mDozeParameters.getPulseVisibleDurationExtended());
- mFullyPulsing = true;
- }
- };
-
private final Runnable mPulseOutExtended = new Runnable() {
@Override
public void run() {
@@ -354,38 +174,13 @@
mHandler.removeCallbacks(mPulseOutExtended);
if (DEBUG) Log.d(TAG, "Pulse out, mDozing=" + mDozing);
if (!mDozing) return;
- startScrimAnimation(true /* inFront */, 1,
- mDozeParameters.getPulseOutDuration(),
- Interpolators.ALPHA_IN, mPulseOutFinishing);
+ mScrimController.transitionTo(ScrimState.AOD,
+ new ScrimController.Callback() {
+ @Override
+ public void onDisplayBlanked() {
+ pulseFinished();
+ }
+ });
}
};
-
- private final Runnable mPulseOutFinishing = new Runnable() {
- @Override
- public void run() {
- if (DEBUG) Log.d(TAG, "Pulse out finished");
- DozeLog.tracePulseFinish();
- if (mDozeParameters.getAlwaysOn() && mDozing) {
- // Setting power states can block rendering. For AOD, delay finishing the pulse and
- // setting the power state until the fully black scrim had time to hit the
- // framebuffer.
- mHandler.postDelayed(mPulseOutFinished, 30);
- } else {
- mPulseOutFinished.run();
- }
- }
- };
-
- private final Runnable mPulseOutFinished = new Runnable() {
- @Override
- public void run() {
- // Signal that the pulse is all finished so we can turn the screen off now.
- DozeScrimController.this.pulseFinished();
- if (mDozeParameters.getAlwaysOn()) {
- // Setting power states can happen after we push out the frame. Make sure we
- // stay fully opaque until the power state request reaches the lower levels.
- setDozeInFrontAlphaDelayed(mAodFrontScrimOpacity, 100);
- }
- }
- };
-}
+}
\ No newline at end of file
diff --git a/com/android/systemui/statusbar/phone/FingerprintUnlockController.java b/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
index 91369db..80d4061 100644
--- a/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
+++ b/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
@@ -181,9 +181,9 @@
}
private boolean pulsingOrAod() {
- boolean pulsing = mDozeScrimController.isPulsing();
- boolean dozingWithScreenOn = mStatusBar.isDozing() && !mStatusBar.isScreenFullyOff();
- return pulsing || dozingWithScreenOn;
+ final ScrimState scrimState = mScrimController.getState();
+ return scrimState == ScrimState.AOD
+ || scrimState == ScrimState.PULSING;
}
@Override
@@ -246,15 +246,12 @@
true /* allowEnterAnimation */);
} else if (mMode == MODE_WAKE_AND_UNLOCK){
Trace.beginSection("MODE_WAKE_AND_UNLOCK");
- mDozeScrimController.abortDoze();
} else {
Trace.beginSection("MODE_WAKE_AND_UNLOCK_FROM_DREAM");
mUpdateMonitor.awakenFromDream();
}
mStatusBarWindowManager.setStatusBarFocusable(false);
mKeyguardViewMediator.onWakeAndUnlocking();
- mScrimController.setWakeAndUnlocking();
- mDozeScrimController.setWakeAndUnlocking();
if (mStatusBar.getNavigationBarView() != null) {
mStatusBar.getNavigationBarView().setWakeAndUnlocking(true);
}
@@ -269,6 +266,7 @@
}
private void showBouncer() {
+ mScrimController.transitionTo(ScrimState.BOUNCER);
mStatusBarKeyguardViewManager.animateCollapsePanels(
FINGERPRINT_COLLAPSE_SPEEDUP_FACTOR);
mPendingShowBouncer = false;
diff --git a/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index a6691b1..da809c1 100644
--- a/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -333,7 +333,7 @@
return false;
}
- public void onOverlayChanged() {
+ public void onThemeChanged() {
@ColorInt int textColor = Utils.getColorAttr(mContext, R.attr.wallpaperTextColor);
@ColorInt int iconColor = Utils.getDefaultColor(mContext, Color.luminance(textColor) < 0.5 ?
R.color.dark_mode_icon_color_single_tone :
diff --git a/com/android/systemui/statusbar/phone/LockIcon.java b/com/android/systemui/statusbar/phone/LockIcon.java
index 5c9446c..34486db 100644
--- a/com/android/systemui/statusbar/phone/LockIcon.java
+++ b/com/android/systemui/statusbar/phone/LockIcon.java
@@ -250,7 +250,7 @@
}
break;
case STATE_FACE_UNLOCK:
- iconRes = com.android.internal.R.drawable.ic_account_circle;
+ iconRes = R.drawable.ic_account_circle;
break;
case STATE_FINGERPRINT:
// If screen is off and device asleep, use the draw on animation so the first frame
diff --git a/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
index c950036..b81a3b0 100644
--- a/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
+++ b/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
@@ -80,7 +80,8 @@
@Override
protected boolean isLightsOut(int mode) {
- return super.isLightsOut(mode) || (mAutoDim && !mWallpaperVisible);
+ return super.isLightsOut(mode) || (mAutoDim && !mWallpaperVisible
+ && mode != MODE_WARNING);
}
public LightBarTransitionsController getLightTransitionsController() {
@@ -108,7 +109,9 @@
// ok, everyone, stop it right there
navButtons.animate().cancel();
- final float navButtonsAlpha = lightsOut ? 0.6f : 1f;
+ // Bump percentage by 10% if dark.
+ float darkBump = mLightTransitionsController.getCurrentDarkIntensity() / 10;
+ final float navButtonsAlpha = lightsOut ? 0.6f + darkBump : 1f;
if (!animate) {
navButtons.setAlpha(navButtonsAlpha);
@@ -130,6 +133,9 @@
for (int i = buttonDispatchers.size() - 1; i >= 0; i--) {
buttonDispatchers.valueAt(i).setDarkIntensity(darkIntensity);
}
+ if (mAutoDim) {
+ applyLightsOut(false, true);
+ }
}
private final View.OnTouchListener mLightsOutListener = new View.OnTouchListener() {
diff --git a/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 0f246c6..836efff 100644
--- a/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -209,7 +209,7 @@
mAddAnimationStartIndex = Math.min(mAddAnimationStartIndex, childIndex);
}
}
- if (mDark && child instanceof StatusBarIconView) {
+ if (child instanceof StatusBarIconView) {
((StatusBarIconView) child).setDark(mDark, false, 0);
}
}
diff --git a/com/android/systemui/statusbar/phone/NotificationPanelView.java b/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 86a8f41..17e3599 100644
--- a/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -239,6 +239,7 @@
private ValueAnimator mDarkAnimator;
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private boolean mUserSetupComplete;
+ private int mQsNotificationTopPadding;
public NotificationPanelView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -307,6 +308,8 @@
R.dimen.max_notification_fadeout_height);
mIndicationBottomPadding = getResources().getDimensionPixelSize(
R.dimen.keyguard_indication_bottom_padding);
+ mQsNotificationTopPadding = getResources().getDimensionPixelSize(
+ R.dimen.qs_notification_keyguard_padding);
}
public void updateResources() {
@@ -330,7 +333,7 @@
}
}
- public void onOverlayChanged() {
+ public void onThemeChanged() {
// Re-inflate the status view group.
int index = indexOfChild(mKeyguardStatusView);
removeView(mKeyguardStatusView);
@@ -818,7 +821,7 @@
private float getQsExpansionFraction() {
return Math.min(1f, (mQsExpansionHeight - mQsMinExpansionHeight)
- / (getTempQsMaxExpansion() - mQsMinExpansionHeight));
+ / (mQsMaxExpansionHeight - mQsMinExpansionHeight));
}
@Override
@@ -1361,7 +1364,7 @@
// take the maximum and linearly interpolate with the panel expansion for a nice motion.
int maxNotifications = mClockPositionResult.stackScrollerPadding
- mClockPositionResult.stackScrollerPaddingAdjustment;
- int maxQs = getTempQsMaxExpansion();
+ int maxQs = mQsMaxExpansionHeight + mQsNotificationTopPadding;
int max = mStatusBarState == StatusBarState.KEYGUARD
? Math.max(maxNotifications, maxQs)
: maxQs;
@@ -1375,7 +1378,7 @@
// from a scrolled quick settings.
return interpolate(getQsExpansionFraction(),
mNotificationStackScroller.getIntrinsicPadding(),
- mQsMaxExpansionHeight);
+ mQsMaxExpansionHeight + mQsNotificationTopPadding);
} else {
return mQsExpansionHeight;
}
@@ -1544,7 +1547,7 @@
/ (panelHeightQsExpanded - panelHeightQsCollapsed);
}
setQsExpansion(mQsMinExpansionHeight
- + t * (getTempQsMaxExpansion() - mQsMinExpansionHeight));
+ + t * (mQsMaxExpansionHeight - mQsMinExpansionHeight));
}
updateExpandedHeight(expandedHeight);
updateHeader();
@@ -1566,14 +1569,6 @@
}
}
- /**
- * @return a temporary override of {@link #mQsMaxExpansionHeight}, which is needed when
- * collapsing QS / the panel when QS was scrolled
- */
- private int getTempQsMaxExpansion() {
- return mQsMaxExpansionHeight;
- }
-
private int calculatePanelHeightShade() {
int emptyBottomMargin = mNotificationStackScroller.getEmptyBottomMargin();
int maxHeight = mNotificationStackScroller.getHeight() - emptyBottomMargin
@@ -1596,6 +1591,10 @@
}
int maxQsHeight = mQsMaxExpansionHeight;
+ if (mKeyguardShowing) {
+ maxQsHeight += mQsNotificationTopPadding;
+ }
+
// If an animation is changing the size of the QS panel, take the animated value.
if (mQsSizeChangeAnimator != null) {
maxQsHeight = (int) mQsSizeChangeAnimator.getAnimatedValue();
diff --git a/com/android/systemui/statusbar/phone/ScrimController.java b/com/android/systemui/statusbar/phone/ScrimController.java
index 702afa3..3a36776 100644
--- a/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/com/android/systemui/statusbar/phone/ScrimController.java
@@ -25,7 +25,9 @@
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.os.Handler;
import android.os.Trace;
+import android.util.Log;
import android.util.MathUtils;
import android.view.View;
import android.view.ViewGroup;
@@ -34,12 +36,14 @@
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.internal.colorextraction.ColorExtractor.GradientColors;
import com.android.internal.colorextraction.ColorExtractor.OnColorsChangedListener;
import com.android.internal.graphics.ColorUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Dependency;
+import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.statusbar.ExpandableNotificationRow;
@@ -47,7 +51,10 @@
import com.android.systemui.statusbar.ScrimView;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.stack.ViewState;
+import com.android.systemui.util.wakelock.DelayedWakeLock;
+import com.android.systemui.util.wakelock.WakeLock;
+import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.function.Consumer;
@@ -56,33 +63,54 @@
* security method gets shown).
*/
public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
- OnHeadsUpChangedListener, OnColorsChangedListener {
+ OnHeadsUpChangedListener, OnColorsChangedListener, Dumpable {
+
+ private static final String TAG = "ScrimController";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
public static final long ANIMATION_DURATION = 220;
public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR
= new PathInterpolator(0f, 0, 0.7f, 1f);
public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR_LOCKED
= new PathInterpolator(0.3f, 0f, 0.8f, 1f);
- // Default alpha value for most scrims, if unsure use this constant
+ /**
+ * Default alpha value for most scrims.
+ */
public static final float GRADIENT_SCRIM_ALPHA = 0.45f;
- // A scrim varies its opacity based on a busyness factor, for example
- // how many notifications are currently visible.
+ /**
+ * A scrim varies its opacity based on a busyness factor, for example
+ * how many notifications are currently visible.
+ */
public static final float GRADIENT_SCRIM_ALPHA_BUSY = 0.70f;
+ /**
+ * The most common scrim, the one under the keyguard.
+ */
protected static final float SCRIM_BEHIND_ALPHA_KEYGUARD = GRADIENT_SCRIM_ALPHA;
+ /**
+ * We fade out the bottom scrim when the bouncer is visible.
+ */
protected static final float SCRIM_BEHIND_ALPHA_UNLOCKING = 0.2f;
- private static final float SCRIM_IN_FRONT_ALPHA = GRADIENT_SCRIM_ALPHA_BUSY;
- private static final float SCRIM_IN_FRONT_ALPHA_LOCKED = GRADIENT_SCRIM_ALPHA_BUSY;
- private static final int TAG_KEY_ANIM = R.id.scrim;
+ /**
+ * Opacity of the scrim behind the bouncer (the one doing actual background protection.)
+ */
+ protected static final float SCRIM_IN_FRONT_ALPHA_LOCKED = GRADIENT_SCRIM_ALPHA_BUSY;
+
+ static final int TAG_KEY_ANIM = R.id.scrim;
+ static final int TAG_KEY_ANIM_BLANK = R.id.scrim_blanking;
private static final int TAG_KEY_ANIM_TARGET = R.id.scrim_target;
private static final int TAG_START_ALPHA = R.id.scrim_alpha_start;
private static final int TAG_END_ALPHA = R.id.scrim_alpha_end;
private static final float NOT_INITIALIZED = -1;
- private final LightBarController mLightBarController;
+ private ScrimState mState = ScrimState.UNINITIALIZED;
+ private final Context mContext;
protected final ScrimView mScrimBehind;
protected final ScrimView mScrimInFront;
- private final UnlockMethodCache mUnlockMethodCache;
private final View mHeadsUpScrim;
+ private final LightBarController mLightBarController;
+ private final UnlockMethodCache mUnlockMethodCache;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final DozeParameters mDozeParameters;
private final SysuiColorExtractor mColorExtractor;
private GradientColors mLockColors;
@@ -94,61 +122,53 @@
protected float mScrimBehindAlphaKeyguard = SCRIM_BEHIND_ALPHA_KEYGUARD;
protected float mScrimBehindAlphaUnlocking = SCRIM_BEHIND_ALPHA_UNLOCKING;
- protected boolean mKeyguardShowing;
private float mFraction;
private boolean mDarkenWhileDragging;
- protected boolean mBouncerShowing;
- protected boolean mBouncerIsKeyguard = false;
- private boolean mWakeAndUnlocking;
protected boolean mAnimateChange;
private boolean mUpdatePending;
private boolean mTracking;
private boolean mAnimateKeyguardFadingOut;
- protected long mDurationOverride = -1;
+ protected long mAnimationDuration = -1;
private long mAnimationDelay;
private Runnable mOnAnimationFinished;
private boolean mDeferFinishedListener;
private final Interpolator mInterpolator = new DecelerateInterpolator();
- private boolean mDozing;
- private float mDozeInFrontAlpha;
- private float mDozeBehindAlpha;
private float mCurrentInFrontAlpha = NOT_INITIALIZED;
private float mCurrentBehindAlpha = NOT_INITIALIZED;
- private float mCurrentHeadsUpAlpha = NOT_INITIALIZED;
+ private int mCurrentInFrontTint;
+ private int mCurrentBehindTint;
private int mPinnedHeadsUpCount;
private float mTopHeadsUpDragAmount;
private View mDraggedHeadsUpView;
- private boolean mForceHideScrims;
- private boolean mSkipFirstFrame;
- private boolean mDontAnimateBouncerChanges;
private boolean mKeyguardFadingOutInProgress;
- private boolean mAnimatingDozeUnlock;
private ValueAnimator mKeyguardFadeoutAnimation;
- /** Wake up from AOD transition is starting; need fully opaque front scrim */
- private boolean mWakingUpFromAodStarting;
- /** Wake up from AOD transition is in progress; need black tint */
- private boolean mWakingUpFromAodInProgress;
- /** Wake up from AOD transition is animating; need to reset when animation finishes */
- private boolean mWakingUpFromAodAnimationRunning;
- private boolean mScrimsVisble;
+ private boolean mScrimsVisible;
private final Consumer<Boolean> mScrimVisibleListener;
+ private boolean mBlankScreen;
+ private boolean mScreenBlankingCallbackCalled;
+ private Callback mCallback;
+
+ private final WakeLock mWakeLock;
+ private boolean mWakeLockHeld;
public ScrimController(LightBarController lightBarController, ScrimView scrimBehind,
- ScrimView scrimInFront, View headsUpScrim,
- Consumer<Boolean> scrimVisibleListener) {
+ ScrimView scrimInFront, View headsUpScrim, Consumer<Boolean> scrimVisibleListener,
+ DozeParameters dozeParameters) {
mScrimBehind = scrimBehind;
mScrimInFront = scrimInFront;
mHeadsUpScrim = headsUpScrim;
mScrimVisibleListener = scrimVisibleListener;
- final Context context = scrimBehind.getContext();
- mUnlockMethodCache = UnlockMethodCache.getInstance(context);
- mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(context);
+ mContext = scrimBehind.getContext();
+ mUnlockMethodCache = UnlockMethodCache.getInstance(mContext);
+ mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
mLightBarController = lightBarController;
- mScrimBehindAlphaResValue = context.getResources().getFloat(R.dimen.scrim_behind_alpha);
+ mScrimBehindAlphaResValue = mContext.getResources().getFloat(R.dimen.scrim_behind_alpha);
+ mWakeLock = createWakeLock();
// Scrim alpha is initially set to the value on the resource but might be changed
// to make sure that text on top of it is legible.
mScrimBehindAlpha = mScrimBehindAlphaResValue;
+ mDozeParameters = dozeParameters;
mColorExtractor = Dependency.get(SysuiColorExtractor.class);
mColorExtractor.addOnColorsChangedListener(this);
@@ -158,22 +178,90 @@
ColorExtractor.TYPE_DARK, true /* ignoreVisibility */);
mNeedsDrawableColorUpdate = true;
+ final ScrimState[] states = ScrimState.values();
+ for (int i = 0; i < states.length; i++) {
+ states[i].init(mScrimInFront, mScrimBehind, mDozeParameters);
+ states[i].setScrimBehindAlphaKeyguard(mScrimBehindAlphaKeyguard);
+ }
+ mState = ScrimState.UNINITIALIZED;
+
updateHeadsUpScrim(false);
updateScrims();
}
- public void setKeyguardShowing(boolean showing) {
- mKeyguardShowing = showing;
+ public void transitionTo(ScrimState state) {
+ transitionTo(state, null);
+ }
- // Showing/hiding the keyguard means that scrim colors have to be switched
- mNeedsDrawableColorUpdate = true;
- scheduleUpdate();
+ public void transitionTo(ScrimState state, Callback callback) {
+ if (state == mState) {
+ return;
+ } else if (DEBUG) {
+ Log.d(TAG, "State changed to: " + state);
+ }
+
+ if (state == ScrimState.UNINITIALIZED) {
+ throw new IllegalArgumentException("Cannot change to UNINITIALIZED.");
+ }
+
+ if (mCallback != null) {
+ mCallback.onCancelled();
+ }
+ mCallback = callback;
+
+ state.prepare(mState);
+ mScreenBlankingCallbackCalled = false;
+ mAnimationDelay = 0;
+ mBlankScreen = state.getBlanksScreen();
+ mAnimateChange = state.getAnimateChange();
+ mAnimationDuration = state.getAnimationDuration();
+ mCurrentInFrontTint = state.getFrontTint();
+ mCurrentBehindTint = state.getBehindTint();
+ mCurrentInFrontAlpha = state.getFrontAlpha();
+ mCurrentBehindAlpha = state.getBehindAlpha();
+
+ // Showing/hiding the keyguard means that scrim colors have to be switched, not necessary
+ // to do the same when you're just showing the brightness mirror.
+ mNeedsDrawableColorUpdate = state != ScrimState.BRIGHTNESS_MIRROR;
+
+ if (mKeyguardFadeoutAnimation != null) {
+ mKeyguardFadeoutAnimation.cancel();
+ }
+
+ mState = state;
+
+ // Do not let the device sleep until we're done with all animations
+ if (!mWakeLockHeld) {
+ if (mWakeLock != null) {
+ mWakeLockHeld = true;
+ mWakeLock.acquire();
+ } else {
+ Log.w(TAG, "Cannot hold wake lock, it has not been set yet");
+ }
+ }
+
+ if (!mKeyguardUpdateMonitor.needsSlowUnlockTransition()) {
+ scheduleUpdate();
+ } else {
+ // In case the user isn't unlocked, make sure to delay a bit because the system is hosed
+ // with too many things at this case, in order to not skip the initial frames.
+ mScrimInFront.postOnAnimationDelayed(this::scheduleUpdate, 16);
+ mAnimationDelay = StatusBar.FADE_KEYGUARD_START_DELAY;
+ }
+ }
+
+ public ScrimState getState() {
+ return mState;
}
protected void setScrimBehindValues(float scrimBehindAlphaKeyguard,
float scrimBehindAlphaUnlocking) {
mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard;
mScrimBehindAlphaUnlocking = scrimBehindAlphaUnlocking;
+ ScrimState[] states = ScrimState.values();
+ for (int i = 0; i < states.length; i++) {
+ states[i].setScrimBehindAlphaKeyguard(scrimBehindAlphaKeyguard);
+ }
scheduleUpdate();
}
@@ -186,131 +274,59 @@
mTracking = false;
}
+ /**
+ * Current state of the shade expansion when pulling it from the top.
+ * This value is 1 when on top of the keyguard and goes to 0 as the user drags up.
+ *
+ * The expansion fraction is tied to the scrim opacity.
+ *
+ * @param fraction From 0 to 1 where 0 means collapse and 1 expanded.
+ */
public void setPanelExpansion(float fraction) {
if (mFraction != fraction) {
mFraction = fraction;
- scheduleUpdate();
+
+ if (mState == ScrimState.UNLOCKED) {
+ // Darken scrim as you pull down the shade when unlocked
+ float behindFraction = getInterpolatedFraction();
+ behindFraction = (float) Math.pow(behindFraction, 0.8f);
+ mCurrentBehindAlpha = behindFraction * mScrimBehindAlphaKeyguard;
+ mCurrentInFrontAlpha = 0;
+ } else if (mState == ScrimState.KEYGUARD) {
+ if (mUpdatePending) {
+ return;
+ }
+
+ // Either darken of make the scrim transparent when you
+ // pull down the shade
+ float interpolatedFract = getInterpolatedFraction();
+ if (mDarkenWhileDragging) {
+ mCurrentBehindAlpha = MathUtils.lerp(mScrimBehindAlphaUnlocking,
+ mScrimBehindAlphaKeyguard, interpolatedFract);
+ mCurrentInFrontAlpha = (1f - interpolatedFract) * SCRIM_IN_FRONT_ALPHA_LOCKED;
+ } else {
+ mCurrentBehindAlpha = MathUtils.lerp(0 /* start */, mScrimBehindAlphaKeyguard,
+ interpolatedFract);
+ mCurrentInFrontAlpha = 0;
+ }
+ } else {
+ Log.w(TAG, "Invalid state, cannot set panel expansion when: " + mState);
+ return;
+ }
+
if (mPinnedHeadsUpCount != 0) {
updateHeadsUpScrim(false);
}
- if (mKeyguardFadeoutAnimation != null && mTracking) {
- mKeyguardFadeoutAnimation.cancel();
- }
+
+ updateScrim(false /* animate */, mScrimInFront, mCurrentInFrontAlpha);
+ updateScrim(false /* animate */, mScrimBehind, mCurrentBehindAlpha);
}
}
- public void setBouncerShowing(boolean showing) {
- mBouncerShowing = showing;
- mAnimateChange = !mTracking && !mDontAnimateBouncerChanges && !mKeyguardFadingOutInProgress;
- scheduleUpdate();
- }
-
- /** Prepares the wakeUpFromAod animation (while turning on screen); Forces black scrims. */
- public void prepareWakeUpFromAod() {
- if (mWakingUpFromAodInProgress) {
- return;
- }
- mWakingUpFromAodInProgress = true;
- mWakingUpFromAodStarting = true;
- mAnimateChange = false;
- scheduleUpdate();
- onPreDraw();
- }
-
- /** Starts the wakeUpFromAod animation (once screen is on); animate to transparent scrims. */
- public void wakeUpFromAod() {
- if (mWakeAndUnlocking || mAnimateKeyguardFadingOut) {
- // Wake and unlocking has a separate transition that must not be interfered with.
- mWakingUpFromAodStarting = false;
- mWakingUpFromAodInProgress = false;
- return;
- }
- if (mWakingUpFromAodStarting) {
- mWakingUpFromAodInProgress = true;
- mWakingUpFromAodStarting = false;
- mAnimateChange = true;
- scheduleUpdate();
- }
- }
-
- public void setWakeAndUnlocking() {
- mWakeAndUnlocking = true;
- mAnimatingDozeUnlock = true;
- mWakingUpFromAodStarting = false;
- mWakingUpFromAodInProgress = false;
- scheduleUpdate();
- }
-
- public void animateKeyguardFadingOut(long delay, long duration, Runnable onAnimationFinished,
- boolean skipFirstFrame) {
- mWakeAndUnlocking = false;
- mAnimateKeyguardFadingOut = true;
- mDurationOverride = duration;
- mAnimationDelay = delay;
- mAnimateChange = true;
- mSkipFirstFrame = skipFirstFrame;
- mOnAnimationFinished = onAnimationFinished;
-
- if (!mKeyguardUpdateMonitor.needsSlowUnlockTransition()) {
- scheduleUpdate();
-
- // No need to wait for the next frame to be drawn for this case - onPreDraw will execute
- // the changes we just scheduled.
- onPreDraw();
- } else {
-
- // In case the user isn't unlocked, make sure to delay a bit because the system is hosed
- // with too many things in this case, in order to not skip the initial frames.
- mScrimInFront.postOnAnimationDelayed(this::scheduleUpdate, 16);
- }
- }
-
- public void abortKeyguardFadingOut() {
- if (mAnimateKeyguardFadingOut) {
- endAnimateKeyguardFadingOut(true /* force */);
- }
- }
-
- public void animateKeyguardUnoccluding(long duration) {
- mAnimateChange = false;
- setScrimBehindAlpha(0f);
- mAnimateChange = true;
- scheduleUpdate();
- mDurationOverride = duration;
- }
-
- public void animateGoingToFullShade(long delay, long duration) {
- mDurationOverride = duration;
- mAnimationDelay = delay;
- mAnimateChange = true;
- scheduleUpdate();
- }
-
- public void setDozing(boolean dozing) {
- if (mDozing != dozing) {
- mDozing = dozing;
- scheduleUpdate();
- }
- }
-
- public void setDozeInFrontAlpha(float alpha) {
- mDozeInFrontAlpha = alpha;
- updateScrimColor(mScrimInFront);
- }
-
- public void setDozeBehindAlpha(float alpha) {
- mDozeBehindAlpha = alpha;
- updateScrimColor(mScrimBehind);
- }
-
- public float getDozeBehindAlpha() {
- return mDozeBehindAlpha;
- }
-
- public float getDozeInFrontAlpha() {
- return mDozeInFrontAlpha;
- }
-
+ /**
+ * Keyguard and shade scrim opacity varies according to how many notifications are visible.
+ * @param notificationCount Number of visible notifications.
+ */
public void setNotificationCount(int notificationCount) {
final float maxNotificationDensity = 3;
float notificationDensity = Math.min(notificationCount / maxNotificationDensity, 1f);
@@ -319,15 +335,11 @@
notificationDensity);
if (mScrimBehindAlphaKeyguard != newAlpha) {
mScrimBehindAlphaKeyguard = newAlpha;
- mAnimateChange = true;
- scheduleUpdate();
- }
- }
- private float getScrimInFrontAlpha() {
- return mKeyguardUpdateMonitor.needsSlowUnlockTransition()
- ? SCRIM_IN_FRONT_ALPHA_LOCKED
- : SCRIM_IN_FRONT_ALPHA;
+ if (mState == ScrimState.KEYGUARD || mState == ScrimState.BOUNCER) {
+ scheduleUpdate();
+ }
+ }
}
/**
@@ -352,7 +364,7 @@
if (mNeedsDrawableColorUpdate) {
mNeedsDrawableColorUpdate = false;
final GradientColors currentScrimColors;
- if (mKeyguardShowing) {
+ if (mState == ScrimState.KEYGUARD || mState == ScrimState.BOUNCER) {
// Always animate color changes if we're seeing the keyguard
mScrimInFront.setColors(mLockColors, true /* animated */);
mScrimBehind.setColors(mLockColors, true /* animated */);
@@ -375,77 +387,31 @@
mLightBarController.setScrimColor(mScrimInFront.getColors());
}
- if (mAnimateKeyguardFadingOut || mForceHideScrims) {
- setScrimInFrontAlpha(0f);
- setScrimBehindAlpha(0f);
- } else if (mWakeAndUnlocking) {
- // During wake and unlock, we first hide everything behind a black scrim, which then
- // gets faded out from animateKeyguardFadingOut. This must never be animated.
- mAnimateChange = false;
- if (mDozing) {
- setScrimInFrontAlpha(0f);
- setScrimBehindAlpha(1f);
- } else {
- setScrimInFrontAlpha(1f);
- setScrimBehindAlpha(0f);
- }
- } else if (!mKeyguardShowing && !mBouncerShowing && !mWakingUpFromAodStarting) {
- updateScrimNormal();
- setScrimInFrontAlpha(0);
- } else {
- updateScrimKeyguard();
- }
- mAnimateChange = false;
+ setScrimInFrontAlpha(mCurrentInFrontAlpha);
+ setScrimBehindAlpha(mCurrentBehindAlpha);
+
dispatchScrimsVisible();
}
private void dispatchScrimsVisible() {
boolean scrimsVisible = mScrimBehind.getViewAlpha() > 0 || mScrimInFront.getViewAlpha() > 0;
- if (mScrimsVisble != scrimsVisible) {
- mScrimsVisble = scrimsVisible;
+ if (mScrimsVisible != scrimsVisible) {
+ mScrimsVisible = scrimsVisible;
mScrimVisibleListener.accept(scrimsVisible);
}
}
- private void updateScrimKeyguard() {
- if (mTracking && mDarkenWhileDragging) {
- float behindFraction = Math.max(0, Math.min(mFraction, 1));
- float fraction = 1 - behindFraction;
- fraction = (float) Math.pow(fraction, 0.8f);
- behindFraction = (float) Math.pow(behindFraction, 0.8f);
- setScrimInFrontAlpha(fraction * getScrimInFrontAlpha());
- setScrimBehindAlpha(behindFraction * mScrimBehindAlphaKeyguard);
- } else if (mBouncerShowing && !mBouncerIsKeyguard) {
- setScrimInFrontAlpha(getScrimInFrontAlpha());
- updateScrimNormal();
- } else if (mBouncerShowing) {
- setScrimInFrontAlpha(0f);
- setScrimBehindAlpha(mScrimBehindAlpha);
- } else {
- float fraction = Math.max(0, Math.min(mFraction, 1));
- if (mWakingUpFromAodStarting) {
- setScrimInFrontAlpha(1f);
- } else {
- setScrimInFrontAlpha(0f);
- }
- setScrimBehindAlpha(fraction
- * (mScrimBehindAlphaKeyguard - mScrimBehindAlphaUnlocking)
- + mScrimBehindAlphaUnlocking);
- }
- }
-
- private void updateScrimNormal() {
+ private float getInterpolatedFraction() {
float frac = mFraction;
// let's start this 20% of the way down the screen
frac = frac * 1.2f - 0.2f;
if (frac <= 0) {
- setScrimBehindAlpha(0);
+ return 0;
} else {
// woo, special effects
- final float k = (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f))));
- setScrimBehindAlpha(k * mScrimBehindAlpha);
+ return (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f))));
}
}
@@ -455,102 +421,76 @@
private void setScrimInFrontAlpha(float alpha) {
setScrimAlpha(mScrimInFront, alpha);
- if (alpha == 0f) {
- mScrimInFront.setClickable(false);
- } else {
- // Eat touch events (unless dozing).
- mScrimInFront.setClickable(!mDozing);
- }
}
private void setScrimAlpha(View scrim, float alpha) {
- updateScrim(mAnimateChange, scrim, alpha, getCurrentScrimAlpha(scrim));
- }
-
- protected float getDozeAlpha(View scrim) {
- return scrim == mScrimBehind ? mDozeBehindAlpha : mDozeInFrontAlpha;
- }
-
- protected float getCurrentScrimAlpha(View scrim) {
- return scrim == mScrimBehind ? mCurrentBehindAlpha
- : scrim == mScrimInFront ? mCurrentInFrontAlpha
- : mCurrentHeadsUpAlpha;
- }
-
- private void setCurrentScrimAlpha(View scrim, float alpha) {
- if (scrim == mScrimBehind) {
- mCurrentBehindAlpha = alpha;
- mLightBarController.setScrimAlpha(mCurrentBehindAlpha);
- } else if (scrim == mScrimInFront) {
- mCurrentInFrontAlpha = alpha;
+ if (alpha == 0f) {
+ scrim.setClickable(false);
} else {
- alpha = Math.max(0.0f, Math.min(1.0f, alpha));
- mCurrentHeadsUpAlpha = alpha;
+ // Eat touch events (unless dozing).
+ scrim.setClickable(!(mState == ScrimState.AOD));
}
+ updateScrim(mAnimateChange, scrim, alpha);
}
- private void updateScrimColor(View scrim) {
- float alpha1 = getCurrentScrimAlpha(scrim);
+ private void updateScrimColor(View scrim, float alpha, int tint) {
+ alpha = Math.max(0, Math.min(1.0f, alpha));
if (scrim instanceof ScrimView) {
ScrimView scrimView = (ScrimView) scrim;
- float dozeAlpha = getDozeAlpha(scrim);
- float alpha = 1 - (1 - alpha1) * (1 - dozeAlpha);
- alpha = Math.max(0, Math.min(1.0f, alpha));
- scrimView.setViewAlpha(alpha);
Trace.traceCounter(Trace.TRACE_TAG_APP,
scrim == mScrimInFront ? "front_scrim_alpha" : "back_scrim_alpha",
(int) (alpha * 255));
- int dozeTint = Color.TRANSPARENT;
-
- boolean dozing = mAnimatingDozeUnlock || mDozing;
- boolean frontScrimDozing = mWakingUpFromAodInProgress;
- if (dozing || frontScrimDozing && scrim == mScrimInFront) {
- dozeTint = Color.BLACK;
- }
Trace.traceCounter(Trace.TRACE_TAG_APP,
scrim == mScrimInFront ? "front_scrim_tint" : "back_scrim_tint",
- dozeTint == Color.BLACK ? 1 : 0);
+ Color.alpha(tint));
- scrimView.setTint(dozeTint);
+ scrimView.setTint(tint);
+ scrimView.setViewAlpha(alpha);
} else {
- scrim.setAlpha(alpha1);
+ scrim.setAlpha(alpha);
}
dispatchScrimsVisible();
}
- private void startScrimAnimation(final View scrim, float target) {
- float current = getCurrentScrimAlpha(scrim);
- ValueAnimator anim = ValueAnimator.ofFloat(current, target);
+ private int getCurrentScrimTint(View scrim) {
+ return scrim == mScrimInFront ? mCurrentInFrontTint : mCurrentBehindTint;
+ }
+
+ private void startScrimAnimation(final View scrim, float current, float target) {
+ ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
+ final int initialScrimTint = scrim instanceof ScrimView ? ((ScrimView) scrim).getTint() :
+ Color.TRANSPARENT;
anim.addUpdateListener(animation -> {
- float alpha = (float) animation.getAnimatedValue();
- setCurrentScrimAlpha(scrim, alpha);
- updateScrimColor(scrim);
+ final float animAmount = (float) animation.getAnimatedValue();
+ final int finalScrimTint = scrim == mScrimInFront ?
+ mCurrentInFrontTint : mCurrentBehindTint;
+ float alpha = MathUtils.lerp(current, target, animAmount);
+ int tint = ColorUtils.blendARGB(initialScrimTint, finalScrimTint, animAmount);
+ updateScrimColor(scrim, alpha, tint);
dispatchScrimsVisible();
});
anim.setInterpolator(getInterpolator());
anim.setStartDelay(mAnimationDelay);
- anim.setDuration(mDurationOverride != -1 ? mDurationOverride : ANIMATION_DURATION);
+ anim.setDuration(mAnimationDuration);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
+ if (mKeyguardFadingOutInProgress) {
+ mKeyguardFadeoutAnimation = null;
+ mKeyguardFadingOutInProgress = false;
+ }
+ onFinished();
+
+ scrim.setTag(TAG_KEY_ANIM, null);
+ scrim.setTag(TAG_KEY_ANIM_TARGET, null);
+ dispatchScrimsVisible();
+
if (!mDeferFinishedListener && mOnAnimationFinished != null) {
mOnAnimationFinished.run();
mOnAnimationFinished = null;
}
- if (mKeyguardFadingOutInProgress) {
- mKeyguardFadeoutAnimation = null;
- mKeyguardFadingOutInProgress = false;
- mAnimatingDozeUnlock = false;
- }
- if (mWakingUpFromAodAnimationRunning && !mDeferFinishedListener) {
- mWakingUpFromAodAnimationRunning = false;
- mWakingUpFromAodInProgress = false;
- }
- scrim.setTag(TAG_KEY_ANIM, null);
- scrim.setTag(TAG_KEY_ANIM_TARGET, null);
- dispatchScrimsVisible();
}
});
anim.start();
@@ -558,12 +498,6 @@
mKeyguardFadingOutInProgress = true;
mKeyguardFadeoutAnimation = anim;
}
- if (mWakingUpFromAodInProgress) {
- mWakingUpFromAodAnimationRunning = true;
- }
- if (mSkipFirstFrame) {
- anim.setCurrentPlayTime(16);
- }
scrim.setTag(TAG_KEY_ANIM, anim);
scrim.setTag(TAG_KEY_ANIM_TARGET, target);
}
@@ -582,19 +516,33 @@
public boolean onPreDraw() {
mScrimBehind.getViewTreeObserver().removeOnPreDrawListener(this);
mUpdatePending = false;
- if (mDontAnimateBouncerChanges) {
- mDontAnimateBouncerChanges = false;
+ if (mCallback != null) {
+ mCallback.onStart();
}
updateScrims();
- mDurationOverride = -1;
- mAnimationDelay = 0;
- mSkipFirstFrame = false;
// Make sure that we always call the listener even if we didn't start an animation.
endAnimateKeyguardFadingOut(false /* force */);
return true;
}
+ private void onFinished() {
+ if (mWakeLockHeld) {
+ mWakeLock.release();
+ mWakeLockHeld = false;
+ }
+ if (mCallback != null) {
+ mCallback.onFinished();
+ mCallback = null;
+ }
+ // When unlocking with fingerprint, we'll fade the scrims from black to transparent.
+ // At the end of the animation we need to remove the tint.
+ if (mState == ScrimState.UNLOCKED) {
+ mCurrentInFrontTint = Color.TRANSPARENT;
+ mCurrentBehindTint = Color.TRANSPARENT;
+ }
+ }
+
private void endAnimateKeyguardFadingOut(boolean force) {
mAnimateKeyguardFadingOut = false;
if (force || (!isAnimating(mScrimInFront) && !isAnimating(mScrimBehind))) {
@@ -603,8 +551,6 @@
mOnAnimationFinished = null;
}
mKeyguardFadingOutInProgress = false;
- if (!mWakeAndUnlocking || force)
- mAnimatingDozeUnlock = false;
}
}
@@ -641,16 +587,19 @@
}
private void updateHeadsUpScrim(boolean animate) {
- updateScrim(animate, mHeadsUpScrim, calculateHeadsUpAlpha(), mCurrentHeadsUpAlpha);
+ updateScrim(animate, mHeadsUpScrim, calculateHeadsUpAlpha());
}
- private void updateScrim(boolean animate, View scrim, float alpha, float currentAlpha) {
- if (mKeyguardFadingOutInProgress && mKeyguardFadeoutAnimation.getCurrentPlayTime() != 0) {
- return;
- }
+ @VisibleForTesting
+ void setOnAnimationFinished(Runnable onAnimationFinished) {
+ mOnAnimationFinished = onAnimationFinished;
+ }
- ValueAnimator previousAnimator = ViewState.getChildTag(scrim,
- TAG_KEY_ANIM);
+ private void updateScrim(boolean animate, View scrim, float alpha) {
+ final float currentAlpha = scrim instanceof ScrimView ? ((ScrimView) scrim).getViewAlpha()
+ : scrim.getAlpha();
+
+ ValueAnimator previousAnimator = ViewState.getChildTag(scrim, TAG_KEY_ANIM);
float animEndValue = -1;
if (previousAnimator != null) {
if (animate || alpha == currentAlpha) {
@@ -664,9 +613,37 @@
animEndValue = ViewState.getChildTag(scrim, TAG_END_ALPHA);
}
}
- if (alpha != currentAlpha && alpha != animEndValue) {
+
+ final boolean blankingInProgress = mScrimInFront.getTag(TAG_KEY_ANIM_BLANK) != null;
+ if (mBlankScreen || blankingInProgress) {
+ if (!blankingInProgress) {
+ blankDisplay();
+ }
+ return;
+ } else if (!mScreenBlankingCallbackCalled) {
+ // Not blanking the screen. Letting the callback know that we're ready
+ // to replace what was on the screen before.
+ if (mCallback != null) {
+ mCallback.onDisplayBlanked();
+ mScreenBlankingCallbackCalled = true;
+ }
+ }
+
+ // TODO factor mLightBarController out of this class
+ if (scrim == mScrimBehind) {
+ mLightBarController.setScrimAlpha(alpha);
+ }
+
+ final ScrimView scrimView = scrim instanceof ScrimView ? (ScrimView) scrim : null;
+ final boolean wantsAlphaUpdate = alpha != currentAlpha && alpha != animEndValue;
+ final boolean wantsTintUpdate = scrimView != null
+ && scrimView.getTint() != getCurrentScrimTint(scrimView);
+
+ if (wantsAlphaUpdate || wantsTintUpdate) {
if (animate) {
- startScrimAnimation(scrim, alpha);
+ final float fromAlpha = scrimView == null ? scrim.getAlpha()
+ : scrimView.getViewAlpha();
+ startScrimAnimation(scrim, fromAlpha, alpha);
scrim.setTag(TAG_START_ALPHA, currentAlpha);
scrim.setTag(TAG_END_ALPHA, alpha);
} else {
@@ -685,13 +662,62 @@
previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
} else {
// update the alpha directly
- setCurrentScrimAlpha(scrim, alpha);
- updateScrimColor(scrim);
+ updateScrimColor(scrim, alpha, getCurrentScrimTint(scrim));
+ onFinished();
}
}
+ } else {
+ onFinished();
}
}
+ private void blankDisplay() {
+ final float initialAlpha = mScrimInFront.getViewAlpha();
+ final int initialTint = mScrimInFront.getTint();
+ ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+ anim.addUpdateListener(animation -> {
+ final float amount = (float) animation.getAnimatedValue();
+ float animAlpha = MathUtils.lerp(initialAlpha, 1, amount);
+ int animTint = ColorUtils.blendARGB(initialTint, Color.BLACK, amount);
+ updateScrimColor(mScrimInFront, animAlpha, animTint);
+ dispatchScrimsVisible();
+ });
+ anim.setInterpolator(getInterpolator());
+ anim.setDuration(mDozeParameters.getPulseInDuration());
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (mCallback != null) {
+ mCallback.onDisplayBlanked();
+ mScreenBlankingCallbackCalled = true;
+ }
+ Runnable blankingCallback = () -> {
+ mScrimInFront.setTag(TAG_KEY_ANIM_BLANK, null);
+ mBlankScreen = false;
+ // Try again.
+ updateScrims();
+ };
+
+ // Setting power states can happen after we push out the frame. Make sure we
+ // stay fully opaque until the power state request reaches the lower levels.
+ getHandler().postDelayed(blankingCallback, 100);
+
+ }
+ });
+ anim.start();
+ mScrimInFront.setTag(TAG_KEY_ANIM_BLANK, anim);
+
+ // Finish animation if we're already at its final state
+ if (initialAlpha == 1 && mScrimInFront.getTint() == Color.BLACK) {
+ anim.end();
+ }
+ }
+
+ @VisibleForTesting
+ protected Handler getHandler() {
+ return Handler.getMain();
+ }
+
/**
* Set the amount the current top heads up view is dragged. The range is from 0 to 1 and 0 means
* the heads up is in its resting space and 1 means it's fully dragged out.
@@ -719,23 +745,13 @@
return alpha * expandFactor;
}
- public void forceHideScrims(boolean hide, boolean animated) {
- mForceHideScrims = hide;
- mAnimateChange = animated;
- scheduleUpdate();
- }
-
- public void dontAnimateBouncerChangesUntilNextFrame() {
- mDontAnimateBouncerChanges = true;
- }
-
public void setExcludedBackgroundArea(Rect area) {
mScrimBehind.setExcludedArea(area);
}
public int getBackgroundColor() {
int color = mLockColors.getMainColor();
- return Color.argb((int) (mScrimBehind.getAlpha() * Color.alpha(color)),
+ return Color.argb((int) (mScrimBehind.getViewAlpha() * Color.alpha(color)),
Color.red(color), Color.green(color), Color.blue(color));
}
@@ -764,27 +780,41 @@
}
if ((which & WallpaperManager.FLAG_SYSTEM) != 0) {
mSystemColors = mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM,
- ColorExtractor.TYPE_DARK, mKeyguardShowing);
+ ColorExtractor.TYPE_DARK, mState != ScrimState.UNLOCKED);
mNeedsDrawableColorUpdate = true;
scheduleUpdate();
}
}
- public void dump(PrintWriter pw) {
- pw.println(" ScrimController:");
+ @VisibleForTesting
+ protected WakeLock createWakeLock() {
+ return new DelayedWakeLock(getHandler(),
+ WakeLock.createPartial(mContext, "Doze"));
+ }
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println(" ScrimController:");
+ pw.print(" state:"); pw.println(mState);
pw.print(" frontScrim:"); pw.print(" viewAlpha="); pw.print(mScrimInFront.getViewAlpha());
pw.print(" alpha="); pw.print(mCurrentInFrontAlpha);
- pw.print(" dozeAlpha="); pw.print(mDozeInFrontAlpha);
pw.print(" tint=0x"); pw.println(Integer.toHexString(mScrimInFront.getTint()));
pw.print(" backScrim:"); pw.print(" viewAlpha="); pw.print(mScrimBehind.getViewAlpha());
pw.print(" alpha="); pw.print(mCurrentBehindAlpha);
- pw.print(" dozeAlpha="); pw.print(mDozeBehindAlpha);
pw.print(" tint=0x"); pw.println(Integer.toHexString(mScrimBehind.getTint()));
- pw.print(" mBouncerShowing="); pw.println(mBouncerShowing);
pw.print(" mTracking="); pw.println(mTracking);
- pw.print(" mForceHideScrims="); pw.println(mForceHideScrims);
+ }
+
+ public interface Callback {
+ default void onStart() {
+ }
+ default void onDisplayBlanked() {
+ }
+ default void onFinished() {
+ }
+ default void onCancelled() {
+ }
}
}
diff --git a/com/android/systemui/statusbar/phone/ScrimState.java b/com/android/systemui/statusbar/phone/ScrimState.java
new file mode 100644
index 0000000..0db98f3
--- /dev/null
+++ b/com/android/systemui/statusbar/phone/ScrimState.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2017 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 android.graphics.Color;
+import android.os.Trace;
+
+import com.android.systemui.statusbar.ScrimView;
+
+/**
+ * Possible states of the ScrimController state machine.
+ */
+public enum ScrimState {
+
+ /**
+ * Initial state.
+ */
+ UNINITIALIZED,
+
+ /**
+ * On the lock screen.
+ */
+ KEYGUARD {
+
+ @Override
+ public void prepare(ScrimState previousState) {
+ // DisplayPowerManager will blank the screen, we'll just
+ // set our scrim to black in this frame to avoid flickering and
+ // fade it out afterwards.
+ mBlankScreen = previousState == ScrimState.AOD;
+ if (previousState == ScrimState.AOD) {
+ updateScrimColor(mScrimInFront, 1, Color.BLACK);
+ }
+ mCurrentBehindAlpha = mScrimBehindAlphaKeyguard;
+ mCurrentInFrontAlpha = 0;
+ }
+ },
+
+ /**
+ * Showing password challenge.
+ */
+ BOUNCER {
+ @Override
+ public void prepare(ScrimState previousState) {
+ mCurrentBehindAlpha = ScrimController.SCRIM_BEHIND_ALPHA_UNLOCKING;
+ mCurrentInFrontAlpha = ScrimController.SCRIM_IN_FRONT_ALPHA_LOCKED;
+ }
+ },
+
+ /**
+ * Changing screen brightness from quick settings.
+ */
+ BRIGHTNESS_MIRROR {
+ @Override
+ public void prepare(ScrimState previousState) {
+ mCurrentBehindAlpha = 0;
+ mCurrentInFrontAlpha = 0;
+ }
+ },
+
+ /**
+ * Always on display or screen off.
+ */
+ AOD {
+ @Override
+ public void prepare(ScrimState previousState) {
+ if (previousState == ScrimState.PULSING) {
+ updateScrimColor(mScrimInFront, 1, Color.BLACK);
+ }
+ final boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn();
+ mBlankScreen = previousState == ScrimState.PULSING;
+ mCurrentBehindAlpha = 1;
+ mCurrentInFrontAlpha = alwaysOnEnabled ? mAodFrontScrimAlpha : 1f;
+ mCurrentInFrontTint = Color.BLACK;
+ mCurrentBehindTint = Color.BLACK;
+ // DisplayPowerManager will blank the screen for us, we just need
+ // to set our state.
+ mAnimateChange = false;
+ }
+ },
+
+ /**
+ * When phone wakes up because you received a notification.
+ */
+ PULSING {
+ @Override
+ public void prepare(ScrimState previousState) {
+ mCurrentBehindAlpha = 1;
+ mCurrentInFrontAlpha = 0;
+ mCurrentInFrontTint = Color.BLACK;
+ mCurrentBehindTint = Color.BLACK;
+ mBlankScreen = true;
+ updateScrimColor(mScrimInFront, 1, Color.BLACK);
+ }
+ },
+
+ /**
+ * Unlocked on top of an app (launcher or any other activity.)
+ */
+ UNLOCKED {
+ @Override
+ public void prepare(ScrimState previousState) {
+ mCurrentBehindAlpha = 0;
+ mCurrentInFrontAlpha = 0;
+ mAnimationDuration = StatusBar.FADE_KEYGUARD_DURATION;
+
+ if (previousState == ScrimState.AOD) {
+ // Fade from black to transparent when coming directly from AOD
+ updateScrimColor(mScrimInFront, 1, Color.BLACK);
+ updateScrimColor(mScrimBehind, 1, Color.BLACK);
+ // Scrims should still be black at the end of the transition.
+ mCurrentInFrontTint = Color.BLACK;
+ mCurrentBehindTint = Color.BLACK;
+ mBlankScreen = true;
+ } else {
+ // Scrims should still be black at the end of the transition.
+ mCurrentInFrontTint = Color.TRANSPARENT;
+ mCurrentBehindTint = Color.TRANSPARENT;
+ mBlankScreen = false;
+ }
+ }
+ };
+
+ boolean mBlankScreen = false;
+ long mAnimationDuration = ScrimController.ANIMATION_DURATION;
+ int mCurrentInFrontTint = Color.TRANSPARENT;
+ int mCurrentBehindTint = Color.TRANSPARENT;
+ boolean mAnimateChange = true;
+ float mCurrentInFrontAlpha;
+ float mCurrentBehindAlpha;
+ float mAodFrontScrimAlpha;
+ float mScrimBehindAlphaKeyguard;
+ ScrimView mScrimInFront;
+ ScrimView mScrimBehind;
+ DozeParameters mDozeParameters;
+
+ public void init(ScrimView scrimInFront, ScrimView scrimBehind, DozeParameters dozeParameters) {
+ mScrimInFront = scrimInFront;
+ mScrimBehind = scrimBehind;
+ mDozeParameters = dozeParameters;
+ }
+
+ public void prepare(ScrimState previousState) {
+ }
+
+ public float getFrontAlpha() {
+ return mCurrentInFrontAlpha;
+ }
+
+ public float getBehindAlpha() {
+ return mCurrentBehindAlpha;
+ }
+
+ public int getFrontTint() {
+ return mCurrentInFrontTint;
+ }
+
+ public int getBehindTint() {
+ return mCurrentBehindTint;
+ }
+
+ public long getAnimationDuration() {
+ return mAnimationDuration;
+ }
+
+ public boolean getBlanksScreen() {
+ return mBlankScreen;
+ }
+
+ public void updateScrimColor(ScrimView scrim, float alpha, int tint) {
+ Trace.traceCounter(Trace.TRACE_TAG_APP,
+ scrim == mScrimInFront ? "front_scrim_alpha" : "back_scrim_alpha",
+ (int) (alpha * 255));
+
+ Trace.traceCounter(Trace.TRACE_TAG_APP,
+ scrim == mScrimInFront ? "front_scrim_tint" : "back_scrim_tint",
+ Color.alpha(tint));
+
+ scrim.setTint(tint);
+ scrim.setViewAlpha(alpha);
+ }
+
+ public boolean getAnimateChange() {
+ return mAnimateChange;
+ }
+
+ public void setAodFrontScrimAlpha(float aodFrontScrimAlpha) {
+ mAodFrontScrimAlpha = aodFrontScrimAlpha;
+ }
+
+ public void setScrimBehindAlphaKeyguard(float scrimBehindAlphaKeyguard) {
+ mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard;
+ }
+}
\ No newline at end of file
diff --git a/com/android/systemui/statusbar/phone/StatusBar.java b/com/android/systemui/statusbar/phone/StatusBar.java
index 6775615..dc8100f 100644
--- a/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/com/android/systemui/statusbar/phone/StatusBar.java
@@ -79,7 +79,6 @@
import android.graphics.drawable.Drawable;
import android.media.AudioAttributes;
import android.media.MediaMetadata;
-import android.media.session.MediaSessionManager;
import android.metrics.LogMaker;
import android.net.Uri;
import android.os.AsyncTask;
@@ -239,6 +238,7 @@
.OnChildLocationsChangedListener;
import com.android.systemui.util.NotificationChannels;
import com.android.systemui.util.leak.LeakDetector;
+import com.android.systemui.util.wakelock.WakeLock;
import com.android.systemui.volume.VolumeComponent;
import java.io.FileDescriptor;
@@ -387,6 +387,7 @@
private VolumeComponent mVolumeComponent;
private BrightnessMirrorController mBrightnessMirrorController;
+ private boolean mBrightnessMirrorVisible;
protected FingerprintUnlockController mFingerprintUnlockController;
private LightBarController mLightBarController;
protected LockscreenWallpaper mLockscreenWallpaper;
@@ -647,6 +648,31 @@
}
};
+ // Notifies StatusBarKeyguardViewManager every time the keyguard transition is over,
+ // this animation is tied to the scrim for historic reasons.
+ // TODO: notify when keyguard has faded away instead of the scrim.
+ private final ScrimController.Callback mUnlockScrimCallback = new ScrimController
+ .Callback() {
+ @Override
+ public void onFinished() {
+ notifyKeyguardState();
+ }
+
+ @Override
+ public void onCancelled() {
+ notifyKeyguardState();
+ }
+
+ private void notifyKeyguardState() {
+ if (mStatusBarKeyguardViewManager == null) {
+ Log.w(TAG, "Tried to notify keyguard visibility when "
+ + "mStatusBarKeyguardViewManager was null");
+ return;
+ }
+ mStatusBarKeyguardViewManager.onKeyguardFadedAway();
+ }
+ };
+
private NotificationMessagingUtil mMessagingUtil;
private KeyguardUserSwitcher mKeyguardUserSwitcher;
private UserSwitcherController mUserSwitcherController;
@@ -919,7 +945,14 @@
mNotificationPanel = mStatusBarWindow.findViewById(R.id.notification_panel);
mStackScroller = mStatusBarWindow.findViewById(R.id.notification_stack_scroller);
mGutsManager = new NotificationGutsManager(this, mStackScroller,
- mCheckSaveListener, mContext);
+ mCheckSaveListener, mContext,
+ key -> {
+ try {
+ mBarService.onNotificationSettingsViewed(key);
+ } catch (RemoteException e) {
+ // if we're here we're dead
+ }
+ });
mNotificationPanel.setStatusBar(this);
mNotificationPanel.setGroupManager(mGroupManager);
mAboveShelfObserver = new AboveShelfObserver(mStackScroller);
@@ -1039,7 +1072,7 @@
if (mStatusBarWindowManager != null) {
mStatusBarWindowManager.setScrimsVisible(scrimsVisible);
}
- });
+ }, new DozeParameters(mContext));
if (mScrimSrcModeEnabled) {
Runnable runnable = () -> {
boolean asSrc = mBackdrop.getVisibility() != View.VISIBLE;
@@ -1075,7 +1108,10 @@
final QSTileHost qsh = SystemUIFactory.getInstance().createQSTileHost(mContext, this,
mIconController);
mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow,
- mScrimController);
+ (visible) -> {
+ mBrightnessMirrorVisible = visible;
+ updateScrimController();
+ });
fragmentHostManager.addTagListener(QS.TAG, (tag, f) -> {
QS qs = (QS) f;
if (qs instanceof QSFragment) {
@@ -1222,13 +1258,13 @@
reevaluateStyles();
}
- private void reinflateViews() {
+ private void onThemeChanged() {
reevaluateStyles();
// Clock and bottom icons
- mNotificationPanel.onOverlayChanged();
+ mNotificationPanel.onThemeChanged();
// The status bar on the keyguard is a special layout.
- if (mKeyguardStatusBar != null) mKeyguardStatusBar.onOverlayChanged();
+ if (mKeyguardStatusBar != null) mKeyguardStatusBar.onThemeChanged();
// Recreate Indication controller because internal references changed
mKeyguardIndicationController =
SystemUIFactory.getInstance().createKeyguardIndicationController(mContext,
@@ -1239,11 +1275,8 @@
.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
mKeyguardIndicationController.setVisible(mState == StatusBarState.KEYGUARD);
mKeyguardIndicationController.setDozing(mDozing);
- if (mBrightnessMirrorController != null) {
- mBrightnessMirrorController.onOverlayChanged();
- }
if (mStatusBarKeyguardViewManager != null) {
- mStatusBarKeyguardViewManager.onOverlayChanged();
+ mStatusBarKeyguardViewManager.onThemeChanged();
}
if (mAmbientIndicationContainer instanceof AutoReinflateContainer) {
((AutoReinflateContainer) mAmbientIndicationContainer).inflateLayout();
@@ -1258,6 +1291,13 @@
updateEmptyShadeView();
}
+ @Override
+ public void onOverlayChanged() {
+ if (mBrightnessMirrorController != null) {
+ mBrightnessMirrorController.onOverlayChanged();
+ }
+ }
+
private void updateNotificationsOnDensityOrFontScaleChanged() {
ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
for (int i = 0; i < activeNotifications.size(); i++) {
@@ -1447,8 +1487,7 @@
mDozeScrimController, keyguardViewMediator,
mScrimController, this, UnlockMethodCache.getInstance(mContext));
mStatusBarKeyguardViewManager = keyguardViewMediator.registerStatusBar(this,
- getBouncerContainer(), mScrimController,
- mFingerprintUnlockController);
+ getBouncerContainer(), mFingerprintUnlockController);
mKeyguardIndicationController
.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
mFingerprintUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
@@ -1470,6 +1509,11 @@
}
}, REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY);
}
+ try {
+ mBarService.onNotificationDirectReplied(entry.key);
+ } catch (RemoteException e) {
+ // system process is dead if we're here.
+ }
}
});
@@ -1785,9 +1829,14 @@
final int id = n.getId();
final int userId = n.getUserId();
try {
- // TODO: record actual dismissal surface
+ int dismissalSurface = NotificationStats.DISMISSAL_SHADE;
+ if (isHeadsUp(n.getKey())) {
+ dismissalSurface = NotificationStats.DISMISSAL_PEEK;
+ } else if (mStackScroller.hasPulsingNotifications()) {
+ dismissalSurface = NotificationStats.DISMISSAL_AOD;
+ }
mBarService.onNotificationClear(pkg, tag, id, userId, n.getKey(),
- NotificationStats.DISMISSAL_OTHER);
+ dismissalSurface);
if (FORCE_REMOTE_INPUT_HISTORY
&& mKeysKeptForRemoteInput.contains(n.getKey())) {
mKeysKeptForRemoteInput.remove(n.getKey());
@@ -2620,7 +2669,7 @@
}
public boolean isPulsing() {
- return mDozeScrimController.isPulsing();
+ return mDozeScrimController != null && mDozeScrimController.isPulsing();
}
@Override
@@ -3328,7 +3377,7 @@
}
if (mScrimController != null) {
- mScrimController.dump(pw);
+ mScrimController.dump(fd, pw, args);
}
if (DUMPTRUCK) {
@@ -3393,7 +3442,19 @@
private void addStatusBarWindow() {
makeStatusBarView();
mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class);
- mRemoteInputController = new RemoteInputController(mHeadsUpManager);
+ mRemoteInputController = new RemoteInputController(new RemoteInputController.Delegate() {
+ public void setRemoteInputActive(NotificationData.Entry entry,
+ boolean remoteInputActive) {
+ mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
+ }
+ public void lockScrollTo(NotificationData.Entry entry) {
+ mStackScroller.lockScrollTo(entry.row);
+ }
+ public void requestDisallowLongPressAndDismiss() {
+ mStackScroller.requestDisallowLongPress();
+ mStackScroller.requestDisallowDismiss();
+ }
+ });
mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
}
@@ -4061,7 +4122,6 @@
releaseGestureWakeLock();
runLaunchTransitionEndRunnable();
mLaunchTransitionFadingAway = false;
- mScrimController.forceHideScrims(false /* hide */, false /* animated */);
updateMediaMetaData(true /* metaDataChanged */, true);
}
@@ -4094,7 +4154,7 @@
if (beforeFading != null) {
beforeFading.run();
}
- mScrimController.forceHideScrims(true /* hide */, false /* animated */);
+ updateScrimController();
updateMediaMetaData(false, true);
mNotificationPanel.setAlpha(1);
mStackScroller.setParentNotFullyVisible(true);
@@ -4125,6 +4185,13 @@
.setStartDelay(0)
.setDuration(FADE_KEYGUARD_DURATION_PULSING)
.setInterpolator(ScrimController.KEYGUARD_FADE_OUT_INTERPOLATOR)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ hideKeyguard();
+ mStatusBarKeyguardViewManager.onKeyguardFadedAway();
+ }
+ })
.start();
}
@@ -4132,7 +4199,6 @@
* Plays the animation when an activity that was occluding Keyguard goes away.
*/
public void animateKeyguardUnoccluding() {
- mScrimController.animateKeyguardUnoccluding(500);
mNotificationPanel.setExpandedFraction(0f);
animateExpandNotificationsPanel();
}
@@ -4320,11 +4386,6 @@
mAmbientIndicationContainer.setVisibility(View.INVISIBLE);
}
}
- if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) {
- mScrimController.setKeyguardShowing(true);
- } else {
- mScrimController.setKeyguardShowing(false);
- }
mNotificationPanel.setBarState(mState, mKeyguardFadingAway, goingToFullShade);
updateTheme();
updateDozingState();
@@ -4332,6 +4393,7 @@
updateStackScrollerState(goingToFullShade, fromShadeLocked);
updateNotifications();
checkBarModes();
+ updateScrimController();
updateMediaMetaData(false, mState != StatusBarState.KEYGUARD);
mKeyguardMonitor.notifyKeyguardState(mStatusBarKeyguardViewManager.isShowing(),
mUnlockMethodCache.isMethodSecure(),
@@ -4369,7 +4431,7 @@
if (mContext.getThemeResId() != themeResId) {
mContext.setTheme(themeResId);
if (inflated) {
- reinflateViews();
+ onThemeChanged();
}
}
@@ -4395,11 +4457,10 @@
boolean animate = !mDozing && mDozeServiceHost.shouldAnimateWakeup();
mNotificationPanel.setDozing(mDozing, animate);
mStackScroller.setDark(mDozing, animate, mWakeUpTouchLocation);
- mScrimController.setDozing(mDozing);
+ mDozeScrimController.setDozing(mDozing);
mKeyguardIndicationController.setDozing(mDozing);
mNotificationPanel.setDark(mDozing, animate);
updateQsExpansionEnabled();
- mDozeScrimController.setDozing(mDozing, animate);
updateRowStates();
Trace.endSection();
}
@@ -4894,6 +4955,7 @@
if (mStatusBarView != null) mStatusBarView.setBouncerShowing(bouncerShowing);
updateHideIconsForBouncer(true /* animate */);
recomputeDisableFlags(true /* animate */);
+ updateScrimController();
}
public void cancelCurrentTouch() {
@@ -4945,12 +5007,10 @@
mStackScroller.setAnimationsEnabled(true);
mVisualStabilityManager.setScreenOn(true);
mNotificationPanel.setTouchDisabled(false);
-
- maybePrepareWakeUpFromAod();
-
mDozeServiceHost.stopDozing();
updateVisibleToUser();
updateIsKeyguard();
+ updateScrimController();
}
};
@@ -4960,18 +5020,16 @@
mFalsingManager.onScreenTurningOn();
mNotificationPanel.onScreenTurningOn();
- maybePrepareWakeUpFromAod();
-
if (mLaunchCameraOnScreenTurningOn) {
mNotificationPanel.launchCamera(false, mLastCameraLaunchSource);
mLaunchCameraOnScreenTurningOn = false;
}
+
+ updateScrimController();
}
@Override
public void onScreenTurnedOn() {
- mScrimController.wakeUpFromAod();
- mDozeScrimController.onScreenTurnedOn();
}
@Override
@@ -4989,13 +5047,6 @@
return mWakefulnessLifecycle.getWakefulness();
}
- private void maybePrepareWakeUpFromAod() {
- int wakefulness = mWakefulnessLifecycle.getWakefulness();
- if (mDozing && wakefulness == WAKEFULNESS_WAKING && !isPulsing()) {
- mScrimController.prepareWakeUpFromAod();
- }
- }
-
private void vibrateForCameraGesture() {
// Make sure to pass -1 for repeat so VibratorService doesn't stop us when going to sleep.
mVibrator.vibrate(mCameraLaunchGestureVibePattern, -1 /* repeat */);
@@ -5078,12 +5129,12 @@
if (!mDeviceInteractive) {
// Avoid flickering of the scrim when we instant launch the camera and the bouncer
// comes on.
- mScrimController.dontAnimateBouncerChangesUntilNextFrame();
mGestureWakeLock.acquire(LAUNCH_TRANSITION_TIMEOUT_MS + 1000L);
}
if (isScreenTurningOnOrOn()) {
if (DEBUG_CAMERA_LIFT) Slog.d(TAG, "Launching camera");
mNotificationPanel.launchCamera(mDeviceInteractive /* animate */, source);
+ updateScrimController();
} else {
// We need to defer the camera launch until the screen comes on, since otherwise
// we will dismiss us too early since we are waiting on an activity to be drawn and
@@ -5125,15 +5176,16 @@
private void updateDozing() {
Trace.beginSection("StatusBar#updateDozing");
// When in wake-and-unlock while pulsing, keep dozing state until fully unlocked.
- mDozing = mDozingRequested && mState == StatusBarState.KEYGUARD
+ boolean dozing = mDozingRequested && mState == StatusBarState.KEYGUARD
|| mFingerprintUnlockController.getMode()
== FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
// When in wake-and-unlock we may not have received a change to mState
// but we still should not be dozing, manually set to false.
if (mFingerprintUnlockController.getMode() ==
FingerprintUnlockController.MODE_WAKE_AND_UNLOCK) {
- mDozing = false;
+ dozing = false;
}
+ mDozing = dozing;
mStatusBarWindowManager.setDozing(mDozing);
mStatusBarKeyguardViewManager.setDozing(mDozing);
if (mAmbientIndicationContainer instanceof DozeReceiver) {
@@ -5143,6 +5195,24 @@
Trace.endSection();
}
+ public void updateScrimController() {
+ if (mBouncerShowing) {
+ mScrimController.transitionTo(ScrimState.BOUNCER);
+ } else if (mLaunchCameraOnScreenTurningOn || isInLaunchTransition()) {
+ mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback);
+ } else if (mBrightnessMirrorVisible) {
+ mScrimController.transitionTo(ScrimState.BRIGHTNESS_MIRROR);
+ } else if (isPulsing()) {
+ // Handled in DozeScrimController#setPulsing
+ } else if (mDozing) {
+ mScrimController.transitionTo(ScrimState.AOD);
+ } else if (mIsKeyguard) {
+ mScrimController.transitionTo(ScrimState.KEYGUARD);
+ } else {
+ mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback);
+ }
+ }
+
public boolean isKeyguardShowing() {
if (mStatusBarKeyguardViewManager == null) {
Slog.i(TAG, "isKeyguardShowing() called before startKeyguard(), returning true");
@@ -5202,7 +5272,6 @@
}
mDozeScrimController.pulse(new PulseCallback() {
-
@Override
public void onPulseStarted() {
callback.onPulseStarted();
@@ -5287,11 +5356,6 @@
}
@Override
- public void abortPulsing() {
- mDozeScrimController.abortPulsing();
- }
-
- @Override
public void extendPulse() {
mDozeScrimController.extendPulse();
}
@@ -5327,7 +5391,7 @@
@Override
public void setAodDimmingScrim(float scrimOpacity) {
- mDozeScrimController.setAodDimmingScrim(scrimOpacity);
+ ScrimState.AOD.setAodFrontScrimAlpha(scrimOpacity);
}
public void dispatchDoubleTap(float viewX, float viewY) {
diff --git a/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 09828dc..ef05bbb 100644
--- a/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -24,7 +24,6 @@
import android.content.Context;
import android.os.Bundle;
import android.os.SystemClock;
-import android.os.Trace;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
@@ -71,17 +70,14 @@
protected final Context mContext;
private final StatusBarWindowManager mStatusBarWindowManager;
- private final boolean mDisplayBlanksAfterDoze;
protected LockPatternUtils mLockPatternUtils;
protected ViewMediatorCallback mViewMediatorCallback;
protected StatusBar mStatusBar;
- private ScrimController mScrimController;
private FingerprintUnlockController mFingerprintUnlockController;
private ViewGroup mContainer;
- private boolean mScreenTurnedOn;
protected KeyguardBouncer mBouncer;
protected boolean mShowing;
protected boolean mOccluded;
@@ -95,12 +91,10 @@
private boolean mLastBouncerDismissible;
protected boolean mLastRemoteInputActive;
private boolean mLastDozing;
- private boolean mLastDeferScrimFadeOut;
private int mLastFpMode;
private OnDismissAction mAfterKeyguardGoneAction;
private final ArrayList<Runnable> mAfterKeyguardGoneRunnables = new ArrayList<>();
- private boolean mDeferScrimFadeOut;
// Dismiss action to be launched when we stop dozing or the keyguard is gone.
private DismissWithActionRequest mPendingWakeupAction;
@@ -125,18 +119,14 @@
mLockPatternUtils = lockPatternUtils;
mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class);
KeyguardUpdateMonitor.getInstance(context).registerCallback(mUpdateMonitorCallback);
- mDisplayBlanksAfterDoze = context.getResources().getBoolean(
- com.android.internal.R.bool.config_displayBlanksAfterDoze);
}
public void registerStatusBar(StatusBar statusBar,
ViewGroup container,
- ScrimController scrimController,
FingerprintUnlockController fingerprintUnlockController,
DismissCallbackRegistry dismissCallbackRegistry) {
mStatusBar = statusBar;
mContainer = container;
- mScrimController = scrimController;
mFingerprintUnlockController = fingerprintUnlockController;
mBouncer = SystemUIFactory.getInstance().createKeyguardBouncer(mContext,
mViewMediatorCallback, mLockPatternUtils, container, dismissCallbackRegistry);
@@ -149,7 +139,6 @@
public void show(Bundle options) {
mShowing = true;
mStatusBarWindowManager.setKeyguardShowing(true);
- mScrimController.abortKeyguardFadingOut();
reset(true /* hideBouncerWhenShowing */);
}
@@ -253,15 +242,7 @@
}
public void onScreenTurnedOn() {
- Trace.beginSection("StatusBarKeyguardViewManager#onScreenTurnedOn");
- mScreenTurnedOn = true;
- if (mDeferScrimFadeOut) {
- mDeferScrimFadeOut = false;
- animateScrimControllerKeyguardFadingOut(0, WAKE_AND_UNLOCK_SCRIM_FADEOUT_DURATION_MS,
- true /* skipFirstFrame */);
- updateStates();
- }
- Trace.endSection();
+ // TODO: remove
}
@Override
@@ -285,7 +266,7 @@
}
public void onScreenTurnedOff() {
- mScreenTurnedOn = false;
+ // TODO: remove
}
public void notifyDeviceWakeUpRequested() {
@@ -374,10 +355,6 @@
mStatusBarWindowManager.setKeyguardFadingAway(true);
hideBouncer(true /* destroyView */);
updateStates();
- mScrimController.animateKeyguardFadingOut(
- StatusBar.FADE_KEYGUARD_START_DELAY,
- StatusBar.FADE_KEYGUARD_DURATION, null,
- false /* skipFirstFrame */);
}
}, new Runnable() {
@Override
@@ -400,36 +377,16 @@
mFingerprintUnlockController.startKeyguardFadingAway();
hideBouncer(true /* destroyView */);
if (wakeUnlockPulsing) {
- mStatusBarWindowManager.setKeyguardFadingAway(true);
mStatusBar.fadeKeyguardWhilePulsing();
- animateScrimControllerKeyguardFadingOut(delay, fadeoutDuration,
- mStatusBar::hideKeyguard, false /* skipFirstFrame */);
+ wakeAndUnlockDejank();
} else {
mFingerprintUnlockController.startKeyguardFadingAway();
mStatusBar.setKeyguardFadingAway(startTime, delay, fadeoutDuration);
boolean staying = mStatusBar.hideKeyguard();
if (!staying) {
mStatusBarWindowManager.setKeyguardFadingAway(true);
- if (mFingerprintUnlockController.getMode() == MODE_WAKE_AND_UNLOCK) {
- boolean turnedOnSinceAuth =
- mFingerprintUnlockController.hasScreenTurnedOnSinceAuthenticating();
- if (!mScreenTurnedOn || mDisplayBlanksAfterDoze && !turnedOnSinceAuth) {
- // Not ready to animate yet; either because the screen is not on yet,
- // or it is on but will turn off before waking out of doze.
- mDeferScrimFadeOut = true;
- } else {
-
- // Screen is already on, don't defer with fading out.
- animateScrimControllerKeyguardFadingOut(0,
- WAKE_AND_UNLOCK_SCRIM_FADEOUT_DURATION_MS,
- true /* skipFirstFrame */);
- }
- } else {
- animateScrimControllerKeyguardFadingOut(delay, fadeoutDuration,
- false /* skipFirstFrame */);
- }
+ wakeAndUnlockDejank();
} else {
- mScrimController.animateGoingToFullShade(delay, fadeoutDuration);
mStatusBar.finishKeyguardFadingAway();
mFingerprintUnlockController.finishKeyguardFadingAway();
}
@@ -444,35 +401,22 @@
hideBouncer(true /* destroyView */);
}
- public void onOverlayChanged() {
+ public void onThemeChanged() {
hideBouncer(true /* destroyView */);
mBouncer.prepare();
}
- private void animateScrimControllerKeyguardFadingOut(long delay, long duration,
- boolean skipFirstFrame) {
- animateScrimControllerKeyguardFadingOut(delay, duration, null /* endRunnable */,
- skipFirstFrame);
+ public void onKeyguardFadedAway() {
+ mContainer.postDelayed(() -> mStatusBarWindowManager.setKeyguardFadingAway(false),
+ 100);
+ mStatusBar.finishKeyguardFadingAway();
+ mFingerprintUnlockController.finishKeyguardFadingAway();
+ WindowManagerGlobal.getInstance().trimMemory(
+ ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
+
}
- private void animateScrimControllerKeyguardFadingOut(long delay, long duration,
- final Runnable endRunnable, boolean skipFirstFrame) {
- Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "Fading out", 0);
- mScrimController.animateKeyguardFadingOut(delay, duration, new Runnable() {
- @Override
- public void run() {
- if (endRunnable != null) {
- endRunnable.run();
- }
- mContainer.postDelayed(() -> mStatusBarWindowManager.setKeyguardFadingAway(false),
- 100);
- mStatusBar.finishKeyguardFadingAway();
- mFingerprintUnlockController.finishKeyguardFadingAway();
- WindowManagerGlobal.getInstance().trimMemory(
- ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
- Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, "Fading out", 0);
- }
- }, skipFirstFrame);
+ private void wakeAndUnlockDejank() {
if (mFingerprintUnlockController.getMode() == MODE_WAKE_AND_UNLOCK
&& LatencyTracker.isEnabled(mContext)) {
DejankUtils.postAfterTraversal(() ->
@@ -593,7 +537,6 @@
if (bouncerShowing != mLastBouncerShowing || mFirstUpdate) {
mStatusBarWindowManager.setBouncerShowing(bouncerShowing);
mStatusBar.setBouncerShowing(bouncerShowing);
- mScrimController.setBouncerShowing(bouncerShowing);
}
KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
@@ -611,7 +554,6 @@
mLastBouncerDismissible = bouncerDismissible;
mLastRemoteInputActive = remoteInputActive;
mLastDozing = mDozing;
- mLastDeferScrimFadeOut = mDeferScrimFadeOut;
mLastFpMode = mFingerprintUnlockController.getMode();
mStatusBar.onKeyguardViewManagerStatesUpdated();
}
@@ -624,7 +566,7 @@
boolean keyguardShowing = mShowing && !mOccluded;
boolean hideWhileDozing = mDozing && fpMode != MODE_WAKE_AND_UNLOCK_PULSING;
return (!keyguardShowing && !hideWhileDozing || mBouncer.isShowing()
- || mRemoteInputActive) && !mDeferScrimFadeOut;
+ || mRemoteInputActive);
}
/**
@@ -634,7 +576,7 @@
boolean keyguardShowing = mLastShowing && !mLastOccluded;
boolean hideWhileDozing = mLastDozing && mLastFpMode != MODE_WAKE_AND_UNLOCK_PULSING;
return (!keyguardShowing && !hideWhileDozing || mLastBouncerShowing
- || mLastRemoteInputActive) && !mLastDeferScrimFadeOut;
+ || mLastRemoteInputActive);
}
public boolean shouldDismissOnMenuPressed() {
diff --git a/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
index 42ce4c5..a011952 100644
--- a/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
+++ b/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
@@ -16,7 +16,9 @@
package com.android.systemui.statusbar.policy;
+import android.annotation.NonNull;
import android.util.ArraySet;
+import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewPropertyAnimator;
@@ -25,55 +27,52 @@
import com.android.internal.util.Preconditions;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.StatusBarWindowView;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
+import java.util.function.Consumer;
+
/**
* Controls showing and hiding of the brightness mirror.
*/
public class BrightnessMirrorController
implements CallbackController<BrightnessMirrorController.BrightnessMirrorListener> {
- private final NotificationStackScrollLayout mStackScroller;
- public long TRANSITION_DURATION_OUT = 150;
- public long TRANSITION_DURATION_IN = 200;
+ private final static long TRANSITION_DURATION_OUT = 150;
+ private final static long TRANSITION_DURATION_IN = 200;
private final StatusBarWindowView mStatusBarWindow;
- private final ScrimController mScrimController;
+ private final NotificationStackScrollLayout mStackScroller;
+ private final Consumer<Boolean> mVisibilityCallback;
private final View mNotificationPanel;
private final ArraySet<BrightnessMirrorListener> mBrightnessMirrorListeners = new ArraySet<>();
private final int[] mInt2Cache = new int[2];
private View mBrightnessMirror;
public BrightnessMirrorController(StatusBarWindowView statusBarWindow,
- ScrimController scrimController) {
+ @NonNull Consumer<Boolean> visibilityCallback) {
mStatusBarWindow = statusBarWindow;
mBrightnessMirror = statusBarWindow.findViewById(R.id.brightness_mirror);
mNotificationPanel = statusBarWindow.findViewById(R.id.notification_panel);
- mStackScroller = (NotificationStackScrollLayout) statusBarWindow.findViewById(
- R.id.notification_stack_scroller);
- mScrimController = scrimController;
+ mStackScroller = statusBarWindow.findViewById(R.id.notification_stack_scroller);
+ mVisibilityCallback = visibilityCallback;
}
public void showMirror() {
mBrightnessMirror.setVisibility(View.VISIBLE);
mStackScroller.setFadingOut(true);
- mScrimController.forceHideScrims(true /* hide */, true /* animated */);
+ mVisibilityCallback.accept(true);
outAnimation(mNotificationPanel.animate())
.withLayer();
}
public void hideMirror() {
- mScrimController.forceHideScrims(false /* hide */, true /* animated */);
+ mVisibilityCallback.accept(false);
inAnimation(mNotificationPanel.animate())
.withLayer()
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- mBrightnessMirror.setVisibility(View.INVISIBLE);
- mStackScroller.setFadingOut(false);
- }
+ .withEndAction(() -> {
+ mBrightnessMirror.setVisibility(View.INVISIBLE);
+ mStackScroller.setFadingOut(false);
});
}
@@ -128,9 +127,11 @@
}
private void reinflate() {
+ ContextThemeWrapper qsThemeContext =
+ new ContextThemeWrapper(mBrightnessMirror.getContext(), R.style.qs_theme);
int index = mStatusBarWindow.indexOfChild(mBrightnessMirror);
mStatusBarWindow.removeView(mBrightnessMirror);
- mBrightnessMirror = LayoutInflater.from(mBrightnessMirror.getContext()).inflate(
+ mBrightnessMirror = LayoutInflater.from(qsThemeContext).inflate(
R.layout.brightness_mirror, mStatusBarWindow, false);
mStatusBarWindow.addView(mBrightnessMirror, index);
diff --git a/com/android/systemui/statusbar/policy/RemoteInputView.java b/com/android/systemui/statusbar/policy/RemoteInputView.java
index 37b0de4..4fc5044 100644
--- a/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -53,11 +53,9 @@
import com.android.internal.logging.nano.MetricsProto;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.notification.NotificationViewWrapper;
-import com.android.systemui.statusbar.stack.ScrollContainer;
import com.android.systemui.statusbar.stack.StackStateAnimator;
/**
@@ -82,8 +80,6 @@
private NotificationData.Entry mEntry;
- private ScrollContainer mScrollContainer;
- private View mScrollContainerChild;
private boolean mRemoved;
private int mRevealCx;
@@ -347,41 +343,16 @@
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- findScrollContainer();
- if (mScrollContainer != null) {
- mScrollContainer.requestDisallowLongPress();
- mScrollContainer.requestDisallowDismiss();
- }
+ mController.requestDisallowLongPressAndDismiss();
}
return super.onInterceptTouchEvent(ev);
}
public boolean requestScrollTo() {
- findScrollContainer();
- mScrollContainer.lockScrollTo(mScrollContainerChild);
+ mController.lockScrollTo(mEntry);
return true;
}
- private void findScrollContainer() {
- if (mScrollContainer == null) {
- mScrollContainerChild = null;
- ViewParent p = this;
- while (p != null) {
- if (mScrollContainerChild == null && p instanceof ExpandableView) {
- mScrollContainerChild = (View) p;
- }
- if (p.getParent() instanceof ScrollContainer) {
- mScrollContainer = (ScrollContainer) p.getParent();
- if (mScrollContainerChild == null) {
- mScrollContainerChild = (View) p;
- }
- break;
- }
- p = p.getParent();
- }
- }
- }
-
public boolean isActive() {
return mEditText.isFocused() && mEditText.isEnabled();
}
diff --git a/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java b/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
index 527addf..f5ae88b 100644
--- a/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
+++ b/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
@@ -154,7 +154,9 @@
avatar = new UserIconDrawable(avatarSize)
.setIcon(rawAvatar).setBadgeIfManagedUser(mContext, userId).bake();
} else {
- avatar = UserIcons.getDefaultUserIcon(isGuest? UserHandle.USER_NULL : userId,
+ avatar = UserIcons.getDefaultUserIcon(
+ context.getResources(),
+ isGuest? UserHandle.USER_NULL : userId,
lightIcon);
}
diff --git a/com/android/systemui/statusbar/policy/UserSwitcherController.java b/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 700c01a..7006d38 100644
--- a/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -747,7 +747,8 @@
if (item.isAddUser) {
return context.getDrawable(R.drawable.ic_add_circle_qs);
}
- Drawable icon = UserIcons.getDefaultUserIcon(item.resolveId(), /* light= */ false);
+ Drawable icon = UserIcons.getDefaultUserIcon(
+ context.getResources(), item.resolveId(), /* light= */ false);
if (item.isGuest) {
icon.setColorFilter(Utils.getColorAttr(context, android.R.attr.colorForeground),
Mode.SRC_IN);
@@ -910,6 +911,7 @@
context.getString(android.R.string.cancel), this);
setButton(DialogInterface.BUTTON_POSITIVE,
context.getString(R.string.guest_exit_guest_dialog_remove), this);
+ SystemUIDialog.setWindowOnTop(this);
setCanceledOnTouchOutside(false);
mGuestId = guestId;
mTargetId = targetId;
@@ -937,6 +939,7 @@
context.getString(android.R.string.cancel), this);
setButton(DialogInterface.BUTTON_POSITIVE,
context.getString(android.R.string.ok), this);
+ SystemUIDialog.setWindowOnTop(this);
}
@Override
@@ -957,7 +960,7 @@
}
int id = user.id;
Bitmap icon = UserIcons.convertToBitmap(UserIcons.getDefaultUserIcon(
- id, /* light= */ false));
+ mContext.getResources(), id, /* light= */ false));
mUserManager.setUserIcon(id, icon);
switchToUserId(id);
}
diff --git a/com/android/systemui/statusbar/stack/AnimationProperties.java b/com/android/systemui/statusbar/stack/AnimationProperties.java
index ebb0a6d..2f6e658 100644
--- a/com/android/systemui/statusbar/stack/AnimationProperties.java
+++ b/com/android/systemui/statusbar/stack/AnimationProperties.java
@@ -36,7 +36,12 @@
* @return an animation filter for this animation.
*/
public AnimationFilter getAnimationFilter() {
- return new AnimationFilter();
+ return new AnimationFilter() {
+ @Override
+ public boolean shouldAnimateProperty(Property property) {
+ return true;
+ }
+ };
}
/**
diff --git a/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
index fe53104..c0241e3 100644
--- a/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
+++ b/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
@@ -1260,4 +1260,17 @@
public boolean isUserLocked() {
return mUserLocked;
}
+
+ public void setCurrentBottomRoundness(float currentBottomRoundness) {
+ boolean last = true;
+ for (int i = mChildren.size() - 1; i >= 0; i--) {
+ ExpandableNotificationRow child = mChildren.get(i);
+ if (child.getVisibility() == View.GONE) {
+ continue;
+ }
+ float bottomRoundness = last ? currentBottomRoundness : 0.0f;
+ child.setBottomRoundness(bottomRoundness, isShown() /* animate */);
+ last = false;
+ }
+ }
}
diff --git a/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index efe049a..ebebfac 100644
--- a/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -30,6 +30,7 @@
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
+import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
@@ -75,6 +76,7 @@
import com.android.systemui.statusbar.DismissView;
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.ExpandableOutlineView;
import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.NotificationGuts;
@@ -82,8 +84,10 @@
import com.android.systemui.statusbar.NotificationSnooze;
import com.android.systemui.statusbar.StackScrollerDecorView;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.FakeShadowView;
import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -108,8 +112,7 @@
public class NotificationStackScrollLayout extends ViewGroup
implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener,
- NotificationMenuRowPlugin.OnMenuEventListener, ScrollContainer,
- VisibilityLocationProvider {
+ NotificationMenuRowPlugin.OnMenuEventListener, VisibilityLocationProvider {
public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
private static final String TAG = "StackScroller";
@@ -121,12 +124,23 @@
* Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
*/
private static final int INVALID_POINTER = -1;
+ private static final AnimatableProperty SIDE_PADDINGS = AnimatableProperty.from(
+ "sidePaddings",
+ NotificationStackScrollLayout::setCurrentSidePadding,
+ NotificationStackScrollLayout::getCurrentSidePadding,
+ R.id.side_padding_animator_tag,
+ R.id.side_padding_animator_end_tag,
+ R.id.side_padding_animator_start_tag);
+ private static final AnimationProperties SIDE_PADDING_PROPERTIES =
+ new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
private ExpandHelper mExpandHelper;
private NotificationSwipeHelper mSwipeHelper;
private boolean mSwipingInProgress;
private int mCurrentStackHeight = Integer.MAX_VALUE;
private final Paint mBackgroundPaint = new Paint();
+ private final Path mBackgroundPath = new Path();
+ private final float[] mBackgroundRadii = new float[8];
private final boolean mShouldDrawNotificationBackground;
private float mExpandedHeight;
@@ -157,6 +171,7 @@
private int mTopPadding;
private int mBottomMargin;
private int mBottomInset = 0;
+ private float mCurrentSidePadding;
/**
* The algorithm which calculates the properties for our children
@@ -383,6 +398,9 @@
private int mCachedBackgroundColor;
private boolean mHeadsUpGoingAwayAnimationsAllowed = true;
private Runnable mAnimateScroll = this::animateScroll;
+ private int mCornerRadius;
+ private int mLockscreenSidePaddings;
+ private int mSidePaddings;
public NotificationStackScrollLayout(Context context) {
this(context, null);
@@ -419,6 +437,8 @@
res.getBoolean(R.bool.config_fadeNotificationsOnDismiss);
updateWillNotDraw();
+ mBackgroundPaint.setAntiAlias(true);
+ mBackgroundPaint.setStyle(Paint.Style.FILL);
if (DEBUG) {
mDebugPaint = new Paint();
mDebugPaint.setColor(0xffff0000);
@@ -466,8 +486,7 @@
protected void onDraw(Canvas canvas) {
if (mShouldDrawNotificationBackground && !mAmbientState.isDark()
&& mCurrentBounds.top < mCurrentBounds.bottom) {
- canvas.drawRect(0, mCurrentBounds.top, getWidth(), mCurrentBounds.bottom,
- mBackgroundPaint);
+ canvas.drawPath(mBackgroundPath, mBackgroundPaint);
}
if (DEBUG) {
@@ -520,8 +539,12 @@
R.dimen.min_top_overscroll_to_qs);
mStatusBarHeight = res.getDimensionPixelOffset(R.dimen.status_bar_height);
mBottomMargin = res.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom);
+ mLockscreenSidePaddings = res.getDimensionPixelSize(
+ R.dimen.notification_lockscreen_side_paddings);
mMinInteractionHeight = res.getDimensionPixelSize(
R.dimen.notification_min_interaction_height);
+ mCornerRadius = res.getDimensionPixelSize(
+ Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius));
}
public void setDrawBackgroundAsSrc(boolean asSrc) {
@@ -1195,7 +1218,6 @@
mScrollingEnabled = enable;
}
- @Override
public void lockScrollTo(View v) {
if (mForcedScroll == v) {
return;
@@ -1204,7 +1226,6 @@
scrollTo(v);
}
- @Override
public boolean scrollTo(View v) {
ExpandableView expandableView = (ExpandableView) v;
int positionInLinearLayout = getPositionInLinearLayout(v);
@@ -2221,9 +2242,31 @@
mScrimController.setExcludedBackgroundArea(
mFadingOut || mParentNotFullyVisible || mAmbientState.isDark() || mIsClipped ? null
: mCurrentBounds);
+ updateBackgroundPath();
invalidate();
}
+ private void updateBackgroundPath() {
+ mBackgroundPath.reset();
+ float topRoundness = 0;
+ if (mFirstVisibleBackgroundChild != null) {
+ topRoundness = mFirstVisibleBackgroundChild.getCurrentBackgroundRadiusTop();
+ }
+ topRoundness = onKeyguard() ? mCornerRadius : topRoundness;
+ float bottomRoundNess = mCornerRadius;
+ mBackgroundRadii[0] = topRoundness;
+ mBackgroundRadii[1] = topRoundness;
+ mBackgroundRadii[2] = topRoundness;
+ mBackgroundRadii[3] = topRoundness;
+ mBackgroundRadii[4] = bottomRoundNess;
+ mBackgroundRadii[5] = bottomRoundNess;
+ mBackgroundRadii[6] = bottomRoundNess;
+ mBackgroundRadii[7] = bottomRoundNess;
+ mBackgroundPath.addRoundRect(mCurrentSidePadding, mCurrentBounds.top,
+ getWidth() - mCurrentSidePadding, mCurrentBounds.bottom, mBackgroundRadii,
+ Path.Direction.CCW);
+ }
+
/**
* Update the background bounds to the new desired bounds
*/
@@ -2236,6 +2279,8 @@
mBackgroundBounds.left = mTempInt2[0];
mBackgroundBounds.right = mTempInt2[0] + getWidth();
}
+ mBackgroundBounds.left += mCurrentSidePadding;
+ mBackgroundBounds.right -= mCurrentSidePadding;
if (!mIsExpanded) {
mBackgroundBounds.top = 0;
mBackgroundBounds.bottom = 0;
@@ -2820,16 +2865,45 @@
private void updateFirstAndLastBackgroundViews() {
ActivatableNotificationView firstChild = getFirstChildWithBackground();
ActivatableNotificationView lastChild = getLastChildWithBackground();
+ boolean firstChanged = firstChild != mFirstVisibleBackgroundChild;
+ boolean lastChanged = lastChild != mLastVisibleBackgroundChild;
if (mAnimationsEnabled && mIsExpanded) {
- mAnimateNextBackgroundTop = firstChild != mFirstVisibleBackgroundChild;
- mAnimateNextBackgroundBottom = lastChild != mLastVisibleBackgroundChild;
+ mAnimateNextBackgroundTop = firstChanged;
+ mAnimateNextBackgroundBottom = lastChanged;
} else {
mAnimateNextBackgroundTop = false;
mAnimateNextBackgroundBottom = false;
}
+ if (firstChanged && mFirstVisibleBackgroundChild != null
+ && !mFirstVisibleBackgroundChild.isRemoved()) {
+ mFirstVisibleBackgroundChild.setTopRoundness(0.0f,
+ mFirstVisibleBackgroundChild.isShown());
+ }
+ if (lastChanged && mLastVisibleBackgroundChild != null
+ && !mLastVisibleBackgroundChild.isRemoved()) {
+ mLastVisibleBackgroundChild.setBottomRoundness(0.0f,
+ mLastVisibleBackgroundChild.isShown());
+ }
mFirstVisibleBackgroundChild = firstChild;
mLastVisibleBackgroundChild = lastChild;
mAmbientState.setLastVisibleBackgroundChild(lastChild);
+ applyRoundedNess();
+ }
+
+ private void applyRoundedNess() {
+ if (mFirstVisibleBackgroundChild != null) {
+ mFirstVisibleBackgroundChild.setTopRoundness(
+ mStatusBarState == StatusBarState.KEYGUARD ? 1.0f : 0.0f,
+ mFirstVisibleBackgroundChild.isShown()
+ && !mChildrenToAddAnimated.contains(mFirstVisibleBackgroundChild));
+ }
+ if (mLastVisibleBackgroundChild != null) {
+ mLastVisibleBackgroundChild.setBottomRoundness(1.0f,
+ mLastVisibleBackgroundChild.isShown()
+ && !mChildrenToAddAnimated.contains(mLastVisibleBackgroundChild));
+ }
+ updateBackgroundPath();
+ invalidate();
}
private void onViewAddedInternal(View child) {
@@ -2838,6 +2912,7 @@
generateAddAnimation(child, false /* fromMoreCard */);
updateAnimationState(child);
updateChronometerForChild(child);
+ updateCurrentSidePaddings(child);
}
private void updateHideSensitiveForChild(View child) {
@@ -3321,12 +3396,10 @@
}
}
- @Override
public void requestDisallowLongPress() {
cancelLongPress();
}
- @Override
public void requestDisallowDismiss() {
mDisallowDismissInThisMotion = true;
}
@@ -4285,6 +4358,43 @@
public void setStatusBarState(int statusBarState) {
mStatusBarState = statusBarState;
mAmbientState.setStatusBarState(statusBarState);
+ applyRoundedNess();
+ updateSidePaddings();
+ }
+
+ private void updateSidePaddings() {
+ int sidePaddings = mStatusBarState == StatusBarState.KEYGUARD ? mLockscreenSidePaddings : 0;
+ if (sidePaddings != mSidePaddings) {
+ boolean animate = isShown();
+ mSidePaddings = sidePaddings;
+ PropertyAnimator.setProperty(this, SIDE_PADDINGS, sidePaddings,
+ SIDE_PADDING_PROPERTIES, animate);
+ }
+ }
+
+ protected void setCurrentSidePadding(float sidePadding) {
+ mCurrentSidePadding = sidePadding;
+ updateBackground();
+ applySidePaddingsToChildren();
+ }
+
+ private void applySidePaddingsToChildren() {
+ for (int i = 0; i < getChildCount(); i++) {
+ View view = getChildAt(i);
+ updateCurrentSidePaddings(view);
+ }
+ }
+
+ private void updateCurrentSidePaddings(View view) {
+ if (!(view instanceof ExpandableOutlineView)) {
+ return;
+ }
+ ExpandableOutlineView outlineView = (ExpandableOutlineView) view;
+ outlineView.setCurrentSidePaddings(mCurrentSidePadding);
+ }
+
+ protected float getCurrentSidePadding() {
+ return mCurrentSidePadding;
}
public void setExpandingVelocity(float expandingVelocity) {
diff --git a/com/android/systemui/statusbar/stack/ScrollContainer.java b/com/android/systemui/statusbar/stack/ScrollContainer.java
deleted file mode 100644
index b9d12ce..0000000
--- a/com/android/systemui/statusbar/stack/ScrollContainer.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2016 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.stack;
-
-import android.view.View;
-
-/**
- * Interface for container layouts that scroll and listen for long presses. A child that
- * wants to handle long press can use this to cancel the parents long press logic or request
- * to be made visible by scrolling to it.
- */
-public interface ScrollContainer {
- /**
- * Request that the view does not perform long press for the current touch.
- */
- void requestDisallowLongPress();
-
- /**
- * Request that the view is made visible by scrolling to it.
- * Return true if it scrolls.
- */
- boolean scrollTo(View v);
-
- /**
- * Like {@link #scrollTo(View)}, but keeps the scroll locked until the user
- * scrolls, or {@param v} loses focus or is detached.
- */
- void lockScrollTo(View v);
-
- /**
- * Request that the view does not dismiss for the current touch.
- */
- void requestDisallowDismiss();
-}
diff --git a/com/android/systemui/statusbar/stack/ViewState.java b/com/android/systemui/statusbar/stack/ViewState.java
index 27b730c..682b849 100644
--- a/com/android/systemui/statusbar/stack/ViewState.java
+++ b/com/android/systemui/statusbar/stack/ViewState.java
@@ -21,7 +21,6 @@
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
-import android.app.Notification;
import android.util.Property;
import android.view.View;
import android.view.animation.Interpolator;
@@ -29,7 +28,7 @@
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.statusbar.ExpandableView;
-import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -64,8 +63,8 @@
private static final int TAG_START_TRANSLATION_Z = R.id.translation_z_animator_start_value_tag;
private static final int TAG_START_ALPHA = R.id.alpha_animator_start_value_tag;
- private static final PropertyAnimator.AnimatableProperty SCALE_X_PROPERTY
- = new PropertyAnimator.AnimatableProperty() {
+ private static final AnimatableProperty SCALE_X_PROPERTY
+ = new AnimatableProperty() {
@Override
public int getAnimationStartTag() {
@@ -88,8 +87,8 @@
}
};
- private static final PropertyAnimator.AnimatableProperty SCALE_Y_PROPERTY
- = new PropertyAnimator.AnimatableProperty() {
+ private static final AnimatableProperty SCALE_Y_PROPERTY
+ = new AnimatableProperty() {
@Override
public int getAnimationStartTag() {
@@ -251,7 +250,7 @@
return getChildTag(view, tag) != null;
}
- public static boolean isAnimating(View view, PropertyAnimator.AnimatableProperty property) {
+ public static boolean isAnimating(View view, AnimatableProperty property) {
return getChildTag(view, property.getAnimatorTag()) != null;
}
@@ -403,7 +402,7 @@
startZTranslationAnimation(view, NO_NEW_ANIMATIONS);
}
- private void updateAnimation(View view, PropertyAnimator.AnimatableProperty property,
+ private void updateAnimation(View view, AnimatableProperty property,
float endValue) {
PropertyAnimator.startAnimation(view, property, endValue, NO_NEW_ANIMATIONS);
}
diff --git a/com/android/systemui/usb/UsbPermissionActivity.java b/com/android/systemui/usb/UsbPermissionActivity.java
index 87d11b2..4606aee 100644
--- a/com/android/systemui/usb/UsbPermissionActivity.java
+++ b/com/android/systemui/usb/UsbPermissionActivity.java
@@ -235,7 +235,7 @@
intent.putExtra(UsbManager.EXTRA_DEVICE, mDevice);
if (mPermissionGranted) {
service.grantDevicePermission(mDevice, mUid);
- if (mAlwaysUse.isChecked()) {
+ if (mAlwaysUse != null && mAlwaysUse.isChecked()) {
final int userId = UserHandle.getUserId(mUid);
service.setDevicePackage(mDevice, mPackageName, userId);
}
@@ -245,7 +245,7 @@
intent.putExtra(UsbManager.EXTRA_ACCESSORY, mAccessory);
if (mPermissionGranted) {
service.grantAccessoryPermission(mAccessory, mUid);
- if (mAlwaysUse.isChecked()) {
+ if (mAlwaysUse != null && mAlwaysUse.isChecked()) {
final int userId = UserHandle.getUserId(mUid);
service.setAccessoryPackage(mAccessory, mPackageName, userId);
}
diff --git a/com/android/systemui/volume/VolumeDialogImpl.java b/com/android/systemui/volume/VolumeDialogImpl.java
index 4b8f581..383d327 100644
--- a/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/com/android/systemui/volume/VolumeDialogImpl.java
@@ -207,6 +207,7 @@
} else {
addExistingRows();
}
+ updateRowsH(getActiveRow());
}
private ColorStateList loadColorStateList(int colorResId) {
@@ -440,7 +441,8 @@
.withEndAction(() -> mDialog.dismiss())
.setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator())
.start();
- if (mAccessibilityMgr.isEnabled()) {
+ if (mAccessibilityMgr.isObservedEventType(
+ AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED)) {
AccessibilityEvent event =
AccessibilityEvent.obtain(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
event.setPackageName(mContext.getPackageName());
diff --git a/com/android/uiautomator/testrunner/UiAutomatorTestCase.java b/com/android/uiautomator/testrunner/UiAutomatorTestCase.java
index 3d5476d..7c9aede 100644
--- a/com/android/uiautomator/testrunner/UiAutomatorTestCase.java
+++ b/com/android/uiautomator/testrunner/UiAutomatorTestCase.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2013 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.
@@ -16,55 +16,24 @@
package com.android.uiautomator.testrunner;
-import android.content.Context;
+import android.app.Instrumentation;
import android.os.Bundle;
-import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.SystemClock;
-import android.view.inputmethod.InputMethodInfo;
+import android.test.InstrumentationTestCase;
-import com.android.internal.view.IInputMethodManager;
+import com.android.uiautomator.core.InstrumentationUiAutomatorBridge;
import com.android.uiautomator.core.UiDevice;
-import junit.framework.TestCase;
-
-import java.util.List;
-
/**
- * UI automation test should extend this class. This class provides access
- * to the following:
- * {@link UiDevice} instance
- * {@link Bundle} for command line parameters.
- * @since API Level 16
+ * UI Automator test case that is executed on the device.
* @deprecated New tests should be written using UI Automator 2.0 which is available as part of the
* Android Testing Support Library.
*/
@Deprecated
-public class UiAutomatorTestCase extends TestCase {
+public class UiAutomatorTestCase extends InstrumentationTestCase {
- private static final String DISABLE_IME = "disable_ime";
- private static final String DUMMY_IME_PACKAGE = "com.android.testing.dummyime";
- private UiDevice mUiDevice;
private Bundle mParams;
private IAutomationSupport mAutomationSupport;
- private boolean mShouldDisableIme = false;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mShouldDisableIme = "true".equals(mParams.getString(DISABLE_IME));
- if (mShouldDisableIme) {
- setDummyIme();
- }
- }
-
- @Override
- protected void tearDown() throws Exception {
- if (mShouldDisableIme) {
- restoreActiveIme();
- }
- super.tearDown();
- }
/**
* Get current instance of {@link UiDevice}. Works similar to calling the static
@@ -72,7 +41,7 @@
* @since API Level 16
*/
public UiDevice getUiDevice() {
- return mUiDevice;
+ return UiDevice.getInstance();
}
/**
@@ -85,34 +54,43 @@
return mParams;
}
+ void setAutomationSupport(IAutomationSupport automationSupport) {
+ mAutomationSupport = automationSupport;
+ }
+
/**
* Provides support for running tests to report interim status
*
* @return IAutomationSupport
* @since API Level 16
+ * @deprecated Use {@link Instrumentation#sendStatus(int, Bundle)} instead
*/
public IAutomationSupport getAutomationSupport() {
+ if (mAutomationSupport == null) {
+ mAutomationSupport = new InstrumentationAutomationSupport(getInstrumentation());
+ }
return mAutomationSupport;
}
/**
- * package private
- * @param uiDevice
+ * Initializes this test case.
+ *
+ * @param params Instrumentation arguments.
*/
- void setUiDevice(UiDevice uiDevice) {
- mUiDevice = uiDevice;
- }
-
- /**
- * package private
- * @param params
- */
- void setParams(Bundle params) {
+ void initialize(Bundle params) {
mParams = params;
- }
- void setAutomationSupport(IAutomationSupport automationSupport) {
- mAutomationSupport = automationSupport;
+ // check if this is a monkey test mode
+ String monkeyVal = mParams.getString("monkey");
+ if (monkeyVal != null) {
+ // only if the monkey key is specified, we alter the state of monkey
+ // else we should leave things as they are.
+ getInstrumentation().getUiAutomation().setRunAsMonkey(Boolean.valueOf(monkeyVal));
+ }
+
+ UiDevice.getInstance().initialize(new InstrumentationUiAutomatorBridge(
+ getInstrumentation().getContext(),
+ getInstrumentation().getUiAutomation()));
}
/**
@@ -123,28 +101,4 @@
public void sleep(long ms) {
SystemClock.sleep(ms);
}
-
- private void setDummyIme() throws RemoteException {
- IInputMethodManager im = IInputMethodManager.Stub.asInterface(ServiceManager
- .getService(Context.INPUT_METHOD_SERVICE));
- List<InputMethodInfo> infos = im.getInputMethodList();
- String id = null;
- for (InputMethodInfo info : infos) {
- if (DUMMY_IME_PACKAGE.equals(info.getComponent().getPackageName())) {
- id = info.getId();
- }
- }
- if (id == null) {
- throw new RuntimeException(String.format(
- "Required testing fixture missing: IME package (%s)", DUMMY_IME_PACKAGE));
- }
- im.setInputMethod(null, id);
- }
-
- private void restoreActiveIme() throws RemoteException {
- // TODO: figure out a way to restore active IME
- // Currently retrieving active IME requires querying secure settings provider, which is hard
- // to do without a Context; so the caveat here is that to make the post test device usable,
- // the active IME needs to be manually switched.
- }
}